1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use bisection_search::{BisectionSearch, Interval};
use simba::scalar::RealField;

/// Define the parameters required to solve a refraction problem.
///
/// In the following drawing, we want to find `x` when we know `w`, `h`, `d`,
/// and the ratio of refractive indices `n` where `n = n2/n1`.
///
/// `a` and `b` are angles, `n1` and `n2` are refractive indices, `d`, `h` and
/// `w` are lengths.
///
/// ```text
///                                                      ---
///                                                 ----/ a|
///               n1                           ----/       |
///                                       ----/            |
///                                 -----/                 |
///                            ----/                       | w
///                       ----/                            |
///                  ----/                                 |
///           x   --/                    d-x               |
///      |--------|----------------------------------------|
///      |       /
///      | h     |
///      |      /
///      |     /
///      |    /                   n2
///      |    |
///      |   /
///      |  /
///      | /
///      |b|
///      |/
///
///    sin(a)   n2
///    ------ = -- = n
///    sin(b)   n1
/// ```
///
/// See [this](https:///math.stackexchange.com/questions/150769) for an
/// explanation of the problem and this solution. Ascii drawing done with
/// [textik.com](https://textik.com).
#[derive(Clone)]
pub struct RefractionEq<T> {
    /// Distance along the refractive boundary
    pub d: T,
    /// Height below the refractive boundary
    pub h: T,
    /// Height above the refractive boundary
    pub w: T,
    /// Ratio of refractive indices
    pub n: T,
}

impl<T: RealField + Copy> RefractionEq<T> {
    /// Evaluate the refraction equation at location `x`.
    pub fn f(&self, x: T) -> T {
        let RefractionEq { d, h, w, n } = self.clone();

        let d_minus_x = d - x;
        d_minus_x * (x * x + h * h).sqrt() / (x * (d_minus_x * d_minus_x + w * w).sqrt()) - n
    }
}

pub fn find_root<T>(a: T, b: T, eq: RefractionEq<T>, tolerance: T) -> Option<T>
where
    T: RealField + Copy,
{
    let interval = match Interval::new(a, b) {
        Some(i) => i,
        None => return None,
    };

    let mut bisect = BisectionSearch::new(interval, |x| eq.f(*x));
    loop {
        bisect = bisect.step();
        if bisect.interval.size() < tolerance {
            break;
        }
    }
    return Some(*bisect.interval.a());
}

#[cfg(test)]
mod tests {
    #[test]
    fn example1() {
        // https://math.stackexchange.com/questions/150769/refraction-equation-quartic-equation/151249#comment347929_151050
        // 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
        let f = crate::RefractionEq {
            d: 30.0,
            h: 5.0,
            w: 1.0,
            n: 1.33,
        };
        let eps = 0.01;
        assert!(f64::abs(f.f(5.7)) < eps);
    }

    #[test]
    fn test_derivative() {
        let refraction = crate::RefractionEq {
            d: 30.0,
            h: 5.0,
            w: 1.0,
            n: 1.33,
        };

        // Evaluate until a given tolerance.
        let val = crate::find_root(
            0.0, 30.0, // Initial guess
            refraction, 1e-10,
        )
        .unwrap();
        let eps = 0.01;
        assert!(f64::abs(val - 5.7) < eps);
    }
}