1use crate::errors::RocheError;
2use crate::{Star, Vec3, brightspot_position, ingress_egress};
3use crate::{fblink, set_earth_iangle, x_l1};
4use pyo3::prelude::*;
5use std::f64::consts::{FRAC_PI_2, TAU};
6
7
8#[pyfunction]
27#[pyo3(signature = (q, iangle, dpwd, ntheta=100, dr=1.0e-5, rmax=0.1))]
28pub fn wdradius(q: f64, iangle: f64, dpwd: f64, ntheta: i32, dr: f64, rmax: f64) -> Result<f64, RocheError> {
29 let mut r1: f64;
30 let mut r1_lo: f64 = 0.0;
31 let mut r1_hi: f64 = rmax;
32 let mut phi3: f64;
33 let mut phi4: f64;
34 let mut dp: f64;
35 while r1_hi - r1_lo > dr {
36 r1 = (r1_lo + r1_hi) / 2.0;
37 (phi3, phi4) = wdphases(q, iangle, r1, -1.0, ntheta)?;
38 dp = phi4 - phi3;
39 if dp > dpwd {r1_hi = r1} else {r1_lo = r1}
40 }
41 Ok((r1_lo + r1_hi) / 2.0)
42}
43
44
45#[pyfunction]
64#[pyo3(signature = (q, iangle, r1, r2=-1.0, ntheta=200))]
65pub fn wdphases(
66 q: f64,
67 iangle: f64,
68 r1: f64,
69 r2: f64,
70 ntheta: i32,
71) -> Result<(f64, f64), RocheError> {
72 let ffac: f64 = if r2 <= 0.0 {
73 1.0
74 } else {
75 r2 / (1.0 - x_l1(q)?)
76 };
77 let mut phi4lo: f64 = 0.0;
79 let mut phi4hi: f64 = 0.25;
80 let mut phi4: f64 = 0.0;
81
82 if !eclipsed_4(q, iangle, phi4lo, r1, ffac, ntheta)? {
83 let message = format!("no eclipse at all for q={}, i={}, r1={}", q, iangle, r1);
84 return Err(RocheError::WdphasesError(message));
85 }
86
87 while phi4hi - phi4lo > r1 / (ntheta as f64) / 10.0 {
88 phi4 = (phi4lo + phi4hi) / 2.0;
89 if eclipsed_4(q, iangle, phi4, r1, ffac, ntheta)? {
90 phi4lo = phi4;
91 } else {
92 phi4hi = phi4;
93 }
94 }
95
96 let mut phi3lo: f64 = 0.0;
98 let mut phi3hi: f64 = 0.25;
99 let mut phi3: f64 = 0.0;
100
101 if uneclipsed_3(q, iangle, phi3lo, r1, ffac, ntheta)? {
102 return Ok((-1.0, phi4));
103 }
104
105 while phi3hi - phi3lo > r1 / (ntheta as f64) / 10.0 {
106 phi3 = (phi3lo + phi3hi) / 2.0;
107 if uneclipsed_3(q, iangle, phi3, r1, ffac, ntheta)? {
108 phi3hi = phi3;
109 } else {
110 phi3lo = phi3;
111 }
112 }
113
114 Ok((phi3, phi4))
115}
116
117fn xyv(iangle: f64, r1: f64, phase: f64) -> (Vec3, Vec3) {
122 let (sinp, cosp) = (TAU * phase).sin_cos();
123 let x = Vec3::new(-r1 * sinp, r1 * cosp, 0.0);
124 let iangle_radians = iangle.to_radians();
125 let (sini, cosi) = iangle_radians.sin_cos();
126 let y = Vec3::new(-r1 * cosi * cosp, -r1 * cosi * sinp, r1 * sini);
127 (x, y)
128}
129
130fn uneclipsed_3(
136 q: f64,
137 iangle: f64,
138 phase: f64,
139 r1: f64,
140 ffac: f64,
141 ntheta: i32,
142) -> Result<bool, RocheError> {
143 let (x, y) = xyv(iangle, r1, phase);
144 let mut theta: f64;
145 let mut v: Vec3;
146 let mut sint: f64;
147 let mut cost: f64;
148 for i in 0..ntheta {
149 theta = FRAC_PI_2 * (i as f64 / (ntheta as f64 - 1.0));
150 (sint, cost) = theta.sin_cos();
151 v = -x * cost + y * sint;
152 if !fblink(
153 q,
154 Star::Secondary,
155 1.0,
156 ffac,
157 1.0e-5,
158 &set_earth_iangle(iangle, phase),
159 &v,
160 )? {
161 return Ok(true);
162 }
163 }
164
165 Ok(false)
166}
167
168fn eclipsed_4(
174 q: f64,
175 iangle: f64,
176 phase: f64,
177 r1: f64,
178 ffac: f64,
179 ntheta: i32,
180) -> Result<bool, RocheError> {
181 let (x, y) = xyv(iangle, r1, phase);
182 let mut theta: f64;
183 let mut v: Vec3;
184 let mut sint: f64;
185 let mut cost: f64;
186 for i in 0..ntheta {
187 theta = FRAC_PI_2 * (i as f64 / (ntheta as f64 - 1.0));
188 (sint, cost) = theta.sin_cos();
189 v = x * cost - y * sint;
190 if fblink(
191 q,
192 Star::Secondary,
193 1.0,
194 ffac,
195 1.0e-5,
196 &set_earth_iangle(iangle, phase),
197 &v,
198 )? {
199 return Ok(true);
200 }
201 }
202
203 Ok(false)
204}
205
206#[pyfunction]
207pub fn bsphases(q: f64, iangle: f64, rbs: f64) -> Result<(f64, f64), RocheError> {
209 let r = brightspot_position(q, rbs, 1.0e-7, 1.0e-2)?;
210 let mut ingress: f64 = 0.0;
211 let mut egress: f64 = 0.0;
212 let eclipse = ingress_egress(
213 q,
214 Star::Secondary,
215 1.0,
216 1.0,
217 iangle,
218 1.0e-7,
219 &r,
220 &mut ingress,
221 &mut egress,
222 )?;
223 if !eclipse {
224 return Err(RocheError::WdphasesError(
225 "point is not eclipsed".to_string(),
226 ));
227 }
228 Ok((ingress - 1.0, egress - 1.0))
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn wdradius_test() -> Result<(), RocheError> {
237 assert_eq!(wdradius(0.2, 87.0, 0.008, 100, 1.0e-5, 0.1)?, 0.026266479492187505);
238 Ok(())
239 }
240
241 #[test]
242 fn wdphases_test() -> Result<(), RocheError> {
243 assert_eq!(
245 wdphases(0.2, 90.0, 0.015, 0.20, 200)?,
246 (0.027637481689453125, 0.032169342041015625)
247 );
248 assert_eq!(
249 wdphases(0.2, 85.0, 0.015, 0.20, 200)?,
250 (0.023677825927734375, 0.029010772705078125)
251 );
252 assert_eq!(
253 wdphases(0.2, 80.0, 0.015, 0.20, 200)?,
254 (-1.0, 0.015842437744140625)
255 );
256 assert!(wdphases(0.2, 60.0, 0.015, 0.20, 200).is_err());
257 Ok(())
258 }
259
260 #[test]
261 fn bsphases_test() -> Result<(), RocheError> {
262 let (phi_in, phi_eg) = bsphases(0.2, 90.0, 0.2)?;
264 assert!((phi_in - -0.016291638617189408).abs() < 1.0e-7);
265 assert!((phi_eg - 0.07258765600962591).abs() < 1.0e-7);
266
267 let (phi_in, phi_eg) = bsphases(0.2, 85.0, 0.2)?;
268 assert!((phi_in - -0.013903522665196566).abs() < 1.0e-7);
269 assert!((phi_eg - 0.07018328081120417).abs() < 1.0e-7);
270
271 assert!(bsphases(0.2, 60.0, 0.2).is_err());
272 Ok(())
273 }
274}