Skip to main content

sweph_sys/
lib.rs

1//! Raw FFI bindings to the Swiss Ephemeris C library.
2//!
3//! The C sources (v2.10.03, pinned in `vendor/UPSTREAM_COMMIT`) are vendored
4//! and compiled by `build.rs` with `TLSOFF`, so the library keeps **one
5//! process-wide state** (ephemeris path, open file handles, caches) shared by
6//! all threads. The Swiss Ephemeris is not thread-safe: callers must
7//! serialize every call into this crate. The companion `sweph` crate does
8//! that behind a mutex and is the recommended entry point; use these raw
9//! bindings directly only if you provide equivalent locking.
10//!
11//! Swiss Ephemeris is © Astrodienst AG, dual-licensed AGPL-3.0 or the Swiss
12//! Ephemeris Professional License. This crate is distributed under AGPL-3.0.
13
14use std::os::raw::{c_char, c_double, c_int};
15
16// ---------------------------------------------------------------------------
17// Body numbers (ipl)
18// ---------------------------------------------------------------------------
19pub const SE_SUN: c_int = 0;
20pub const SE_MOON: c_int = 1;
21pub const SE_MERCURY: c_int = 2;
22pub const SE_VENUS: c_int = 3;
23pub const SE_MARS: c_int = 4;
24pub const SE_JUPITER: c_int = 5;
25pub const SE_SATURN: c_int = 6;
26pub const SE_URANUS: c_int = 7;
27pub const SE_NEPTUNE: c_int = 8;
28pub const SE_PLUTO: c_int = 9;
29pub const SE_MEAN_NODE: c_int = 10;
30pub const SE_TRUE_NODE: c_int = 11;
31pub const SE_MEAN_APOG: c_int = 12;
32pub const SE_OSCU_APOG: c_int = 13;
33pub const SE_EARTH: c_int = 14;
34pub const SE_CHIRON: c_int = 15;
35pub const SE_PHOLUS: c_int = 16;
36pub const SE_CERES: c_int = 17;
37pub const SE_PALLAS: c_int = 18;
38pub const SE_JUNO: c_int = 19;
39pub const SE_VESTA: c_int = 20;
40/// Add a minor-planet catalogue number to this offset to address any
41/// numbered asteroid (requires the corresponding `se*.se1` asteroid file).
42pub const SE_AST_OFFSET: c_int = 10000;
43
44// ---------------------------------------------------------------------------
45// Calendar flags (gregflag)
46// ---------------------------------------------------------------------------
47pub const SE_JUL_CAL: c_int = 0;
48pub const SE_GREG_CAL: c_int = 1;
49
50// ---------------------------------------------------------------------------
51// Ephemeris / computation flags (iflag)
52// ---------------------------------------------------------------------------
53pub const SEFLG_JPLEPH: c_int = 1;
54pub const SEFLG_SWIEPH: c_int = 2;
55pub const SEFLG_MOSEPH: c_int = 4;
56/// Mask of the ephemeris-source bits. `swe_calc_ut` returns the flags it
57/// actually used — compare against this mask to detect a silent fallback
58/// (e.g. Swiss requested, Moshier substituted because data files are absent).
59pub const SEFLG_EPHMASK: c_int = SEFLG_JPLEPH | SEFLG_SWIEPH | SEFLG_MOSEPH;
60pub const SEFLG_HELCTR: c_int = 8;
61pub const SEFLG_TRUEPOS: c_int = 16;
62pub const SEFLG_J2000: c_int = 32;
63pub const SEFLG_NONUT: c_int = 64;
64pub const SEFLG_SPEED3: c_int = 128;
65pub const SEFLG_SPEED: c_int = 256;
66pub const SEFLG_NOGDEFL: c_int = 512;
67pub const SEFLG_NOABERR: c_int = 1024;
68pub const SEFLG_ASTROMETRIC: c_int = SEFLG_NOABERR | SEFLG_NOGDEFL;
69pub const SEFLG_EQUATORIAL: c_int = 2048;
70pub const SEFLG_XYZ: c_int = 4096;
71pub const SEFLG_RADIANS: c_int = 8192;
72pub const SEFLG_BARYCTR: c_int = 16384;
73pub const SEFLG_TOPOCTR: c_int = 32768;
74pub const SEFLG_SIDEREAL: c_int = 65536;
75
76// ---------------------------------------------------------------------------
77// Sidereal modes (swe_set_sid_mode)
78// ---------------------------------------------------------------------------
79pub const SE_SIDM_FAGAN_BRADLEY: c_int = 0;
80pub const SE_SIDM_LAHIRI: c_int = 1;
81pub const SE_SIDM_DELUCE: c_int = 2;
82pub const SE_SIDM_RAMAN: c_int = 3;
83pub const SE_SIDM_KRISHNAMURTI: c_int = 5;
84
85// ---------------------------------------------------------------------------
86// Eclipse type bitmask (ifltype filter + retflag from when_glob/when)
87// ---------------------------------------------------------------------------
88pub const SE_ECL_CENTRAL: c_int = 1;
89pub const SE_ECL_NONCENTRAL: c_int = 2;
90pub const SE_ECL_TOTAL: c_int = 4;
91pub const SE_ECL_ANNULAR: c_int = 8;
92pub const SE_ECL_PARTIAL: c_int = 16;
93pub const SE_ECL_ANNULAR_TOTAL: c_int = 32;
94pub const SE_ECL_PENUMBRAL: c_int = 64;
95
96/// Size the C API requires for `serr` error-message buffers (AS_MAXCH).
97pub const SE_MAX_STNAME: usize = 256;
98
99extern "C" {
100    pub fn swe_set_ephe_path(path: *const c_char);
101    pub fn swe_close();
102    pub fn swe_version(svers: *mut c_char) -> *mut c_char;
103
104    pub fn swe_calc_ut(
105        tjd_ut: c_double,
106        ipl: c_int,
107        iflag: c_int,
108        xx: *mut c_double,
109        serr: *mut c_char,
110    ) -> c_int;
111
112    pub fn swe_calc(
113        tjd_et: c_double,
114        ipl: c_int,
115        iflag: c_int,
116        xx: *mut c_double,
117        serr: *mut c_char,
118    ) -> c_int;
119
120    pub fn swe_get_planet_name(ipl: c_int, name: *mut c_char) -> *mut c_char;
121
122    pub fn swe_julday(
123        year: c_int,
124        month: c_int,
125        day: c_int,
126        hour: c_double,
127        gregflag: c_int,
128    ) -> c_double;
129
130    pub fn swe_revjul(
131        jd: c_double,
132        gregflag: c_int,
133        jyear: *mut c_int,
134        jmon: *mut c_int,
135        jday: *mut c_int,
136        jut: *mut c_double,
137    );
138
139    pub fn swe_utc_to_jd(
140        iyear: c_int,
141        imonth: c_int,
142        iday: c_int,
143        ihour: c_int,
144        imin: c_int,
145        dsec: c_double,
146        gregflag: c_int,
147        dret: *mut c_double,
148        serr: *mut c_char,
149    ) -> c_int;
150
151    pub fn swe_deltat(tjd: c_double) -> c_double;
152
153    pub fn swe_houses(
154        tjd_ut: c_double,
155        geolat: c_double,
156        geolon: c_double,
157        hsys: c_int,
158        cusps: *mut c_double,
159        ascmc: *mut c_double,
160    ) -> c_int;
161
162    pub fn swe_houses_ex(
163        tjd_ut: c_double,
164        iflag: c_int,
165        geolat: c_double,
166        geolon: c_double,
167        hsys: c_int,
168        cusps: *mut c_double,
169        ascmc: *mut c_double,
170    ) -> c_int;
171
172    pub fn swe_house_name(hsys: c_int) -> *const c_char;
173
174    pub fn swe_set_topo(geolon: c_double, geolat: c_double, geoalt: c_double);
175
176    pub fn swe_set_sid_mode(sid_mode: c_int, t0: c_double, ayan_t0: c_double);
177
178    pub fn swe_get_ayanamsa_ex_ut(
179        tjd_ut: c_double,
180        iflag: c_int,
181        daya: *mut c_double,
182        serr: *mut c_char,
183    ) -> c_int;
184
185    pub fn swe_sol_eclipse_when_glob(
186        tjd_start: c_double,
187        ifl: c_int,
188        ifltype: c_int,
189        tret: *mut c_double,
190        backward: c_int,
191        serr: *mut c_char,
192    ) -> c_int;
193
194    pub fn swe_lun_eclipse_when(
195        tjd_start: c_double,
196        ifl: c_int,
197        ifltype: c_int,
198        tret: *mut c_double,
199        backward: c_int,
200        serr: *mut c_char,
201    ) -> c_int;
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    // The C library is not thread-safe and `cargo test` runs tests on
209    // multiple threads, so every test serializes on this lock — the same
210    // discipline the `sweph` crate applies for real callers.
211    static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
212
213    #[test]
214    fn julday_j2000() {
215        let _guard = TEST_LOCK.lock().unwrap();
216        let jd = unsafe { swe_julday(2000, 1, 1, 12.0, SE_GREG_CAL) };
217        assert!((jd - 2451545.0).abs() < 0.0001, "JD was {jd}");
218    }
219
220    #[test]
221    fn calc_sun_position() {
222        let _guard = TEST_LOCK.lock().unwrap();
223        unsafe {
224            let jd = swe_julday(2000, 1, 1, 12.0, SE_GREG_CAL);
225            let mut xx = [0.0_f64; 6];
226            let mut serr = [0i8; SE_MAX_STNAME];
227            let ret = swe_calc_ut(
228                jd,
229                SE_SUN,
230                SEFLG_SPEED | SEFLG_SWIEPH,
231                xx.as_mut_ptr(),
232                serr.as_mut_ptr().cast(),
233            );
234            assert!(ret >= 0, "swe_calc_ut failed: {ret}");
235            let sun_lon = xx[0];
236            assert!(
237                sun_lon > 270.0 && sun_lon < 290.0,
238                "Sun longitude was {sun_lon}"
239            );
240        }
241    }
242
243    #[test]
244    fn version_string() {
245        let _guard = TEST_LOCK.lock().unwrap();
246        let mut buf = [0i8; SE_MAX_STNAME];
247        let s = unsafe {
248            swe_version(buf.as_mut_ptr().cast());
249            std::ffi::CStr::from_ptr(buf.as_ptr().cast())
250                .to_string_lossy()
251                .into_owned()
252        };
253        assert!(s.starts_with("2.10"), "unexpected version: {s}");
254    }
255}