Skip to main content

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}