1use crate::errors::RocheError;
2use crate::{Etype, Star, Vec3};
3use crate::{dbrent, pot_min, x_l1, x_l1_1, x_l1_2, x_l2, x_l3};
4use crate::{drpot1, drpot2, rpot_grad, rpot_val, rpot1, rpot2};
5use crate::{set_earth, sphere_eclipse, sphere_eclipse_vector};
6use std::f64::consts::TAU;
7
8pub struct RocheContext {
17 pub q: f64,
18 pub star: Star,
19 pub spin: f64,
20 pub x_l1: f64,
21}
22
23impl RocheContext {
24 pub fn new(q: f64, star: Star, spin: f64) -> Result<Self, RocheError> {
25 let x_l1: f64 = match star {
26 Star::Primary => x_l1_1(q, spin)?,
27 Star::Secondary => x_l1_2(q, spin)?,
28 };
29 Ok(Self {
30 q,
31 star,
32 spin,
33 x_l1,
34 })
35 }
36
37 pub fn potential(&self, earth: &Vec3, p: &Vec3, lam: f64) -> Result<f64, RocheError> {
38 rpot_val(self.q, self.star, self.spin, earth, p, lam)
39 }
40
41 pub fn gradient(&self, earth: &Vec3, p: &Vec3, lam: f64) -> Result<(f64, f64), RocheError> {
42 let (dp, dl) = rpot_grad(self.q, self.star, self.spin, earth, p, lam)?;
43 Ok((dp, dl))
44 }
45
46 pub fn potential_grad(
47 &self,
48 earth: &Vec3,
49 p: &Vec3,
50 lam: f64,
51 ) -> Result<(f64, f64, f64), RocheError> {
52 let f = self.potential(earth, p, lam)?;
53 let (dp, dl) = rpot_grad(self.q, self.star, self.spin, earth, p, lam)?;
54 Ok((f, dp, dl))
55 }
56
57 pub fn ref_sphere(&self, ffac: f64) -> Result<(f64, f64), RocheError> {
58 let tref: f64;
59 let rref: f64;
60 let pref: f64;
61 if self.star == Star::Primary {
62 tref = self.x_l1;
63 rref = tref * 1.0_f64.min(1.001 * ffac);
64 pref = rpot1(
65 self.q,
66 self.spin,
67 &Vec3 {
68 x: ffac * tref,
69 y: 0.0,
70 z: 0.0,
71 },
72 )?;
73 Ok((rref, pref))
74 } else if self.star == Star::Secondary {
75 tref = 1.0 - self.x_l1;
76 rref = tref * 1.0_f64.min(1.001 * ffac);
77 pref = rpot2(
78 self.q,
79 self.spin,
80 &Vec3 {
81 x: 1.0 - ffac * tref,
82 y: 0.0,
83 z: 0.0,
84 },
85 )?;
86 Ok((rref, pref))
87 } else {
88 let message = format!("{:?} is not and instance of Star.", self.star);
89 Err(RocheError::ParameterError(message))
90 }
91 }
92
93 pub fn fblink(&self, ffac: f64, acc: f64, earth: &Vec3, p: &Vec3) -> Result<bool, RocheError> {
94 let (rref, pref) = self.ref_sphere(ffac)?;
95
96 let cofm: Vec3 = match self.star {
97 Star::Primary => Vec3::cofm1(),
98 Star::Secondary => Vec3::cofm2(),
99 };
100
101 let mut lam1 = 0.0;
103 let mut lam2 = 0.0;
104 if !sphere_eclipse_vector(earth, p, &cofm, rref, &mut lam1, &mut lam2) {
105 return Ok(false);
106 }
107 if lam1 == 0.0 {
108 return Ok(true);
109 }
110
111 let func = |lam: f64| self.potential(earth, p, lam);
113
114 let mut nstep: i32 = 1;
118 let mut step: f64 = lam2 - lam1;
119
120 let mut f1: f64 = 0.0;
121 let mut f2: f64 = 0.0;
122 let mut flam: f64 = 1.0;
123 let mut lam: f64 = lam1;
124
125 while step > acc {
126 lam = lam1 + step / 2.0;
127
128 for _ in 0..nstep {
129 flam = func(lam)?;
130 if flam <= pref {
131 return Ok(true);
132 }
133
134 if nstep == 1 {
136 f1 = func(lam1)?;
137 f2 = func(lam2)?;
138 }
139
140 if flam < f1 && flam < f2 {
141 break;
142 }
143
144 lam += step;
145 }
146 if flam < f1 && flam < f2 {
147 break;
148 }
149 step /= 2.0;
150 nstep *= 2;
151 }
152
153 if flam < f1 && flam < f2 {
154 let dfunc = |lam: f64| {
158 let (_dp, dl) = self.gradient(earth, p, lam)?;
159 Ok(dl)
160 };
161
162 let (_xmin, flam) =
163 dbrent(lam1, lam, lam2, func, dfunc, acc, true, pref)?;
164
165 Ok(flam < pref)
166 } else {
167 Ok(false)
170 }
171 }
172
173 pub fn face(
174 &self,
175 direction: Vec3,
176 rref: f64,
177 pref: f64,
178 acc: f64,
179 ) -> Result<(Vec3, Vec3, f64, f64), RocheError> {
180 let mut pvec: Vec3;
181 let mut r: f64;
182
183 let cofm: Vec3 = match self.star {
184 Star::Primary => Vec3::cofm1(),
185 Star::Secondary => Vec3::cofm2(),
186 };
187
188 let rp: fn(f64, f64, &Vec3) -> Result<f64, RocheError> = match self.star {
189 Star::Primary => rpot1,
190 Star::Secondary => rpot2,
191 };
192
193 let drp: fn(f64, f64, &Vec3) -> Result<Vec3, RocheError> = match self.star {
194 Star::Primary => drpot1,
195 Star::Secondary => drpot2,
196 };
197
198 let mut tref: f64 = rp(self.q, self.spin, &(cofm + rref * direction))?;
199 if tref < pref {
200 let message = format!(
201 "point at reference radius {} appears to be at lower potential {} than the reference potential {}",
202 rref, tref, pref
203 );
204 return Err(RocheError::FaceError(message));
205 }
206
207 let mut r1: f64 = rref / 2.;
208 let mut r2: f64 = rref;
209 tref = pref + 1.;
210
211 const MAXSEARCH: i32 = 30;
212 let mut i: i32 = 0;
213 while i < MAXSEARCH && tref > pref {
214 r1 = r2 / 2.;
215 tref = rp(self.q, self.spin, &(cofm + r1 * direction))?;
216 if tref > pref {
217 r2 = r1;
218 }
219 i += 1;
220 }
221 if tref > pref {
222 let message = "could not find a radius with a potential below the reference potential; probably bad inputs.";
223 return Err(RocheError::FaceError(message.to_string()));
224 }
225
226 const MAXCHOP: i32 = 100;
227 let mut nchop: i32 = 0;
228 while r2 - r1 > acc && nchop < MAXCHOP {
229 r = (r1 + r2) / 2.;
230 pvec = cofm + r * direction;
231 if rp(self.q, self.spin, &pvec)? < pref {
232 r1 = r;
233 } else {
234 r2 = r;
235 }
236 nchop += 1;
237 }
238 if nchop == MAXCHOP {
239 return Err(RocheError::FaceError(
240 "reached maximum number of binary chops".to_string(),
241 ));
242 }
243 r = (r1 + r2) / 2.;
244 pvec = cofm + r * direction;
245 let mut dvec: Vec3 = drp(self.q, self.spin, &pvec)?;
246 let g = dvec.length();
247 dvec /= g;
248 Ok((pvec, dvec, r, g))
249 }
250
251 pub fn ingress_egress(
252 &self,
253 ffac: f64,
254 iangle: f64,
255 delta: f64,
256 r: &Vec3,
257 ingress: &mut f64,
258 egress: &mut f64,
259 ) -> Result<bool, RocheError> {
260 let rref: f64;
261 let pref: f64;
262 (rref, pref) = self.ref_sphere(ffac)?;
263 let ri: f64 = iangle.to_radians();
264 let (sini, cosi) = ri.sin_cos();
265
266 let cofm: Vec3 = match self.star {
267 Star::Primary => Vec3::cofm1(),
268 Star::Secondary => Vec3::cofm2(),
269 };
270
271 let mut phi1: f64 = 0.0;
272 let mut phi2: f64 = 0.0;
273 let mut lam1: f64 = 0.0;
274 let mut lam2: f64 = 0.0;
275 let mut phi: f64 = 0.0;
276 let mut lam: f64 = 0.0;
277
278 if sphere_eclipse(
279 cosi, sini, r, &cofm, rref, &mut phi1, &mut phi2, &mut lam1, &mut lam2,
280 ) {
281 let acc: f64 = 2. * (2. * TAU * (lam2 - lam1) * delta).sqrt();
282
283 if self.pot_min(
284 cosi, sini, r, phi1, phi2, lam1, lam2, rref, pref, acc, &mut phi, &mut lam,
285 )? {
286 let mut pin: f64 = phi;
287 let mut pout: f64 = phi1;
288 let mut pmid: f64;
289
290 while (pin - pout).abs() > delta {
291 pmid = (pin + pout) / 2.0;
292 if self
293 .fblink(ffac, acc, &set_earth(cosi, sini, pmid), r)
294 .unwrap()
295 {
296 pin = pmid;
297 } else {
298 pout = pmid;
299 }
300 }
301 *ingress = (pin + pout) / 2.0;
302 *ingress -= ingress.floor();
303
304 pin = phi;
305 pout = phi2;
306 while (pin - pout).abs() > delta {
307 pmid = (pin + pout) / 2.;
308 if self
309 .fblink(ffac, acc, &set_earth(cosi, sini, pmid), r)
310 .unwrap()
311 {
312 pin = pmid;
313 } else {
314 pout = pmid;
315 }
316 }
317 *egress = (pin + pout) / 2.0;
318 *egress -= egress.floor();
319 if *egress < *ingress {
320 *egress += 1.0;
321 }
322 Ok(true)
323 } else {
324 Ok(false)
325 }
326 } else {
327 Ok(false)
328 }
329 }
330
331 pub fn star_eclipse(
332 &self,
333 r: f64,
334 ffac: f64,
335 iangle: f64,
336 posn: &Vec3,
337 delta: f64,
338 roche: bool,
339 star: Star,
340 eclipses: &mut Etype,
341 ) -> Result<(), RocheError> {
342 let ri = iangle.to_radians();
343 let (sini, cosi) = ri.sin_cos();
344 let cofm = match star {
345 Star::Primary => Vec3::cofm1(),
346 Star::Secondary => Vec3::cofm2(),
347 };
348 let mut lam1: f64 = 0.0;
349 let mut lam2: f64 = 0.0;
350 let mut ingress: f64 = 0.0;
351 let mut egress: f64 = 0.0;
352 if (roche && self.ingress_egress(ffac, iangle, delta, posn, &mut ingress, &mut egress)?)
354 || (!roche
355 && sphere_eclipse(
356 cosi,
357 sini,
358 posn,
359 &cofm,
360 r,
361 &mut ingress,
362 &mut egress,
363 &mut lam1,
364 &mut lam2,
365 ))
366 {
367 eclipses.push((ingress, egress));
368 }
369 Ok(())
370 }
371
372 pub fn pot_min(
373 &self,
374 cosi: f64,
375 sini: f64,
376 p: &Vec3,
377 phi1: f64,
378 phi2: f64,
379 lam1: f64,
380 lam2: f64,
381 rref: f64,
382 pref: f64,
383 acc: f64,
384 phi: &mut f64,
385 lam: &mut f64,
386 ) -> Result<bool, RocheError> {
387 pot_min(
388 self.q, self.star, self.spin, cosi, sini, p, phi1, phi2, lam1, lam2, rref, pref, acc,
389 phi, lam,
390 )
391 }
392
393 pub fn x_l1(&self) -> Result<f64, RocheError> {
394 x_l1(self.q)
395 }
396
397 pub fn x_l1_asyncronous(&self) -> Result<f64, RocheError> {
398 match self.star {
399 Star::Primary => Ok(x_l1_1(self.q, self.spin)?),
400 Star::Secondary => Ok(x_l1_2(self.q, self.spin)?),
401 }
402 }
403
404 pub fn x_l2(&self) -> Result<f64, RocheError> {
405 x_l2(self.q)
406 }
407
408 pub fn x_l3(&self) -> Result<f64, RocheError> {
409 x_l3(self.q)
410 }
411}