1use super::*;
6
7#[derive(Clone)]
8pub struct BinomialMeanWiggleFamily {
9 pub y: Array1<f64>,
10 pub weights: Array1<f64>,
11 pub link_kind: InverseLink,
12 pub wiggle_knots: Array1<f64>,
13 pub wiggle_degree: usize,
14 pub policy: gam_runtime::resource::ResourcePolicy,
19}
20
21pub(crate) struct BinomialMeanWiggleGeometry {
22 pub(crate) basis: Array2<f64>,
23 pub(crate) basis_d1: Array2<f64>,
24 pub(crate) basis_d2: Array2<f64>,
25 pub(crate) basis_d3: Array2<f64>,
26 pub(crate) dq_dq0: Array1<f64>,
27 pub(crate) d2q_dq02: Array1<f64>,
28 pub(crate) d3q_dq03: Array1<f64>,
29 pub(crate) d4q_dq04: Array1<f64>,
30}
31
32pub(crate) struct BinomialMeanWiggleJointPsiDirection {
33 pub(crate) x_eta_psi: Option<Array2<f64>>,
34 pub(crate) z_eta_psi: Array1<f64>,
35}
36
37impl BinomialMeanWiggleFamily {
38 pub const BLOCK_ETA: usize = 0;
39 pub const BLOCK_WIGGLE: usize = 1;
40
41 pub(crate) fn wiggle_basiswith_options(
42 &self,
43 q0: ArrayView1<'_, f64>,
44 options: BasisOptions,
45 ) -> Result<Array2<f64>, String> {
46 monotone_wiggle_basis_with_derivative_order(
47 q0,
48 &self.wiggle_knots,
49 self.wiggle_degree,
50 options.derivative_order,
51 )
52 }
53
54 pub(crate) fn wiggle_design(&self, q0: ArrayView1<'_, f64>) -> Result<Array2<f64>, String> {
55 self.wiggle_basiswith_options(q0, BasisOptions::value())
56 }
57
58 pub(crate) fn wiggle_dq_dq0(
59 &self,
60 q0: ArrayView1<'_, f64>,
61 beta_link_wiggle: ArrayView1<'_, f64>,
62 ) -> Result<Array1<f64>, String> {
63 let d_constrained = self.wiggle_basiswith_options(q0, BasisOptions::first_derivative())?;
64 if d_constrained.ncols() != beta_link_wiggle.len() {
65 return Err(GamlssError::DimensionMismatch { reason: format!(
66 "wiggle derivative/beta mismatch: basis has {} columns but beta_link_wiggle has {} coefficients",
67 d_constrained.ncols(),
68 beta_link_wiggle.len()
69 ) }.into());
70 }
71 Ok(d_constrained.dot(&beta_link_wiggle) + 1.0)
72 }
73
74 pub(crate) fn wiggle_d2q_dq02(
75 &self,
76 q0: ArrayView1<'_, f64>,
77 beta_link_wiggle: ArrayView1<'_, f64>,
78 ) -> Result<Array1<f64>, String> {
79 let d2 = self.wiggle_basiswith_options(q0, BasisOptions::second_derivative())?;
80 if d2.ncols() != beta_link_wiggle.len() {
81 return Err(GamlssError::DimensionMismatch { reason: format!(
82 "wiggle second-derivative/beta mismatch: basis has {} columns but beta_link_wiggle has {} coefficients",
83 d2.ncols(),
84 beta_link_wiggle.len()
85 ) }.into());
86 }
87 Ok(d2.dot(&beta_link_wiggle))
88 }
89
90 pub(crate) fn wiggle_d3basis_constrained(
91 &self,
92 q0: ArrayView1<'_, f64>,
93 ) -> Result<Array2<f64>, String> {
94 monotone_wiggle_basis_with_derivative_order(q0, &self.wiggle_knots, self.wiggle_degree, 3)
95 }
96
97 pub(crate) fn wiggle_d3q_dq03(
98 &self,
99 q0: ArrayView1<'_, f64>,
100 beta_link_wiggle: ArrayView1<'_, f64>,
101 ) -> Result<Array1<f64>, String> {
102 let d3 = self.wiggle_d3basis_constrained(q0)?;
103 if d3.ncols() != beta_link_wiggle.len() {
104 return Err(GamlssError::DimensionMismatch { reason: format!(
105 "wiggle third-derivative/beta mismatch: basis has {} columns but beta_link_wiggle has {} coefficients",
106 d3.ncols(),
107 beta_link_wiggle.len()
108 ) }.into());
109 }
110 Ok(d3.dot(&beta_link_wiggle))
111 }
112
113 pub(crate) fn wiggle_d4q_dq04(
114 &self,
115 q0: ArrayView1<'_, f64>,
116 beta_link_wiggle: ArrayView1<'_, f64>,
117 ) -> Result<Array1<f64>, String> {
118 let d4 = monotone_wiggle_basis_with_derivative_order(
119 q0,
120 &self.wiggle_knots,
121 self.wiggle_degree,
122 4,
123 )?;
124 if d4.ncols() != beta_link_wiggle.len() {
125 return Err(GamlssError::DimensionMismatch { reason: format!(
126 "wiggle fourth-derivative/beta mismatch: basis has {} columns but beta_link_wiggle has {} coefficients",
127 d4.ncols(),
128 beta_link_wiggle.len()
129 ) }.into());
130 }
131 Ok(d4.dot(&beta_link_wiggle))
132 }
133
134 pub(crate) fn wiggle_geometry(
135 &self,
136 q0: ArrayView1<'_, f64>,
137 beta_link_wiggle: ArrayView1<'_, f64>,
138 ) -> Result<BinomialMeanWiggleGeometry, String> {
139 let basis = self.wiggle_design(q0)?;
140 let basis_d1 = self.wiggle_basiswith_options(q0, BasisOptions::first_derivative())?;
141 let basis_d2 = self.wiggle_basiswith_options(q0, BasisOptions::second_derivative())?;
142 let basis_d3 = self.wiggle_d3basis_constrained(q0)?;
143 let dq_dq0 = self.wiggle_dq_dq0(q0, beta_link_wiggle)?;
144 let d2q_dq02 = self.wiggle_d2q_dq02(q0, beta_link_wiggle)?;
145 let d3q_dq03 = self.wiggle_d3q_dq03(q0, beta_link_wiggle)?;
146 let d4q_dq04 = self.wiggle_d4q_dq04(q0, beta_link_wiggle)?;
147 Ok(BinomialMeanWiggleGeometry {
148 basis,
149 basis_d1,
150 basis_d2,
151 basis_d3,
152 dq_dq0,
153 d2q_dq02,
154 d3q_dq03,
155 d4q_dq04,
156 })
157 }
158
159 pub(crate) fn neglog_q_derivatives(
160 &self,
161 y: f64,
162 weight: f64,
163 q: f64,
164 ) -> Result<(f64, f64, f64), String> {
165 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
166 .map_err(|e| format!("fixed-link wiggle inverse-link evaluation failed: {e}"))?;
167 Ok(binomial_neglog_q_derivatives_dispatch(
171 y,
172 weight,
173 q,
174 jet.mu,
175 jet.d1,
176 jet.d2,
177 jet.d3,
178 &self.link_kind,
179 ))
180 }
181
182 pub(crate) fn neglog_q_fourth_derivative(
183 &self,
184 y: f64,
185 weight: f64,
186 q: f64,
187 ) -> Result<f64, String> {
188 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
189 .map_err(|e| format!("fixed-link wiggle inverse-link evaluation failed: {e}"))?;
190 binomial_neglog_q_fourth_derivative_dispatch(
192 y,
193 weight,
194 q,
195 jet.mu,
196 jet.d1,
197 jet.d2,
198 jet.d3,
199 &self.link_kind,
200 )
201 }
202
203 pub(crate) fn dense_eta_design_fromspecs<'a>(
204 &self,
205 specs: &'a [ParameterBlockSpec],
206 ) -> Result<Cow<'a, Array2<f64>>, String> {
207 if specs.len() != 2 {
208 return Err(GamlssError::DimensionMismatch {
209 reason: format!(
210 "BinomialMeanWiggleFamily expects 2 specs, got {}",
211 specs.len()
212 ),
213 }
214 .into());
215 }
216 Ok(match specs[Self::BLOCK_ETA].design.as_dense_ref() {
217 Some(d) => Cow::Borrowed(d),
218 None => Cow::Owned(
219 specs[Self::BLOCK_ETA]
220 .design
221 .try_to_dense_with_policy(
222 &self.policy.material_policy(),
223 "BinomialMeanWiggle dense_eta_design_fromspecs eta",
224 )
225 .map_err(|e| e.to_string())?
226 .as_ref()
227 .clone(),
228 ),
229 })
230 }
231
232 pub(crate) fn exact_newton_joint_psi_direction(
233 &self,
234 block_states: &[ParameterBlockState],
235 derivative_blocks: &[Vec<CustomFamilyBlockPsiDerivative>],
236 psi_index: usize,
237 x_eta: &Array2<f64>,
238 ) -> Result<Option<BinomialMeanWiggleJointPsiDirection>, String> {
239 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
240 if derivative_blocks.len() != 2 {
241 return Err(GamlssError::DimensionMismatch { reason: format!(
242 "BinomialMeanWiggleFamily joint psi direction expects 2 derivative block lists, got {}",
243 derivative_blocks.len()
244 ) }.into());
245 }
246 let n = self.y.len();
247 let p_eta = x_eta.ncols();
248 let beta_eta = &block_states[Self::BLOCK_ETA].beta;
249 let mut global = 0usize;
250 for (block_idx, block_derivs) in derivative_blocks.iter().enumerate() {
251 for deriv in block_derivs {
252 if global == psi_index {
253 if block_idx != Self::BLOCK_ETA {
254 return Ok(None);
255 }
256 let x_eta_psi_map = resolve_custom_family_x_psi_map(
257 deriv,
258 n,
259 p_eta,
260 0..n,
261 "BinomialMeanWiggleFamily eta",
262 &self.policy,
263 )?;
264 let x_eta_psi = x_eta_psi_map.row_chunk(0..n)?;
265 let z_eta_psi = x_eta_psi.dot(beta_eta);
266 return Ok(Some(BinomialMeanWiggleJointPsiDirection {
267 x_eta_psi: Some(x_eta_psi),
268 z_eta_psi,
269 }));
270 }
271 global += 1;
272 }
273 }
274 Ok(None)
275 }
276
277 pub(crate) fn exact_newton_joint_psi_action(
278 &self,
279 block_states: &[ParameterBlockState],
280 derivative_blocks: &[Vec<CustomFamilyBlockPsiDerivative>],
281 psi_index: usize,
282 p_eta: usize,
283 ) -> Result<Option<(CustomFamilyPsiDesignAction, Array1<f64>)>, String> {
284 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
285 if derivative_blocks.len() != 2 {
286 return Err(GamlssError::DimensionMismatch { reason: format!(
287 "BinomialMeanWiggleFamily joint psi action expects 2 derivative block lists, got {}",
288 derivative_blocks.len()
289 ) }.into());
290 }
291 let n = self.y.len();
292 let beta_eta = &block_states[Self::BLOCK_ETA].beta;
293 let mut global = 0usize;
294 for (block_idx, block_derivs) in derivative_blocks.iter().enumerate() {
295 for deriv in block_derivs {
296 if global == psi_index {
297 if block_idx != Self::BLOCK_ETA {
298 return Ok(None);
299 }
300 let action = match CustomFamilyPsiDesignAction::from_first_derivative(
301 deriv,
302 n,
303 p_eta,
304 0..n,
305 "BinomialMeanWiggleFamily eta",
306 ) {
307 Ok(action) => action,
308 Err(_) => return Ok(None),
309 };
310 let z_eta_psi = action.forward_mul(beta_eta.view());
311 return Ok(Some((action, z_eta_psi)));
312 }
313 global += 1;
314 }
315 }
316 Ok(None)
317 }
318
319 pub(crate) fn bmw_static_hessian_operator(
320 &self,
321 block_states: &[ParameterBlockState],
322 x_eta_arc: Arc<Array2<f64>>,
323 ) -> Result<Arc<RowCoeffOperator>, String> {
324 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
325 let eta = &block_states[Self::BLOCK_ETA].eta;
326 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
327 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
328 let n = self.y.len();
329 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
330 return Err(GamlssError::DimensionMismatch {
331 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
332 }
333 .into());
334 }
335 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
336 let p_eta = x_eta_arc.ncols();
337 let pw = geom.basis.ncols();
338 let mut coeff_eta = Array1::<f64>::zeros(n);
339 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
340 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
341 let mut coeff_ww = Array1::<f64>::zeros(n);
342 for row in 0..n {
343 let q = eta[row] + etaw[row];
344 let (m1, m2, _) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
345 let a = geom.dq_dq0[row];
346 let b = geom.d2q_dq02[row];
347 coeff_eta[row] = hessian_coeff_fromobjective_q_terms(m1, m2, a, a, b);
348 coeff_etaw_b[row] = m2 * a;
349 coeff_etaw_d1[row] = m1;
350 coeff_ww[row] = m2;
351 }
352 Ok(Arc::new(RowCoeffOperator::from_directions(
353 vec![p_eta, pw],
354 vec![
355 (0, x_eta_arc),
356 (1, Arc::new(geom.basis)),
357 (1, Arc::new(geom.basis_d1)),
358 ],
359 vec![
360 (0, 0, coeff_eta),
361 (0, 1, coeff_etaw_b),
362 (0, 2, coeff_etaw_d1),
363 (1, 1, coeff_ww),
364 ],
365 n,
366 )))
367 }
368
369 pub(crate) fn bmw_directional_operator(
370 &self,
371 block_states: &[ParameterBlockState],
372 x_eta_arc: Arc<Array2<f64>>,
373 d_beta_flat: &Array1<f64>,
374 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
375 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
376 let eta = &block_states[Self::BLOCK_ETA].eta;
377 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
378 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
379 let n = self.y.len();
380 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
381 return Err(GamlssError::DimensionMismatch {
382 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
383 }
384 .into());
385 }
386 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
387 let p_eta = x_eta_arc.ncols();
388 let pw = geom.basis.ncols();
389 let total = p_eta + pw;
390 if d_beta_flat.len() != total {
391 return Err(GamlssError::DimensionMismatch {
392 reason: format!(
393 "BinomialMeanWiggleFamily joint d_beta length mismatch: got {}, expected {}",
394 d_beta_flat.len(),
395 total
396 ),
397 }
398 .into());
399 }
400 let u_eta = d_beta_flat.slice(s![0..p_eta]).to_owned();
401 let uw = d_beta_flat.slice(s![p_eta..total]).to_owned();
402 let xi = fast_av(x_eta_arc.as_ref(), &u_eta);
403 let phi = fast_av(&geom.basis, &uw);
404 let basis1_u = fast_av(&geom.basis_d1, &uw);
405 let basis2_u = fast_av(&geom.basis_d2, &uw);
406
407 let mut coeff_eta = Array1::<f64>::zeros(n);
408 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
409 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
410 let mut coeff_etaw_d2 = Array1::<f64>::zeros(n);
411 let mut coeff_ww_bb = Array1::<f64>::zeros(n);
412 let mut coeff_ww_db = Array1::<f64>::zeros(n);
413 for row in 0..n {
414 let q = eta[row] + etaw[row];
415 let (m1, m2, m3) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
416 let a = geom.dq_dq0[row];
417 let b = geom.d2q_dq02[row];
418 let c = geom.d3q_dq03[row];
419 let q_u = a * xi[row] + phi[row];
420 let a_u = b * xi[row] + basis1_u[row];
421 let b_u = c * xi[row] + basis2_u[row];
422 coeff_eta[row] = directionalhessian_coeff_fromobjective_q_terms(
423 m1, m2, m3, q_u, a, a, b, a_u, a_u, b_u,
424 );
425 coeff_etaw_b[row] = m3 * q_u * a + m2 * a_u;
426 coeff_etaw_d1[row] = m2 * (a * xi[row] + q_u);
427 coeff_etaw_d2[row] = m1 * xi[row];
428 coeff_ww_bb[row] = m3 * q_u;
429 coeff_ww_db[row] = m2 * xi[row];
430 }
431 Ok(Some(Arc::new(RowCoeffOperator::from_directions(
432 vec![p_eta, pw],
433 vec![
434 (0, x_eta_arc),
435 (1, Arc::new(geom.basis)),
436 (1, Arc::new(geom.basis_d1)),
437 (1, Arc::new(geom.basis_d2)),
438 ],
439 vec![
440 (0, 0, coeff_eta),
441 (0, 1, coeff_etaw_b),
442 (0, 2, coeff_etaw_d1),
443 (0, 3, coeff_etaw_d2),
444 (1, 1, coeff_ww_bb),
445 (1, 2, coeff_ww_db),
446 ],
447 n,
448 ))))
449 }
450
451 pub(crate) fn bmw_second_directional_operator(
452 &self,
453 block_states: &[ParameterBlockState],
454 x_eta_arc: Arc<Array2<f64>>,
455 d_beta_u_flat: &Array1<f64>,
456 d_beta_v_flat: &Array1<f64>,
457 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
458 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
459 let eta = &block_states[Self::BLOCK_ETA].eta;
460 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
461 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
462 let n = self.y.len();
463 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
464 return Err(GamlssError::DimensionMismatch {
465 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
466 }
467 .into());
468 }
469 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
470 let p_eta = x_eta_arc.ncols();
471 let pw = geom.basis.ncols();
472 let total = p_eta + pw;
473 if d_beta_u_flat.len() != total || d_beta_v_flat.len() != total {
474 return Err(GamlssError::DimensionMismatch { reason: format!(
475 "BinomialMeanWiggleFamily joint second d_beta length mismatch: got {} and {}, expected {}",
476 d_beta_u_flat.len(),
477 d_beta_v_flat.len(),
478 total
479 ) }.into());
480 }
481 let u_eta = d_beta_u_flat.slice(s![0..p_eta]).to_owned();
482 let v_eta = d_beta_v_flat.slice(s![0..p_eta]).to_owned();
483 let uw = d_beta_u_flat.slice(s![p_eta..total]).to_owned();
484 let vw = d_beta_v_flat.slice(s![p_eta..total]).to_owned();
485
486 let xi_u = fast_av(x_eta_arc.as_ref(), &u_eta);
487 let xi_v = fast_av(x_eta_arc.as_ref(), &v_eta);
488 let phi_u = fast_av(&geom.basis, &uw);
489 let phi_v = fast_av(&geom.basis, &vw);
490 let b1u = fast_av(&geom.basis_d1, &uw);
491 let b1v = fast_av(&geom.basis_d1, &vw);
492 let b2u = fast_av(&geom.basis_d2, &uw);
493 let b2v = fast_av(&geom.basis_d2, &vw);
494 let b3u = fast_av(&geom.basis_d3, &uw);
495 let b3v = fast_av(&geom.basis_d3, &vw);
496
497 let mut coeff_eta = Array1::<f64>::zeros(n);
498 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
499 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
500 let mut coeff_etaw_d2 = Array1::<f64>::zeros(n);
501 let mut coeff_etaw_d3 = Array1::<f64>::zeros(n);
502 let mut coeff_ww_bb = Array1::<f64>::zeros(n);
503 let mut coeff_ww_db = Array1::<f64>::zeros(n);
504 let mut coeff_ww_ddb = Array1::<f64>::zeros(n);
505 let mut coeff_ww_dd = Array1::<f64>::zeros(n);
506
507 for row in 0..n {
508 let q = eta[row] + etaw[row];
509 let (m1, m2, m3) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
510 let m4 = self.neglog_q_fourth_derivative(self.y[row], self.weights[row], q)?;
511 let a = geom.dq_dq0[row];
512 let b = geom.d2q_dq02[row];
513 let c = geom.d3q_dq03[row];
514 let d = geom.d4q_dq04[row];
515
516 let q_u = a * xi_u[row] + phi_u[row];
517 let a_u = b * xi_u[row] + b1u[row];
518 let b_u = c * xi_u[row] + b2u[row];
519 let q_v = a * xi_v[row] + phi_v[row];
520 let a_v = b * xi_v[row] + b1v[row];
521 let b_v = c * xi_v[row] + b2v[row];
522 let q_uv = b * xi_u[row] * xi_v[row] + b1u[row] * xi_v[row] + b1v[row] * xi_u[row];
523 let a_uv = c * xi_u[row] * xi_v[row] + b2u[row] * xi_v[row] + b2v[row] * xi_u[row];
524 let b_uv = d * xi_u[row] * xi_v[row] + b3u[row] * xi_v[row] + b3v[row] * xi_u[row];
525
526 coeff_eta[row] = second_directionalhessian_coeff_fromobjective_q_terms(
527 m1, m2, m3, m4, q_u, q_v, q_uv, a, a, b, a_u, a_v, a_u, a_v, a_uv, a_uv, b_u, b_v,
528 b_uv,
529 );
530 let d2_c_b = m4 * q_u * q_v * a + m3 * (q_uv * a + q_u * a_v + q_v * a_u) + m2 * a_uv;
531 let dc_b_u = m3 * q_u * a + m2 * a_u;
532 let dc_b_v = m3 * q_v * a + m2 * a_v;
533 let c_b_static = m2 * a;
534 let d2_c_b1 = m3 * q_u * q_v + m2 * q_uv;
535 let dc_b1_u = m2 * q_u;
536 let dc_b1_v = m2 * q_v;
537
538 coeff_etaw_b[row] = d2_c_b;
539 coeff_etaw_d1[row] = dc_b_u * xi_v[row] + dc_b_v * xi_u[row] + d2_c_b1;
540 coeff_etaw_d2[row] =
541 c_b_static * xi_u[row] * xi_v[row] + dc_b1_u * xi_v[row] + dc_b1_v * xi_u[row];
542 coeff_etaw_d3[row] = m1 * xi_u[row] * xi_v[row];
543
544 let dw = m2;
545 let dw_u = m3 * q_u;
546 let dw_v = m3 * q_v;
547 let dw_uv = m4 * q_u * q_v + m3 * q_uv;
548 let xixj = xi_u[row] * xi_v[row];
549 coeff_ww_bb[row] = dw_uv;
550 coeff_ww_db[row] = dw_v * xi_u[row] + dw_u * xi_v[row];
551 coeff_ww_ddb[row] = dw * xixj;
552 coeff_ww_dd[row] = 2.0 * dw * xixj;
553 }
554
555 Ok(Some(Arc::new(RowCoeffOperator::from_directions(
556 vec![p_eta, pw],
557 vec![
558 (0, x_eta_arc),
559 (1, Arc::new(geom.basis)),
560 (1, Arc::new(geom.basis_d1)),
561 (1, Arc::new(geom.basis_d2)),
562 (1, Arc::new(geom.basis_d3)),
563 ],
564 vec![
565 (0, 0, coeff_eta),
566 (0, 1, coeff_etaw_b),
567 (0, 2, coeff_etaw_d1),
568 (0, 3, coeff_etaw_d2),
569 (0, 4, coeff_etaw_d3),
570 (1, 1, coeff_ww_bb),
571 (1, 2, coeff_ww_db),
572 (1, 3, coeff_ww_ddb),
573 (2, 2, coeff_ww_dd),
574 ],
575 n,
576 ))))
577 }
578
579 pub fn block_effective_jacobian(
585 specs: &[ParameterBlockSpec],
586 block_idx: usize,
587 ) -> Result<Box<dyn BlockEffectiveJacobian>, String> {
588 crate::block_layout::block_jacobian::AdditiveWiggleBlockLayout {
589 family: "BinomialMeanWiggleFamily",
590 n_outputs: 1,
591 additive_blocks: &[Self::BLOCK_ETA],
592 wiggle_block: Some(Self::BLOCK_WIGGLE),
593 }
594 .block_effective_jacobian(specs, block_idx)
595 }
596}
597
598impl CustomFamily for BinomialMeanWiggleFamily {
599 fn exact_newton_joint_hessian_beta_dependent(&self) -> bool {
600 true
601 }
602
603 fn joint_jeffreys_term_required(&self) -> bool {
641 false
642 }
643
644 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
645 let p_total = specs
649 .iter()
650 .map(|s| s.design.ncols() as u64)
651 .fold(0u64, |acc, p| acc.saturating_add(p));
652 (self.y.len() as u64).saturating_mul(p_total.max(1))
653 }
654
655 fn block_linear_constraints(
656 &self,
657 _: &[ParameterBlockState],
658 block_idx: usize,
659 spec: &ParameterBlockSpec,
660 ) -> Result<Option<LinearInequalityConstraints>, String> {
661 if block_idx != Self::BLOCK_WIGGLE {
662 return Ok(None);
663 }
664 Ok(monotone_wiggle_nonnegative_constraints(spec.design.ncols()))
665 }
666
667 fn post_update_block_beta(
668 &self,
669 _: &[ParameterBlockState],
670 block_idx: usize,
671 block_spec: &ParameterBlockSpec,
672 beta: Array1<f64>,
673 ) -> Result<Array1<f64>, String> {
674 assert!(!block_spec.name.is_empty());
675 if block_idx != Self::BLOCK_WIGGLE {
676 return Ok(beta);
677 }
678 let beta = project_monotone_wiggle_beta_nonnegative(beta);
679 validate_monotone_wiggle_beta_nonnegative(&beta, "BinomialMeanWiggleFamily post-update")?;
680 Ok(beta)
681 }
682
683 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
684 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
685 let eta = &block_states[Self::BLOCK_ETA].eta;
686 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
687 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
688 let n = self.y.len();
689 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
690 return Err(GamlssError::DimensionMismatch {
691 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
692 }
693 .into());
694 }
695 let dq_dq0 = self.wiggle_dq_dq0(eta.view(), betaw.view())?;
696 if dq_dq0.len() != n {
697 return Err(GamlssError::DimensionMismatch {
698 reason: format!(
699 "BinomialMeanWiggleFamily dq/dq0 length mismatch: got {}, expected {}",
700 dq_dq0.len(),
701 n
702 ),
703 }
704 .into());
705 }
706
707 let mut ll = 0.0;
708 let mut z_eta = Array1::<f64>::zeros(n);
709 let mut w_eta = Array1::<f64>::zeros(n);
710 let mut z_wiggle = Array1::<f64>::zeros(n);
711 let mut w_wiggle = Array1::<f64>::zeros(n);
712 for i in 0..n {
713 let q = eta[i] + etaw[i];
714 let (mu_q, d1_q) = inverse_link_mu_d1_for_inverse_link(&self.link_kind, q)
715 .map_err(|e| format!("fixed-link wiggle inverse-link evaluation failed: {e}"))?;
716 let yi = self.y[i];
717 let wi = self.weights[i];
718 ll += binomial_location_scale_log_likelihood(yi, wi, q, &self.link_kind, mu_q)?;
719
720 let mu = mu_q.clamp(1e-12, 1.0 - 1e-12);
721 let var = (mu * (1.0 - mu)).max(MIN_PROB);
722 let dmu_deta = d1_q * dq_dq0[i];
723 let dmu_dw = d1_q;
724 if wi == 0.0 || !var.is_finite() {
725 z_eta[i] = eta[i];
726 z_wiggle[i] = etaw[i];
727 continue;
728 }
729
730 if dmu_deta.is_finite() {
731 w_eta[i] = floor_positiveweight(wi * (dmu_deta * dmu_deta / var), MIN_WEIGHT);
732 z_eta[i] = eta[i] + (yi - mu) / signedwith_floor(dmu_deta, MIN_DERIV);
733 } else {
734 z_eta[i] = eta[i];
735 }
736
737 if dmu_dw.is_finite() {
738 w_wiggle[i] = floor_positiveweight(wi * (dmu_dw * dmu_dw / var), MIN_WEIGHT);
739 z_wiggle[i] = etaw[i] + (yi - mu) / signedwith_floor(dmu_dw, MIN_DERIV);
740 } else {
741 z_wiggle[i] = etaw[i];
742 }
743 }
744
745 Ok(FamilyEvaluation {
746 log_likelihood: ll,
747 blockworking_sets: vec![
748 BlockWorkingSet::diagonal_checked(z_eta, w_eta)?,
749 BlockWorkingSet::diagonal_checked(z_wiggle, w_wiggle)?,
750 ],
751 })
752 }
753
754 fn block_geometry(
755 &self,
756 block_states: &[ParameterBlockState],
757 spec: &ParameterBlockSpec,
758 ) -> Result<(DesignMatrix, Array1<f64>), String> {
759 if spec.name != "wiggle" {
760 return Ok((spec.design.clone(), spec.offset.clone()));
761 }
762 if block_states.is_empty() {
763 return Err(GamlssError::UnsupportedConfiguration {
764 reason: "wiggle geometry requires eta block".to_string(),
765 }
766 .into());
767 }
768 let eta = &block_states[Self::BLOCK_ETA].eta;
769 if eta.len() != self.y.len() {
770 return Err(GamlssError::DimensionMismatch {
771 reason: "BinomialMeanWiggleFamily eta size mismatch".to_string(),
772 }
773 .into());
774 }
775 let x = self.wiggle_design(eta.view())?;
776 if x.ncols() != spec.design.ncols() {
777 return Err(GamlssError::DimensionMismatch {
778 reason: format!(
779 "dynamic wiggle design col mismatch: got {}, expected {}",
780 x.ncols(),
781 spec.design.ncols()
782 ),
783 }
784 .into());
785 }
786 let nrows = x.nrows();
787 Ok((
788 DesignMatrix::Dense(gam_linalg::matrix::DenseDesignMatrix::from(x)),
789 Array1::zeros(nrows),
790 ))
791 }
792
793 fn block_geometry_is_dynamic(&self) -> bool {
794 true
795 }
796
797 fn exact_newton_joint_hessian_workspace(
798 &self,
799 block_states: &[ParameterBlockState],
800 specs: &[ParameterBlockSpec],
801 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
802 let x_eta = self.dense_eta_design_fromspecs(specs)?.into_owned();
803 let workspace =
804 BinomialMeanWiggleHessianWorkspace::new(self.clone(), block_states.to_vec(), x_eta)?;
805 Ok(Some(Arc::new(workspace)))
806 }
807
808 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
809 self.dense_eta_design_fromspecs(specs).is_ok()
810 }
811
812 fn exact_newton_joint_hessian_with_specs(
813 &self,
814 block_states: &[ParameterBlockState],
815 specs: &[ParameterBlockSpec],
816 ) -> Result<Option<Array2<f64>>, String> {
817 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
818 let x_eta = self.dense_eta_design_fromspecs(specs)?;
819 let eta = &block_states[Self::BLOCK_ETA].eta;
820 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
821 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
822 let n = self.y.len();
823 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
824 return Err(GamlssError::DimensionMismatch {
825 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
826 }
827 .into());
828 }
829 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
830 let p_eta = x_eta.ncols();
831 let pw = geom.basis.ncols();
832 let mut coeff_eta = Array1::<f64>::zeros(n);
833 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
834 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
835 let mut coeff_ww = Array1::<f64>::zeros(n);
836 for row in 0..n {
837 let q = eta[row] + etaw[row];
838 let (m1, m2, _) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
839 let a = geom.dq_dq0[row];
840 let b = geom.d2q_dq02[row];
841 coeff_eta[row] = hessian_coeff_fromobjective_q_terms(m1, m2, a, a, b);
842 coeff_etaw_b[row] = m2 * a;
843 coeff_etaw_d1[row] = m1;
844 coeff_ww[row] = m2;
845 }
846 let h_eta_eta = xt_diag_x_dense(&x_eta, &coeff_eta)?;
847 let h_eta_w = xt_diag_y_dense(&x_eta, &coeff_etaw_b, &geom.basis)?
848 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d1, &geom.basis_d1)?;
849 let h_ww = xt_diag_x_dense(&geom.basis, &coeff_ww)?;
850 assert_eq!(h_eta_eta.nrows(), p_eta);
851 assert_eq!(h_ww.nrows(), pw);
852 Ok(Some(binomial_pack_mean_wiggle_joint_symmetrichessian(
853 &h_eta_eta, &h_eta_w, &h_ww,
854 )))
855 }
856
857 fn exact_newton_joint_hessian_directional_derivative_with_specs(
858 &self,
859 block_states: &[ParameterBlockState],
860 specs: &[ParameterBlockSpec],
861 d_beta_flat: &Array1<f64>,
862 ) -> Result<Option<Array2<f64>>, String> {
863 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
864 let x_eta = self.dense_eta_design_fromspecs(specs)?;
865 let eta = &block_states[Self::BLOCK_ETA].eta;
866 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
867 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
868 let n = self.y.len();
869 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
870 return Err(GamlssError::DimensionMismatch {
871 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
872 }
873 .into());
874 }
875 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
876 let p_eta = x_eta.ncols();
877 let pw = geom.basis.ncols();
878 if d_beta_flat.len() != p_eta + pw {
879 return Err(GamlssError::DimensionMismatch {
880 reason: format!(
881 "BinomialMeanWiggleFamily joint d_beta length mismatch: got {}, expected {}",
882 d_beta_flat.len(),
883 p_eta + pw
884 ),
885 }
886 .into());
887 }
888 let u_eta = d_beta_flat.slice(s![0..p_eta]).to_owned();
889 let uw = d_beta_flat.slice(s![p_eta..p_eta + pw]).to_owned();
890 let xi = x_eta.dot(&u_eta);
891 let phi = geom.basis.dot(&uw);
892 let basis1_u = geom.basis_d1.dot(&uw);
893 let basis2_u = geom.basis_d2.dot(&uw);
894
895 let mut coeff_eta = Array1::<f64>::zeros(n);
896 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
897 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
898 let mut coeff_etaw_d2 = Array1::<f64>::zeros(n);
899 let mut coeff_ww_bb = Array1::<f64>::zeros(n);
900 let mut coeff_ww_db = Array1::<f64>::zeros(n);
901 for row in 0..n {
902 let q = eta[row] + etaw[row];
903 let (m1, m2, m3) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
904 let a = geom.dq_dq0[row];
905 let b = geom.d2q_dq02[row];
906 let c = geom.d3q_dq03[row];
907 let q_u = a * xi[row] + phi[row];
908 let a_u = b * xi[row] + basis1_u[row];
909 let b_u = c * xi[row] + basis2_u[row];
910 coeff_eta[row] = directionalhessian_coeff_fromobjective_q_terms(
911 m1, m2, m3, q_u, a, a, b, a_u, a_u, b_u,
912 );
913 coeff_etaw_b[row] = m3 * q_u * a + m2 * a_u;
914 coeff_etaw_d1[row] = m2 * (a * xi[row] + q_u);
915 coeff_etaw_d2[row] = m1 * xi[row];
916 coeff_ww_bb[row] = m3 * q_u;
917 coeff_ww_db[row] = m2 * xi[row];
918 }
919
920 let d_h_eta_eta = xt_diag_x_dense(&x_eta, &coeff_eta)?;
921 let d_h_eta_w = xt_diag_y_dense(&x_eta, &coeff_etaw_b, &geom.basis)?
922 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d1, &geom.basis_d1)?
923 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d2, &geom.basis_d2)?;
924 let a_ww = xt_diag_y_dense(&geom.basis_d1, &coeff_ww_db, &geom.basis)?;
925 let d_h_ww = xt_diag_x_dense(&geom.basis, &coeff_ww_bb)? + &a_ww + a_ww.t();
926 Ok(Some(binomial_pack_mean_wiggle_joint_symmetrichessian(
927 &d_h_eta_eta,
928 &d_h_eta_w,
929 &d_h_ww,
930 )))
931 }
932
933 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
991 &self,
992 block_states: &[ParameterBlockState],
993 specs: &[ParameterBlockSpec],
994 d_beta_u_flat: &Array1<f64>,
995 d_beta_v_flat: &Array1<f64>,
996 ) -> Result<Option<Array2<f64>>, String> {
997 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
998 let x_eta = self.dense_eta_design_fromspecs(specs)?;
999 let eta = &block_states[Self::BLOCK_ETA].eta;
1000 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
1001 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
1002 let n = self.y.len();
1003 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
1004 return Err(GamlssError::DimensionMismatch {
1005 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
1006 }
1007 .into());
1008 }
1009 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
1010 let p_eta = x_eta.ncols();
1011 let pw = geom.basis.ncols();
1012 let total = p_eta + pw;
1013 if d_beta_u_flat.len() != total || d_beta_v_flat.len() != total {
1014 return Err(GamlssError::DimensionMismatch { reason: format!(
1015 "BinomialMeanWiggleFamily joint second d_beta length mismatch: got {} and {}, expected {}",
1016 d_beta_u_flat.len(),
1017 d_beta_v_flat.len(),
1018 total
1019 ) }.into());
1020 }
1021
1022 let u_eta = d_beta_u_flat.slice(s![0..p_eta]).to_owned();
1024 let v_eta = d_beta_v_flat.slice(s![0..p_eta]).to_owned();
1025 let uw = d_beta_u_flat.slice(s![p_eta..total]).to_owned();
1026 let vw = d_beta_v_flat.slice(s![p_eta..total]).to_owned();
1027
1028 let xi_u = x_eta.dot(&u_eta); let xi_v = x_eta.dot(&v_eta); let phi_u = geom.basis.dot(&uw); let phi_v = geom.basis.dot(&vw); let b1u = geom.basis_d1.dot(&uw); let b1v = geom.basis_d1.dot(&vw);
1035 let b2u = geom.basis_d2.dot(&uw); let b2v = geom.basis_d2.dot(&vw);
1037 let b3u = geom.basis_d3.dot(&uw); let b3v = geom.basis_d3.dot(&vw);
1039
1040 let basis_u = scale_matrix_rows(&geom.basis_d1, &xi_u)?; let basis_v = scale_matrix_rows(&geom.basis_d1, &xi_v)?; let basis_uv = scale_matrix_rows(&geom.basis_d2, &(&xi_u * &xi_v))?; let mut coeff_eta = Array1::<f64>::zeros(n);
1057
1058 let mut coeff_etaw_b = Array1::<f64>::zeros(n);
1093 let mut coeff_etaw_d1 = Array1::<f64>::zeros(n);
1094 let mut coeff_etaw_d2 = Array1::<f64>::zeros(n);
1095 let mut coeff_etaw_d3 = Array1::<f64>::zeros(n);
1096
1097 let mut dw = Array1::<f64>::zeros(n);
1122 let mut dw_u = Array1::<f64>::zeros(n);
1123 let mut dw_v = Array1::<f64>::zeros(n);
1124 let mut dw_uv = Array1::<f64>::zeros(n);
1125
1126 for row in 0..n {
1127 let q = eta[row] + etaw[row];
1128 let (m1, m2, m3) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
1129 let m4 = self.neglog_q_fourth_derivative(self.y[row], self.weights[row], q)?;
1130 let a = geom.dq_dq0[row];
1131 let b = geom.d2q_dq02[row];
1132 let c = geom.d3q_dq03[row];
1133 let d = geom.d4q_dq04[row];
1134
1135 let q_u = a * xi_u[row] + phi_u[row];
1137 let a_u = b * xi_u[row] + b1u[row];
1138 let b_u = c * xi_u[row] + b2u[row];
1139
1140 let q_v = a * xi_v[row] + phi_v[row];
1142 let a_v = b * xi_v[row] + b1v[row];
1143 let b_v = c * xi_v[row] + b2v[row];
1144
1145 let q_uv = b * xi_u[row] * xi_v[row] + b1u[row] * xi_v[row] + b1v[row] * xi_u[row];
1147 let a_uv = c * xi_u[row] * xi_v[row] + b2u[row] * xi_v[row] + b2v[row] * xi_u[row];
1148 let b_uv = d * xi_u[row] * xi_v[row] + b3u[row] * xi_v[row] + b3v[row] * xi_u[row];
1149
1150 coeff_eta[row] = second_directionalhessian_coeff_fromobjective_q_terms(
1156 m1, m2, m3, m4, q_u, q_v, q_uv, a, a, b, a_u, a_v, a_u, a_v, a_uv, a_uv, b_u, b_v, b_uv, );
1163
1164 let d2_c_b = m4 * q_u * q_v * a + m3 * (q_uv * a + q_u * a_v + q_v * a_u) + m2 * a_uv;
1170 let dc_b_u = m3 * q_u * a + m2 * a_u;
1172 let dc_b_v = m3 * q_v * a + m2 * a_v;
1173 let c_b_static = m2 * a;
1175 let d2_c_b1 = m3 * q_u * q_v + m2 * q_uv;
1177 let dc_b1_u = m2 * q_u;
1179 let dc_b1_v = m2 * q_v;
1180
1181 coeff_etaw_b[row] = d2_c_b;
1182 coeff_etaw_d1[row] = dc_b_u * xi_v[row] + dc_b_v * xi_u[row] + d2_c_b1;
1183 coeff_etaw_d2[row] =
1184 c_b_static * xi_u[row] * xi_v[row] + dc_b1_u * xi_v[row] + dc_b1_v * xi_u[row];
1185 coeff_etaw_d3[row] = m1 * xi_u[row] * xi_v[row];
1186
1187 dw[row] = m2;
1190 dw_u[row] = m3 * q_u;
1191 dw_v[row] = m3 * q_v;
1192 dw_uv[row] = m4 * q_u * q_v + m3 * q_uv;
1193 }
1194
1195 let d2_h_eta_eta = xt_diag_x_dense(&x_eta, &coeff_eta)?;
1197
1198 let d2_h_eta_w = xt_diag_y_dense(&x_eta, &coeff_etaw_b, &geom.basis)?
1205 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d1, &geom.basis_d1)?
1206 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d2, &geom.basis_d2)?
1207 + &xt_diag_y_dense(&x_eta, &coeff_etaw_d3, &geom.basis_d3)?;
1208
1209 let a_ab = xt_diag_y_dense(&basis_uv, &dw, &geom.basis)?;
1220 let a_ij = xt_diag_y_dense(&basis_u, &dw, &basis_v)?;
1221 let a_iwj = xt_diag_y_dense(&basis_u, &dw_v, &geom.basis)?;
1222 let a_jwi = xt_diag_y_dense(&basis_v, &dw_u, &geom.basis)?;
1223 let d2_h_ww = &a_ab
1224 + &a_ab.t()
1225 + &a_ij
1226 + a_ij.t()
1227 + &a_iwj
1228 + a_iwj.t()
1229 + &a_jwi
1230 + a_jwi.t()
1231 + &xt_diag_x_dense(&geom.basis, &dw_uv)?;
1232
1233 Ok(Some(binomial_pack_mean_wiggle_joint_symmetrichessian(
1234 &d2_h_eta_eta,
1235 &d2_h_eta_w,
1236 &d2_h_ww,
1237 )))
1238 }
1239
1240 fn exact_newton_joint_psi_terms(
1241 &self,
1242 block_states: &[ParameterBlockState],
1243 specs: &[ParameterBlockSpec],
1244 derivative_blocks: &[Vec<CustomFamilyBlockPsiDerivative>],
1245 psi_index: usize,
1246 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
1247 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
1248 if derivative_blocks.len() != 2 {
1249 return Err(GamlssError::DimensionMismatch { reason: format!(
1250 "BinomialMeanWiggleFamily joint psi terms expect 2 derivative block lists, got {}",
1251 derivative_blocks.len()
1252 ) }.into());
1253 }
1254 let x_eta = self.dense_eta_design_fromspecs(specs)?;
1255 let eta = &block_states[Self::BLOCK_ETA].eta;
1256 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
1257 let betaw = &block_states[Self::BLOCK_WIGGLE].beta;
1258 let n = self.y.len();
1259 if eta.len() != n || etaw.len() != n || self.weights.len() != n {
1260 return Err(GamlssError::DimensionMismatch {
1261 reason: "BinomialMeanWiggleFamily input size mismatch".to_string(),
1262 }
1263 .into());
1264 }
1265 let geom = self.wiggle_geometry(eta.view(), betaw.view())?;
1266 let p_eta = x_eta.ncols();
1267 let pw = geom.basis.ncols();
1268 let implicit_dir =
1269 self.exact_newton_joint_psi_action(block_states, derivative_blocks, psi_index, p_eta)?;
1270 let dense_dir = if implicit_dir.is_none() {
1271 self.exact_newton_joint_psi_direction(
1272 block_states,
1273 derivative_blocks,
1274 psi_index,
1275 &x_eta,
1276 )?
1277 } else {
1278 None
1279 };
1280 let z_eta_psi = if let Some((_, ref z_eta_psi)) = implicit_dir {
1281 z_eta_psi
1282 } else if let Some(ref dir_a) = dense_dir {
1283 &dir_a.z_eta_psi
1284 } else {
1285 return Ok(None);
1286 };
1287
1288 let mut objective_psi = 0.0;
1289 let mut score_eta_xa = Array1::<f64>::zeros(n);
1290 let mut score_eta_x = Array1::<f64>::zeros(n);
1291 let mut score_w_b = Array1::<f64>::zeros(n);
1292 let mut score_w_d1 = Array1::<f64>::zeros(n);
1293
1294 let mut coeff_eta_eta_xx = Array1::<f64>::zeros(n);
1295 let mut coeff_eta_eta_xa_x = Array1::<f64>::zeros(n);
1296 let mut coeff_eta_w_xa_b = Array1::<f64>::zeros(n);
1297 let mut coeff_eta_w_x_b = Array1::<f64>::zeros(n);
1298 let mut coeff_eta_w_x_d1 = Array1::<f64>::zeros(n);
1299 let mut coeff_eta_w_xa_d1 = Array1::<f64>::zeros(n);
1300 let mut coeff_eta_w_x_d2 = Array1::<f64>::zeros(n);
1301 let mut coeff_ww_bb = Array1::<f64>::zeros(n);
1302 let mut coeff_ww_db = Array1::<f64>::zeros(n);
1303
1304 for row in 0..n {
1305 let q = eta[row] + etaw[row];
1306 let (m1, m2, m3) = self.neglog_q_derivatives(self.y[row], self.weights[row], q)?;
1307 let z_a = z_eta_psi[row];
1308 let a = geom.dq_dq0[row];
1309 let b = geom.d2q_dq02[row];
1310 let c = geom.d3q_dq03[row];
1311 let q_a = a * z_a;
1312
1313 objective_psi += m1 * q_a;
1314
1315 score_eta_xa[row] = m1 * a;
1316 score_eta_x[row] = m2 * q_a * a + m1 * b * z_a;
1317 score_w_b[row] = m2 * q_a;
1318 score_w_d1[row] = m1 * z_a;
1319
1320 coeff_eta_eta_xx[row] =
1321 m3 * q_a * a * a + m2 * (2.0 * a * b * z_a + q_a * b) + m1 * c * z_a;
1322 coeff_eta_eta_xa_x[row] = m2 * a * a + m1 * b;
1323 coeff_eta_w_xa_b[row] = m2 * a;
1324 coeff_eta_w_x_b[row] = m3 * q_a * a + m2 * b * z_a;
1325 coeff_eta_w_x_d1[row] = m2 * (a * z_a + q_a);
1326 coeff_eta_w_xa_d1[row] = m1;
1327 coeff_eta_w_x_d2[row] = m1 * z_a;
1328 coeff_ww_bb[row] = m3 * q_a;
1329 coeff_ww_db[row] = m2 * z_a;
1330 }
1331
1332 let score_w = gam_linalg::faer_ndarray::fast_atv(&geom.basis, &score_w_b)
1333 + gam_linalg::faer_ndarray::fast_atv(&geom.basis_d1, &score_w_d1);
1334
1335 if let Some((action, _)) = implicit_dir {
1336 let score_eta = action.transpose_mul(score_eta_xa.view())
1337 + gam_linalg::faer_ndarray::fast_atv(x_eta.as_ref(), &score_eta_x);
1338 let score_psi = binomial_pack_mean_wiggle_joint_score(&score_eta, &score_w);
1339 let x_eta_arc = shared_dense_arc(x_eta.as_ref());
1340 let basis_arc = Arc::new(geom.basis.clone());
1341 let basis_d1_arc = Arc::new(geom.basis_d1.clone());
1342 let basis_d2_arc = Arc::new(geom.basis_d2.clone());
1343 let zeros = Array1::<f64>::zeros(n);
1344 let operator = CustomFamilyJointPsiOperator::new(
1345 p_eta + pw,
1346 vec![
1347 CustomFamilyJointDesignChannel::new(
1348 0..p_eta,
1349 Arc::clone(&x_eta_arc),
1350 Some(action),
1351 ),
1352 CustomFamilyJointDesignChannel::new(
1353 p_eta..p_eta + pw,
1354 Arc::clone(&basis_arc),
1355 None,
1356 ),
1357 CustomFamilyJointDesignChannel::new(
1358 p_eta..p_eta + pw,
1359 Arc::clone(&basis_d1_arc),
1360 None,
1361 ),
1362 CustomFamilyJointDesignChannel::new(
1363 p_eta..p_eta + pw,
1364 Arc::clone(&basis_d2_arc),
1365 None,
1366 ),
1367 ],
1368 vec![
1369 CustomFamilyJointDesignPairContribution::new(
1370 0,
1371 0,
1372 coeff_eta_eta_xa_x.clone(),
1373 coeff_eta_eta_xx.clone(),
1374 ),
1375 CustomFamilyJointDesignPairContribution::new(
1376 0,
1377 1,
1378 coeff_eta_w_xa_b.clone(),
1379 coeff_eta_w_x_b.clone(),
1380 ),
1381 CustomFamilyJointDesignPairContribution::new(
1382 1,
1383 0,
1384 coeff_eta_w_xa_b.clone(),
1385 coeff_eta_w_x_b.clone(),
1386 ),
1387 CustomFamilyJointDesignPairContribution::new(
1388 0,
1389 2,
1390 coeff_eta_w_xa_d1.clone(),
1391 coeff_eta_w_x_d1.clone(),
1392 ),
1393 CustomFamilyJointDesignPairContribution::new(
1394 2,
1395 0,
1396 coeff_eta_w_xa_d1.clone(),
1397 coeff_eta_w_x_d1.clone(),
1398 ),
1399 CustomFamilyJointDesignPairContribution::new(
1400 0,
1401 3,
1402 zeros.clone(),
1403 coeff_eta_w_x_d2.clone(),
1404 ),
1405 CustomFamilyJointDesignPairContribution::new(
1406 3,
1407 0,
1408 zeros.clone(),
1409 coeff_eta_w_x_d2.clone(),
1410 ),
1411 CustomFamilyJointDesignPairContribution::new(
1412 1,
1413 1,
1414 zeros.clone(),
1415 coeff_ww_bb.clone(),
1416 ),
1417 CustomFamilyJointDesignPairContribution::new(
1418 2,
1419 1,
1420 zeros.clone(),
1421 coeff_ww_db.clone(),
1422 ),
1423 CustomFamilyJointDesignPairContribution::new(1, 2, zeros, coeff_ww_db.clone()),
1424 ],
1425 );
1426 return Ok(Some(crate::custom_family::ExactNewtonJointPsiTerms {
1427 objective_psi,
1428 score_psi,
1429 hessian_psi: Array2::zeros((0, 0)),
1430 hessian_psi_operator: Some(std::sync::Arc::new(operator)),
1431 }));
1432 }
1433
1434 let dir_a =
1435 dense_dir.expect("dense psi direction should exist when implicit direction is absent");
1436 let x_eta_psi = dir_a
1437 .x_eta_psi
1438 .as_ref()
1439 .expect("dense eta psi design should exist when implicit direction is absent");
1440 let score_psi = binomial_pack_mean_wiggle_joint_score(
1441 &(gam_linalg::faer_ndarray::fast_atv(x_eta_psi, &score_eta_xa)
1442 + gam_linalg::faer_ndarray::fast_atv(x_eta.as_ref(), &score_eta_x)),
1443 &score_w,
1444 );
1445 let a_eta_eta = xt_diag_y_dense(x_eta_psi, &coeff_eta_eta_xa_x, &x_eta)?;
1446 let h_eta_eta = &a_eta_eta + &a_eta_eta.t() + &xt_diag_x_dense(&x_eta, &coeff_eta_eta_xx)?;
1447 let h_eta_w = xt_diag_y_dense(x_eta_psi, &coeff_eta_w_xa_b, &geom.basis)?
1448 + &xt_diag_y_dense(&x_eta, &coeff_eta_w_x_b, &geom.basis)?
1449 + &xt_diag_y_dense(&x_eta, &coeff_eta_w_x_d1, &geom.basis_d1)?
1450 + &xt_diag_y_dense(x_eta_psi, &coeff_eta_w_xa_d1, &geom.basis_d1)?
1451 + &xt_diag_y_dense(&x_eta, &coeff_eta_w_x_d2, &geom.basis_d2)?;
1452 let a_ww = xt_diag_y_dense(&geom.basis_d1, &coeff_ww_db, &geom.basis)?;
1453 let h_ww = xt_diag_x_dense(&geom.basis, &coeff_ww_bb)? + &a_ww + a_ww.t();
1454
1455 Ok(Some(crate::custom_family::ExactNewtonJointPsiTerms {
1456 objective_psi,
1457 score_psi,
1458 hessian_psi: binomial_pack_mean_wiggle_joint_symmetrichessian(
1459 &h_eta_eta, &h_eta_w, &h_ww,
1460 ),
1461 hessian_psi_operator: None,
1462 }))
1463 }
1464}
1465
1466pub(crate) struct BinomialMeanWiggleHessianWorkspace {
1467 pub(crate) family: BinomialMeanWiggleFamily,
1468 pub(crate) block_states: Vec<ParameterBlockState>,
1469 pub(crate) x_eta: Arc<Array2<f64>>,
1470 pub(crate) hessian_operator: Arc<RowCoeffOperator>,
1471}
1472
1473impl BinomialMeanWiggleHessianWorkspace {
1474 pub(crate) fn new(
1475 family: BinomialMeanWiggleFamily,
1476 block_states: Vec<ParameterBlockState>,
1477 x_eta: Array2<f64>,
1478 ) -> Result<Self, String> {
1479 let x_eta = Arc::new(x_eta);
1480 let hessian_operator = family.bmw_static_hessian_operator(&block_states, x_eta.clone())?;
1481 Ok(Self {
1482 family,
1483 block_states,
1484 x_eta,
1485 hessian_operator,
1486 })
1487 }
1488}
1489
1490impl ExactNewtonJointHessianWorkspace for BinomialMeanWiggleHessianWorkspace {
1491 fn hessian_matvec_available(&self) -> bool {
1492 true
1493 }
1494
1495 fn hessian_matvec(&self, v: &Array1<f64>) -> Result<Option<Array1<f64>>, String> {
1496 Ok(Some(gam_problem::HyperOperator::mul_vec(
1497 self.hessian_operator.as_ref(),
1498 v,
1499 )))
1500 }
1501
1502 fn hessian_diagonal(&self) -> Result<Option<Array1<f64>>, String> {
1503 Ok(None)
1504 }
1505
1506 fn directional_derivative(
1507 &self,
1508 d_beta_flat: &Array1<f64>,
1509 ) -> Result<Option<Array2<f64>>, String> {
1510 Ok(self
1511 .directional_derivative_operator(d_beta_flat)?
1512 .map(|operator| operator.to_dense()))
1513 }
1514
1515 fn directional_derivative_operator(
1516 &self,
1517 d_beta_flat: &Array1<f64>,
1518 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
1519 self.family
1520 .bmw_directional_operator(&self.block_states, self.x_eta.clone(), d_beta_flat)
1521 }
1522
1523 fn second_directional_derivative(
1524 &self,
1525 d_beta_u_flat: &Array1<f64>,
1526 d_beta_v_flat: &Array1<f64>,
1527 ) -> Result<Option<Array2<f64>>, String> {
1528 Ok(self
1529 .second_directional_derivative_operator(d_beta_u_flat, d_beta_v_flat)?
1530 .map(|operator| operator.to_dense()))
1531 }
1532
1533 fn second_directional_derivative_operator(
1534 &self,
1535 d_beta_u: &Array1<f64>,
1536 d_beta_v: &Array1<f64>,
1537 ) -> Result<Option<Arc<dyn gam_problem::HyperOperator>>, String> {
1538 self.family.bmw_second_directional_operator(
1539 &self.block_states,
1540 self.x_eta.clone(),
1541 d_beta_u,
1542 d_beta_v,
1543 )
1544 }
1545}
1546
1547impl CustomFamilyGenerative for BinomialMeanWiggleFamily {
1548 fn generativespec(
1549 &self,
1550 block_states: &[ParameterBlockState],
1551 ) -> Result<GenerativeSpec, String> {
1552 validate_block_count::<GamlssError>("BinomialMeanWiggleFamily", 2, block_states.len())?;
1553 let eta = &block_states[Self::BLOCK_ETA].eta;
1554 let etaw = &block_states[Self::BLOCK_WIGGLE].eta;
1555 if eta.len() != self.y.len() || etaw.len() != self.y.len() {
1556 return Err(GamlssError::DimensionMismatch {
1557 reason: "BinomialMeanWiggleFamily generative size mismatch".to_string(),
1558 }
1559 .into());
1560 }
1561 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
1562 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, eta[i] + etaw[i])
1563 .map_err(|e| format!("fixed-link wiggle inverse-link evaluation failed: {e}"))?;
1564 Ok(jet.mu)
1565 })?;
1566 Ok(GenerativeSpec {
1567 mean,
1568 noise: NoiseModel::Bernoulli,
1569 })
1570 }
1571}