
The Heliacal Rise of Sirius, Step by Step
This notebook reconstructs the first morning visibility of Sirius from Thebes around 2782 BCE, the era of the first apokatastasis of the Sothic cycle.
The goal is not to treat montu.HeliacalRise as a black box. For each of the four models included in MontuPython we will:
obtain the positions of the Sun and Sirius using MontuPython’s public positional astronomy routines;
write and explain the visibility criterion;
evaluate it morning by morning —and, when appropriate, minute by minute—;
identify the «not visible → visible» transition that defines the heliacal rise;
compare the manual reconstruction with the high-level API.
Important interpretation. A calculated heliacal rise is not a purely geometric instant like a rise or a conjunction. It is a visibility prediction and depends on the model, the atmosphere, the horizon, the observer’s acuity, and the adopted time step.
If running in Google Colab, MontuPython must be installed first. In a local copy of the repository this cell can remain commented out.
[1]:
try:
from google.colab import drive
%pip install -Uq montu
except ImportError:
print("Not running in Colab, skipping installation")
import plotly.io as pio
pio.renderers.default = "notebook_connected"
%load_ext autoreload
%autoreload 2
# Create folders for figures and temporal files
!mkdir -p ./gallery/ ./montu_dem/
Not running in Colab, skipping installation
[2]:
%matplotlib inline
import matplotlib.pyplot as plt
import montu
import numpy as np
import pandas as pd
pd.options.display.float_format = "{:.3f}".format
MontuPython version 0.50.0. 𓇍𓇋𓇋𓏏𓅓𓊵 𓎛𓎡𓄿𓀭𓎛𓈖𓂝𓎡 (ii-ti m Htp, HkAx Hn'-k)
1. Setup, conventions, and input data
We need to fix three elements before discussing visibility:
Observer: Thebes (Luxor), from the MontuPython site catalogue.
Object: Sirius, from the bright-star catalogue. Its catalogue visual magnitude is approximately \(V=-1.44\).
Interval: July 2782 BCE.
MontuPython uses astronomical year numbering: the historical year 2782 BCE is written as -2781, because astronomical year 0 exists. We use calendar='mixed', i.e. the Julian calendar before the Gregorian reform.
All altitudes are apparent and expressed in degrees. Azimuth is measured from north toward east (\(0°\) north, \(90°\) east). JED is the Julian day on the UTC scale that MontuPython uses as a time coordinate.
[3]:
site = montu.Observer(site="thebes")
sirius = montu.Stars(subset="bright", ProperName="Sirius")
sun = montu.Sun()
start_date = montu.Time("139-07-01", calendar="mixed")
end_date = montu.Time("139-07-30", calendar="mixed")
V_SIRIUS = float(sirius.data.iloc[0].Vmag)
start_date, end_date
Loading stellar catalogue montu_stellar_catalogue_v38_bright.csv
[3]:
(Time('139-07-01 02:33:37.2'/'139-07-01 00:00:00'/'[hrw 2921] IV shemu 17'/JED 1772008.5/JTD 1772008.6066806),
Time('139-07-30 02:33:37.2'/'139-07-30 00:00:00'/'[hrw 2922] I akhet 11'/JED 1772037.5/JTD 1772037.6066806))
2. Models available in MontuPython
MontuPython provides a set of routines to estimate the heliacal rise of a star or planet using four models:
Geometric models:
Ptolemy model (``ptolemy``): This is the classical model from Ptolemy’s Almagest. The critical quantity for morning visibility is the Arcus Visionis: the difference between the elevations of the Sun and the observed object above a given horizon (see below) at the exact moment the object rises. This is the most widely used model in archaeoastronomy.
Physical models
Schaefer basic model (``schaefer1987``): This model computes heliacal-rise conditions with atmospheric extinction. Essentially, it requires the sky to be dark enough at dawn — defined by the Sun’s depression below the horizon and the limiting visual magnitude at the object’s altitude.
Schaefer physiological model (``schaefer1985``): Similar to the basic model by the same author, but it includes physiological factors of observation for a more realistic estimate. The model has been tested against real observations.
Belokrylov et al. model (``belokrylov2011``): This model uses elements of Schaefer’s basic approach, but applies corrections derived from real observations for objects of different magnitudes. It is a semi-empirical model and one of the most recent revisions of heliacal-rise theory.
3. The HeliacalRise interface in MontuPython
The details of all models are implemented in MontuPython in a class, HeliacalRise, which computes heliacal rises in a user-friendly way without going deeply into the astronomical and physical details.
3.1 Ptolemy’s method (Arcus Visionis)
The class is used simply by defining the model and its parameters:
[4]:
model = montu.HeliacalRise(
model="ptolemy",
#arcus_visionis_crit=14.0, # assumed Arcus Visionis
#ptolemy_refraction_deg=0.0/60 # astronomical refraction (if desired)
)
Once the class is defined, we compute the conditions over the chosen date interval:
[5]:
rises = model.compute(sirius, site, start_date, end_date, verbose=True)
rises
HeliacalRise verbose — model=ptolemy
quantities: t_rise: local object rise time; AV: solar depression at object rise; AV_crit: critical Arcus Visionis; h_sun: solar altitude
interval: 139-07-01 00:00:00 -> 139-07-30 00:00:00
model parameters:
arcus_visionis_crit=automatic body default
ptolemy_refraction_deg=0.5666666666666667
criterion: AV_calc >= AV_crit and h_sun < 0°
day 001 | 139-07-01 00:00:00 | visible=False | t_rise=05:22:27.403 | AV=-2.348° | AV_crit=14.000° | h_sun=2.348°
day 002 | 139-07-02 00:00:00 | visible=False | t_rise=05:18:31.479 | AV=-1.480° | AV_crit=14.000° | h_sun=1.480°
day 003 | 139-07-03 00:00:00 | visible=False | t_rise=05:14:35.564 | AV=-0.611° | AV_crit=14.000° | h_sun=0.611°
day 004 | 139-07-04 00:00:00 | visible=False | t_rise=05:10:39.658 | AV=0.259° | AV_crit=14.000° | h_sun=-0.259°
day 005 | 139-07-05 00:00:00 | visible=False | t_rise=05:06:43.751 | AV=1.128° | AV_crit=14.000° | h_sun=-1.128°
day 006 | 139-07-06 00:00:00 | visible=False | t_rise=05:02:47.844 | AV=1.998° | AV_crit=14.000° | h_sun=-1.998°
day 007 | 139-07-07 00:00:00 | visible=False | t_rise=04:58:51.938 | AV=2.868° | AV_crit=14.000° | h_sun=-2.868°
day 008 | 139-07-08 00:00:00 | visible=False | t_rise=04:54:56.031 | AV=3.739° | AV_crit=14.000° | h_sun=-3.739°
day 009 | 139-07-09 00:00:00 | visible=False | t_rise=04:51:00.125 | AV=4.609° | AV_crit=14.000° | h_sun=-4.609°
day 010 | 139-07-10 00:00:00 | visible=False | t_rise=04:47:04.210 | AV=5.480° | AV_crit=14.000° | h_sun=-5.480°
day 011 | 139-07-11 00:00:00 | visible=False | t_rise=04:43:08.294 | AV=6.350° | AV_crit=14.000° | h_sun=-6.350°
day 012 | 139-07-12 00:00:00 | visible=False | t_rise=04:39:12.379 | AV=7.221° | AV_crit=14.000° | h_sun=-7.221°
day 013 | 139-07-13 00:00:00 | visible=False | t_rise=04:35:16.464 | AV=8.092° | AV_crit=14.000° | h_sun=-8.092°
day 014 | 139-07-14 00:00:00 | visible=False | t_rise=04:31:20.549 | AV=8.962° | AV_crit=14.000° | h_sun=-8.962°
day 015 | 139-07-15 00:00:00 | visible=False | t_rise=04:27:24.634 | AV=9.833° | AV_crit=14.000° | h_sun=-9.833°
day 016 | 139-07-16 00:00:00 | visible=False | t_rise=04:23:28.727 | AV=10.703° | AV_crit=14.000° | h_sun=-10.703°
day 017 | 139-07-17 00:00:00 | visible=False | t_rise=04:19:32.812 | AV=11.573° | AV_crit=14.000° | h_sun=-11.573°
day 018 | 139-07-18 00:00:00 | visible=False | t_rise=04:15:36.905 | AV=12.443° | AV_crit=14.000° | h_sun=-12.443°
day 019 | 139-07-19 00:00:00 | visible=False | t_rise=04:11:40.999 | AV=13.313° | AV_crit=14.000° | h_sun=-13.313°
day 020 | 139-07-20 00:00:00 | visible=True | t_rise=04:07:45.092 | AV=14.182° | AV_crit=14.000° | h_sun=-14.182°
-> heliacal rise detected
criterion satisfied: AV_calc >= AV_crit and h_sun < 0°
day 021 | 139-07-21 00:00:00 | visible=True | t_rise=04:03:49.194 | AV=15.050° | AV_crit=14.000° | h_sun=-15.050°
day 022 | 139-07-22 00:00:00 | visible=True | t_rise=03:59:53.288 | AV=15.919° | AV_crit=14.000° | h_sun=-15.919°
day 023 | 139-07-23 00:00:00 | visible=True | t_rise=03:55:57.381 | AV=16.786° | AV_crit=14.000° | h_sun=-16.786°
day 024 | 139-07-24 00:00:00 | visible=True | t_rise=03:52:01.466 | AV=17.653° | AV_crit=14.000° | h_sun=-17.653°
day 025 | 139-07-25 00:00:00 | visible=True | t_rise=03:48:05.559 | AV=18.520° | AV_crit=14.000° | h_sun=-18.520°
day 026 | 139-07-26 00:00:00 | visible=True | t_rise=03:44:09.644 | AV=19.386° | AV_crit=14.000° | h_sun=-19.386°
day 027 | 139-07-27 00:00:00 | visible=True | t_rise=03:40:13.729 | AV=20.251° | AV_crit=14.000° | h_sun=-20.251°
day 028 | 139-07-28 00:00:00 | visible=True | t_rise=03:36:17.805 | AV=21.115° | AV_crit=14.000° | h_sun=-21.115°
day 029 | 139-07-29 00:00:00 | visible=True | t_rise=03:32:21.890 | AV=21.978° | AV_crit=14.000° | h_sun=-21.978°
day 030 | 139-07-30 00:00:00 | visible=True | t_rise=03:28:25.975 | AV=22.841° | AV_crit=14.000° | h_sun=-22.841°
[5]:
| model | source | day_jed | jed | local_time | body_altitude_deg | body_azimuth_deg | sun_altitude_deg | sun_azimuth_deg | sun_altitude_formula_deg | vmag | target_horizon_deg | body_ra_hours | body_dec_deg | sun_ra_hours | sun_dec_deg | h_star_deg | h_sun_deg | arcus_visionis_calc_deg | arcus_visionis_crit_deg | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ptolemy | Toomer, G. J. (1998). Ptolemy's Almagest. Prin... | 1772027.500 | 1772027.581 | 04:07:45.092 | -0.567 | 107.392 | -14.182 | 57.377 | -14.182 | -1.440 | -0.567 | 5.384 | -15.880 | 7.780 | 21.392 | -82.792 | -118.719 | 14.182 | 14.000 |
As you can see, the algorithm returns all detected heliacal rises in the interval as a DataFrame. To inspect the date:
[6]:
montu.Time(float(rises.jed[0]), format="jd", calendar="mixed")
[6]:
Time('139-07-20 04:30:48.2'/'139-07-20 01:57:57'/'[hrw 2922] I akhet 1'/JED 1772027.5813773/JTD 1772027.6880579)
The package includes a method to present the result more clearly:
[7]:
model.print_rises(rises)
ptolemy — 1 date(s)
[1] 139-07-20 00:00:00 04:07:45.092 0139-07-19 00:00:00.000000 [hrw 2922] I akhet 1 body -0.57° Sun -14.18°
source: Toomer, G. J. (1998). Ptolemy's Almagest. Princeton University Press. Book XIII, Chapter 7: "On the heliacal risings and settings of the planets".
Or, if you prefer something a bit more polished:
[8]:
model.print_rises(rises, title="Heliacal rise of Sirius — Ptolemy", body_label="Sirius")
Heliacal rise of Sirius — Ptolemy — 1 date(s)
[1] 139-07-20 00:00:00 04:07:45.092 0139-07-19 00:00:00.000000 [hrw 2922] I akhet 1 Sirius -0.57° Sun -14.18°
source: Toomer, G. J. (1998). Ptolemy's Almagest. Princeton University Press. Book XIII, Chapter 7: "On the heliacal risings and settings of the planets".
Save the result:
[9]:
rise_ptolemy_jed = rises.day_jed[0]
3.2 Schaefer (1987)
The same approach as for the other models, but with a different set of input parameters.
[10]:
model = montu.HeliacalRise(
model="schaefer1987",
k=0.25, # atmospheric extinction
limiting_mag_zenith=6.0, # limiting visual magnitude at the zenith
sun_depression=-11.0, # solar depression required for observation
)
rises = model.compute(sirius, site, start_date, end_date, verbose=True)
model.print_rises(rises, title="Heliacal rise of Sirius — Schaefer 1987", body_label="Sirius")
HeliacalRise verbose — model=schaefer1987
quantities: h_star: object altitude; X: air mass; V_observed: extinguished object magnitude; V_limit: limiting magnitude at object altitude
interval: 139-07-01 00:00:00 -> 139-07-30 00:00:00
model parameters:
k=0.25
limiting_mag_zenith=6.0
sun_depression=-11.0
criterion: h_star > 0° and V_observed <= V_limit(local)
day 001 | 139-07-01 00:00:00 | visible=False | h_star=-15.410° | X=inf | V_observed=inf | V_limit=-inf
day 002 | 139-07-02 00:00:00 | visible=False | h_star=-14.455° | X=inf | V_observed=inf | V_limit=-inf
day 003 | 139-07-03 00:00:00 | visible=False | h_star=-13.498° | X=inf | V_observed=inf | V_limit=-inf
day 004 | 139-07-04 00:00:00 | visible=False | h_star=-12.538° | X=inf | V_observed=inf | V_limit=-inf
day 005 | 139-07-05 00:00:00 | visible=False | h_star=-11.576° | X=inf | V_observed=inf | V_limit=-inf
day 006 | 139-07-06 00:00:00 | visible=False | h_star=-10.611° | X=inf | V_observed=inf | V_limit=-inf
day 007 | 139-07-07 00:00:00 | visible=False | h_star=-9.645° | X=inf | V_observed=inf | V_limit=-inf
day 008 | 139-07-08 00:00:00 | visible=False | h_star=-8.677° | X=inf | V_observed=inf | V_limit=-inf
day 009 | 139-07-09 00:00:00 | visible=False | h_star=-7.707° | X=inf | V_observed=inf | V_limit=-inf
day 010 | 139-07-10 00:00:00 | visible=False | h_star=-6.737° | X=inf | V_observed=inf | V_limit=-inf
day 011 | 139-07-11 00:00:00 | visible=False | h_star=-5.765° | X=inf | V_observed=inf | V_limit=-inf
day 012 | 139-07-12 00:00:00 | visible=False | h_star=-4.792° | X=inf | V_observed=inf | V_limit=-inf
day 013 | 139-07-13 00:00:00 | visible=False | h_star=-3.819° | X=inf | V_observed=inf | V_limit=-inf
day 014 | 139-07-14 00:00:00 | visible=False | h_star=-2.845° | X=inf | V_observed=inf | V_limit=-inf
day 015 | 139-07-15 00:00:00 | visible=False | h_star=-1.872° | X=inf | V_observed=inf | V_limit=-inf
day 016 | 139-07-16 00:00:00 | visible=False | h_star=-0.898° | X=inf | V_observed=inf | V_limit=-inf
day 017 | 139-07-17 00:00:00 | visible=False | h_star=0.074° | X=770.605 | V_observed=191.211 | V_limit=-186.401
day 018 | 139-07-18 00:00:00 | visible=False | h_star=1.046° | X=54.757 | V_observed=12.249 | V_limit=-7.439
day 019 | 139-07-19 00:00:00 | visible=False | h_star=2.017° | X=28.405 | V_observed=5.661 | V_limit=-0.851
day 020 | 139-07-20 00:00:00 | visible=False | h_star=2.987° | X=19.188 | V_observed=3.357 | V_limit=1.453
day 021 | 139-07-21 00:00:00 | visible=True | h_star=3.956° | X=14.496 | V_observed=2.184 | V_limit=2.626
-> heliacal rise detected
criterion satisfied: h_star > 0° and V_observed <= V_limit(local)
day 022 | 139-07-22 00:00:00 | visible=True | h_star=4.922° | X=11.655 | V_observed=1.474 | V_limit=3.336
day 023 | 139-07-23 00:00:00 | visible=True | h_star=5.887° | X=9.750 | V_observed=0.998 | V_limit=3.812
day 024 | 139-07-24 00:00:00 | visible=True | h_star=6.849° | X=8.386 | V_observed=0.656 | V_limit=4.154
day 025 | 139-07-25 00:00:00 | visible=True | h_star=7.809° | X=7.360 | V_observed=0.400 | V_limit=4.410
day 026 | 139-07-26 00:00:00 | visible=True | h_star=8.766° | X=6.562 | V_observed=0.200 | V_limit=4.610
day 027 | 139-07-27 00:00:00 | visible=True | h_star=9.720° | X=5.923 | V_observed=0.041 | V_limit=4.769
day 028 | 139-07-28 00:00:00 | visible=True | h_star=10.670° | X=5.401 | V_observed=-0.090 | V_limit=4.900
day 029 | 139-07-29 00:00:00 | visible=True | h_star=11.617° | X=4.966 | V_observed=-0.199 | V_limit=5.009
day 030 | 139-07-30 00:00:00 | visible=True | h_star=12.560° | X=4.598 | V_observed=-0.290 | V_limit=5.100
Heliacal rise of Sirius — Schaefer 1987 — 1 date(s)
[1] 139-07-21 00:00:00 04:24:56.311 0139-07-20 00:00:00.000000 [hrw 2922] I akhet 2 Sirius 3.96° Sun -11.00°
source: Schaefer, B. E. (1987). "Heliacal rise phenomena". Journal for the History of Astronomy, 18(11), 19-33.
Save the result:
[11]:
rise_schaefer1987_jed = rises.day_jed[0]
3.3 Schaefer (1985)
This model scans the twilight. step_minutes=2 means that visibility is checked every two minutes between astronomical dawn and sunrise.
[12]:
model = montu.HeliacalRise(
model="schaefer1985",
k=0.25, # atmospheric extinction coefficient
limiting_mag_zenith=6.0, # limiting visual magnitude at the zenith
step_minutes=2.0, # time step
twilight_sunbelow=-18.0, # solar depression
)
rises = model.compute(sirius, site, start_date, end_date, verbose=True)
model.print_rises(rises, title="Heliacal rise of Sirius — Schaefer 1985", body_label="Sirius")
HeliacalRise verbose — model=schaefer1985
quantities: h_star: object altitude; h_sun: solar altitude; V: object magnitude; V_limit: limiting magnitude at object altitude; B: sky brightness
interval: 139-07-01 00:00:00 -> 139-07-30 00:00:00
model parameters:
k=0.25
limiting_mag_zenith=6.0
step_minutes=2.0
twilight_sunbelow=-18.0
criterion: h_star > 0° and V <= V_limit
morning scan: h_sun=-18.0° to sunrise, every 2.0 minutes
day 001 | 139-07-01 00:00:00 | visible=False
day 002 | 139-07-02 00:00:00 | visible=False
day 003 | 139-07-03 00:00:00 | visible=False
day 004 | 139-07-04 00:00:00 | visible=False
day 005 | 139-07-05 00:00:00 | visible=False
day 006 | 139-07-06 00:00:00 | visible=False
day 007 | 139-07-07 00:00:00 | visible=False
day 008 | 139-07-08 00:00:00 | visible=False
day 009 | 139-07-09 00:00:00 | visible=False
day 010 | 139-07-10 00:00:00 | visible=False
day 011 | 139-07-11 00:00:00 | visible=False
day 012 | 139-07-12 00:00:00 | visible=False
day 013 | 139-07-13 00:00:00 | visible=False
day 014 | 139-07-14 00:00:00 | visible=False
day 015 | 139-07-15 00:00:00 | visible=True | h_star=2.539° | h_sun=-6.949° | V=-1.440 | V_limit=-1.286 | B=653243.240
-> heliacal rise detected
criterion satisfied: h_star > 0° and V <= V_limit
day 016 | 139-07-16 00:00:00 | visible=True | h_star=2.237° | h_sun=-8.159° | V=-1.440 | V_limit=-1.098 | B=200103.753
day 017 | 139-07-17 00:00:00 | visible=True | h_star=1.938° | h_sun=-9.314° | V=-1.440 | V_limit=-1.136 | B=64385.214
day 018 | 139-07-18 00:00:00 | visible=True | h_star=1.640° | h_sun=-10.463° | V=-1.440 | V_limit=-1.354 | B=20733.486
day 019 | 139-07-19 00:00:00 | visible=True | h_star=1.773° | h_sun=-11.221° | V=-1.440 | V_limit=-1.052 | B=9641.547
day 020 | 139-07-20 00:00:00 | visible=True | h_star=1.480° | h_sun=-12.363° | V=-1.440 | V_limit=-1.426 | B=3090.394
day 021 | 139-07-21 00:00:00 | visible=True | h_star=1.617° | h_sun=-13.117° | V=-1.440 | V_limit=-1.033 | B=1425.541
day 022 | 139-07-22 00:00:00 | visible=True | h_star=0.899° | h_sun=-14.632° | V=-1.440 | V_limit=-1.192 | B=288.621
day 023 | 139-07-23 00:00:00 | visible=True | h_star=0.610° | h_sun=-15.759° | V=-1.440 | V_limit=-1.085 | B=67.119
day 024 | 139-07-24 00:00:00 | visible=True | h_star=0.323° | h_sun=-16.882° | V=-1.440 | V_limit=-0.648 | B=0.000
day 025 | 139-07-25 00:00:00 | visible=True | h_star=0.467° | h_sun=-17.628° | V=-1.440 | V_limit=-0.129 | B=0.000
day 026 | 139-07-26 00:00:00 | visible=True | h_star=1.042° | h_sun=-18.000° | V=-1.440 | V_limit=1.465 | B=0.000
day 027 | 139-07-27 00:00:00 | visible=True | h_star=2.045° | h_sun=-18.000° | V=-1.440 | V_limit=3.177 | B=0.000
day 028 | 139-07-28 00:00:00 | visible=True | h_star=3.046° | h_sun=-18.000° | V=-1.440 | V_limit=4.206 | B=0.000
day 029 | 139-07-29 00:00:00 | visible=True | h_star=4.044° | h_sun=-18.000° | V=-1.440 | V_limit=4.886 | B=0.000
day 030 | 139-07-30 00:00:00 | visible=True | h_star=5.040° | h_sun=-18.000° | V=-1.440 | V_limit=5.366 | B=0.000
Heliacal rise of Sirius — Schaefer 1985 — 1 date(s)
[1] 139-07-15 00:00:00 04:41:53.023 0139-07-14 00:00:00.000000 [hrw 2921] I mesut 1 Sirius 2.54° Sun -6.95°
source: Schaefer, B.E. 1985, Sky & Telescope 70, 261–263 (BASIC listing, lines 34–35 and 55–81).
Save the result:
[13]:
rise_schaefer1985_jed = rises.day_jed[0]
3.4 Belokrylov et al. (2011)
This model also scans the twilight. It uses reference_extinction as the reference extinction coefficient to correct the star’s magnitude.
[14]:
model = montu.HeliacalRise(
model="belokrylov2011",
k=0.25, # atmospheric extinction coefficient
reference_extinction=0.25, # reference extinction coefficient
step_minutes=2.0, # time step
)
rises = model.compute(sirius, site, start_date, end_date, verbose=True)
model.print_rises(rises, title="Heliacal rise of Sirius — Belokrylov 2011", body_label="Sirius")
HeliacalRise verbose — model=belokrylov2011
quantities: h_star: object altitude; h_sun: solar altitude; m': extinction-corrected magnitude; rho: Sun-object separation; h_theor: limiting solar altitude
interval: 139-07-01 00:00:00 -> 139-07-30 00:00:00
model parameters:
k=0.25
reference_extinction=0.25
step_minutes=2.0
twilight_sunbelow=-18.0
criterion: h_star > 0° and h_sun <= h_theor
morning scan: h_sun=-18.0° to sunrise, every 2.0 minutes
day 001 | 139-07-01 00:00:00 | visible=False
day 002 | 139-07-02 00:00:00 | visible=False
day 003 | 139-07-03 00:00:00 | visible=False
day 004 | 139-07-04 00:00:00 | visible=False
day 005 | 139-07-05 00:00:00 | visible=False
day 006 | 139-07-06 00:00:00 | visible=False
day 007 | 139-07-07 00:00:00 | visible=False
day 008 | 139-07-08 00:00:00 | visible=False
day 009 | 139-07-09 00:00:00 | visible=False
day 010 | 139-07-10 00:00:00 | visible=False
day 011 | 139-07-11 00:00:00 | visible=False
day 012 | 139-07-12 00:00:00 | visible=False
day 013 | 139-07-13 00:00:00 | visible=False
day 014 | 139-07-14 00:00:00 | visible=True | h_star=2.417° | h_sun=-6.105° | m'=2.628 | rho=48.064° | h_theor=-6.039°
-> heliacal rise detected
criterion satisfied: h_star > 0° and h_sun <= h_theor
day 015 | 139-07-15 00:00:00 | visible=True | h_star=1.684° | h_sun=-7.776° | m'=3.575 | rho=48.591° | h_theor=-7.186°
day 016 | 139-07-16 00:00:00 | visible=True | h_star=1.381° | h_sun=-8.944° | m'=4.087 | rho=49.113° | h_theor=-7.797°
day 017 | 139-07-17 00:00:00 | visible=True | h_star=1.509° | h_sun=-9.704° | m'=3.859 | rho=49.644° | h_theor=-7.499°
day 018 | 139-07-18 00:00:00 | visible=True | h_star=1.211° | h_sun=-10.851° | m'=4.414 | rho=50.184° | h_theor=-9.409°
day 019 | 139-07-19 00:00:00 | visible=True | h_star=1.345° | h_sun=-11.608° | m'=4.154 | rho=50.734° | h_theor=-7.825°
day 020 | 139-07-20 00:00:00 | visible=True | h_star=1.051° | h_sun=-12.747° | m'=4.757 | rho=51.291° | h_theor=-11.296°
day 021 | 139-07-21 00:00:00 | visible=True | h_star=1.188° | h_sun=-13.500° | m'=4.461 | rho=51.859° | h_theor=-9.616°
day 022 | 139-07-22 00:00:00 | visible=True | h_star=0.899° | h_sun=-14.632° | m'=5.118 | rho=52.433° | h_theor=-13.281°
day 023 | 139-07-23 00:00:00 | visible=True | h_star=1.040° | h_sun=-15.382° | m'=4.783 | rho=53.017° | h_theor=-11.384°
day 024 | 139-07-24 00:00:00 | visible=True | h_star=0.753° | h_sun=-16.507° | m'=5.500 | rho=53.607° | h_theor=-15.381°
day 025 | 139-07-25 00:00:00 | visible=True | h_star=0.897° | h_sun=-17.254° | m'=5.123 | rho=54.206° | h_theor=-13.247°
day 026 | 139-07-26 00:00:00 | visible=True | h_star=1.042° | h_sun=-18.000° | m'=4.778 | rho=54.812° | h_theor=-11.295°
day 027 | 139-07-27 00:00:00 | visible=True | h_star=2.045° | h_sun=-18.000° | m'=3.066 | rho=55.427° | h_theor=-6.328°
day 028 | 139-07-28 00:00:00 | visible=True | h_star=3.046° | h_sun=-18.000° | m'=2.038 | rho=56.049° | h_theor=-5.042°
day 029 | 139-07-29 00:00:00 | visible=True | h_star=4.044° | h_sun=-18.000° | m'=1.358 | rho=56.678° | h_theor=-4.184°
day 030 | 139-07-30 00:00:00 | visible=True | h_star=5.040° | h_sun=-18.000° | m'=0.878 | rho=57.312° | h_theor=-3.573°
Heliacal rise of Sirius — Belokrylov 2011 — 1 date(s)
[1] 139-07-14 00:00:00 04:45:14.533 0139-07-13 00:00:00.000000 [hrw 2921] IV shemu 30 Sirius 2.42° Sun -6.11°
source: Belokrylov, R. O., Belokrylov, S. V., & Nickiforov, M. G. (2011). "Model of the stellar visibility during twilight". Bulgarian Astronomical Journal, 16, 50-72.
Save the result:
[15]:
rise_belokrylov2011_jed = rises.day_jed[0]
4. Opening the black box
Next we explain, in basic terms, what the HeliacalRise.compute method does internally for each model, so that it is clear how MontuPython’s own tools are used for this important calculation, and also which formulas each model applies.
To do this we use two dates in each case: one on which the heliacal rise does not occur and one on which it does, so we can see when the criterion fails and when it is satisfied.
4.1. Ptolemy model: Arcus Visionis
The source used by MontuPython for this model is: Toomer, G. J. (1998). Ptolemy’s Almagest. Princeton University Press. Book XIII, Chapter 7: “On the heliacal risings and settings of the planets”
This model asks where the Sun is when Sirius rises on the eastern horizon (\(h_\star=0\)). If \(h_\odot\) is the solar altitude at Sirius’s rising time, the calculated arc of vision is
Ptolemy’s method assumes that Sirius will be visible if \(AV_{\rm calc}\ge AV_{\rm crit}\).
MontuPython adopts \(AV_{\rm crit}=15°\) for first-magnitude stars. This is a historical threshold, not a consequence of geometry. For other bodies, the value of \(AV_{\rm crit}\) changes according to Ptolemy’s accumulated experience and his sources:
Venus: \(5^\circ\)
Jupiter: \(10^\circ\)
Mercury: \(10^\circ\)
Saturn: \(11^\circ\)
Mars: \(11.5^\circ\)
To calculate the Arcus Visionis we need, each day: 1) Sirius’s rising time, 2) the Sun’s altitude above the horizon, and 3) the Arcus Visionis between the Sun and Sirius at rising. In MontuPython we do it like this:
Let’s try first with the day before the heliacal rise. The rising time is:
[16]:
t_test = montu.Time(rise_ptolemy_jed - 1, format="jd", calendar="mixed")
conditions_sirius = sirius.conditions_in_sky(t_test, site)
jed_rise_star_utc = conditions_sirius.rise_time[0]
site.get_local_time(jed_rise_star_utc)
[16]:
'04:11:49.837'
Sirius’s and the Sun’s altitudes at rising are:
[17]:
mt_rise = montu.Time(jed_rise_star_utc, format="jd", calendar="mixed")
h_sirius = sirius.where_in_sky(mt_rise, site).el[0]
sun.where_in_sky(mt_rise, site)
h_sun = sun.position.el
arcus_visionis_calc = h_sirius - h_sun
h_sirius, h_sun, arcus_visionis_calc
[17]:
(np.float64(-0.5349187387273291),
-13.28456084132389,
np.float64(12.74964210259656))
As we can see, the calculated Arcus Visionis for that day is smaller than the threshold \(AV_{\rm crit}=15°\). Therefore that day is not considered the heliacal-rise day.
Now let’s try the heliacal-rise day:
[18]:
t_test = montu.Time(rise_ptolemy_jed, format="jd", calendar="mixed")
conditions_sirius = sirius.conditions_in_sky(t_test, site)
jed_rise_star_utc = conditions_sirius.rise_time[0]
mt_rise = montu.Time(jed_rise_star_utc, format="jd", calendar="mixed")
h_sirius = sirius.where_in_sky(mt_rise, site).el[0]
sun.where_in_sky(mt_rise, site)
h_sun = sun.position.el
arcus_visionis_calc = h_sirius - h_sun
site.get_local_time(jed_rise_star_utc), h_sirius, h_sun, arcus_visionis_calc
[18]:
('04:07:53.948',
np.float64(-0.5348629198277512),
-14.153722050001877,
np.float64(13.618859130174126))
On this day the Arcus Visionis is already larger, and therefore this is the heliacal rise.
The following day gives:
[19]:
t_test = montu.Time(rise_ptolemy_jed + 1, format="jd", calendar="mixed")
conditions_sirius = sirius.conditions_in_sky(t_test, site)
jed_rise_star_utc = conditions_sirius.rise_time[0]
mt_rise = montu.Time(jed_rise_star_utc, format="jd", calendar="mixed")
h_sirius = sirius.where_in_sky(mt_rise, site).el[0]
sun.where_in_sky(mt_rise, site)
h_sun = sun.position.el
arcus_visionis_calc = h_sirius - h_sun
site.get_local_time(jed_rise_star_utc), h_sirius, h_sun, arcus_visionis_calc
[19]:
('04:03:58.050',
np.float64(-0.5348388207339038),
-15.02248540016071,
np.float64(14.487646579426805))
The Arcus Visionis is already greater than the critical value.
It is worth noting that Sirius’s altitude at rising is negative because MontuPython’s rise and set models include atmospheric refraction, which in a standard atmosphere is close to 34 arcminutes. MontuPython actually computes this refraction from the environmental conditions of the observing site (see the Observer class).
4.2. Schaefer (1987): fixed solar depression
The source used by MontuPython is Schaefer, B. E. (1987), “Heliacal rise phenomena”, Journal for the History of Astronomy 18(11), 19–33.
This model does not wait for Sirius’s rising time. It evaluates the sky once each morning: when the Sun reaches the chosen depression \(h_\odot=-11°\). MontuPython obtains that instant with Sun.when_is_twilight().
For Sirius at altitude \(h_\star>0\), the air mass is approximated by the approximation of a plain-parallel atmosphere:
With catalogue magnitude \(V\), extinction coefficient \(k\), and zenith limiting magnitude \(V_{\rm lim,z}\), the observed magnitude of the star \(V_{\rm obs}\) and the limiting magnitude at the height at which it is observed \(V_{\rm lim}(h_\star)\) is computed using:
Since smaller magnitudes correspond to brighter objects, a margin \(V_{\rm lim}-V_{\rm obs}<0\) means that Sirius is still not visible. The criterion for observability is:
This criterion is fullfilled at many nigths during the year. For finding the heliacal rise, the algorithm sweep several twilights, finding the first where the condition is fulfilled.
Each morning we need: 1) the instant when the Sun is at \(-11°\), 2) Sirius’s altitude at that instant, and 3) the air mass and the two magnitudes. We start with the day before the heliacal rise:
[20]:
t_test = montu.Time(rise_schaefer1987_jed - 1, format="jd", calendar="mixed")
# Morning instant with the Sun at -11°
jed_twilight = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-11.0))
mt_twilight = montu.Time(jed_twilight, format="jd", calendar="mixed")
site.get_local_time(jed_twilight)
[20]:
'04:24:19.401'
Now we get the elevation of the star:
[21]:
h_sirius = sirius.where_in_sky(mt_twilight, site).el[0]
k = 0.25
V_lim_zenith = 6.0
X = 1 / np.sin(np.deg2rad(h_sirius))
V_obs = V_SIRIUS + k * X
V_lim = V_lim_zenith - k * (X - 1)
print(f"Tested date: {t_test.readable.datemix}")
print(f"Elevation of Sirius at twilight ({site.get_local_time(jed_twilight)}): {h_sirius:.2f}°")
print(f"Air mass: {X:.2f}")
print(f"Observed magnitude: {V_obs:.2f}")
print(f"Limiting magnitude: {V_lim:.2f}")
Tested date: 139-07-20 00:00:00
Elevation of Sirius at twilight (04:24:19.401): 2.99°
Air mass: 19.19
Observed magnitude: 3.36
Limiting magnitude: 1.45
The previous day Sirius is already above the horizon when the Sun reaches \(-11°\), but \(V_{\rm obs}>V_{\rm lim}\): its extinguished magnitude is still too faint compared with the local threshold. That is why the morning does not count as a heliacal rise.
The heliacal-rise day:
[22]:
t_test = montu.Time(rise_schaefer1987_jed, format="jd", calendar="mixed")
jed_twilight = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-11.0))
mt_twilight = montu.Time(jed_twilight, format="jd", calendar="mixed")
h_sirius = sirius.where_in_sky(mt_twilight, site).el[0]
sun.where_in_sky(mt_twilight, site)
h_sun = sun.position.el
X = 1 / np.sin(np.deg2rad(h_sirius))
V_obs = V_SIRIUS + k * X
V_lim = V_lim_zenith - k * (X - 1)
print(f"Tested date: {t_test.readable.datemix} ({t_test.readable.datesot})")
print(f"Elevation of Sirius at twilight ({site.get_local_time(jed_twilight)}): {h_sirius:.2f}°")
print(f"Air mass: {X:.2f}")
print(f"Observed magnitude: {V_obs:.2f}")
print(f"Limiting magnitude: {V_lim:.2f}")
Tested date: 139-07-21 00:00:00 ([hrw 2922] I akhet 2)
Elevation of Sirius at twilight (04:24:56.311): 3.96°
Air mass: 14.50
Observed magnitude: 2.18
Limiting magnitude: 2.63
Now Sirius is higher (\(h_\star\approx 4.5°\)), the air mass is lower, and the margin becomes positive. That is the first visible morning according to Schaefer (1987).
4.3. Schaefer (1985): twilight scan
The source used by MontuPython is Schaefer, B. E. (1985), “Predicting Heliacal Risings and Settings”, Sky & Telescope 70, $$ dawn (\(h_\odot=-18°\)) to sunrise in steps of 2 minutes. At each step it recalculates the positions of the Sun and Sirius. If Sirius is below the horizon, it moves to the next step.
The air mass uses Rozenberg’s formula, which is more stable near the horizon:
At each step, with \(z\), \(h_\odot\), and the azimuthal separation \(\Delta A\) in radians it compute the following parameter:
This parameters allows us to calculate the sky-brightnesss:
The physiological threshold is
with \((C_5,K_5)=(1.58\times10^{-10},0.0126)\) if \(B<1649\), and \((4.4668\times10^{-9},1.258\times10^{-6})\) otherwise.
Finally the limiting magnitude is given by,
Sirius is visible when \(V\le V_{\rm lim}\). The first step that satisfies the criterion is the reported instant.
Let’s first define Rozenberg’s air mass and the model parameters. Then we evaluate the day before the heliacal rise:
[23]:
def rozenberg_airmass(h_star):
z = np.deg2rad(90.0 - h_star)
cos_z = np.cos(z)
return 1 / (cos_z + 0.025 * np.exp(-11 * cos_z))
k = 0.25
V_lim_zenith = 6.0
step_minutes = 2.0
t_test = montu.Time(rise_schaefer1985_jed - 1, format="jd", calendar="mixed")
dawn_jed = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-18.0))
sun.conditions_in_sky(t_test, site)
sunrise_jed = sun.condition.rise_time
print("Scan window:", site.get_local_time(dawn_jed), "→", site.get_local_time(sunrise_jed))
Scan window: 03:43:14.538 → 05:12:00.485
Let’s scan:
[24]:
found = False
for at_jed in np.arange(dawn_jed, sunrise_jed, step_minutes / 1440):
mt = montu.Time(at_jed, format="jd", calendar="mixed")
star = sirius.where_in_sky(mt, site).iloc[0]
if star.el <= 0:
continue
# Compute the air mass
z = np.deg2rad(90.0 - star.el)
h_sun = np.deg2rad(sun.position.el)
X = rozenberg_airmass(star.el)
# Compute the angular separation
sun.where_in_sky(mt, site)
delta_az = np.deg2rad(abs((star.az - sun.position.az + 180) % 360 - 180))
# Compute the parameters for the sky brightness
x = -0.2 * (V_lim_zenith - 7.93 + k)
B0 = 79.4 * (10**x - 1)**2 - 589 * k
L5 = 4.75 - delta_az * z / 3 + h_sun * (8.2 * z + 12) + 2.86 * z
B = B0 + (k / 0.20) * 10**L5 if L5 >= -2.07 else B0 + 589 * k
C5, K5 = (1.58e-10, 0.0126) if B < 1649 else (4.4668e-9, 1.258e-6)
E_th = C5 * (1 + np.sqrt(K5 * B))**2
V_lim = -16.57 - k * X - 2.5 * np.log10(E_th)
# Check if the star is visible
visible = V_SIRIUS <= V_lim
print(
site.get_local_time(at_jed),
f"| h_star={star.el:5.2f}°",
f"| h_sun={sun.position.el:6.2f}°",
f"| V_lim={V_lim:6.2f}",
f"| V_star={V_SIRIUS:6.2f}",
f"| margin={V_lim - V_SIRIUS:6.2f}",
f"| visible={visible}",
)
if visible:
found = True
break
print()
print("Was any visible instant found?", found)
04:35:14.537 | h_star= 0.27° | h_sun= -8.20° | V_lim= -2.52 | V_star= -1.44 | margin= -1.08 | visible=False
04:37:14.538 | h_star= 0.70° | h_sun= -7.80° | V_lim= -3.93 | V_star= -1.44 | margin= -2.49 | visible=False
04:39:14.539 | h_star= 1.13° | h_sun= -7.39° | V_lim= -3.02 | V_star= -1.44 | margin= -1.58 | visible=False
04:41:14.540 | h_star= 1.56° | h_sun= -6.97° | V_lim= -2.38 | V_star= -1.44 | margin= -0.94 | visible=False
04:43:14.532 | h_star= 1.99° | h_sun= -6.55° | V_lim= -1.93 | V_star= -1.44 | margin= -0.49 | visible=False
04:45:14.533 | h_star= 2.42° | h_sun= -6.11° | V_lim= -1.64 | V_star= -1.44 | margin= -0.20 | visible=False
04:47:14.534 | h_star= 2.84° | h_sun= -5.64° | V_lim= -1.46 | V_star= -1.44 | margin= -0.02 | visible=False
04:49:14.535 | h_star= 3.27° | h_sun= -5.12° | V_lim= -1.40 | V_star= -1.44 | margin= 0.04 | visible=True
Was any visible instant found? True
The previous day Sirius does rise during twilight, but in every step \(V>V_{\rm lim}\): the sky is still too bright or the star too low. There is no heliacal rise.
Let’s repeat the same scan on the heliacal-rise day:
[25]:
t_test = montu.Time(rise_schaefer1985_jed, format="jd", calendar="mixed")
dawn_jed = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-18.0))
sun.conditions_in_sky(t_test, site)
sunrise_jed = sun.condition.rise_time
print("Scan window:", site.get_local_time(dawn_jed), "→", site.get_local_time(sunrise_jed))
print()
for at_jed in np.arange(dawn_jed, sunrise_jed, step_minutes / 1440):
mt = montu.Time(at_jed, format="jd", calendar="mixed")
star = sirius.where_in_sky(mt, site).iloc[0]
if star.el <= 0:
continue
sun.where_in_sky(mt, site)
z = np.deg2rad(90.0 - star.el)
h_sun = np.deg2rad(sun.position.el)
delta_az = np.deg2rad(abs((star.az - sun.position.az + 180) % 360 - 180))
X = rozenberg_airmass(star.el)
x = -0.2 * (V_lim_zenith - 7.93 + k)
B0 = 79.4 * (10**x - 1)**2 - 589 * k
L5 = 4.75 - delta_az * z / 3 + h_sun * (8.2 * z + 12) + 2.86 * z
B = B0 + (k / 0.20) * 10**L5 if L5 >= -2.07 else B0 + 589 * k
C5, K5 = (1.58e-10, 0.0126) if B < 1649 else (4.4668e-9, 1.258e-6)
E_th = C5 * (1 + np.sqrt(K5 * B))**2
V_lim = -16.57 - k * X - 2.5 * np.log10(E_th)
visible = V_SIRIUS <= V_lim
print(
site.get_local_time(at_jed),
f"| h_star={star.el:5.2f}°",
f"| h_sun={sun.position.el:6.2f}°",
f"| V_lim={V_lim:6.2f}",
f"| V_star={V_SIRIUS:6.2f}",
f"| margin={V_lim - V_SIRIUS:6.2f}",
f"| visible={visible}",
)
if visible:
print()
print("First visible instant → this is the heliacal rise according to Schaefer (1985).")
break
Scan window: 03:43:53.029 → 05:12:29.558
04:31:53.027 | h_star= 0.40° | h_sun= -8.96° | V_lim= -4.66 | V_star= -1.44 | margin= -3.22 | visible=False
04:33:53.028 | h_star= 0.83° | h_sun= -8.57° | V_lim= -3.46 | V_star= -1.44 | margin= -2.02 | visible=False
04:35:53.028 | h_star= 1.26° | h_sun= -8.18° | V_lim= -2.61 | V_star= -1.44 | margin= -1.17 | visible=False
04:37:53.029 | h_star= 1.68° | h_sun= -7.78° | V_lim= -2.01 | V_star= -1.44 | margin= -0.57 | visible=False
04:39:53.030 | h_star= 2.11° | h_sun= -7.37° | V_lim= -1.58 | V_star= -1.44 | margin= -0.14 | visible=False
04:41:53.023 | h_star= 2.54° | h_sun= -6.95° | V_lim= -1.29 | V_star= -1.44 | margin= 0.15 | visible=True
First visible instant → this is the heliacal rise according to Schaefer (1985).
The transition is clear: for several minutes the margin is negative, and at the first step with a positive margin the model declares the heliacal rise.
4.4. Belokrylov et al. (2011): limiting solar altitude
The source used by MontuPython is Belokrylov, R. O., Belokrylov, S. V., and Nickiforov, M. G. (2011), “Model of the stellar visibility during twilight”, Bulgarian Astronomical Journal 16, 50–72, equations (5)–(8).
This model also scans the twilight from \(h_\odot=-18°\) to sunrise, but translates visibility into a limiting solar altitude. First it corrects the magnitude for extinction (with \(k_0=0.25\)):
Then it obtains the limiting solar altitude (this comes from empirical fits):
The Sun–Sirius angular separation is
and the correction for proximity to the Sun,
The star is visible if \(h_\odot\le h_{\rm theor}\).
Let’s evaluate the day before the heliacal rise with the same kind of scan:
[26]:
k = 0.25
k0 = 0.25
step_minutes = 2.0
t_test = montu.Time(rise_belokrylov2011_jed - 1, format="jd", calendar="mixed")
dawn_jed = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-18.0))
sun.conditions_in_sky(t_test, site)
sunrise_jed = sun.condition.rise_time
print("Scan window:", site.get_local_time(dawn_jed), "→", site.get_local_time(sunrise_jed))
Scan window: 03:42:36.885 → 05:11:31.895
Let’s scan:
[27]:
found = False
for at_jed in np.arange(dawn_jed, sunrise_jed, step_minutes / 1440):
mt = montu.Time(at_jed, format="jd", calendar="mixed")
star = sirius.where_in_sky(mt, site).iloc[0]
if star.el <= 0:
continue
# Compute the air mass
X = rozenberg_airmass(star.el)
m_prime = V_SIRIUS + k * (X - 1) + (k - k0)
h_lim = -2.47 - 1.23 * m_prime if m_prime < 4.2 else 15.62 - 5.61 * m_prime
# Compute the angular separation
sun.where_in_sky(mt, site)
h1, A1 = np.deg2rad(star.el), np.deg2rad(star.az)
h2, A2 = np.deg2rad(sun.position.el), np.deg2rad(sun.position.az)
cos_rho = np.sin(h1) * np.sin(h2) + np.cos(h1) * np.cos(h2) * np.cos(A1 - A2)
rho = np.rad2deg(np.arccos(np.clip(cos_rho, -1, 1)))
# Compute the correction for proximity to the Sun
delta_h = -0.0338 * max(0, 58 - rho)
h_theor = h_lim + delta_h
visible = sun.position.el <= h_theor
print(
site.get_local_time(at_jed),
f"| h_star={star.el:5.2f}°",
f"| h_sun={sun.position.el:6.2f}°",
f"| m'={m_prime:5.2f}",
f"| h_theor={h_theor:6.2f}°",
f"| visible={visible}",
)
if visible:
found = True
break
print()
print("Was any visible instant found?", found)
04:38:36.886 | h_star= 0.15° | h_sun= -7.41° | m'= 7.59 | h_theor=-27.31° | visible=False
04:40:36.887 | h_star= 0.58° | h_sun= -7.00° | m'= 6.00 | h_theor=-18.37° | visible=False
04:42:36.888 | h_star= 1.01° | h_sun= -6.57° | m'= 4.85 | h_theor=-11.92° | visible=False
04:44:36.889 | h_star= 1.44° | h_sun= -6.13° | m'= 3.98 | h_theor= -7.72° | visible=False
04:46:36.890 | h_star= 1.87° | h_sun= -5.67° | m'= 3.30 | h_theor= -6.88° | visible=False
04:48:36.882 | h_star= 2.30° | h_sun= -5.15° | m'= 2.76 | h_theor= -6.22° | visible=False
04:50:36.883 | h_star= 2.72° | h_sun= -4.53° | m'= 2.32 | h_theor= -5.68° | visible=False
04:52:36.884 | h_star= 3.15° | h_sun= -3.56° | m'= 1.95 | h_theor= -5.23° | visible=False
04:54:36.885 | h_star= 3.58° | h_sun= -2.86° | m'= 1.64 | h_theor= -4.85° | visible=False
04:56:36.886 | h_star= 4.00° | h_sun= -2.47° | m'= 1.38 | h_theor= -4.53° | visible=False
04:58:36.887 | h_star= 4.43° | h_sun= -2.16° | m'= 1.15 | h_theor= -4.25° | visible=False
05:00:36.888 | h_star= 4.85° | h_sun= -1.88° | m'= 0.96 | h_theor= -4.01° | visible=False
05:02:36.889 | h_star= 5.28° | h_sun= -1.60° | m'= 0.78 | h_theor= -3.79° | visible=False
05:04:36.890 | h_star= 5.70° | h_sun= -1.32° | m'= 0.63 | h_theor= -3.60° | visible=False
05:06:36.882 | h_star= 6.12° | h_sun= -1.03° | m'= 0.50 | h_theor= -3.44° | visible=False
05:08:36.883 | h_star= 6.54° | h_sun= -0.73° | m'= 0.37 | h_theor= -3.29° | visible=False
05:10:36.884 | h_star= 6.96° | h_sun= -0.41° | m'= 0.27 | h_theor= -3.15° | visible=False
Was any visible instant found? False
The previous day the Sun never becomes low enough relative to \(h_{\rm theor}\). There is no heliacal rise.
The heliacal-rise day:
[28]:
t_test = montu.Time(rise_belokrylov2011_jed, format="jd", calendar="mixed")
dawn_jed = min(montu.Sun.when_is_twilight(t_test, site, sunbelow=-18.0))
sun.conditions_in_sky(t_test, site)
sunrise_jed = sun.condition.rise_time
print("Scan window:", site.get_local_time(dawn_jed), "→", site.get_local_time(sunrise_jed))
print()
for at_jed in np.arange(dawn_jed, sunrise_jed, step_minutes / 1440):
mt = montu.Time(at_jed, format="jd", calendar="mixed")
star = sirius.where_in_sky(mt, site).iloc[0]
if star.el <= 0:
continue
sun.where_in_sky(mt, site)
X = rozenberg_airmass(star.el)
m_prime = V_SIRIUS + k * (X - 1) + (k - k0)
h_lim = -2.47 - 1.23 * m_prime if m_prime < 4.2 else 15.62 - 5.61 * m_prime
h1, A1 = np.deg2rad(star.el), np.deg2rad(star.az)
h2, A2 = np.deg2rad(sun.position.el), np.deg2rad(sun.position.az)
cos_rho = np.sin(h1) * np.sin(h2) + np.cos(h1) * np.cos(h2) * np.cos(A1 - A2)
rho = np.rad2deg(np.arccos(np.clip(cos_rho, -1, 1)))
delta_h = -0.0338 * max(0, 58 - rho)
h_theor = h_lim + delta_h
visible = sun.position.el <= h_theor
print(
site.get_local_time(at_jed),
f"| h_star={star.el:5.2f}°",
f"| h_sun={sun.position.el:6.2f}°",
f"| m'={m_prime:5.2f}",
f"| rho={rho:5.1f}°",
f"| h_theor={h_theor:6.2f}°",
f"| visible={visible}",
)
if visible:
print()
print("First visible instant → this is the heliacal rise according to Belokrylov (2011).")
break
Scan window: 03:43:14.538 → 05:12:00.485
04:35:14.537 | h_star= 0.27° | h_sun= -8.20° | m'= 7.09 | rho= 48.1° | h_theor=-24.47° | visible=False
04:37:14.538 | h_star= 0.70° | h_sun= -7.80° | m'= 5.64 | rho= 48.1° | h_theor=-16.35° | visible=False
04:39:14.539 | h_star= 1.13° | h_sun= -7.39° | m'= 4.58 | rho= 48.1° | h_theor=-10.41° | visible=False
04:41:14.540 | h_star= 1.56° | h_sun= -6.97° | m'= 3.77 | rho= 48.1° | h_theor= -7.45° | visible=False
04:43:14.532 | h_star= 1.99° | h_sun= -6.55° | m'= 3.14 | rho= 48.1° | h_theor= -6.67° | visible=False
04:45:14.533 | h_star= 2.42° | h_sun= -6.11° | m'= 2.63 | rho= 48.1° | h_theor= -6.04° | visible=True
First visible instant → this is the heliacal rise according to Belokrylov (2011).
Here the criterion is read differently: instead of comparing magnitudes, we check whether the Sun’s actual altitude is already below the theoretical altitude \(h_{\rm theor}\) allowed for that position of Sirius.
5. Comparison of the four models
Let’s summarize the dates obtained with the HeliacalRise interface in section 3. All four models use the same site, the same interval, and the same star; only the visibility criterion changes.
[29]:
print("Ptolemy")
print(" Date:", montu.Time(rise_ptolemy_jed, format="jd").readable.datemix)
print(" Sothic:", montu.Time(rise_ptolemy_jed, format="jd").readable.datesot)
print()
print("Schaefer 1987")
print(" Date:", montu.Time(rise_schaefer1987_jed, format="jd").readable.datemix)
print(" Sothic:", montu.Time(rise_schaefer1987_jed, format="jd").readable.datesot)
print()
print("Schaefer 1985")
print(" Date:", montu.Time(rise_schaefer1985_jed, format="jd").readable.datemix)
print(" Sothic:", montu.Time(rise_schaefer1985_jed, format="jd").readable.datesot)
print()
print("Belokrylov 2011")
print(" Date:", montu.Time(rise_belokrylov2011_jed, format="jd").readable.datemix)
print(" Sothic:", montu.Time(rise_belokrylov2011_jed, format="jd").readable.datesot)
Ptolemy
Date: 139-07-20 00:00:00
Sothic: [hrw 2922] I akhet 1
Schaefer 1987
Date: 139-07-21 00:00:00
Sothic: [hrw 2922] I akhet 2
Schaefer 1985
Date: 139-07-15 00:00:00
Sothic: [hrw 2921] I mesut 1
Belokrylov 2011
Date: 139-07-14 00:00:00
Sothic: [hrw 2921] IV shemu 30
With the adopted parameters, Ptolemy and Schaefer (1987) place the transition on 19 July 2782 BCE; Schaefer (1985) and Belokrylov et al. (2011) place it on 11 July. The eight-day difference is not a numerical error: each algorithm defines «visible» differently.
In addition, \(k\) changes with aerosols and humidity; \(V_{\rm lim,z}\) depends on the observer and the night-sky background; an elevated horizon delays the appearance; and clouds or haze can dominate the result. That is why a calculated heliacal-rise date should always be reported together with the model and its parameters.
6. Conclusions
We have deliberately separated two layers:
geometry, computed by MontuPython with
Time,Observer,Stars.where_in_sky,Stars.conditions_in_sky,Sun.where_in_sky,Sun.when_is_twilight, andSun.conditions_in_sky;visibility, reconstructed in open cells from the equations of each model.
The heliacal rise is the first morning that goes from invisible to visible, not simply the first visible instant found within an interval. The step-by-step reconstructions in section 4 show why one day fails the criterion and the next one satisfies it.
This disagreement between models is scientifically informative: it quantifies how «first visibility» depends on the observational model. For a historical study it is advisable always to report site, horizon, atmospheric parameters, model, time step, and calendar system, rather than presenting a calculated date as exact.
Implemented references
Schaefer, B. E. (1985), “Predicting Heliacal Risings and Settings”, Sky & Telescope 70, 261–263; BASIC listing, lines 34–35 and 55–81.
Schaefer, B. E. (1987), “Heliacal rise phenomena”, Journal for the History of Astronomy 18(11), 19–33.
Belokrylov, R. O., Belokrylov, S. V., and Nickiforov, M. G. (2011), “Model of the stellar visibility during twilight”, Bulgarian Astronomical Journal 16, 50–72, equations (5)–(8).
Toomer, G. J. (1998), Ptolemy’s Almagest, Princeton University Press, Book XIII, Chapter 7.
Powered by MontuPython. For more examples see MontuPython GitHub repo.
Jorge I. Zuluaga © 2023-present