Nutze Skyfield fuer praezisere Daemmerungsberechnung

This commit is contained in:
2026-07-13 19:35:39 +02:00
parent dc5292735f
commit 97357a88e0
1092 changed files with 370248 additions and 61 deletions
+325
View File
@@ -0,0 +1,325 @@
# -*- coding: utf-8 -*-
"""Routines for computing magnitudes.
Planetary routines adapted from:
https://arxiv.org/pdf/1808.01973.pdf
Which links to:
https://sourceforge.net/projects/planetary-magnitudes/
Which has directories with three successive versions of their magnitude
computation, the most recent of which provides the files on which this
Python code is based:
Ap_Mag_V3.f90
Ap_Mag_Output_V3.txt
Ap_Mag_Input_V3.txt
* ``r`` planet’s distance from the Sun.
* ``delta`` from Earth?
* ``ph_ang`` illumination phase angle (degrees)
"""
from numpy import array, clip, exp, log10, nan, sin, where
from .constants import RAD2DEG
from .functions import angle_between, length_of
from .naifcodes import _target_name
# See "design/planet_tilts.py" in the Skyfield repository.
_SATURN_POLE = array([0.08547883, 0.07323576, 0.99364475])
_SATURN_POLE_2D = _SATURN_POLE[:, None]
_URANUS_POLE = array([-0.21199958, -0.94155916, -0.26176809])
_URANUS_POLE_2D = _URANUS_POLE[:, None]
def planetary_magnitude(position):
"""Given the position of a planet, return its visual magnitude.
>>> from skyfield.api import load
>>> from skyfield.magnitudelib import planetary_magnitude
>>> ts = load.timescale()
>>> t = ts.utc(2020, 7, 31)
>>> eph = load('de421.bsp')
>>> astrometric = eph['earth'].at(t).observe(eph['jupiter barycenter'])
>>> print('%.2f' % planetary_magnitude(astrometric))
-2.73
The formulae are from `Mallama and Hilton “Computing Apparent
Planetary Magnitude for the Astronomical Almanac” (2018)
<https://arxiv.org/pdf/1808.01973.pdf>`_. Two of the formulae have
inherent limits:
* Saturn’s magnitude is unknown and the function will return ``nan``
(the floating-point value “Not a Number”) if the “illumination
phase angle” — the angle of the vertex observer-Saturn-Sun —
exceeds 6.5°.
* Neptune’s magnitude is unknown and will return ``nan`` if the
illumination phase angle exceeds 1.9° and the position's date is
before the year 2000.
And one formula is not fully implemented (though contributions are
welcome!):
* Skyfield does not compute which features on Mars are facing the
observer, which can introduce an error of ±0.06 magnitude.
"""
target = position.target
function = _FUNCTIONS.get(target)
if function is None:
name = _target_name(target)
raise ValueError('cannot compute the magnitude of target %s' % name)
# Shamelessly treat the Sun as sitting at the Solar System Barycenter.
sun_to_observer = position.center_barycentric.xyz.au
observer_to_planet = position.xyz.au
sun_to_planet = sun_to_observer + observer_to_planet
r = length_of(sun_to_planet)
delta = length_of(observer_to_planet)
ph_ang = angle_between(sun_to_planet, observer_to_planet) * RAD2DEG
if function is _saturn_magnitude:
if len(sun_to_planet.shape) > 1:
pole = _SATURN_POLE_2D
else:
pole = _SATURN_POLE
a = angle_between(pole, sun_to_planet)
sun_sub_lat = a * RAD2DEG - 90.0
a = angle_between(pole, observer_to_planet)
observer_sub_lat = a * RAD2DEG - 90.0
return function(r, delta, ph_ang, sun_sub_lat, observer_sub_lat)
if function is _uranus_magnitude:
if len(sun_to_planet.shape) > 1:
pole = _URANUS_POLE_2D
else:
pole = _URANUS_POLE
a = angle_between(pole, sun_to_planet)
sun_sub_lat = a * RAD2DEG - 90.0
a = angle_between(pole, observer_to_planet)
observer_sub_lat = a * RAD2DEG - 90.0
return function(r, delta, ph_ang, sun_sub_lat, observer_sub_lat)
if function is _neptune_magnitude:
year = position.t.J
return function(r, delta, ph_ang, year)
return function(r, delta, ph_ang)
def _mercury_magnitude(r, delta, ph_ang):
distance_mag_factor = 5 * log10(r * delta)
ph_ang_factor = (
6.3280e-02 * ph_ang
- 1.6336e-03 * ph_ang**2
+ 3.3644e-05 * ph_ang**3
- 3.4265e-07 * ph_ang**4
+ 1.6893e-09 * ph_ang**5
- 3.0334e-12 * ph_ang**6
)
return -0.613 + distance_mag_factor + ph_ang_factor
def _venus_magnitude(r, delta, ph_ang):
distance_mag_factor = 5 * log10(r * delta)
condition = ph_ang < 163.7
a0 = where(condition, 0.0, 236.05828 + 4.384)
a1 = where(condition, - 1.044E-03, - 2.81914E+00)
a2 = where(condition, + 3.687E-04, + 8.39034E-03)
a3 = where(condition, - 2.814E-06, 0.0)
a4 = where(condition, + 8.938E-09, 0.0)
ph_ang_factor = a4
for a in a3, a2, a1, a0:
ph_ang_factor *= ph_ang
ph_ang_factor += a
return -4.384 + distance_mag_factor + ph_ang_factor
def _earth_magnitude(r, delta, ph_ang):
distance_mag_factor = 5 * log10(r * delta)
ph_ang_factor = -1.060e-03 * ph_ang + 2.054e-04 * ph_ang**2
return -3.99 + distance_mag_factor + ph_ang_factor
def _mars_magnitude(r, delta, ph_ang):
r_mag_factor = 2.5 * log10(r * r)
delta_mag_factor = 2.5 * log10(delta * delta)
distance_mag_factor = r_mag_factor + delta_mag_factor
geocentric_phase_angle_limit = 50.0
condition = ph_ang <= geocentric_phase_angle_limit
a = where(condition, 2.267E-02, - 0.02573)
b = where(condition, - 1.302E-04, 0.0003445)
ph_ang_factor = a * ph_ang + b * ph_ang**2
# Compute the effective central meridian longitude
# eff_CM = ( sub_earth_long + sub_sun_long ) / 2.
# if ( abs ( sub_earth_long - sub_sun_long ) > 180. ):
# Eff_CM = Eff_CM + 180.
# if ( Eff_CM > 360. ):
# Eff_CM = Eff_CM - 360.
# ! Use Stirling interpolation to determine the magnitude correction
# call Mars_Stirling ( 'R', eff_CM, mag_corr_rot )
# Convert the ecliptic longitude to Ls
# Ls = h_ecl_long + Ls_offset
# if ( Ls > 360. ) Ls = Ls - 360.
# if ( Ls < 0. ) Ls = Ls + 360.
# Use Stirling interpolation to determine the magnitude correction
# call Mars_Stirling ( 'O', Ls, mag_corr_orb )
# Until effects from Mars rotation are written up:
mag_corr_rot = 0.0
mag_corr_orb = 0.0
# Add factors to determine the apparent magnitude
ap_mag = where(ph_ang <= geocentric_phase_angle_limit, -1.601, -0.367)
ap_mag += distance_mag_factor + ph_ang_factor + mag_corr_rot + mag_corr_orb
return ap_mag
def _jupiter_magnitude(r, delta, ph_ang):
distance_mag_factor = 5 * log10(r * delta)
geocentric_phase_angle_limit = 12.0
ph_ang_pi = ph_ang / 180.0
ph_ang_factor = where(
ph_ang <= geocentric_phase_angle_limit,
(6.16E-04 * ph_ang - 3.7E-04) * ph_ang,
-2.5 * log10(
((((- 1.876 * ph_ang_pi + 2.809) * ph_ang_pi - 0.062) * ph_ang_pi
- 0.363) * ph_ang_pi - 1.507) * ph_ang_pi + 1.0
),
)
ap_mag = where(
ph_ang <= geocentric_phase_angle_limit,
-9.395 + distance_mag_factor + ph_ang_factor,
-9.428 + distance_mag_factor + ph_ang_factor,
)
return ap_mag
def _saturn_magnitude(r, delta, ph_ang, sun_sub_lat, earth_sub_lat, rings=True):
# Note that sun_sub_lat and earth_sub_lat should be saturnicentric
# latitude, not saturnidetic.
r_mag_factor = 2.5 * log10(r * r)
delta_mag_factor = 2.5 * log10(delta * delta)
distance_mag_factor = r_mag_factor + delta_mag_factor
# Then take the square root of the product of the saturnicentric
# latitude of the Sun and that of Earth but set to zero when the
# signs are opposite
product = sun_sub_lat * earth_sub_lat
signs_same = product >= 0.0
square_root = product ** where(signs_same, 0.5, 0.0) # avoid sqrt(neg)
sub_lat_geoc = where(signs_same, square_root, 0.0)
# Compute the effect of phase angle and inclination
geocentric_phase_angle_limit = 6.5
geocentric_inclination_limit = 27.0
is_within_geocentric_bounds = (
(ph_ang <= geocentric_phase_angle_limit)
& (sub_lat_geoc <= geocentric_inclination_limit)
)
ap_mag = where(
is_within_geocentric_bounds,
where(
rings,
# Use equation #10 for globe+rings and geocentric circumstances.
-8.914 - 1.825 * sin(sub_lat_geoc / RAD2DEG) + 0.026 * ph_ang
- 0.378 * sin(sub_lat_geoc / RAD2DEG) * exp(-2.25 * ph_ang),
# Use equation #11 for globe-alone and geocentric circumstances
-8.95 - 3.7e-4 * ph_ang + 6.16e-4 * ph_ang**2,
),
where(
(ph_ang > geocentric_phase_angle_limit) & _not(rings),
# Use equation #12 for globe-alone beyond geocentric phase
# angle limit
-8.94 + 2.446e-4 * ph_ang + 2.672e-4 * ph_ang**2
- 1.506e-6 * ph_ang**3 +4.767e-9 * ph_ang**4,
nan,
),
)
ap_mag = ap_mag + distance_mag_factor
return ap_mag
def _not(b):
return 1 - b # since ~b raises a DeprecationWarning in Python 3.13
def _uranus_magnitude(r, delta, ph_ang,
sun_sub_lat_planetog, earth_sub_lat_planetog):
distance_mag_factor = 5.0 * log10 (r * delta)
sub_lat_planetog = (abs(sun_sub_lat_planetog)
+ abs(earth_sub_lat_planetog)) / 2.0
sub_lat_factor = -0.00084 * sub_lat_planetog
geocentric_phase_angle_limit = 3.1
ap_mag = -7.110 + distance_mag_factor + sub_lat_factor
ap_mag += where(
ph_ang > geocentric_phase_angle_limit,
(1.045e-4 * ph_ang + 6.587e-3) * ph_ang,
0.0,
)
return ap_mag
def _neptune_magnitude(r, delta, ph_ang, year):
r_mag_factor = 2.5 * log10(r * r)
delta_mag_factor = 2.5 * log10(delta * delta)
distance_mag_factor = r_mag_factor + delta_mag_factor
# Equation 16 compute the magnitude at unit distance as a function of time
ap_mag = clip(-6.89 - 0.0054 * (year - 1980.0), -7.00, -6.89)
ap_mag += distance_mag_factor
geocentric_phase_angle_limit = 1.9
ap_mag = where(
ph_ang > geocentric_phase_angle_limit,
# Add phase angle factor from equation 17
# Check the year because equation 17 only pertains to t > 2000.0
where(
year >= 2000.0,
ap_mag + 7.944e-3 * ph_ang + 9.617e-5 * ph_ang**2,
nan,
),
# Otherwise leave the value unchanged.
ap_mag,
)
return ap_mag
_FUNCTIONS = {
199: _mercury_magnitude,
299: _venus_magnitude,
399: _earth_magnitude,
499: _mars_magnitude,
599: _jupiter_magnitude,
699: _saturn_magnitude,
799: _uranus_magnitude,
899: _neptune_magnitude,
# Some planets can be reasonably identified with their barycenter.
1: _mercury_magnitude,
2: _venus_magnitude,
4: _mars_magnitude,
5: _jupiter_magnitude,
6: _saturn_magnitude,
7: _uranus_magnitude,
8: _neptune_magnitude,
}