Skip to main content

rust_roche/
disc_eclipse.rs

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