Skip to main content

roche/
disc_eclipse.rs

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