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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/// Function to be minimized.
///
/// The core trait that users implement. `error_def()` returns the change in FCN
/// that corresponds to one standard deviation (1.0 for chi-square, 0.5 for
/// log-likelihood).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GradientParameterSpace {
Internal,
External,
}
pub trait FCN {
/// Evaluate the function at the given parameter values.
fn value(&self, par: &[f64]) -> f64;
/// FCN change corresponding to 1-sigma. Default = 1.0 (chi-square).
fn error_def(&self) -> f64 {
1.0
}
/// ROOT-compatible alias for `error_def()`.
fn up(&self) -> f64 {
self.error_def()
}
/// Optional mutable setter for FCN error definition.
///
/// Default no-op keeps backwards compatibility for immutable FCNs.
fn set_error_def(&mut self, _up: f64) {}
/// Whether this FCN provides analytical gradients.
fn has_gradient(&self) -> bool {
false
}
/// Optional gradient with access to previous derivative state.
fn gradient_with_prev_result(
&self,
_par: &[f64],
_previous_grad: Option<&[f64]>,
_previous_g2: Option<&[f64]>,
_previous_gstep: Option<&[f64]>,
) -> Vec<f64> {
Vec::new()
}
/// Defines whether supplied derivatives live in internal or external space.
fn grad_parameter_space(&self) -> GradientParameterSpace {
GradientParameterSpace::External
}
/// Optional diagonal second-derivative contract.
fn g2(&self, _par: &[f64]) -> Vec<f64> {
Vec::new()
}
/// Optional Hessian contract (packed upper-triangle format).
fn hessian(&self, _par: &[f64]) -> Vec<f64> {
Vec::new()
}
/// Whether `hessian()` is implemented.
fn has_hessian(&self) -> bool {
false
}
/// Whether `g2()` is implemented.
fn has_g2(&self) -> bool {
false
}
}
/// Blanket impl: any `Fn(&[f64]) -> f64` is a valid FCN with error_def = 1.0.
impl<F> FCN for F
where
F: Fn(&[f64]) -> f64,
{
fn value(&self, par: &[f64]) -> f64 {
self(par)
}
}
/// FCN that also provides analytical gradients.
pub trait FCNGradient: FCN {
/// Compute the gradient vector at the given parameter values.
fn gradient(&self, par: &[f64]) -> Vec<f64>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closure_as_fcn() {
let f = |x: &[f64]| x[0] * x[0] + x[1] * x[1];
assert!((FCN::value(&f, &[3.0, 4.0]) - 25.0).abs() < 1e-15);
assert!((f.error_def() - 1.0).abs() < 1e-15);
}
#[test]
fn struct_fcn() {
struct Rosenbrock;
impl FCN for Rosenbrock {
fn value(&self, p: &[f64]) -> f64 {
(1.0 - p[0]).powi(2) + 100.0 * (p[1] - p[0] * p[0]).powi(2)
}
fn error_def(&self) -> f64 {
0.5
}
}
let f = Rosenbrock;
assert!((f.value(&[1.0, 1.0])).abs() < 1e-15);
assert!((f.error_def() - 0.5).abs() < 1e-15);
assert!((f.up() - 0.5).abs() < 1e-15);
assert!(!f.has_hessian());
assert!(!f.has_g2());
assert_eq!(f.grad_parameter_space(), GradientParameterSpace::External);
}
#[test]
fn gradient_contract_defaults() {
struct QuadGrad;
impl FCN for QuadGrad {
fn value(&self, p: &[f64]) -> f64 {
p[0] * p[0]
}
}
impl FCNGradient for QuadGrad {
fn gradient(&self, p: &[f64]) -> Vec<f64> {
vec![2.0 * p[0]]
}
}
let f = QuadGrad;
assert_eq!(FCNGradient::gradient(&f, &[3.0]), vec![6.0]);
assert!(!FCN::has_gradient(&f));
assert!(FCN::gradient_with_prev_result(&f, &[3.0], None, None, None).is_empty());
}
}