libephemeris 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libephemeris/__init__.py +151 -0
- libephemeris/angles.py +106 -0
- libephemeris/arabic_parts.py +232 -0
- libephemeris/constants.py +318 -0
- libephemeris/crossing.py +326 -0
- libephemeris/fixed_stars.py +228 -0
- libephemeris/houses.py +1625 -0
- libephemeris/lunar.py +269 -0
- libephemeris/minor_bodies.py +398 -0
- libephemeris/planets.py +1228 -0
- libephemeris/state.py +213 -0
- libephemeris/time_utils.py +136 -0
- libephemeris/utils.py +36 -0
- libephemeris-0.1.6.dist-info/METADATA +376 -0
- libephemeris-0.1.6.dist-info/RECORD +18 -0
- libephemeris-0.1.6.dist-info/WHEEL +5 -0
- libephemeris-0.1.6.dist-info/licenses/LICENSE +13 -0
- libephemeris-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Swiss Ephemeris API-compatible constants for libephemeris.
|
|
3
|
+
|
|
4
|
+
This module defines all constants used for planetary calculations, including:
|
|
5
|
+
- Planet/Body IDs: Numeric identifiers for celestial bodies
|
|
6
|
+
- Calculation Flags: Bitwise flags controlling observation parameters
|
|
7
|
+
- Sidereal Modes: Ayanamsha systems for sidereal astrology
|
|
8
|
+
- Calendar Systems: Julian vs Gregorian calendar selection
|
|
9
|
+
- Eclipse Types: Classification of solar and lunar eclipses
|
|
10
|
+
|
|
11
|
+
Constants are organized into logical groups for easy navigation.
|
|
12
|
+
Values match Swiss Ephemeris v2.x for API compatibility.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# PLANET AND BODY IDENTIFIERS
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
# Special values
|
|
20
|
+
SE_ECL_NUT: int = -1 # Nutation and obliquity calculation
|
|
21
|
+
|
|
22
|
+
# Major planets (traditional + modern)
|
|
23
|
+
SE_SUN: int = 0
|
|
24
|
+
SE_MOON: int = 1
|
|
25
|
+
SE_MERCURY: int = 2
|
|
26
|
+
SE_VENUS: int = 3
|
|
27
|
+
SE_MARS: int = 4
|
|
28
|
+
SE_JUPITER: int = 5
|
|
29
|
+
SE_SATURN: int = 6
|
|
30
|
+
SE_URANUS: int = 7
|
|
31
|
+
SE_NEPTUNE: int = 8
|
|
32
|
+
SE_PLUTO: int = 9
|
|
33
|
+
|
|
34
|
+
# Lunar nodes and apsides
|
|
35
|
+
SE_MEAN_NODE: int = 10 # Mean lunar node (Dragon's Head)
|
|
36
|
+
SE_TRUE_NODE: int = 11 # True (osculating) lunar node
|
|
37
|
+
SE_MEAN_APOG: int = 12 # Mean lunar apogee (Black Moon Lilith)
|
|
38
|
+
SE_OSCU_APOG: int = 13 # Osculating (true) lunar apogee
|
|
39
|
+
|
|
40
|
+
# Earth and centaurs
|
|
41
|
+
SE_EARTH: int = 14
|
|
42
|
+
SE_CHIRON: int = 15 # Centaur between Saturn and Uranus
|
|
43
|
+
SE_PHOLUS: int = 16 # Centaur beyond Saturn
|
|
44
|
+
|
|
45
|
+
# Main belt asteroids
|
|
46
|
+
SE_CERES: int = 17
|
|
47
|
+
SE_PALLAS: int = 18
|
|
48
|
+
SE_JUNO: int = 19
|
|
49
|
+
SE_VESTA: int = 20
|
|
50
|
+
|
|
51
|
+
# Interpolated lunar apsides
|
|
52
|
+
SE_INTP_APOG: int = 21 # Interpolated apogee
|
|
53
|
+
SE_INTP_PERG: int = 22 # Interpolated perigee
|
|
54
|
+
|
|
55
|
+
# Count and offsets
|
|
56
|
+
SE_NPLANETS: int = 23 # Total number of standard planet IDs
|
|
57
|
+
SE_AST_OFFSET: int = 10000 # Offset for asteroid catalog numbers
|
|
58
|
+
SE_VARUNA: int = SE_AST_OFFSET + 20000 # TNO Varuna
|
|
59
|
+
SE_FICT_OFFSET: int = 40 # Offset for fictitious bodies
|
|
60
|
+
SE_NFICT_ELEM: int = 15 # Number of fictitious elements
|
|
61
|
+
SE_COMET_OFFSET: int = 1000 # Offset for comet IDs
|
|
62
|
+
SE_NALL_NAT_POINTS: int = (
|
|
63
|
+
SE_NPLANETS + SE_NFICT_ELEM + SE_AST_OFFSET + SE_COMET_OFFSET
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Trans-Neptunian Objects (TNOs) - Catalog number + offset
|
|
67
|
+
SE_ERIS: int = 136199 + SE_AST_OFFSET # Largest known dwarf planet
|
|
68
|
+
SE_SEDNA: int = 90377 + SE_AST_OFFSET # Detached TNO
|
|
69
|
+
SE_HAUMEA: int = 136108 + SE_AST_OFFSET # Fast-rotating dwarf planet
|
|
70
|
+
SE_MAKEMAKE: int = 136472 + SE_AST_OFFSET # Classical Kuiper belt object
|
|
71
|
+
SE_IXION: int = 28978 + SE_AST_OFFSET # Plutino
|
|
72
|
+
SE_ORCUS: int = 90482 + SE_AST_OFFSET # Plutino, "anti-Pluto"
|
|
73
|
+
SE_QUAOAR: int = 50000 + SE_AST_OFFSET # Classical KBO
|
|
74
|
+
|
|
75
|
+
# =============================================================================
|
|
76
|
+
# VIRTUAL POINTS AND CALCULATED POSITIONS
|
|
77
|
+
# =============================================================================
|
|
78
|
+
|
|
79
|
+
# Fixed Stars (high offset to avoid ID collisions)
|
|
80
|
+
SE_FIXSTAR_OFFSET: int = 1000000
|
|
81
|
+
SE_REGULUS: int = SE_FIXSTAR_OFFSET + 1 # Alpha Leonis
|
|
82
|
+
SE_SPICA_STAR: int = SE_FIXSTAR_OFFSET + 2 # Alpha Virginis
|
|
83
|
+
|
|
84
|
+
# Astrological Angles (requires observer location)
|
|
85
|
+
SE_ANGLE_OFFSET: int = 2000000
|
|
86
|
+
SE_ASCENDANT: int = SE_ANGLE_OFFSET + 1 # Rising sign/degree
|
|
87
|
+
SE_MC: int = SE_ANGLE_OFFSET + 2 # Medium Coeli (Midheaven)
|
|
88
|
+
SE_DESCENDANT: int = SE_ANGLE_OFFSET + 3 # Setting point (Asc + 180°)
|
|
89
|
+
SE_IC: int = SE_ANGLE_OFFSET + 4 # Imum Coeli (MC + 180°)
|
|
90
|
+
SE_VERTEX: int = SE_ANGLE_OFFSET + 5 # Western intersection of prime vertical
|
|
91
|
+
SE_ANTIVERTEX: int = SE_ANGLE_OFFSET + 6 # Eastern intersection (Vertex + 180°)
|
|
92
|
+
|
|
93
|
+
# Arabic Parts (Lots) - Require pre-calculated planetary positions
|
|
94
|
+
SE_ARABIC_OFFSET: int = 3000000
|
|
95
|
+
SE_PARS_FORTUNAE: int = SE_ARABIC_OFFSET + 1 # Part of Fortune
|
|
96
|
+
SE_PARS_SPIRITUS: int = SE_ARABIC_OFFSET + 2 # Part of Spirit
|
|
97
|
+
SE_PARS_AMORIS: int = SE_ARABIC_OFFSET + 3 # Part of Eros/Love
|
|
98
|
+
SE_PARS_FIDEI: int = SE_ARABIC_OFFSET + 4 # Part of Faith
|
|
99
|
+
|
|
100
|
+
# =============================================================================
|
|
101
|
+
# CALCULATION FLAGS
|
|
102
|
+
# =============================================================================
|
|
103
|
+
# Ephemeris selection (currently only SWIEPH/JPL mode supported)
|
|
104
|
+
SEFLG_JPLEPH: int = 1 # Use JPL ephemeris
|
|
105
|
+
SEFLG_SWIEPH: int = 2 # Use Swiss Ephemeris (libephemeris uses Skyfield/JPL)
|
|
106
|
+
SEFLG_MOSEPH: int = 4 # Use Moshier ephemeris (not supported)
|
|
107
|
+
|
|
108
|
+
# Observer location and reference frame
|
|
109
|
+
SEFLG_HELCTR: int = 8 # Heliocentric position
|
|
110
|
+
SEFLG_TRUEPOS: int = 16 # True geometric position (no light time)
|
|
111
|
+
SEFLG_J2000: int = 32 # J2000.0 reference frame
|
|
112
|
+
SEFLG_NONUT: int = 64 # No nutation
|
|
113
|
+
SEFLG_SPEED3: int = 128 # High precision speed (3 calls)
|
|
114
|
+
SEFLG_SPEED: int = 256 # Calculate velocity
|
|
115
|
+
SEFLG_NOGDEFL: int = 512 # No gravitational deflection
|
|
116
|
+
SEFLG_NOABERR: int = 1024 # No aberration
|
|
117
|
+
SEFLG_ASTROMETRIC: int = SEFLG_NOABERR | SEFLG_NOGDEFL # Astrometric position
|
|
118
|
+
SEFLG_EQUATORIAL: int = 2048 # Equatorial coordinates (RA/Dec)
|
|
119
|
+
SEFLG_XYZ: int = 4096 # Cartesian coordinates
|
|
120
|
+
SEFLG_RADIANS: int = 8192 # Return angles in radians
|
|
121
|
+
SEFLG_BARYCTR: int = 16384 # Barycentric position
|
|
122
|
+
SEFLG_TOPOCTR: int = 32768 # Topocentric position (requires swe_set_topo)
|
|
123
|
+
SEFLG_SIDEREAL: int = 65536 # Sidereal positions
|
|
124
|
+
SEFLG_ICRS: int = 131072 # ICRS reference frame
|
|
125
|
+
|
|
126
|
+
# =============================================================================
|
|
127
|
+
# PYSWISSEPH-COMPATIBLE FLAG ALIASES (FLG_* instead of SEFLG_*)
|
|
128
|
+
# =============================================================================
|
|
129
|
+
# pyswisseph uses FLG_* prefix while Swiss Ephemeris C library uses SEFLG_*
|
|
130
|
+
# These aliases provide full API compatibility with pyswisseph
|
|
131
|
+
|
|
132
|
+
FLG_JPLEPH: int = SEFLG_JPLEPH
|
|
133
|
+
FLG_SWIEPH: int = SEFLG_SWIEPH
|
|
134
|
+
FLG_MOSEPH: int = SEFLG_MOSEPH
|
|
135
|
+
FLG_HELCTR: int = SEFLG_HELCTR
|
|
136
|
+
FLG_TRUEPOS: int = SEFLG_TRUEPOS
|
|
137
|
+
FLG_J2000: int = SEFLG_J2000
|
|
138
|
+
FLG_NONUT: int = SEFLG_NONUT
|
|
139
|
+
FLG_SPEED3: int = SEFLG_SPEED3
|
|
140
|
+
FLG_SPEED: int = SEFLG_SPEED
|
|
141
|
+
FLG_NOGDEFL: int = SEFLG_NOGDEFL
|
|
142
|
+
FLG_NOABERR: int = SEFLG_NOABERR
|
|
143
|
+
FLG_ASTROMETRIC: int = SEFLG_ASTROMETRIC
|
|
144
|
+
FLG_EQUATORIAL: int = SEFLG_EQUATORIAL # Equatorial coordinates (RA/Dec)
|
|
145
|
+
FLG_XYZ: int = SEFLG_XYZ
|
|
146
|
+
FLG_RADIANS: int = SEFLG_RADIANS
|
|
147
|
+
FLG_BARYCTR: int = SEFLG_BARYCTR
|
|
148
|
+
FLG_TOPOCTR: int = SEFLG_TOPOCTR
|
|
149
|
+
FLG_SIDEREAL: int = SEFLG_SIDEREAL
|
|
150
|
+
FLG_ICRS: int = SEFLG_ICRS
|
|
151
|
+
|
|
152
|
+
# Other aliases
|
|
153
|
+
AST_OFFSET: int = SE_AST_OFFSET
|
|
154
|
+
|
|
155
|
+
# =============================================================================
|
|
156
|
+
# SIDEREAL (AYANAMSHA) MODES
|
|
157
|
+
# =============================================================================
|
|
158
|
+
|
|
159
|
+
# Western sidereal traditions
|
|
160
|
+
SE_SIDM_FAGAN_BRADLEY: int = 0 # Fagan-Bradley (Synetic Vernal Point)
|
|
161
|
+
SE_SIDM_LAHIRI: int = 1 # Lahiri (Chitrapaksha, Indian standard)
|
|
162
|
+
SE_SIDM_DELUCE: int = 2 # De Luce
|
|
163
|
+
SE_SIDM_RAMAN: int = 3 # B.V. Raman
|
|
164
|
+
SE_SIDM_USHASHASHI: int = 4 # Ushashashi
|
|
165
|
+
SE_SIDM_KRISHNAMURTI: int = 5 # K.S. Krishnamurti (KP)
|
|
166
|
+
SE_SIDM_DJWHAL_KHUL: int = 6 # Djwhal Khul (Alice Bailey)
|
|
167
|
+
SE_SIDM_YUKTESHWAR: int = 7 # Yukteshwar
|
|
168
|
+
SE_SIDM_JN_BHASIN: int = 8 # J.N. Bhasin
|
|
169
|
+
|
|
170
|
+
# Babylonian traditions
|
|
171
|
+
SE_SIDM_BABYL_KUGLER1: int = 9 # Kugler variant 1
|
|
172
|
+
SE_SIDM_BABYL_KUGLER2: int = 10 # Kugler variant 2
|
|
173
|
+
SE_SIDM_BABYL_KUGLER3: int = 11 # Kugler variant 3
|
|
174
|
+
SE_SIDM_BABYL_HUBER: int = 12 # Huber
|
|
175
|
+
SE_SIDM_BABYL_ETPSC: int = 13 # ETPSC
|
|
176
|
+
SE_SIDM_BABYL_BRITTON: int = 38 # Britton
|
|
177
|
+
|
|
178
|
+
# Star-based ayanamshas
|
|
179
|
+
SE_SIDM_ALDEBARAN_15TAU: int = 14 # Aldebaran at 15° Taurus
|
|
180
|
+
SE_SIDM_TRUE_CITRA: int = 27 # True position of Spica (180° Citra)
|
|
181
|
+
SE_SIDM_TRUE_REVATI: int = 28 # True position of Revati
|
|
182
|
+
SE_SIDM_TRUE_PUSHYA: int = 29 # True position of Pushya (Cancri)
|
|
183
|
+
SE_SIDM_TRUE_MULA: int = 35 # True position of Mula (λ Scorpii)
|
|
184
|
+
SE_SIDM_TRUE_SHEORAN: int = 39 # True Sheoran
|
|
185
|
+
|
|
186
|
+
# Historical epochs
|
|
187
|
+
SE_SIDM_HIPPARCHOS: int = 15 # Hipparchos (128 BC)
|
|
188
|
+
SE_SIDM_SASSANIAN: int = 16 # Sassanian
|
|
189
|
+
SE_SIDM_J2000: int = 18 # J2000.0 (no ayanamsha)
|
|
190
|
+
SE_SIDM_J1900: int = 19 # J1900.0
|
|
191
|
+
SE_SIDM_B1950: int = 20 # B1950.0
|
|
192
|
+
|
|
193
|
+
# Indian traditions
|
|
194
|
+
SE_SIDM_SURYASIDDHANTA: int = 21 # Suryasiddhanta
|
|
195
|
+
SE_SIDM_SURYASIDDHANTA_MSUN: int = 22 # Suryasiddhanta (mean Sun)
|
|
196
|
+
SE_SIDM_ARYABHATA: int = 23 # Aryabhata
|
|
197
|
+
SE_SIDM_ARYABHATA_MSUN: int = 24 # Aryabhata (mean Sun)
|
|
198
|
+
SE_SIDM_ARYABHATA_522: int = 37 # Aryabhata 522
|
|
199
|
+
SE_SIDM_SS_REVATI: int = 25 # Suryasiddhanta Revati
|
|
200
|
+
SE_SIDM_SS_CITRA: int = 26 # Suryasiddhanta Citra
|
|
201
|
+
|
|
202
|
+
# Galactic alignment systems
|
|
203
|
+
SE_SIDM_GALCENT_0SAG: int = 17 # Galactic Center at 0° Sagittarius
|
|
204
|
+
SE_SIDM_GALCENT_RGILBRAND: int = 30 # Galactic Center (Gil Brand)
|
|
205
|
+
SE_SIDM_GALCENT_MULA_WILHELM: int = 36 # Galactic Center at Mula (Wilhelm)
|
|
206
|
+
SE_SIDM_GALCENT_COCHRANE: int = 40 # Galactic Center (Cochrane)
|
|
207
|
+
SE_SIDM_GALEQU_IAU1958: int = 31 # Galactic Equator (IAU 1958)
|
|
208
|
+
SE_SIDM_GALEQU_TRUE: int = 32 # Galactic Equator (True)
|
|
209
|
+
SE_SIDM_GALEQU_MULA: int = 33 # Galactic Equator at Mula
|
|
210
|
+
SE_SIDM_GALEQU_FIORENZA: int = 41 # Galactic Equator (Fiorenza)
|
|
211
|
+
SE_SIDM_GALALIGN_MARDYKS: int = 34 # Galactic Alignment (Mardyks)
|
|
212
|
+
|
|
213
|
+
# Other systems
|
|
214
|
+
SE_SIDM_VALENS_MOON: int = 42 # Vettius Valens (Moon-based)
|
|
215
|
+
SE_SIDM_USER: int = 255 # User-defined ayanamsha
|
|
216
|
+
|
|
217
|
+
# =============================================================================
|
|
218
|
+
# PYSWISSEPH-COMPATIBLE SIDEREAL MODE ALIASES (SIDM_* instead of SE_SIDM_*)
|
|
219
|
+
# =============================================================================
|
|
220
|
+
# pyswisseph uses SIDM_* prefix while Swiss Ephemeris C library uses SE_SIDM_*
|
|
221
|
+
|
|
222
|
+
# Western sidereal traditions
|
|
223
|
+
SIDM_FAGAN_BRADLEY: int = SE_SIDM_FAGAN_BRADLEY
|
|
224
|
+
SIDM_LAHIRI: int = SE_SIDM_LAHIRI
|
|
225
|
+
SIDM_DELUCE: int = SE_SIDM_DELUCE
|
|
226
|
+
SIDM_RAMAN: int = SE_SIDM_RAMAN
|
|
227
|
+
SIDM_USHASHASHI: int = SE_SIDM_USHASHASHI
|
|
228
|
+
SIDM_KRISHNAMURTI: int = SE_SIDM_KRISHNAMURTI
|
|
229
|
+
SIDM_DJWHAL_KHUL: int = SE_SIDM_DJWHAL_KHUL
|
|
230
|
+
SIDM_YUKTESHWAR: int = SE_SIDM_YUKTESHWAR
|
|
231
|
+
SIDM_JN_BHASIN: int = SE_SIDM_JN_BHASIN
|
|
232
|
+
|
|
233
|
+
# Babylonian traditions
|
|
234
|
+
SIDM_BABYL_KUGLER1: int = SE_SIDM_BABYL_KUGLER1
|
|
235
|
+
SIDM_BABYL_KUGLER2: int = SE_SIDM_BABYL_KUGLER2
|
|
236
|
+
SIDM_BABYL_KUGLER3: int = SE_SIDM_BABYL_KUGLER3
|
|
237
|
+
SIDM_BABYL_HUBER: int = SE_SIDM_BABYL_HUBER
|
|
238
|
+
SIDM_BABYL_ETPSC: int = SE_SIDM_BABYL_ETPSC
|
|
239
|
+
SIDM_BABYL_BRITTON: int = SE_SIDM_BABYL_BRITTON
|
|
240
|
+
|
|
241
|
+
# Star-based ayanamshas
|
|
242
|
+
SIDM_ALDEBARAN_15TAU: int = SE_SIDM_ALDEBARAN_15TAU
|
|
243
|
+
SIDM_TRUE_CITRA: int = SE_SIDM_TRUE_CITRA
|
|
244
|
+
SIDM_TRUE_REVATI: int = SE_SIDM_TRUE_REVATI
|
|
245
|
+
SIDM_TRUE_PUSHYA: int = SE_SIDM_TRUE_PUSHYA
|
|
246
|
+
SIDM_TRUE_MULA: int = SE_SIDM_TRUE_MULA
|
|
247
|
+
SIDM_TRUE_SHEORAN: int = SE_SIDM_TRUE_SHEORAN
|
|
248
|
+
|
|
249
|
+
# Historical epochs
|
|
250
|
+
SIDM_HIPPARCHOS: int = SE_SIDM_HIPPARCHOS
|
|
251
|
+
SIDM_SASSANIAN: int = SE_SIDM_SASSANIAN
|
|
252
|
+
SIDM_J2000: int = SE_SIDM_J2000
|
|
253
|
+
SIDM_J1900: int = SE_SIDM_J1900
|
|
254
|
+
SIDM_B1950: int = SE_SIDM_B1950
|
|
255
|
+
|
|
256
|
+
# Indian traditions
|
|
257
|
+
SIDM_SURYASIDDHANTA: int = SE_SIDM_SURYASIDDHANTA
|
|
258
|
+
SIDM_SURYASIDDHANTA_MSUN: int = SE_SIDM_SURYASIDDHANTA_MSUN
|
|
259
|
+
SIDM_ARYABHATA: int = SE_SIDM_ARYABHATA
|
|
260
|
+
SIDM_ARYABHATA_MSUN: int = SE_SIDM_ARYABHATA_MSUN
|
|
261
|
+
SIDM_ARYABHATA_522: int = SE_SIDM_ARYABHATA_522
|
|
262
|
+
SIDM_SS_REVATI: int = SE_SIDM_SS_REVATI
|
|
263
|
+
SIDM_SS_CITRA: int = SE_SIDM_SS_CITRA
|
|
264
|
+
|
|
265
|
+
# Galactic alignment systems
|
|
266
|
+
SIDM_GALCENT_0SAG: int = SE_SIDM_GALCENT_0SAG
|
|
267
|
+
SIDM_GALCENT_RGILBRAND: int = SE_SIDM_GALCENT_RGILBRAND
|
|
268
|
+
SIDM_GALCENT_MULA_WILHELM: int = SE_SIDM_GALCENT_MULA_WILHELM
|
|
269
|
+
SIDM_GALCENT_COCHRANE: int = SE_SIDM_GALCENT_COCHRANE
|
|
270
|
+
SIDM_GALEQU_IAU1958: int = SE_SIDM_GALEQU_IAU1958
|
|
271
|
+
SIDM_GALEQU_TRUE: int = SE_SIDM_GALEQU_TRUE
|
|
272
|
+
SIDM_GALEQU_MULA: int = SE_SIDM_GALEQU_MULA
|
|
273
|
+
SIDM_GALEQU_FIORENZA: int = SE_SIDM_GALEQU_FIORENZA
|
|
274
|
+
SIDM_GALALIGN_MARDYKS: int = SE_SIDM_GALALIGN_MARDYKS
|
|
275
|
+
|
|
276
|
+
# Other systems
|
|
277
|
+
SIDM_VALENS_MOON: int = SE_SIDM_VALENS_MOON
|
|
278
|
+
SIDM_USER: int = SE_SIDM_USER
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# =============================================================================
|
|
282
|
+
# CALENDAR SYSTEMS
|
|
283
|
+
# =============================================================================
|
|
284
|
+
|
|
285
|
+
SE_JUL_CAL: int = 0 # Julian calendar
|
|
286
|
+
SE_GREG_CAL: int = 1 # Gregorian calendar
|
|
287
|
+
|
|
288
|
+
# =============================================================================
|
|
289
|
+
# ECLIPSE TYPES AND FLAGS
|
|
290
|
+
# =============================================================================
|
|
291
|
+
# Eclipse geometry types
|
|
292
|
+
SE_ECL_CENTRAL: int = 1 # Central eclipse (Moon's shadow axis crosses Earth)
|
|
293
|
+
SE_ECL_NONCENTRAL: int = 2 # Non-central eclipse
|
|
294
|
+
SE_ECL_TOTAL: int = 4 # Total eclipse (Sun/Earth completely covered)
|
|
295
|
+
SE_ECL_ANNULAR: int = 8 # Annular eclipse (ring of fire)
|
|
296
|
+
SE_ECL_PARTIAL: int = 16 # Partial eclipse
|
|
297
|
+
SE_ECL_ANNULAR_TOTAL: int = 32 # Hybrid eclipse (annular at some locations, total at others)
|
|
298
|
+
SE_ECL_PENUMBRAL: int = 64 # Penumbral lunar eclipse
|
|
299
|
+
|
|
300
|
+
# Composite eclipse type masks
|
|
301
|
+
SE_ECL_ALLTYPES_SOLAR: int = (
|
|
302
|
+
SE_ECL_CENTRAL
|
|
303
|
+
| SE_ECL_NONCENTRAL
|
|
304
|
+
| SE_ECL_TOTAL
|
|
305
|
+
| SE_ECL_ANNULAR
|
|
306
|
+
| SE_ECL_PARTIAL
|
|
307
|
+
| SE_ECL_ANNULAR_TOTAL
|
|
308
|
+
)
|
|
309
|
+
SE_ECL_ALLTYPES_LUNAR: int = SE_ECL_TOTAL | SE_ECL_PARTIAL | SE_ECL_PENUMBRAL
|
|
310
|
+
|
|
311
|
+
# Eclipse visibility and contact flags
|
|
312
|
+
SE_ECL_VISIBLE: int = 128 # Eclipse visible at location
|
|
313
|
+
SE_ECL_MAX_VISIBLE: int = 256 # Maximum phase visible
|
|
314
|
+
SE_ECL_1ST_VISIBLE: int = 512 # First contact visible
|
|
315
|
+
SE_ECL_2ND_VISIBLE: int = 1024 # Second contact visible
|
|
316
|
+
SE_ECL_3RD_VISIBLE: int = 2048 # Third contact visible
|
|
317
|
+
SE_ECL_4TH_VISIBLE: int = 4096 # Fourth contact visible
|
|
318
|
+
SE_ECL_ONE_TRY: int = 32768 # Try only once (optimization flag)
|
libephemeris/crossing.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Crossing event calculations for libephemeris.
|
|
3
|
+
|
|
4
|
+
Finds exact times when the Sun or Moon cross specific ecliptic longitudes.
|
|
5
|
+
Uses Newton-Raphson iteration for sub-arcsecond precision.
|
|
6
|
+
|
|
7
|
+
Functions:
|
|
8
|
+
- swe_solcross_ut: Sun crossing events (e.g., ingresses, equinoxes)
|
|
9
|
+
- swe_mooncross_ut: Moon crossing events (for lunar mansion calculations)
|
|
10
|
+
- swe_cross_ut: Generic planet crossing
|
|
11
|
+
|
|
12
|
+
FIXME: Precision - Newton-Raphson convergence tolerance
|
|
13
|
+
Current tolerance: 1 arcsecond (1/3600°)
|
|
14
|
+
Iterations: 20-30 max
|
|
15
|
+
Typical convergence: 3-6 iterations for Sun, 5-10 for Moon
|
|
16
|
+
|
|
17
|
+
For sub-arcsecond precision, consider iterative refinement or
|
|
18
|
+
use Swiss Ephemeris swe_solcross_ut2 with higher tolerances.
|
|
19
|
+
|
|
20
|
+
Algorithm: Initial linear estimate + Newton-Raphson refinement
|
|
21
|
+
References: Meeus "Astronomical Algorithms" Ch. 5 (interpolation)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Tuple
|
|
25
|
+
from .constants import SEFLG_SWIEPH, SEFLG_SPEED, SE_SUN, SE_MOON
|
|
26
|
+
from .planets import swe_calc_ut
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def swe_solcross_ut(
|
|
30
|
+
x2cross: float, jd_ut: float, flag: int = SEFLG_SWIEPH
|
|
31
|
+
) -> float:
|
|
32
|
+
"""
|
|
33
|
+
Find when the Sun crosses a specific ecliptic longitude.
|
|
34
|
+
|
|
35
|
+
Searches FORWARD in time for the next crossing after jd_ut.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
x2cross: Target ecliptic longitude in degrees (0-360)
|
|
39
|
+
jd_ut: Julian Day (UT) to start search from
|
|
40
|
+
flag: Calculation flags (SEFLG_SWIEPH, etc.)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
float: Julian Day of crossing (UT)
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
RuntimeError: If convergence fails or calculation error occurs
|
|
47
|
+
|
|
48
|
+
Algorithm:
|
|
49
|
+
1. Get current Sun position and velocity
|
|
50
|
+
2. Linear estimate: dt = (target - current) / velocity
|
|
51
|
+
3. Refine with Newton-Raphson: jd_new = jd + (target - actual) / velocity
|
|
52
|
+
4. Converge to < 1 arcsecond (~2.8 seconds of time)
|
|
53
|
+
|
|
54
|
+
Precision:
|
|
55
|
+
Typically < 1 arcsecond (< 5 seconds of time for Sun)
|
|
56
|
+
|
|
57
|
+
FIXME: Precision - Convergence tolerance 1 arcsec
|
|
58
|
+
For higher precision, reduce tolerance in line 74
|
|
59
|
+
Swiss Ephemeris achieves ~0.001 arcsec with tighter iteration
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> # Find next Aries ingress (0°)
|
|
63
|
+
>>> jd_ingress = swe_solcross_ut(0.0, jd_now)
|
|
64
|
+
>>> # Find summer solstice (90°)
|
|
65
|
+
>>> jd_solstice = swe_solcross_ut(90.0, jd_now)
|
|
66
|
+
"""
|
|
67
|
+
x2cross = x2cross % 360.0
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
pos, _ = swe_calc_ut(jd_ut, SE_SUN, flag | SEFLG_SPEED)
|
|
71
|
+
lon_start = pos[0]
|
|
72
|
+
speed = pos[3] # degrees/day
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise RuntimeError(f"Failed to calculate Sun position: {e}")
|
|
75
|
+
|
|
76
|
+
# Calculate angular distance to target (always forward)
|
|
77
|
+
diff = (x2cross - lon_start) % 360.0
|
|
78
|
+
|
|
79
|
+
# Handle retrograde motion
|
|
80
|
+
if speed < 0 and diff > 0:
|
|
81
|
+
diff -= 360.0
|
|
82
|
+
|
|
83
|
+
# If already very close, look for next complete crossing
|
|
84
|
+
if abs(diff) < 1e-5:
|
|
85
|
+
if speed > 0:
|
|
86
|
+
diff += 360.0
|
|
87
|
+
else:
|
|
88
|
+
diff -= 360.0
|
|
89
|
+
|
|
90
|
+
# Initial time estimate (linear approximation)
|
|
91
|
+
if speed == 0:
|
|
92
|
+
speed = 0.9856 # Average Sun motion ~1°/day
|
|
93
|
+
|
|
94
|
+
dt_guess = diff / speed
|
|
95
|
+
jd_guess = jd_ut + dt_guess
|
|
96
|
+
|
|
97
|
+
# FIXME: Precision - Newton-Raphson max iterations = 20
|
|
98
|
+
# May fail for very slow bodies or near stationary points
|
|
99
|
+
jd = jd_guess
|
|
100
|
+
for iteration in range(20):
|
|
101
|
+
try:
|
|
102
|
+
pos, _ = swe_calc_ut(jd, SE_SUN, flag | SEFLG_SPEED)
|
|
103
|
+
lon = pos[0]
|
|
104
|
+
speed = pos[3]
|
|
105
|
+
except Exception as e:
|
|
106
|
+
raise RuntimeError(f"Failed to calculate Sun position during iteration: {e}")
|
|
107
|
+
|
|
108
|
+
# Angular difference to target
|
|
109
|
+
diff = (x2cross - lon) % 360.0
|
|
110
|
+
if diff > 180:
|
|
111
|
+
diff -= 360
|
|
112
|
+
|
|
113
|
+
# FIXME: Precision - Convergence tolerance 1 arcsecond
|
|
114
|
+
# Check convergence (< 1 arcsecond = 1/3600 degree)
|
|
115
|
+
if abs(diff) < 1.0 / 3600.0:
|
|
116
|
+
return jd
|
|
117
|
+
|
|
118
|
+
# Newton-Raphson step
|
|
119
|
+
if abs(speed) < 0.01:
|
|
120
|
+
speed = 0.9856
|
|
121
|
+
|
|
122
|
+
jd += diff / speed
|
|
123
|
+
|
|
124
|
+
# Safety: prevent divergence
|
|
125
|
+
if abs(jd - jd_guess) > 366:
|
|
126
|
+
raise RuntimeError("Solar crossing search diverged")
|
|
127
|
+
|
|
128
|
+
raise RuntimeError("Maximum iterations reached in solar crossing calculation")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def swe_mooncross_ut(
|
|
132
|
+
x2cross: float, jd_ut: float, flag: int = SEFLG_SWIEPH
|
|
133
|
+
) -> float:
|
|
134
|
+
"""
|
|
135
|
+
Find when the Moon crosses a specific ecliptic longitude.
|
|
136
|
+
|
|
137
|
+
Searches FORWARD in time for the next crossing after jd_ut.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
x2cross: Target ecliptic longitude in degrees (0-360)
|
|
141
|
+
jd_ut: Julian Day (UT) to start search from
|
|
142
|
+
flag: Calculation flags (SEFLG_SWIEPH, etc.)
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
float: Julian Day of crossing (UT)
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
RuntimeError: If convergence fails or calculation error occurs
|
|
149
|
+
|
|
150
|
+
Note:
|
|
151
|
+
Moon moves ~13° per day (27.3 day cycle).
|
|
152
|
+
More variable speed than Sun due to orbit eccentricity.
|
|
153
|
+
Uses 30 iterations vs Sun's 20 for added robustness.
|
|
154
|
+
|
|
155
|
+
FIXME: Precision - Same 1 arcsec tolerance as Sun
|
|
156
|
+
Moon's higher speed means ~0.1 seconds time precision
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> # Find next new moon (Sun-Moon conjunction at same longitude)
|
|
160
|
+
>>> sun_pos, _ = swe_calc_ut(jd_now, SE_SUN, SEFLG_SWIEPH)
|
|
161
|
+
>>> jd_new_moon = swe_mooncross_ut(sun_pos[0], jd_now)
|
|
162
|
+
"""
|
|
163
|
+
x2cross = x2cross % 360.0
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
pos, _ = swe_calc_ut(jd_ut, SE_MOON, flag | SEFLG_SPEED)
|
|
167
|
+
lon_start = pos[0]
|
|
168
|
+
speed = pos[3] # degrees/day
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise RuntimeError(f"Failed to calculate Moon position: {e}")
|
|
171
|
+
|
|
172
|
+
# Calculate initial guess for NEXT crossing
|
|
173
|
+
diff = (x2cross - lon_start) % 360.0
|
|
174
|
+
|
|
175
|
+
if speed < 0 and diff > 0:
|
|
176
|
+
diff -= 360.0
|
|
177
|
+
|
|
178
|
+
if abs(diff) < 1e-5:
|
|
179
|
+
if speed > 0:
|
|
180
|
+
diff += 360.0
|
|
181
|
+
else:
|
|
182
|
+
diff -= 360.0
|
|
183
|
+
|
|
184
|
+
# Initial time estimate
|
|
185
|
+
if speed == 0:
|
|
186
|
+
speed = 13.176 # Average Moon motion ~13.18°/day
|
|
187
|
+
|
|
188
|
+
dt_guess = diff / speed
|
|
189
|
+
jd_guess = jd_ut + dt_guess
|
|
190
|
+
|
|
191
|
+
# FIXME: Precision - Moon uses 30 iterations (vs Sun's 20)
|
|
192
|
+
# Higher iteration count for Moon's variable speed
|
|
193
|
+
jd = jd_guess
|
|
194
|
+
for iteration in range(30):
|
|
195
|
+
try:
|
|
196
|
+
pos, _ = swe_calc_ut(jd, SE_MOON, flag | SEFLG_SPEED)
|
|
197
|
+
lon = pos[0]
|
|
198
|
+
speed = pos[3]
|
|
199
|
+
except Exception as e:
|
|
200
|
+
raise RuntimeError(f"Failed to calculate Moon position during iteration: {e}")
|
|
201
|
+
|
|
202
|
+
# Difference to target
|
|
203
|
+
diff = (x2cross - lon) % 360.0
|
|
204
|
+
if diff > 180:
|
|
205
|
+
diff -= 360
|
|
206
|
+
|
|
207
|
+
# Check convergence (< 1 arcsecond)
|
|
208
|
+
if abs(diff) < 1.0 / 3600.0:
|
|
209
|
+
return jd
|
|
210
|
+
|
|
211
|
+
# Newton-Raphson step
|
|
212
|
+
if abs(speed) < 0.1:
|
|
213
|
+
speed = 13.176
|
|
214
|
+
|
|
215
|
+
jd += diff / speed
|
|
216
|
+
|
|
217
|
+
# Safety check
|
|
218
|
+
if abs(jd - jd_guess) > 31: # More than a month
|
|
219
|
+
raise RuntimeError("Moon crossing search diverged")
|
|
220
|
+
|
|
221
|
+
raise RuntimeError("Maximum iterations reached in moon crossing calculation")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def swe_cross_ut(
|
|
225
|
+
planet_id: int, x2cross: float, jd_ut: float, flag: int = SEFLG_SWIEPH
|
|
226
|
+
) -> float:
|
|
227
|
+
"""
|
|
228
|
+
Find when any planet crosses a specific ecliptic longitude.
|
|
229
|
+
|
|
230
|
+
Generic version for all planets (Mercury, Venus, Mars, etc.).
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
planet_id: Planet ID (SE_MERCURY, SE_VENUS, etc.)
|
|
234
|
+
x2cross: Target ecliptic longitude in degrees (0-360)
|
|
235
|
+
jd_ut: Julian Day (UT) to start search from
|
|
236
|
+
flag: Calculation flags
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
float: Julian Day of crossing (UT)
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
RuntimeError: If convergence fails or calculation error occurs
|
|
243
|
+
|
|
244
|
+
Note:
|
|
245
|
+
Uses adaptive iteration count based on typical planet speed.
|
|
246
|
+
Slower planets (Jupiter, Saturn) may need more iterations.
|
|
247
|
+
|
|
248
|
+
FIXME: Precision - Not optimized for retrograde stations
|
|
249
|
+
Near stationary points (speed ~ 0), convergence is slower
|
|
250
|
+
May need adaptive tolerance or damped Newton-Raphson
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> # Mars ingress into Aries
|
|
254
|
+
>>> jd_mars_aries = swe_cross_ut(SE_MARS, 0.0, jd_now)
|
|
255
|
+
"""
|
|
256
|
+
x2cross = x2cross % 360.0
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
pos, _ = swe_calc_ut(jd_ut, planet_id, flag | SEFLG_SPEED)
|
|
260
|
+
lon_start = pos[0]
|
|
261
|
+
speed = pos[3]
|
|
262
|
+
except Exception as e:
|
|
263
|
+
raise RuntimeError(f"Failed to calculate planet position: {e}")
|
|
264
|
+
|
|
265
|
+
# Estimate typical speed if near zero
|
|
266
|
+
typical_speeds = {
|
|
267
|
+
2: 1.4, # Mercury
|
|
268
|
+
3: 1.2, # Venus
|
|
269
|
+
4: 0.5, # Mars
|
|
270
|
+
5: 0.08, # Jupiter
|
|
271
|
+
6: 0.03, # Saturn
|
|
272
|
+
7: 0.01, # Uranus
|
|
273
|
+
8: 0.006, # Neptune
|
|
274
|
+
}
|
|
275
|
+
speed_default = typical_speeds.get(planet_id, 0.5)
|
|
276
|
+
|
|
277
|
+
# Calculate initial guess
|
|
278
|
+
diff = (x2cross - lon_start) % 360.0
|
|
279
|
+
|
|
280
|
+
if speed < 0 and diff > 0:
|
|
281
|
+
diff -= 360.0
|
|
282
|
+
|
|
283
|
+
if abs(diff) < 1e-5:
|
|
284
|
+
if speed > 0:
|
|
285
|
+
diff += 360.0
|
|
286
|
+
else:
|
|
287
|
+
diff -= 360.0
|
|
288
|
+
|
|
289
|
+
if abs(speed) < 0.001:
|
|
290
|
+
speed = speed_default
|
|
291
|
+
|
|
292
|
+
dt_guess = diff / speed
|
|
293
|
+
jd_guess = jd_ut + dt_guess
|
|
294
|
+
|
|
295
|
+
# Adaptive iteration count
|
|
296
|
+
max_iter = 40 if abs(speed) < 0.1 else 20
|
|
297
|
+
|
|
298
|
+
# FIXME: Precision - Adaptive iterations needed for slow planets
|
|
299
|
+
# Outer planets may need 40+ iterations near stationary points
|
|
300
|
+
jd = jd_guess
|
|
301
|
+
for iteration in range(max_iter):
|
|
302
|
+
try:
|
|
303
|
+
pos, _ = swe_calc_ut(jd, planet_id, flag | SEFLG_SPEED)
|
|
304
|
+
lon = pos[0]
|
|
305
|
+
speed = pos[3]
|
|
306
|
+
except Exception as e:
|
|
307
|
+
raise RuntimeError(f"Failed to calculate planet position during iteration: {e}")
|
|
308
|
+
|
|
309
|
+
diff = (x2cross - lon) % 360.0
|
|
310
|
+
if diff > 180:
|
|
311
|
+
diff -= 360
|
|
312
|
+
|
|
313
|
+
if abs(diff) < 1.0 / 3600.0:
|
|
314
|
+
return jd
|
|
315
|
+
|
|
316
|
+
if abs(speed) < 0.001:
|
|
317
|
+
speed = speed_default
|
|
318
|
+
|
|
319
|
+
jd += diff / speed
|
|
320
|
+
|
|
321
|
+
# Safety: longer range for slower planets
|
|
322
|
+
max_range = 400 if abs(speed_default) < 0.1 else 366
|
|
323
|
+
if abs(jd - jd_guess) > max_range:
|
|
324
|
+
raise RuntimeError("Planet crossing search diverged")
|
|
325
|
+
|
|
326
|
+
raise RuntimeError("Maximum iterations reached in planet crossing calculation")
|