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
//! 三分探索(実数)。

use std::ops::RangeInclusive;

/// 三分探索で極値を探す。
///
/// 関数 $f$ の $[x\_l, x\_r]$ における極大値を $x\^\\ast$ として、
/// $|x-x\^\\ast| \\le \\varepsilon$ なる $x$ を求め、$(x, f(x))$ を返す。
///
/// # Requirements
/// $f$ は凸である。
///
/// # Notes
/// 黄金比を用いて分割する実装のため、関数値を使い回すことができる。
///
/// # Complexity
/// `f` の呼び出しを $\\log\_{\\varphi}(\\frac{x\_r-x\_l}{\\varepsilon}) + 1$ 回行う。
///
/// # Suggestions
/// `f64` に限らずジェネリックにするべき? 関数の返り値も `T: PartialOrd` にする?
///
/// # Examples
/// $f(x) = x\^x$ の最小値を求める。
///
/// $x = 1/e$ のとき、最小値 $e\^{-1/e}$ をとる。
/// cf. [Wolfram|Alpha](https://www.wolframalpha.com/input/?i=y+%3D+x**x)
/// ```
/// use nekolib::algo::extremum_float;
///
/// let p = 3.0_f64;
/// let f = |x: f64| -x.powf(x);
///
/// let xl = 0.0;
/// let xr = 140.0;
/// let eps = 1.0e-8;
/// let (x, y) = extremum_float(xl..=xr, eps, f);
/// let y = -y;
///
/// let e = std::f64::consts::E;
/// assert!(((1.0 / e) - x).abs() < eps);
/// assert!((e.powf(-1.0 / e) - y).abs() < eps);
/// ```
pub fn extremum_float(
    range: RangeInclusive<f64>,
    eps: f64,
    mut f: impl FnMut(f64) -> f64,
) -> (f64, f64) {
    let phi: f64 = (1.0 + 5.0_f64.sqrt()) / 2.0;
    let phi_p1 = phi + 1.0;

    let &(mut xl) = range.start();
    let &(mut xr) = range.end();

    let iter = ((xr - xl) / eps).log(phi) as u32 + 1;

    let mut xml = (phi * xl + xr) / phi_p1;
    let mut xmr = (xl + phi * xr) / phi_p1;
    let mut yml = f(xml);
    let mut ymr = f(xmr);

    for _ in 0..iter {
        if yml > ymr {
            xr = std::mem::replace(&mut xmr, xml);
            ymr = yml;
            xml = (phi * xl + xr) / phi_p1;
            yml = f(xml);
        } else {
            xl = std::mem::replace(&mut xml, xmr);
            yml = ymr;
            xmr = (xl + phi * xr) / phi_p1;
            ymr = f(xmr);
        }
    }
    (xml, yml)
}