gam_solve/estimate/
outer_eval_capture.rs1use ndarray::{Array1, Array2};
15use std::cell::RefCell;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::{Mutex, OnceLock};
18
19#[derive(Clone, Debug)]
22pub struct OuterEvalRecord {
23 pub theta: Array1<f64>,
24 pub cost: f64,
25 pub gradient: Array1<f64>,
26}
27
28#[derive(Clone, Debug)]
38pub struct OuterGradientFdRecord {
39 pub theta: Array1<f64>,
40 pub rho_dim: usize,
41 pub psi_dim: usize,
42 pub cost: f64,
43 pub analytic_psi_gradient: Array1<f64>,
44 pub finite_difference_psi_gradient: Array1<f64>,
45 pub psi_steps: Array1<f64>,
46 pub fixed_beta_psi_gradient: Array1<f64>,
47 pub logdet_h_psi_gradient: Array1<f64>,
48 pub frozen_logdet_h_psi_gradient: Array1<f64>,
49 pub mode_response_logdet_h_psi_gradient: Array1<f64>,
50 pub analytic_mode_response_norm: Array1<f64>,
51 pub finite_difference_mode_response_norm: Array1<f64>,
52 pub mode_response_relative_error: Array1<f64>,
53 pub mode_response_max_abs_error: Array1<f64>,
54 pub logdet_s_psi_gradient: Array1<f64>,
55 pub kkt_psi_gradient: Array1<f64>,
56 pub finite_difference_fixed_beta_psi_gradient: Array1<f64>,
57 pub finite_difference_logdet_h_psi_gradient: Array1<f64>,
58 pub finite_difference_logdet_s_psi_gradient: Array1<f64>,
59 pub finite_difference_kkt_psi_gradient: Array1<f64>,
60}
61
62const MAX_CAPTURED: usize = 8;
64
65static ENABLED: AtomicBool = AtomicBool::new(false);
66
67struct OuterGradientFdCapture {
68 min_psi_dim: usize,
69 record: Option<OuterGradientFdRecord>,
70 components: Vec<(f64, f64, f64, f64, f64, f64)>,
71 criterion_components: Option<(f64, [f64; 4])>,
72 selected_mode: Option<(Array1<f64>, Option<Array2<f64>>)>,
73}
74
75thread_local! {
76 static FD_CAPTURE: RefCell<Option<OuterGradientFdCapture>> = const { RefCell::new(None) };
77}
78
79fn buffer() -> &'static Mutex<Vec<OuterEvalRecord>> {
80 static BUFFER: OnceLock<Mutex<Vec<OuterEvalRecord>>> = OnceLock::new();
81 BUFFER.get_or_init(|| Mutex::new(Vec::new()))
82}
83
84pub fn enable_outer_eval_capture() {
86 buffer().lock().expect("outer-eval capture buffer").clear();
87 ENABLED.store(true, Ordering::Relaxed);
88}
89
90pub fn take_outer_eval_capture() -> Vec<OuterEvalRecord> {
92 ENABLED.store(false, Ordering::Relaxed);
93 std::mem::take(&mut *buffer().lock().expect("outer-eval capture buffer"))
94}
95
96pub fn enable_outer_gradient_fd_capture(min_psi_dim: usize) {
98 FD_CAPTURE.with(|capture| {
99 *capture.borrow_mut() = Some(OuterGradientFdCapture {
100 min_psi_dim,
101 record: None,
102 components: Vec::new(),
103 criterion_components: None,
104 selected_mode: None,
105 });
106 });
107}
108
109pub(crate) fn begin_outer_gradient_component_capture() {
110 FD_CAPTURE.with(|capture| {
111 if let Some(state) = capture.borrow_mut().as_mut() {
112 state.components.clear();
113 }
114 });
115}
116
117pub(crate) fn outer_gradient_component_capture_enabled() -> bool {
118 FD_CAPTURE.with(|capture| {
119 capture
120 .borrow()
121 .as_ref()
122 .is_some_and(|state| state.record.is_none())
123 })
124}
125
126pub(crate) fn record_outer_gradient_component(
127 fixed_beta: f64,
128 logdet_h: f64,
129 frozen_logdet_h: f64,
130 mode_response_logdet_h: f64,
131 logdet_s: f64,
132 kkt: f64,
133) {
134 FD_CAPTURE.with(|capture| {
135 if let Some(state) = capture.borrow_mut().as_mut()
136 && state.record.is_none()
137 {
138 state.components.push((
139 fixed_beta,
140 logdet_h,
141 frozen_logdet_h,
142 mode_response_logdet_h,
143 logdet_s,
144 kkt,
145 ));
146 }
147 });
148}
149
150pub(crate) fn take_outer_gradient_components() -> Vec<(f64, f64, f64, f64, f64, f64)> {
151 FD_CAPTURE.with(|capture| {
152 capture
153 .borrow_mut()
154 .as_mut()
155 .map_or_else(Vec::new, |state| std::mem::take(&mut state.components))
156 })
157}
158
159pub(crate) fn begin_outer_criterion_component_capture() {
160 FD_CAPTURE.with(|capture| {
161 if let Some(state) = capture.borrow_mut().as_mut() {
162 state.criterion_components = None;
163 state.selected_mode = None;
164 }
165 });
166}
167
168pub fn record_outer_criterion_components(cost: f64, components: [f64; 4]) {
175 FD_CAPTURE.with(|capture| {
176 if let Some(state) = capture.borrow_mut().as_mut()
177 && state.record.is_none()
178 {
179 state.criterion_components = Some((cost, components));
180 }
181 });
182}
183
184pub(crate) fn take_outer_criterion_components() -> Option<(f64, [f64; 4])> {
185 FD_CAPTURE.with(|capture| {
186 capture
187 .borrow_mut()
188 .as_mut()
189 .and_then(|state| state.criterion_components.take())
190 })
191}
192
193pub fn record_outer_selected_mode(
201 beta: Array1<f64>,
202 ext_mode_response_cols: Option<Array2<f64>>,
203) {
204 FD_CAPTURE.with(|capture| {
205 if let Some(state) = capture.borrow_mut().as_mut()
206 && state.record.is_none()
207 {
208 state.selected_mode = Some((beta, ext_mode_response_cols));
209 }
210 });
211}
212
213pub(crate) fn take_outer_selected_mode() -> Option<(Array1<f64>, Option<Array2<f64>>)> {
214 FD_CAPTURE.with(|capture| {
215 capture
216 .borrow_mut()
217 .as_mut()
218 .and_then(|state| state.selected_mode.take())
219 })
220}
221
222pub fn take_outer_gradient_fd_capture() -> Option<OuterGradientFdRecord> {
224 FD_CAPTURE.with(|capture| capture.borrow_mut().take().and_then(|state| state.record))
225}
226
227pub(crate) fn outer_gradient_fd_capture_enabled(psi_dim: usize) -> bool {
228 FD_CAPTURE.with(|capture| {
229 capture
230 .borrow()
231 .as_ref()
232 .is_some_and(|state| state.record.is_none() && psi_dim >= state.min_psi_dim)
233 })
234}
235
236pub(crate) fn record_outer_gradient_fd(record: OuterGradientFdRecord) {
237 FD_CAPTURE.with(|capture| {
238 if let Some(state) = capture.borrow_mut().as_mut()
239 && state.record.is_none()
240 && record.psi_dim >= state.min_psi_dim
241 {
242 state.record = Some(record);
243 }
244 });
245}
246
247pub(crate) fn record_outer_eval(theta: &Array1<f64>, cost: f64, gradient: &Array1<f64>) {
250 if !ENABLED.load(Ordering::Relaxed) {
251 return;
252 }
253 let mut b = buffer().lock().expect("outer-eval capture buffer");
254 if b.len() < MAX_CAPTURED {
255 b.push(OuterEvalRecord {
256 theta: theta.clone(),
257 cost,
258 gradient: gradient.clone(),
259 });
260 }
261}