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}