refraction/lib.rs
1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use bisection_search::{BisectionSearch, Interval};
5use simba::scalar::RealField;
6
7/// Define the parameters required to solve a refraction problem.
8///
9/// In the following drawing, we want to find `x` when we know `w`, `h`, `d`,
10/// and the ratio of refractive indices `n` where `n = n2/n1`.
11///
12/// `a` and `b` are angles, `n1` and `n2` are refractive indices, `d`, `h` and
13/// `w` are lengths.
14///
15/// ```text
16/// ---
17/// ----/ a|
18/// n1 ----/ |
19/// ----/ |
20/// -----/ |
21/// ----/ | w
22/// ----/ |
23/// ----/ |
24/// x --/ d-x |
25/// |--------|----------------------------------------|
26/// | /
27/// | h |
28/// | /
29/// | /
30/// | / n2
31/// | |
32/// | /
33/// | /
34/// | /
35/// |b|
36/// |/
37///
38/// sin(a) n2
39/// ------ = -- = n
40/// sin(b) n1
41/// ```
42///
43/// See [this](https:///math.stackexchange.com/questions/150769) for an
44/// explanation of the problem and this solution. Ascii drawing done with
45/// [textik.com](https://textik.com).
46#[derive(Clone)]
47pub struct RefractionEq<T> {
48 /// Distance along the refractive boundary
49 pub d: T,
50 /// Height below the refractive boundary
51 pub h: T,
52 /// Height above the refractive boundary
53 pub w: T,
54 /// Ratio of refractive indices
55 pub n: T,
56}
57
58impl<T: RealField + Copy> RefractionEq<T> {
59 /// Evaluate the refraction equation at location `x`.
60 pub fn f(&self, x: T) -> T {
61 let RefractionEq { d, h, w, n } = self.clone();
62
63 let d_minus_x = d - x;
64 d_minus_x * (x * x + h * h).sqrt() / (x * (d_minus_x * d_minus_x + w * w).sqrt()) - n
65 }
66}
67
68pub fn find_root<T>(a: T, b: T, eq: RefractionEq<T>, tolerance: T) -> Option<T>
69where
70 T: RealField + Copy,
71{
72 let interval = Interval::new(a, b)?;
73
74 let mut bisect = BisectionSearch::new(interval, |x| eq.f(*x));
75 loop {
76 bisect = bisect.step();
77 if bisect.interval.size() < tolerance {
78 break;
79 }
80 }
81 Some(*bisect.interval.a())
82}
83
84#[cfg(test)]
85mod tests {
86 #[test]
87 fn example1() {
88 // https://math.stackexchange.com/questions/150769/refraction-equation-quartic-equation/151249#comment347929_151050
89 // https://www.wolframalpha.com/input/?i=plot+%28%28d-x%29%2Fsqrt%28%28d-x%29%5E2%2Bw%5E2%29%29%2F%28x%2Fsqrt%28x%5E2%2Bh%5E2%29%29+-+1.33+where+w%3D1%2C+h%3D5%2C+d%3D30+for+x%3D0..30
90 let f = crate::RefractionEq {
91 d: 30.0,
92 h: 5.0,
93 w: 1.0,
94 n: 1.33,
95 };
96 let eps = 0.01;
97 assert!(f64::abs(f.f(5.7)) < eps);
98 }
99
100 #[test]
101 fn test_derivative() {
102 let refraction = crate::RefractionEq {
103 d: 30.0,
104 h: 5.0,
105 w: 1.0,
106 n: 1.33,
107 };
108
109 // Evaluate until a given tolerance.
110 let val = crate::find_root(
111 0.0, 30.0, // Initial guess
112 refraction, 1e-10,
113 )
114 .unwrap();
115 let eps = 0.01;
116 assert!(f64::abs(val - 5.7) < eps);
117 }
118}