Skip to main content

rust_roche/
roche_context.rs

1use std::panic;
2use std::f64::consts::TAU;
3use crate::{Vec3, Star};
4use crate::{rpot_val, rpot_grad, rpot1, rpot2, drpot1, drpot2};
5use crate::{pot_min, dbrent, x_l1, x_l1_1, x_l1_2, x_l2, x_l3};
6use crate::{sphere_eclipse, sphere_eclipse_vector, set_earth};
7
8
9///
10/// RocheContext is a basic struct packaging the mass ratio, q=M2/M1,
11/// the star (i.e. the Primary or Secondary), the spin of that star,
12/// and the x-coordinate of the L1 point together with the functions
13/// that accept these values as inputs. x_l1 is calculated on initialisation
14/// with RocheContext::new(q, star, spin) and saves it being recalculated
15/// over and over in loops.
16/// 
17pub struct RocheContext {
18    pub q: f64,
19    pub star: Star,
20    pub spin: f64,
21    pub x_l1: f64,
22}
23
24impl RocheContext {
25
26    pub fn new(q: f64, star: Star, spin: f64) -> Self {
27        if q <= 0. {
28            panic!("q = {} <= 0", q);
29        }
30        let x_l1: f64 = match star {
31            Star::Primary => x_l1_1(q, spin),
32            Star::Secondary => x_l1_2(q, spin),
33        };
34        Self { q, star, spin, x_l1 }
35    }
36
37    pub fn potential(&self, earth: &Vec3, p:&Vec3, lam: f64) -> f64 {
38        rpot_val(self.q, self.star, self.spin, earth, p, lam)
39    }
40
41
42    pub fn gradient(&self, earth: &Vec3, p: &Vec3, lam: f64) -> (f64, f64) {
43        let (dp, dl) = rpot_grad(self.q, self.star, self.spin, earth, p, lam);
44        (dp, dl)
45    }
46
47
48    pub fn potential_grad(&self, earth: &Vec3, p: &Vec3, lam: f64) -> (f64, f64, f64) {
49        let f = self.potential(earth, p, lam);
50        let (dp, dl) = rpot_grad(self.q, self.star, self.spin, earth, p, lam);
51        (f, dp, dl)
52    }
53
54
55    pub fn ref_sphere(&self, ffac: f64) -> (f64, f64) {
56        let tref: f64;
57        let rref: f64;
58        let pref: f64;
59        if self.star == Star::Primary {
60            tref = self.x_l1;
61            rref = tref * 1.0_f64.min(1.001*ffac);
62            pref = rpot1(self.q, self.spin, &Vec3 { x: ffac*tref, y: 0.0, z: 0.0 });
63            (rref, pref)
64        } else if self.star == Star::Secondary {
65            tref = 1.0 - self.x_l1;
66            rref = tref * 1.0_f64.min(1.001*ffac);
67            pref = rpot2(self.q, self.spin, &Vec3 { x: 1.0 - ffac*tref, y: 0.0, z: 0.0 });
68            (rref, pref)
69        } else {
70            panic!("star is not an instance of Star")
71        }
72    }
73
74
75    pub fn fblink(&self, ffac: f64, acc: f64, earth: &Vec3, p: &Vec3) -> Result<bool, &'static str> {
76
77        let (rref, pref) = self.ref_sphere(ffac);
78
79        let cofm: Vec3 = match self.star {
80            Star::Primary => Vec3::cofm1(),
81            Star::Secondary => Vec3::cofm2(),
82        };
83        
84        // First compute the multipliers cutting the reference sphere (if any)
85        let mut lam1 = 0.0;
86        let mut lam2 = 0.0;
87        if !sphere_eclipse_vector(earth, p, &cofm, rref, &mut lam1, &mut lam2) {
88            return Ok(false);
89        }
90        if lam1 == 0.0 {
91            return Ok(true);
92        }
93
94        // Create function objects for 1D minimisation in lambda direction
95        let func = |lam: f64| {
96            self.potential(earth, p, lam)
97        };
98
99        // Now try to bracket a minimum. We just crudely compute function at regularly spaced intervals filling in the
100        // gaps until the step size between the points drops below the threshold. Take every opportunity to jump out early
101        // either if the potential is below the threshold or if we have bracketed a minimum.
102        let mut nstep: i32 = 1;
103        let mut step: f64 = lam2 - lam1;
104
105        let mut f1: f64 = 0.0;
106        let mut f2: f64 = 0.0;
107        let mut flam: f64 = 1.0;
108        let mut lam: f64 = lam1;
109
110        while step > acc {
111
112            lam = lam1 + step/2.0;
113
114            for _ in 0..nstep {
115
116                flam = func(lam);
117                if flam <= pref {
118                    return Ok(true);
119                }
120
121                // Calculate these as late as possible because they may often not be needed
122                if nstep == 1 {
123                    f1 = func(lam1);
124                    f2 = func(lam2);
125                }
126
127                if flam < f1 && flam < f2 {
128                    break;
129                }
130
131                lam += step;
132            }
133            if flam < f1 && flam < f2 {
134                break;
135            }
136            step /= 2.0;
137            nstep *= 2;
138        }
139
140        if flam < f1 && flam < f2 {
141
142            // OK, minimum bracketted, so finally pin it down accurately
143            // Possible that multiple minima could cause problems but I have
144            // never seen this in practice.
145            let dfunc = |lam: f64| {
146                let (_dp, dl) = self.gradient(earth, p, lam);
147                dl
148            };
149
150            let (_xmin, flam) = dbrent(lam1, lam, lam2, |x| func(x), |x| dfunc(x), acc, true, pref)?;
151
152            Ok(flam < pref)
153        } else {
154            // Not bracketted even after a detailed search, and we have not jumped 
155	        // out either, so assume no eclipse
156            Ok(false)
157        }
158
159
160    }
161
162
163    pub fn face(&self, direction: Vec3, rref: f64, pref: f64, acc: f64) -> (Vec3, Vec3, f64, f64) {
164
165        let mut pvec: Vec3;
166        let mut r: f64;
167
168        let cofm: Vec3 = match self.star {
169            Star::Primary => Vec3::cofm1(),
170            Star::Secondary => Vec3::cofm2(),
171        };
172
173        let rp: fn(f64, f64, &Vec3) -> f64 = match self.star {
174            Star::Primary => rpot1,
175            Star::Secondary => rpot2,
176        };
177
178        let drp: fn(f64, f64, &Vec3) -> Vec3 = match self.star {
179            Star::Primary => drpot1,
180            Star::Secondary => drpot2,
181        };
182
183        let mut tref: f64 = rp(self.q, self.spin, &(cofm + rref*direction));
184        if tref < pref {
185            panic!("stuff")
186        }
187
188        let mut r1: f64 = rref/2.;
189        let mut r2: f64 = rref;
190        tref = pref + 1.;
191
192        const MAXSEARCH: i32 = 30;
193        let mut i: i32 = 0;
194        while i < MAXSEARCH && tref > pref {
195            r1 = r2/2.;
196            tref = rp(self.q, self.spin, &(cofm + r1*direction));
197            if tref > pref {
198                r2 = r1;
199            }
200            i+=1;
201        }
202        if tref > pref {
203            panic!("other stuff");
204        }
205
206        const MAXCHOP: i32 = 100;
207        let mut nchop: i32 = 0;
208        while r2 - r1 > acc && nchop < MAXCHOP {
209            r = (r1 + r2)/2.;
210            pvec = cofm + r*direction;
211            if rp(self.q, self.spin, &pvec) < pref {
212                r1 = r;
213            }else {
214                r2 = r;
215            }
216            nchop += 1;
217        }
218        if nchop == MAXCHOP {
219            panic!("even more stuff");
220        }
221        r = (r1 + r2)/2.;
222        pvec = cofm + r*direction;
223        let mut dvec: Vec3 = drp(self.q, self.spin, &pvec);
224        let g = dvec.length();
225        dvec /= g;
226        return (pvec, dvec, r, g)
227    }
228
229
230    pub fn ingress_egress(&self, ffac: f64, iangle: f64, delta: f64, r: &Vec3, ingress: &mut f64, egress: &mut f64) -> bool {
231        let rref: f64;
232        let pref: f64;
233        (rref, pref) = self.ref_sphere(ffac);
234        let ri: f64 = iangle.to_radians();
235        let (sini, cosi) = ri.sin_cos();
236
237        let cofm: Vec3 = match self.star {
238            Star::Primary => Vec3::cofm1(),
239            Star::Secondary => Vec3::cofm2(),
240        };
241
242        let mut phi1: f64 = 0.0;
243        let mut phi2: f64 = 0.0;
244        let mut lam1: f64 = 0.0;
245        let mut lam2: f64 = 0.0;
246        let mut phi: f64 = 0.0;
247        let mut lam: f64 = 0.0;
248
249        if sphere_eclipse(cosi, sini, r, &cofm, rref, &mut phi1, &mut phi2, &mut lam1, &mut lam2) {
250            
251            let acc: f64 = 2.*(2.*TAU*(lam2 - lam1)*delta).sqrt();
252
253            if self.pot_min(cosi, sini, r, phi1, phi2, lam1, lam2, rref, pref, acc, &mut phi, &mut lam) {
254
255                let mut pin: f64 = phi;
256                let mut pout: f64 = phi1;
257                let mut pmid: f64;
258
259                while (pin - pout).abs() > delta {
260                    pmid = (pin + pout)/2.0;
261                    if self.fblink(ffac, acc, &set_earth(cosi, sini, pmid), r).unwrap() {
262                        pin = pmid;
263                    } else {
264                        pout = pmid;
265                    }
266                }
267                *ingress = (pin+pout)/2.0;
268                *ingress = *ingress - ingress.floor();
269
270                pin = phi;
271                pout = phi2;
272                while (pin-pout).abs() > delta {
273                    pmid = (pin+pout)/2.;
274                    if self.fblink(ffac, acc, &set_earth(cosi, sini, pmid), r).unwrap() {
275                        pin = pmid;
276                    } else {
277                        pout = pmid;
278                    }
279                }
280                *egress = (pin+pout)/2.0;
281                *egress = *egress - egress.floor();
282                if *egress < *ingress {
283                    *egress += 1.0;
284                }
285                return true;
286            } else {
287                return false;
288            }
289        } else {
290            return false;
291        }
292
293    }
294
295
296    pub fn pot_min(&self, cosi: f64, sini: f64, p: &Vec3, phi1: f64, phi2: f64, lam1: f64, lam2: f64, rref: f64, pref: f64, acc: f64, phi: &mut f64, lam: &mut f64) -> bool {
297        pot_min(self.q, self.star, self.spin, cosi, sini, p, phi1, phi2, lam1, lam2, rref, pref, acc, phi, lam)
298    }
299
300
301    pub fn x_l1(&self) -> f64 {
302        x_l1(self.q)
303    }
304
305
306    pub fn x_l1_asyncronous(&self) -> f64 {
307        match self.star {
308            Star::Primary => x_l1_1(self.q, self.spin),
309            Star::Secondary => x_l1_2(self.q, self.spin),
310        }
311    }
312
313
314    pub fn x_l2(&self) -> f64 {
315        x_l2(self.q)
316    }
317
318
319    pub fn x_l3(&self) -> f64 {
320        x_l3(self.q)
321    }
322
323}
324