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;
56pub const SEFLG_HELCTR: c_int = 8;
57pub const SEFLG_TRUEPOS: c_int = 16;
58pub const SEFLG_J2000: c_int = 32;
59pub const SEFLG_NONUT: c_int = 64;
60pub const SEFLG_SPEED3: c_int = 128;
61pub const SEFLG_SPEED: c_int = 256;
62pub const SEFLG_NOGDEFL: c_int = 512;
63pub const SEFLG_NOABERR: c_int = 1024;
64pub const SEFLG_ASTROMETRIC: c_int = SEFLG_NOABERR | SEFLG_NOGDEFL;
65pub const SEFLG_EQUATORIAL: c_int = 2048;
66pub const SEFLG_XYZ: c_int = 4096;
67pub const SEFLG_RADIANS: c_int = 8192;
68pub const SEFLG_BARYCTR: c_int = 16384;
69pub const SEFLG_TOPOCTR: c_int = 32768;
70pub const SEFLG_SIDEREAL: c_int = 65536;
71
72// ---------------------------------------------------------------------------
73// Sidereal modes (swe_set_sid_mode)
74// ---------------------------------------------------------------------------
75pub const SE_SIDM_FAGAN_BRADLEY: c_int = 0;
76pub const SE_SIDM_LAHIRI: c_int = 1;
77pub const SE_SIDM_DELUCE: c_int = 2;
78pub const SE_SIDM_RAMAN: c_int = 3;
79pub const SE_SIDM_KRISHNAMURTI: c_int = 5;
80
81// ---------------------------------------------------------------------------
82// Eclipse type bitmask (ifltype filter + retflag from when_glob/when)
83// ---------------------------------------------------------------------------
84pub const SE_ECL_CENTRAL: c_int = 1;
85pub const SE_ECL_NONCENTRAL: c_int = 2;
86pub const SE_ECL_TOTAL: c_int = 4;
87pub const SE_ECL_ANNULAR: c_int = 8;
88pub const SE_ECL_PARTIAL: c_int = 16;
89pub const SE_ECL_ANNULAR_TOTAL: c_int = 32;
90pub const SE_ECL_PENUMBRAL: c_int = 64;
91
92/// Size the C API requires for `serr` error-message buffers (AS_MAXCH).
93pub const SE_MAX_STNAME: usize = 256;
94
95extern "C" {
96    pub fn swe_set_ephe_path(path: *const c_char);
97    pub fn swe_close();
98    pub fn swe_version(svers: *mut c_char) -> *mut c_char;
99
100    pub fn swe_calc_ut(
101        tjd_ut: c_double,
102        ipl: c_int,
103        iflag: c_int,
104        xx: *mut c_double,
105        serr: *mut c_char,
106    ) -> c_int;
107
108    pub fn swe_calc(
109        tjd_et: c_double,
110        ipl: c_int,
111        iflag: c_int,
112        xx: *mut c_double,
113        serr: *mut c_char,
114    ) -> c_int;
115
116    pub fn swe_get_planet_name(ipl: c_int, name: *mut c_char) -> *mut c_char;
117
118    pub fn swe_julday(
119        year: c_int,
120        month: c_int,
121        day: c_int,
122        hour: c_double,
123        gregflag: c_int,
124    ) -> c_double;
125
126    pub fn swe_revjul(
127        jd: c_double,
128        gregflag: c_int,
129        jyear: *mut c_int,
130        jmon: *mut c_int,
131        jday: *mut c_int,
132        jut: *mut c_double,
133    );
134
135    pub fn swe_utc_to_jd(
136        iyear: c_int,
137        imonth: c_int,
138        iday: c_int,
139        ihour: c_int,
140        imin: c_int,
141        dsec: c_double,
142        gregflag: c_int,
143        dret: *mut c_double,
144        serr: *mut c_char,
145    ) -> c_int;
146
147    pub fn swe_deltat(tjd: c_double) -> c_double;
148
149    pub fn swe_houses(
150        tjd_ut: c_double,
151        geolat: c_double,
152        geolon: c_double,
153        hsys: c_int,
154        cusps: *mut c_double,
155        ascmc: *mut c_double,
156    ) -> c_int;
157
158    pub fn swe_houses_ex(
159        tjd_ut: c_double,
160        iflag: c_int,
161        geolat: c_double,
162        geolon: c_double,
163        hsys: c_int,
164        cusps: *mut c_double,
165        ascmc: *mut c_double,
166    ) -> c_int;
167
168    pub fn swe_house_name(hsys: c_int) -> *const c_char;
169
170    pub fn swe_set_topo(geolon: c_double, geolat: c_double, geoalt: c_double);
171
172    pub fn swe_set_sid_mode(sid_mode: c_int, t0: c_double, ayan_t0: c_double);
173
174    pub fn swe_get_ayanamsa_ex_ut(
175        tjd_ut: c_double,
176        iflag: c_int,
177        daya: *mut c_double,
178        serr: *mut c_char,
179    ) -> c_int;
180
181    pub fn swe_sol_eclipse_when_glob(
182        tjd_start: c_double,
183        ifl: c_int,
184        ifltype: c_int,
185        tret: *mut c_double,
186        backward: c_int,
187        serr: *mut c_char,
188    ) -> c_int;
189
190    pub fn swe_lun_eclipse_when(
191        tjd_start: c_double,
192        ifl: c_int,
193        ifltype: c_int,
194        tret: *mut c_double,
195        backward: c_int,
196        serr: *mut c_char,
197    ) -> c_int;
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    // The C library is not thread-safe and `cargo test` runs tests on
205    // multiple threads, so every test serializes on this lock — the same
206    // discipline the `sweph` crate applies for real callers.
207    static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
208
209    #[test]
210    fn julday_j2000() {
211        let _guard = TEST_LOCK.lock().unwrap();
212        let jd = unsafe { swe_julday(2000, 1, 1, 12.0, SE_GREG_CAL) };
213        assert!((jd - 2451545.0).abs() < 0.0001, "JD was {jd}");
214    }
215
216    #[test]
217    fn calc_sun_position() {
218        let _guard = TEST_LOCK.lock().unwrap();
219        unsafe {
220            let jd = swe_julday(2000, 1, 1, 12.0, SE_GREG_CAL);
221            let mut xx = [0.0_f64; 6];
222            let mut serr = [0i8; SE_MAX_STNAME];
223            let ret = swe_calc_ut(
224                jd,
225                SE_SUN,
226                SEFLG_SPEED | SEFLG_SWIEPH,
227                xx.as_mut_ptr(),
228                serr.as_mut_ptr().cast(),
229            );
230            assert!(ret >= 0, "swe_calc_ut failed: {ret}");
231            let sun_lon = xx[0];
232            assert!(
233                sun_lon > 270.0 && sun_lon < 290.0,
234                "Sun longitude was {sun_lon}"
235            );
236        }
237    }
238
239    #[test]
240    fn version_string() {
241        let _guard = TEST_LOCK.lock().unwrap();
242        let mut buf = [0i8; SE_MAX_STNAME];
243        let s = unsafe {
244            swe_version(buf.as_mut_ptr().cast());
245            std::ffi::CStr::from_ptr(buf.as_ptr().cast())
246                .to_string_lossy()
247                .into_owned()
248        };
249        assert!(s.starts_with("2.10"), "unexpected version: {s}");
250    }
251}