-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhessian_demo.rs
More file actions
172 lines (156 loc) · 6.18 KB
/
Copy pathhessian_demo.rs
File metadata and controls
172 lines (156 loc) · 6.18 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use petite_ad::{
mono_ops, mono_ops_fr, mono_ops_rf, mono_ops_rr, MonoAD, MonoAD2FR, MonoAD2RF, MonoAD2RR,
};
/// Demonstrates exact vs. approximate Hessian computation.
///
/// This example shows:
/// 1. How the existing MonoAD.compute_hessian (finite differences) works
/// 2. The new exact Hessian methods (RR, FR, RF)
/// 3. Accuracy comparison between finite differences and exact methods
fn main() {
println!("=== Exact vs Approximate Hessian Demonstration ===\n");
// Example 1: f(x) = sin(x)
// f'(x) = cos(x)
// f''(x) = -sin(x)
println!("Example 1: f(x) = sin(x)");
let ops1 = mono_ops![sin];
let ops1_rr = mono_ops_rr![sin];
let ops1_fr = mono_ops_fr![sin];
let ops1_rf = mono_ops_rf![sin];
let x1: f64 = 0.5;
let expected1 = -x1.sin();
let (value1, _grad_fn1) = MonoAD::compute_grad(&ops1, x1);
let hess_fd1 = MonoAD::compute_hessian(&ops1, x1);
let hess_rr1 = MonoAD2RR::compute_hessian(&ops1_rr, x1);
let hess_fr1 = MonoAD2FR::compute_hessian(&ops1_fr, x1);
let hess_rf1 = MonoAD2RF::compute_hessian(&ops1_rf, x1);
println!(" f({}) = {}", x1, value1);
println!(" Analytical f''({}) = {}", x1, expected1);
println!(
" Finite Difference: {:.10} (error: {:.2e})",
hess_fd1,
(hess_fd1 - expected1).abs()
);
println!(
" RR (Exact): {:.10} (error: {:.2e})",
hess_rr1,
(hess_rr1 - expected1).abs()
);
println!(
" FR (Exact): {:.10} (error: {:.2e})",
hess_fr1,
(hess_fr1 - expected1).abs()
);
println!(
" RF (Exact): {:.10} (error: {:.2e})",
hess_rf1,
(hess_rf1 - expected1).abs()
);
// Example 2: f(x) = exp(x)
// f'(x) = exp(x)
// f''(x) = exp(x)
println!("\nExample 2: f(x) = exp(x)");
let ops2 = mono_ops![exp];
let ops2_rr = mono_ops_rr![exp];
let ops2_fr = mono_ops_fr![exp];
let ops2_rf = mono_ops_rf![exp];
let x2: f64 = 1.5;
let expected2 = x2.exp();
let (value2, _grad_fn2) = MonoAD::compute_grad(&ops2, x2);
let hess_fd2 = MonoAD::compute_hessian(&ops2, x2);
let hess_rr2 = MonoAD2RR::compute_hessian(&ops2_rr, x2);
let hess_fr2 = MonoAD2FR::compute_hessian(&ops2_fr, x2);
let hess_rf2 = MonoAD2RF::compute_hessian(&ops2_rf, x2);
println!(" f({}) = {}", x2, value2);
println!(" Analytical f''({}) = {}", x2, expected2);
println!(
" Finite Difference: {:.10} (error: {:.2e})",
hess_fd2,
(hess_fd2 - expected2).abs()
);
println!(
" RR (Exact): {:.10} (error: {:.2e})",
hess_rr2,
(hess_rr2 - expected2).abs()
);
println!(
" FR (Exact): {:.10} (error: {:.2e})",
hess_fr2,
(hess_fr2 - expected2).abs()
);
println!(
" RF (Exact): {:.10} (error: {:.2e})",
hess_rf2,
(hess_rf2 - expected2).abs()
);
// Example 3: f(x) = exp(sin(sin(x))) - composed function
// Note: operations are applied right-to-left, so [sin, sin, exp] means exp(sin(sin(x)))
// This tests the chain rule handling
println!("\nExample 3: f(x) = exp(sin(sin(x))) [composed]");
let ops3 = mono_ops![sin, sin, exp];
let ops3_rr = mono_ops_rr![sin, sin, exp];
let ops3_fr = mono_ops_fr![sin, sin, exp];
let ops3_rf = mono_ops_rf![sin, sin, exp];
let x3: f64 = 2.0;
let (value3, _grad_fn3) = MonoAD::compute_grad(&ops3, x3);
let hess_fd3 = MonoAD::compute_hessian(&ops3, x3);
let hess_rr3 = MonoAD2RR::compute_hessian(&ops3_rr, x3);
let hess_fr3 = MonoAD2FR::compute_hessian(&ops3_fr, x3);
let hess_rf3 = MonoAD2RF::compute_hessian(&ops3_rf, x3);
// Compute exact value manually for verification: f(x) = exp(sin(sin(x)))
let t1 = x3.sin();
let t2 = t1.sin();
let dt1 = x3.cos();
let dt2 = t1.cos() * dt1;
let ddt1 = -x3.sin();
let ddt2 = -t1.sin() * dt1 * dt1 + t1.cos() * ddt1;
let expected3 = t2.exp() * dt2 * dt2 + t2.exp() * ddt2;
println!(" f({}) = {}", x3, value3);
println!(" Analytical f''({}) = {:.10}", x3, expected3);
println!(
" Finite Difference: {:.10} (error: {:.2e})",
hess_fd3,
(hess_fd3 - expected3).abs()
);
println!(
" RR (Exact): {:.10} (error: {:.2e})",
hess_rr3,
(hess_rr3 - expected3).abs()
);
println!(
" FR (Exact): {:.10} (error: {:.2e})",
hess_fr3,
(hess_fr3 - expected3).abs()
);
println!(
" RF (Exact): {:.10} (error: {:.2e})",
hess_rf3,
(hess_rf3 - expected3).abs()
);
println!("\n=== Summary ===\n");
println!("1. Finite Difference Method (MonoAD::compute_hessian):");
println!(" - Uses ε = 1e-5");
println!(" - Accuracy: ~1e-4 to 1e-6");
println!(" - Pros: Simple, works for all operations");
println!(" - Cons: Numerical approximation, not machine precision");
println!();
println!("2. Exact Methods (MonoAD2RR/FR/RF::compute_hessian):");
println!(" - RR: Reverse-over-Reverse (tracks 2nd derivatives during backward pass)");
println!(" - FR: Forward-over-Reverse (forward-mode on gradient function)");
println!(" - RF: Reverse-over-Forward (reverse-mode on forward-mode)");
println!(" - Accuracy: Machine precision (~1e-15)");
println!(" - Pros: Exact differentiation, no approximation error");
println!(" - Cons: More complex implementation, slightly more computation");
println!();
println!("3. When to Use Which:");
println!(" - Finite differences: Quick prototyping, most practical applications");
println!(" - Exact methods: When you need machine precision for:");
println!(" * Numerical optimization requiring exact Hessians");
println!(" * Scientific computing with strict accuracy requirements");
println!(" * Research applications in automatic differentiation");
println!();
println!("4. Performance Note:");
println!(" - All three exact methods have similar performance");
println!(" - For univariate functions, FR/RF delegate to RR for composed operations");
println!(" - Choice between FR/RF/RR is more relevant for multivariate functions");
}