1use crate::errors::RocheError;
2use crate::potential::{drpot, rpot};
3use crate::x_lagrange::x_l1;
4use crate::{Star, Vec3};
5use pyo3::prelude::*;
6use numpy::{IntoPyArray, PyArray1};
7
8pub struct LineRoche {
10 pub q: f64,
12 pub star: Star,
14 pub dx: f64,
16 pub dy: f64,
18 pub cpot: f64,
20}
21
22impl LineRoche {
23 pub fn new(q: f64, star: Star, dx: f64, dy: f64, cpot: f64) -> Self {
24 Self {
25 q,
26 star,
27 dx,
28 dy,
29 cpot,
30 }
31 }
32
33 pub fn cost(&self, lam: f64) -> Result<(f64, f64), RocheError> {
34 let p: Vec3 = match self.star {
35 Star::Primary => Vec3::new(lam * self.dx, lam * self.dy, 0.0),
36 Star::Secondary => Vec3::new(1.0 + lam * self.dx, lam * self.dy, 0.0),
37 };
38 let f: f64 = rpot(self.q, &p)? - self.cpot;
40 let dp: Vec3 = drpot(self.q, &p)?;
42 let d: f64 = self.dx * dp.x + self.dy * dp.y;
44 Ok((f, d))
45 }
46}
47
48pub fn lobe1(q: f64, n: usize) -> Result<(Vec<f64>, Vec<f64>), RocheError> {
56 const FRAC: f64 = 1.0e-6;
58
59 let rl1: f64 = x_l1(q)?;
61 let p: Vec3 = Vec3::new(rl1, 0.0, 0.0);
62 let cpot: f64 = rpot(q, &p)?;
63
64 let mut xarr: Vec<f64> = Vec::with_capacity(n);
65 let mut yarr: Vec<f64> = Vec::with_capacity(n);
66 for i in 0..n {
67 if i == 0 || i == n - 1 {
68 xarr.push(rl1);
70 yarr.push(0.0);
71 } else {
72 let theta: f64 = (i as f64) * std::f64::consts::PI * 2.0 / ((n as f64) - 1.0);
73 let dx: f64 = theta.cos();
74 let dy: f64 = theta.sin();
75 let line: LineRoche = LineRoche::new(q, Star::Primary, dx, dy, cpot);
76 let lam: f64 = rtsafe(rl1 / 4.0, rl1, |lam| line.cost(lam), FRAC)?;
77 xarr.push(lam * dx);
78 yarr.push(lam * dy);
79 }
80 }
81 Ok((xarr, yarr))
82}
83
84#[pyfunction]
85#[pyo3(name = "lobe1", signature = (q, n=200))]
86pub fn lobe1_py(py: Python, q: f64, n: usize) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
94 let (xarr, yarr) = lobe1(q, n)?;
95 Ok((xarr.into_pyarray(py).unbind(), yarr.into_pyarray(py).unbind()))
96}
97
98pub fn lobe2(q: f64, n: usize) -> Result<(Vec<f64>, Vec<f64>), RocheError> {
106 const FRAC: f64 = 1.0e-6;
108
109 let rl1: f64 = x_l1(q)?;
111 let p: Vec3 = Vec3::new(rl1, 0.0, 0.0);
112 let cpot: f64 = rpot(q, &p)?;
113 let upper: f64 = 1.0 - rl1;
114 let lower: f64 = upper / 4.0;
115 let mut xarr: Vec<f64> = Vec::with_capacity(n);
116 let mut yarr: Vec<f64> = Vec::with_capacity(n);
117 for i in 0..n {
118 if i == 0 || i == n - 1 {
119 xarr.push(rl1);
121 yarr.push(0.0);
122 } else {
123 let theta: f64 = (i as f64) * std::f64::consts::PI * 2.0 / ((n as f64) - 1.0);
124 let dx: f64 = -theta.cos();
125 let dy: f64 = theta.sin();
126 let line: LineRoche = LineRoche::new(q, Star::Secondary, dx, dy, cpot);
127 let lam: f64 = rtsafe(lower, upper, |lam| line.cost(lam), FRAC)?;
128 xarr.push(1.0 + lam * dx);
129 yarr.push(lam * dy);
130 }
131 }
132 Ok((xarr, yarr))
133}
134
135#[pyfunction]
136#[pyo3(name = "lobe2", signature = (q, n=200))]
137pub fn lobe2_py(py: Python, q: f64, n: usize) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
145 let (xarr, yarr) = lobe2(q, n)?;
146 Ok((xarr.into_pyarray(py).unbind(), yarr.into_pyarray(py).unbind()))
147}
148
149pub fn vlobe1(q: f64, n: usize) -> Result<(Vec<f64>, Vec<f64>), RocheError> {
157
158 let mut tvx: f64;
159 let mut tvy: f64;
160
161 let mut vx_arr: Vec<f64> = vec![];
162 let mut vy_arr: Vec<f64> = vec![];
163
164 let (x, y) = lobe1(q, n)?;
165
166 let mu: f64 = q / (1.0 + q);
167 for i in 0..n {
168 tvx = -y[i];
169 tvy = x[i] - mu;
170 vx_arr.push(tvx);
171 vy_arr.push(tvy);
172 }
173 Ok((vx_arr, vy_arr))
174}
175
176#[pyfunction]
177#[pyo3(name = "vlobe1", signature = (q, n=200))]
178pub fn vlobe1_py(py: Python, q: f64, n: usize) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
186 let (xarr, yarr) = vlobe1(q, n)?;
187 Ok((xarr.into_pyarray(py).unbind(), yarr.into_pyarray(py).unbind()))
188}
189
190pub fn vlobe2(q: f64, n: usize) -> Result<(Vec<f64>, Vec<f64>), RocheError> {
198
199 let mut tvx: f64;
200 let mut tvy: f64;
201
202 let mut vx_arr: Vec<f64> = vec![];
203 let mut vy_arr: Vec<f64> = vec![];
204
205 let (x, y) = lobe2(q, n)?;
206
207 let mu: f64 = q / (1.0 + q);
208 for i in 0..n {
209 tvx = -y[i];
210 tvy = x[i] - mu;
211 vx_arr.push(tvx);
212 vy_arr.push(tvy);
213 }
214 Ok((vx_arr, vy_arr))
215}
216
217#[pyfunction]
218#[pyo3(name = "vlobe2", signature = (q, n=200))]
219pub fn vlobe2_py(py: Python, q: f64, n: usize) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
227 let (xarr, yarr) = vlobe2(q, n)?;
228 Ok((xarr.into_pyarray(py).unbind(), yarr.into_pyarray(py).unbind()))
229}
230
231pub fn rtsafe<F>(x1: f64, x2: f64, func: F, xacc: f64) -> Result<f64, RocheError>
239where
240 F: Fn(f64) -> Result<(f64, f64), RocheError>,
241{
242 let mut xlo = x1;
243 let mut xhi = x2;
244 let mut fl;
245 let mut fh;
246 let mut df;
247 const MAXITER: i32 = 100;
248 (fl, _) = func(xlo)?;
249 (fh, _) = func(xhi)?;
250
251 if (fl > 0.0 && fh > 0.0) || (fl < 0.0 && fh < 0.0) {
252 return Err(RocheError::RtsafeError(
253 "Root must be bracketed in rtsafe".to_string(),
254 ));
255 }
256
257 if fl == 0.0 {
259 return Ok(xlo);
260 } else if fh == 0.0 {
261 return Ok(xhi);
262 }
263
264 if fh < 0.0 {
266 std::mem::swap(&mut xlo, &mut xhi);
267 std::mem::swap(&mut fl, &mut fh);
268 }
269
270 let mut rts = 0.5 * (xlo + xhi);
271 let mut dxold = (xhi - xlo).abs();
272 let mut dx = dxold;
273 let mut f;
274 (f, df) = func(rts)?;
275 let mut iter = 0;
276 while iter < MAXITER {
277 if ((rts - xhi) * df - f) * ((rts - xlo) * df - f) >= 0.0
278 || ((2.0 * f).abs() > (dxold * df).abs())
279 {
280 dxold = dx;
282 dx = 0.5 * (xhi - xlo);
283 rts = xlo + dx;
284 if xlo == rts {
285 return Ok(rts);
286 }
287 } else {
288 dxold = dx;
290 dx = f / df;
291 let temp = rts;
292 rts -= dx;
293 if temp == rts {
294 return Ok(rts);
295 }
296 }
297
298 if dx.abs() < xacc {
299 return Ok(rts);
300 }
301
302 (f, df) = func(rts)?;
303 if f < 0.0 {
304 xlo = rts;
305 } else {
306 xhi = rts;
307 }
308 iter += 1;
309 }
310
311 Err(RocheError::RtsafeError(
312 "Maximum number of iterations exceeded in rtsafe".to_string(),
313 ))
314}