roche/disc_eclipse.rs
1use std::f64::consts::TAU;
2use crate::{Etype, Vec3};
3use crate::errors::RocheError;
4
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7// This enumerates the 5 possible outcomes of the LOSC intersection with a circle.
8pub enum Circle {
9 // Line of sight cone starts at or above the circle of interest
10 Above,
11 // Line of sight circle is everywhere inside circle of interest
12 Inside,
13 // Line of sight circle is everywhere outside circle of interest
14 Outside,
15 // Line of sight circle is separated from the circle of interest
16 Separate,
17 // Line of sight circle cone intersects the circle of interest
18 Crossing,
19}
20
21///
22/// disc_eclipse works out phase ranges during which a cylindrically symmetric, flared disc
23/// running between a pair of radii eclipses a given point.
24///
25/// Arguments:
26///
27/// * `iangle`: the orbital inclination, degrees. 90 = edge on.
28/// * `r`: the position vector of the point in question (units of binary separation)
29/// * `rdisc1`: inner disc radius, units of separation
30/// * `rdisc2`: outer disc radius, units of separation
31/// * `beta`: exponent of flaring, so that the height scales as r**beta. beta should be >= 1
32/// * `height disc`: height at unit radius in disc (even if it does not exist)
33///
34/// Returns:
35///
36/// * a vector of ingress and egress phase pairs during which the point in question is eclipsed.
37/// The ingress phase will always be between 0 and 1 while the egress phase will be larger than this, but
38/// by no more than 1 cycle. If the vector is null, no eclipse takes place.
39///
40pub fn disc_eclipse(iangle: f64, rdisc1: f64, rdisc2: f64, beta: f64, height: f64, r: &Vec3) -> Result<Etype, RocheError> {
41
42 if beta <= 1.0 {
43 return Err(RocheError::ParameterError("beta must be >= 1.0".to_string()));
44 }
45
46 // Compute and store cosine and sine of inclination if need be.
47 // let mut iangle_old: f64 = -1.0e30;
48 let sini: f64;
49 let cosi: f64;
50 // if iangle != iangle_old {
51 // iangle_old = iangle;
52 (sini, cosi) = iangle.to_radians().sin_cos();
53 // }
54
55 let mut temp = Etype::new();
56
57 // Compute height of disc at outer boundary
58 let h_out: f64 = height * rdisc2.powf(beta);
59
60 // Deal with points too high ever to be eclipsed whatever the inclination
61 if r.z >= h_out {
62 return Ok(temp);
63 }
64
65 // Special case of exactly edge-on, only curved outer edge matters.
66 if cosi == 0.0 {
67 if r.z.abs() < h_out {
68 let rxy: f64 = (r.x*r.x + r.y*r.y).sqrt();
69 if rxy <= rdisc2 {
70 temp.push((0.0, 1.0));
71 }
72 }
73 return Ok(temp);
74 }
75
76 // Work out distance from axis
77 let rxy: f64 = (r.x*r.x + r.y*r.y).sqrt();
78
79 if rdisc1 < rxy && rxy < rdisc2 && r.z.abs() < height*rxy.powf(beta) {
80 // Point is inside disc and so is eclipsed
81 temp.push((0.0, 1.1));
82 return Ok(temp);
83 }
84
85 let tani: f64 = sini/cosi;
86 let mut result: Circle;
87
88 let mut phase: f64 = 0.0;
89 let mut ingress: f64;
90 let mut egress: f64;
91
92 if rxy < rdisc2 && r.z >= height*rdisc1.max(rxy).powf(beta) {
93
94 // Point is in approximately conical region above the disc. Just need to check whether
95 // it is not occulted by the edge of the disc
96 result = circle_eclipse(rxy, r.z, h_out, rdisc2, tani, &mut phase);
97
98 if result == Circle::Outside {
99 // point will be occulted by the disc edge at all phases
100 temp.push((0.0, 1.1));
101 } else if result == Circle::Crossing {
102 // point partially occulted by disc edge; work out phases
103 let phi0: f64 = r.y.atan2(r.x)/TAU;
104 ingress = phi0 + phase;
105 ingress -= ingress.floor();
106 egress = ingress + 1.0 - 2.0*phase;
107 temp.push((ingress, egress));
108 }
109 return Ok(temp);
110 }
111
112 // Compute the radius of circle formed by LOSC in the plane of
113 // the lower outer rim of the disc
114 let rcone_lo: f64 = 0.0_f64.max(tani*(-h_out - r.z));
115
116 // Circle encloses rim, so no intersection
117 if rcone_lo >= rxy + rdisc2 {
118 return Ok(temp);
119 }
120
121 // Compute the radius of circle formed by LOSC in the plane of
122 // the upper outer rim of the disc
123 let rcone_hi: f64 = tani*(h_out - r.z);
124
125 // Circle disjoint from rim, so no intersection
126 if rxy >= rcone_hi + rdisc2 {
127 return Ok(temp);
128 }
129
130 // For the moment we pretend that the disc has no hole at its centre, so
131 // that we are simply interested in the phases over which eclipse occurs.
132 // At this point we are guaranteed that this will happen. All events are
133 // symmetrically located around a phase defined by x and y only which will
134 // be calculated at the end. We therefore just find the half range which
135 // is called 'eclipse_phase' below.
136
137 let eclipse_phase: f64;
138 if rxy + rcone_lo <= rdisc2 {
139
140 // Cone swept out by line of sight always inside lower face so total eclipse
141 eclipse_phase = 0.5;
142 } else if rxy <= rdisc2 {
143
144 // Points that project close to the z axis which are only
145 // partially obscured by the disc hovering above them.
146 // this means they must be below -HOUT
147 eclipse_phase = cut_phase(rxy, rcone_lo, rdisc2);
148 } else {
149
150 // Points further from the z axis than the outer rim of the disc that
151 // will be eclipsed.
152 if rcone_hi*rcone_hi + rdisc2*rdisc2 >= rxy*rxy
153 && rcone_lo*rcone_lo + rdisc2*rdisc2 <= rxy*rxy {
154
155 // In this case it is the curved outer disc rim that sets the limit
156 eclipse_phase = (rdisc2/rxy).asin()/TAU;
157 } else if rcone_hi*rcone_hi + rdisc2*rdisc2 < rxy*rxy {
158
159 // In this case it is upper outer rim that sets the limit
160 eclipse_phase = cut_phase(rxy, rcone_hi, rdisc2);
161 } else {
162
163 // In this case it is lower outer rim that sets the limit
164 eclipse_phase = cut_phase(rxy, rcone_lo, rdisc2);
165 }
166 }
167
168 // At this point we have covered all cases for the eclipse, whilst ignoring the
169 // possibility of seeing the point through the hole in the middle of the disc.
170 // Now let's calculate the 'appear_phase' if any.
171
172 // First compute height of disc at inner boundary
173 let h_in: f64 = height*rdisc1.powf(beta);
174
175 let mut appear_phase: f64 = -1.0;
176
177 if r.z < -h_out {
178 // In this case the LOSC has to run through 4 circles which are the upper and
179 // lower outer and inner rims.
180
181 // First, the lower outer rim
182 result = circle_eclipse(rxy, r.z, -h_out, rdisc2, tani, &mut phase);
183 if result == Circle::Inside {
184 appear_phase = 0.5;
185 } else if result == Circle::Crossing {
186 appear_phase = appear_phase.min(phase);
187 }
188
189 // Second, the lower inner rim
190 if appear_phase > 0.0 {
191 result = circle_eclipse(rxy, r.z, -h_in, rdisc1, tani, &mut phase);
192 if result == Circle::Crossing {
193 appear_phase = appear_phase.min(phase);
194 } else if result != Circle::Inside {
195 appear_phase = -1.0;
196 }
197 }
198
199 // Fourth, the upper outer rim
200 if appear_phase > 0.0 {
201 result = circle_eclipse(rxy, r.z, h_out, rdisc2, tani, &mut phase);
202 if result == Circle::Crossing {
203 appear_phase = appear_phase.min(phase);
204 } else if result != Circle::Inside {
205 appear_phase = -1.0;
206 }
207 }
208 } else if rxy < rdisc1 {
209
210 if r.z < -h_in {
211
212 // Points hovering around underside of disc. Have to consider just three circles
213
214 // First, the lower inner rim
215 result = circle_eclipse(rxy, r.z, -h_in, rdisc1, tani, &mut phase);
216 if result == Circle::Inside {
217 appear_phase = 0.5;
218 } else if result == Circle::Crossing {
219 appear_phase = phase;
220 }
221
222 // Second, the upper inner rim
223 if appear_phase > 0.0 {
224 result = circle_eclipse(rxy, r.z, h_in, rdisc1, tani, &mut phase);
225 if result == Circle::Crossing {
226 appear_phase = appear_phase.min(phase);
227 } else if result != Circle::Inside {
228 appear_phase = -1.0;
229 }
230 }
231
232 // Third, the upper outer rim
233 if appear_phase > 0.0 {
234 result = circle_eclipse(rxy, r.z, h_out, rdisc2, tani, &mut phase);
235 if result == Circle::Crossing {
236 appear_phase = appear_phase.min(phase);
237 } else if result != Circle::Inside {
238 appear_phase = -1.0;
239 }
240 }
241 } else if r.z < h_in {
242
243 // Points inside hole in middle of disc. Have to consider just two circles
244
245 // First, the upper inner rim
246 result = circle_eclipse(rxy, r.z, h_in, rdisc1, tani, &mut phase);
247 if result == Circle::Inside {
248 appear_phase = 0.0;
249 } else if result == Circle::Crossing {
250 appear_phase = phase;
251 }
252
253 // Second, the upper outer rim
254 if appear_phase > 0.0 {
255 result = circle_eclipse(rxy, r.z, h_out, rdisc2, tani, &mut phase);
256 if result == Circle::Crossing {
257 appear_phase = appear_phase.min(phase);
258 } else if result != Circle::Inside {
259 appear_phase = -1.0;
260 }
261 }
262 }
263 }
264
265 // Here is the central phase
266 let phi0: f64 = r.y.atan2(-r.x/TAU);
267
268 if appear_phase <= 0.0 {
269 ingress = phi0 - eclipse_phase;
270 ingress -= ingress.floor();
271 egress = ingress + 2.0*eclipse_phase;
272 temp.push((ingress, egress));
273 } else if appear_phase < eclipse_phase {
274 ingress = phi0 - eclipse_phase;
275 ingress -= ingress.floor();
276 egress = ingress + (eclipse_phase - appear_phase);
277 temp.push((ingress, egress));
278 ingress = phi0 + appear_phase;
279 ingress -= ingress.floor();
280 egress = ingress + (eclipse_phase - appear_phase);
281 temp.push((ingress, egress));
282 }
283
284 return Ok(temp)
285
286}
287
288
289pub fn circle_eclipse(rxy: f64, z: f64, zcirc: f64, radius: f64, tani: f64, phase: &mut f64) -> Circle {
290
291 // point above circle
292 if z >= zcirc {
293 return Circle::Above;
294 }
295 let rcone: f64 = tani*(zcirc - z);
296
297 // line-of-sight always outside the circle
298 if rcone >= rxy + radius {
299 return Circle::Outside;
300 }
301
302 // line-of-sight circle separate from the circle
303 if rxy >= rcone + radius {
304 return Circle::Separate;
305 }
306
307 // line-of-sight always outside the circle
308 if rxy + rcone <= radius {
309 return Circle::Inside;
310 }
311
312 // crossing case
313 *phase = cut_phase(rxy, rcone, radius);
314
315 Circle::Crossing
316
317}
318
319
320pub fn cut_phase(rxy: f64, rcone: f64, radius: f64) -> f64 {
321
322 // Temporary checks
323 if rxy + rcone <= radius {
324 panic!("rxy + rcone <= radius");
325 }
326 if rxy >= radius + rcone {
327 panic!("rxy >= radius + rcone");
328 }
329 if rcone >= radius + rxy {
330 panic!("rcone >= radius + rxy");
331 }
332
333 ((rxy*rxy + rcone*rcone - radius*radius)/(2.0*rcone*rxy)).acos()/TAU
334}