1use super::*;
6
7impl BinomialLocationScaleFamily {
8 pub const BLOCK_T: usize = 0;
9 pub const BLOCK_LOG_SIGMA: usize = 1;
10
11 pub fn parameternames() -> &'static [&'static str] {
12 &["threshold", "log_sigma"]
13 }
14
15 pub fn parameter_links() -> &'static [ParameterLink] {
16 &[ParameterLink::InverseLink, ParameterLink::Log]
17 }
18
19 pub fn metadata() -> FamilyMetadata {
20 FamilyMetadata {
21 name: "binomial_location_scale",
22 parameternames: Self::parameternames(),
23 parameter_links: Self::parameter_links(),
24 }
25 }
26
27 pub(crate) fn exact_joint_supported(&self) -> bool {
28 self.threshold_design.is_some() && self.log_sigma_design.is_some()
29 }
30
31 pub(crate) fn dense_block_designs(
32 &self,
33 ) -> Result<(Cow<'_, Array2<f64>>, Cow<'_, Array2<f64>>), String> {
34 dense_locscale_block_designs_cached(
35 self.threshold_design.as_ref(),
36 self.log_sigma_design.as_ref(),
37 "BinomialLocationScaleFamily",
38 "BinomialLocationScale",
39 "threshold",
40 &self.policy.material_policy(),
41 )
42 }
43
44 pub(crate) fn dense_block_designs_fromspecs<'a>(
45 &self,
46 specs: &'a [ParameterBlockSpec],
47 ) -> Result<(Cow<'a, Array2<f64>>, Cow<'a, Array2<f64>>), String> {
48 dense_locscale_block_designs_fromspecs(
49 specs,
50 2,
51 "BinomialLocationScaleFamily",
52 "BinomialLocationScale",
53 Self::BLOCK_T,
54 Self::BLOCK_LOG_SIGMA,
55 "threshold",
56 &self.policy.material_policy(),
57 )
58 }
59
60 pub(crate) fn exact_joint_dense_block_designs<'a>(
61 &'a self,
62 specs: Option<&'a [ParameterBlockSpec]>,
63 ) -> Result<Option<(Cow<'a, Array2<f64>>, Cow<'a, Array2<f64>>)>, String> {
64 if self.threshold_design.is_some() && self.log_sigma_design.is_some() {
80 return self.dense_block_designs().map(Some);
81 }
82 if let Some(specs) = specs {
83 return self.dense_block_designs_fromspecs(specs).map(Some);
84 }
85 Ok(None)
86 }
87
88 pub(crate) fn exact_joint_block_designs_owned(
89 &self,
90 specs: Option<&[ParameterBlockSpec]>,
91 ) -> Result<Option<(DesignMatrix, DesignMatrix)>, String> {
92 let designs = if let (Some(x_t), Some(x_ls)) = (
93 self.threshold_design.as_ref(),
94 self.log_sigma_design.as_ref(),
95 ) {
96 Some((x_t.clone(), x_ls.clone()))
97 } else if let Some(specs) = specs {
98 if specs.len() != 2 {
99 return Err(GamlssError::DimensionMismatch { reason: format!(
100 "BinomialLocationScaleFamily spec-aware operator path expects 2 specs, got {}",
101 specs.len()
102 ) }.into());
103 }
104 Some((
105 specs[Self::BLOCK_T].design.clone(),
106 specs[Self::BLOCK_LOG_SIGMA].design.clone(),
107 ))
108 } else {
109 None
110 };
111 let Some((x_t, x_ls)) = designs else {
112 return Ok(None);
113 };
114 let n = self.y.len();
115 if x_t.nrows() != n || x_ls.nrows() != n {
116 return Err(GamlssError::DimensionMismatch { reason: format!(
117 "BinomialLocationScaleFamily operator designs have row mismatch: y={}, threshold={}, log_sigma={}",
118 n,
119 x_t.nrows(),
120 x_ls.nrows()
121 ) }.into());
122 }
123 Ok(Some((x_t, x_ls)))
124 }
125
126 pub(crate) fn exact_newton_joint_gradient_from_designs(
127 &self,
128 block_states: &[ParameterBlockState],
129 x_t: &DesignMatrix,
130 x_ls: &DesignMatrix,
131 ) -> Result<ExactNewtonJointGradientEvaluation, String> {
132 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
133 let n = self.y.len();
134 let eta_t = &block_states[Self::BLOCK_T].eta;
135 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
136 if eta_t.len() != n
137 || eta_ls.len() != n
138 || self.weights.len() != n
139 || x_t.nrows() != n
140 || x_ls.nrows() != n
141 {
142 return Err(
143 "BinomialLocationScaleFamily joint gradient input size mismatch".to_string(),
144 );
145 }
146
147 let core = binomial_location_scale_core(
148 &self.y,
149 &self.weights,
150 eta_t,
151 eta_ls,
152 None,
153 &self.link_kind,
154 )?;
155 let mut grad_eta_t_v = vec![0.0_f64; n];
156 let mut grad_eta_ls_v = vec![0.0_f64; n];
157 let y_slice = self.y.as_slice().expect("y must be contiguous");
158 let w_slice = self.weights.as_slice().expect("weights must be contiguous");
159 let q0_slice = core.q0.as_slice().expect("q0 must be contiguous");
160 let eta_t_slice = eta_t.as_slice().expect("eta_t must be contiguous");
161 let eta_ls_slice = eta_ls.as_slice().expect("eta_ls must be contiguous");
162 let link_kind = &self.link_kind;
163 let gradient_pairs: Result<Vec<(f64, f64)>, String> = (0..n)
164 .into_par_iter()
165 .map(|i| {
166 let gradient = binomial_location_scale_nll_gradient(
167 y_slice[i],
168 w_slice[i],
169 eta_t_slice[i],
170 eta_ls_slice[i],
171 q0_slice[i],
172 core.mu[i],
173 core.dmu_dq[i],
174 core.d2mu_dq2[i],
175 core.d3mu_dq3[i],
176 link_kind,
177 )?;
178 Ok((-gradient[0], -gradient[1]))
179 })
180 .collect();
181 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
182 grad_eta_t_v[i] = g_t;
183 grad_eta_ls_v[i] = g_ls;
184 }
185 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
186 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
187 let grad_t = x_t.transpose_vector_multiply(&grad_eta_t);
188 let grad_ls = x_ls.transpose_vector_multiply(&grad_eta_ls);
189 let total = grad_t.len() + grad_ls.len();
190 let mut gradient = Array1::<f64>::zeros(total);
191 gradient.slice_mut(s![0..grad_t.len()]).assign(&grad_t);
192 gradient.slice_mut(s![grad_t.len()..total]).assign(&grad_ls);
193 Ok(ExactNewtonJointGradientEvaluation {
194 log_likelihood: core.log_likelihood,
195 gradient,
196 })
197 }
198
199 pub(crate) fn exact_newton_joint_hessian_for_specs(
200 &self,
201 block_states: &[ParameterBlockState],
202 specs: Option<&[ParameterBlockSpec]>,
203 ) -> Result<Option<Array2<f64>>, String> {
204 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(specs)? else {
205 return Ok(None);
206 };
207 self.exact_newton_joint_hessian_from_design_matrices(block_states, &x_t, &x_ls)
208 }
209
210 pub(crate) fn exact_newton_joint_hessian_directional_derivative_for_specs(
211 &self,
212 block_states: &[ParameterBlockState],
213 specs: Option<&[ParameterBlockSpec]>,
214 d_beta_flat: &Array1<f64>,
215 ) -> Result<Option<Array2<f64>>, String> {
216 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
217 return Ok(None);
218 };
219 self.exact_newton_joint_hessian_directional_derivative_from_designs(
220 block_states,
221 &x_t,
222 &x_ls,
223 d_beta_flat,
224 )
225 }
226
227 pub(crate) fn exact_newton_joint_hessian_second_directional_derivative_for_specs(
228 &self,
229 block_states: &[ParameterBlockState],
230 specs: Option<&[ParameterBlockSpec]>,
231 d_beta_u_flat: &Array1<f64>,
232 d_betav_flat: &Array1<f64>,
233 ) -> Result<Option<Array2<f64>>, String> {
234 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
235 return Ok(None);
236 };
237 self.exact_newton_joint_hessiansecond_directional_derivative_from_designs(
238 block_states,
239 &x_t,
240 &x_ls,
241 d_beta_u_flat,
242 d_betav_flat,
243 )
244 }
245
246 pub(crate) fn expected_joint_information_from_designs(
247 &self,
248 block_states: &[ParameterBlockState],
249 x_t: &Array2<f64>,
250 x_ls: &Array2<f64>,
251 ) -> Result<Option<Array2<f64>>, String> {
252 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
253 let n = self.y.len();
254 let eta_t = &block_states[Self::BLOCK_T].eta;
255 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
256 if eta_t.len() != n
257 || eta_ls.len() != n
258 || self.weights.len() != n
259 || x_t.nrows() != n
260 || x_ls.nrows() != n
261 {
262 return Err(GamlssError::DimensionMismatch {
263 reason: "BinomialLocationScaleFamily expected information input size mismatch"
264 .to_string(),
265 }
266 .into());
267 }
268 let core = binomial_location_scale_core(
269 &self.y,
270 &self.weights,
271 eta_t,
272 eta_ls,
273 None,
274 &self.link_kind,
275 )?;
276 let rows: Vec<(f64, f64, f64)> = (0..n)
277 .into_par_iter()
278 .map(|i| {
279 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
280 let (f, _, _) = binomial_expected_q_information_derivatives(
281 self.weights[i],
282 core.mu[i],
283 core.dmu_dq[i],
284 core.d2mu_dq2[i],
285 core.d3mu_dq3[i],
286 );
287 (f * q.q_t * q.q_t, f * q.q_t * q.q_ls, f * q.q_ls * q.q_ls)
288 })
289 .collect();
290 let mut coeff_tt = Array1::<f64>::zeros(n);
291 let mut coeff_tl = Array1::<f64>::zeros(n);
292 let mut coeff_ll = Array1::<f64>::zeros(n);
293 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
294 coeff_tt[i] = tt;
295 coeff_tl[i] = tl;
296 coeff_ll[i] = ll;
297 }
298 let pt = x_t.ncols();
299 let pls = x_ls.ncols();
300 let total = pt + pls;
301 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
302 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
303 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
304 let mut h = Array2::<f64>::zeros((total, total));
305 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
306 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
307 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
308 mirror_upper_to_lower(&mut h);
309 Ok(Some(h))
310 }
311
312 pub(crate) fn expected_joint_information_directional_from_designs(
313 &self,
314 block_states: &[ParameterBlockState],
315 x_t: &Array2<f64>,
316 x_ls: &Array2<f64>,
317 d_beta_flat: &Array1<f64>,
318 ) -> Result<Option<Array2<f64>>, String> {
319 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
320 let n = self.y.len();
321 let eta_t = &block_states[Self::BLOCK_T].eta;
322 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
323 if eta_t.len() != n
324 || eta_ls.len() != n
325 || self.weights.len() != n
326 || x_t.nrows() != n
327 || x_ls.nrows() != n
328 {
329 return Err(GamlssError::DimensionMismatch {
330 reason: "BinomialLocationScaleFamily expected dI input size mismatch".to_string(),
331 }
332 .into());
333 }
334 let pt = x_t.ncols();
335 let pls = x_ls.ncols();
336 let total = pt + pls;
337 if d_beta_flat.len() != total {
338 return Err(GamlssError::DimensionMismatch {
339 reason: format!(
340 "BinomialLocationScaleFamily expected dI direction length mismatch: got {}, expected {}",
341 d_beta_flat.len(),
342 total
343 ),
344 }
345 .into());
346 }
347 let d_eta_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
348 let d_eta_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..total]));
349 let core = binomial_location_scale_core(
350 &self.y,
351 &self.weights,
352 eta_t,
353 eta_ls,
354 None,
355 &self.link_kind,
356 )?;
357 let rows: Vec<(f64, f64, f64)> = (0..n)
358 .into_par_iter()
359 .map(|i| {
360 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
361 let u = nonwiggle_q_directional(q, d_eta_t[i], d_eta_ls[i]);
362 let (f, f1, _) = binomial_expected_q_information_derivatives(
363 self.weights[i],
364 core.mu[i],
365 core.dmu_dq[i],
366 core.d2mu_dq2[i],
367 core.d3mu_dq3[i],
368 );
369 let tt = f1 * u.delta_q * q.q_t * q.q_t + 2.0 * f * q.q_t * u.delta_q_t;
370 let tl = f1 * u.delta_q * q.q_t * q.q_ls
371 + f * (u.delta_q_t * q.q_ls + q.q_t * u.delta_q_ls);
372 let ll = f1 * u.delta_q * q.q_ls * q.q_ls + 2.0 * f * q.q_ls * u.delta_q_ls;
373 (tt, tl, ll)
374 })
375 .collect();
376 let mut coeff_tt = Array1::<f64>::zeros(n);
377 let mut coeff_tl = Array1::<f64>::zeros(n);
378 let mut coeff_ll = Array1::<f64>::zeros(n);
379 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
380 coeff_tt[i] = tt;
381 coeff_tl[i] = tl;
382 coeff_ll[i] = ll;
383 }
384 let d_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
385 let d_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
386 let d_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
387 let mut d_h = Array2::<f64>::zeros((total, total));
388 d_h.slice_mut(s![0..pt, 0..pt]).assign(&d_h_tt);
389 d_h.slice_mut(s![0..pt, pt..total]).assign(&d_h_tl);
390 d_h.slice_mut(s![pt..total, pt..total]).assign(&d_h_ll);
391 mirror_upper_to_lower(&mut d_h);
392 Ok(Some(d_h))
393 }
394
395 pub(crate) fn expected_joint_information_second_directional_from_designs(
396 &self,
397 block_states: &[ParameterBlockState],
398 x_t: &Array2<f64>,
399 x_ls: &Array2<f64>,
400 d_beta_u_flat: &Array1<f64>,
401 d_betav_flat: &Array1<f64>,
402 ) -> Result<Option<Array2<f64>>, String> {
403 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
404 let n = self.y.len();
405 let eta_t = &block_states[Self::BLOCK_T].eta;
406 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
407 if eta_t.len() != n
408 || eta_ls.len() != n
409 || self.weights.len() != n
410 || x_t.nrows() != n
411 || x_ls.nrows() != n
412 {
413 return Err(GamlssError::DimensionMismatch {
414 reason: "BinomialLocationScaleFamily expected d2I input size mismatch".to_string(),
415 }
416 .into());
417 }
418 let pt = x_t.ncols();
419 let pls = x_ls.ncols();
420 let total = pt + pls;
421 if d_beta_u_flat.len() != total {
422 return Err(GamlssError::DimensionMismatch { reason: format!(
423 "BinomialLocationScaleFamily expected d2I u direction length mismatch: got {}, expected {}",
424 d_beta_u_flat.len(),
425 total
426 ) }.into());
427 }
428 if d_betav_flat.len() != total {
429 return Err(GamlssError::DimensionMismatch { reason: format!(
430 "BinomialLocationScaleFamily expected d2I v direction length mismatch: got {}, expected {}",
431 d_betav_flat.len(),
432 total
433 ) }.into());
434 }
435 let d_eta_t_u = fast_av(x_t, &d_beta_u_flat.slice(s![0..pt]));
436 let d_eta_ls_u = fast_av(x_ls, &d_beta_u_flat.slice(s![pt..total]));
437 let d_eta_t_v = fast_av(x_t, &d_betav_flat.slice(s![0..pt]));
438 let d_eta_ls_v = fast_av(x_ls, &d_betav_flat.slice(s![pt..total]));
439 let core = binomial_location_scale_core(
440 &self.y,
441 &self.weights,
442 eta_t,
443 eta_ls,
444 None,
445 &self.link_kind,
446 )?;
447 let rows: Vec<(f64, f64, f64)> = (0..n)
448 .into_par_iter()
449 .map(|i| {
450 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
451 let (f, f1, f2) = binomial_expected_q_information_derivatives(
452 self.weights[i],
453 core.mu[i],
454 core.dmu_dq[i],
455 core.d2mu_dq2[i],
456 core.d3mu_dq3[i],
457 );
458 binomial_expected_location_scale_second_coefficients(
459 q,
460 f,
461 f1,
462 f2,
463 d_eta_t_u[i],
464 d_eta_ls_u[i],
465 d_eta_t_v[i],
466 d_eta_ls_v[i],
467 )
468 })
469 .collect();
470 let mut coeff_tt = Array1::<f64>::zeros(n);
471 let mut coeff_tl = Array1::<f64>::zeros(n);
472 let mut coeff_ll = Array1::<f64>::zeros(n);
473 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
474 coeff_tt[i] = tt;
475 coeff_tl[i] = tl;
476 coeff_ll[i] = ll;
477 }
478 let d2_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
479 let d2_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
480 let d2_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
481 let mut d2_h = Array2::<f64>::zeros((total, total));
482 d2_h.slice_mut(s![0..pt, 0..pt]).assign(&d2_h_tt);
483 d2_h.slice_mut(s![0..pt, pt..total]).assign(&d2_h_tl);
484 d2_h.slice_mut(s![pt..total, pt..total]).assign(&d2_h_ll);
485 mirror_upper_to_lower(&mut d2_h);
486 Ok(Some(d2_h))
487 }
488
489 pub(crate) fn expected_joint_contracted_trace_hessian_from_designs(
490 &self,
491 block_states: &[ParameterBlockState],
492 x_t: &Array2<f64>,
493 x_ls: &Array2<f64>,
494 trace_weight: &Array2<f64>,
495 ) -> Result<Option<Array2<f64>>, String> {
496 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
497 let n = self.y.len();
498 let eta_t = &block_states[Self::BLOCK_T].eta;
499 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
500 if eta_t.len() != n
501 || eta_ls.len() != n
502 || self.weights.len() != n
503 || x_t.nrows() != n
504 || x_ls.nrows() != n
505 {
506 return Err(GamlssError::DimensionMismatch {
507 reason: "BinomialLocationScaleFamily expected contracted trace input size mismatch"
508 .to_string(),
509 }
510 .into());
511 }
512 let pt = x_t.ncols();
513 let pls = x_ls.ncols();
514 let total = pt + pls;
515 if trace_weight.dim() != (total, total) {
516 return Err(GamlssError::DimensionMismatch {
517 reason: format!(
518 "BinomialLocationScaleFamily expected contracted trace weight shape {:?} == ({total}, {total})",
519 trace_weight.dim()
520 ),
521 }
522 .into());
523 }
524 let core = binomial_location_scale_core(
525 &self.y,
526 &self.weights,
527 eta_t,
528 eta_ls,
529 None,
530 &self.link_kind,
531 )?;
532 let rows: Vec<(f64, f64, f64)> = (0..n)
533 .into_par_iter()
534 .map(|i| {
535 let mut trace_tt = 0.0;
536 for a in 0..pt {
537 for b in 0..pt {
538 trace_tt += x_t[[i, a]] * trace_weight[[a, b]] * x_t[[i, b]];
539 }
540 }
541 let mut trace_tl = 0.0;
542 for a in 0..pt {
543 for b in 0..pls {
544 trace_tl += x_t[[i, a]]
545 * (trace_weight[[a, pt + b]] + trace_weight[[pt + b, a]])
546 * x_ls[[i, b]];
547 }
548 }
549 let mut trace_ll = 0.0;
550 for a in 0..pls {
551 for b in 0..pls {
552 trace_ll += x_ls[[i, a]] * trace_weight[[pt + a, pt + b]] * x_ls[[i, b]];
553 }
554 }
555 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
556 let (f, f1, f2) = binomial_expected_q_information_derivatives(
557 self.weights[i],
558 core.mu[i],
559 core.dmu_dq[i],
560 core.d2mu_dq2[i],
561 core.d3mu_dq3[i],
562 );
563 let (tt_tt, tt_tl, tt_ll) = binomial_expected_location_scale_second_coefficients(
564 q, f, f1, f2, 1.0, 0.0, 1.0, 0.0,
565 );
566 let (tl_tt, tl_tl, tl_ll) = binomial_expected_location_scale_second_coefficients(
567 q, f, f1, f2, 1.0, 0.0, 0.0, 1.0,
568 );
569 let (ll_tt, ll_tl, ll_ll) = binomial_expected_location_scale_second_coefficients(
570 q, f, f1, f2, 0.0, 1.0, 0.0, 1.0,
571 );
572 (
573 trace_tt * tt_tt + trace_tl * tt_tl + trace_ll * tt_ll,
574 trace_tt * tl_tt + trace_tl * tl_tl + trace_ll * tl_ll,
575 trace_tt * ll_tt + trace_tl * ll_tl + trace_ll * ll_ll,
576 )
577 })
578 .collect();
579 let mut coeff_tt = Array1::<f64>::zeros(n);
580 let mut coeff_tl = Array1::<f64>::zeros(n);
581 let mut coeff_ll = Array1::<f64>::zeros(n);
582 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
583 coeff_tt[i] = tt;
584 coeff_tl[i] = tl;
585 coeff_ll[i] = ll;
586 }
587 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
588 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
589 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
590 let mut h = Array2::<f64>::zeros((total, total));
591 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
592 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
593 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
594 mirror_upper_to_lower(&mut h);
595 Ok(Some(h))
596 }
597
598 pub(crate) fn expected_joint_information_for_specs(
599 &self,
600 block_states: &[ParameterBlockState],
601 specs: Option<&[ParameterBlockSpec]>,
602 ) -> Result<Option<Array2<f64>>, String> {
603 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
604 return Ok(None);
605 };
606 self.expected_joint_information_from_designs(block_states, &x_t, &x_ls)
607 }
608
609 pub(crate) fn expected_joint_information_directional_for_specs(
610 &self,
611 block_states: &[ParameterBlockState],
612 specs: Option<&[ParameterBlockSpec]>,
613 d_beta_flat: &Array1<f64>,
614 ) -> Result<Option<Array2<f64>>, String> {
615 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
616 return Ok(None);
617 };
618 self.expected_joint_information_directional_from_designs(
619 block_states,
620 &x_t,
621 &x_ls,
622 d_beta_flat,
623 )
624 }
625
626 pub(crate) fn expected_joint_information_second_directional_for_specs(
627 &self,
628 block_states: &[ParameterBlockState],
629 specs: Option<&[ParameterBlockSpec]>,
630 d_beta_u_flat: &Array1<f64>,
631 d_betav_flat: &Array1<f64>,
632 ) -> Result<Option<Array2<f64>>, String> {
633 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
634 return Ok(None);
635 };
636 self.expected_joint_information_second_directional_from_designs(
637 block_states,
638 &x_t,
639 &x_ls,
640 d_beta_u_flat,
641 d_betav_flat,
642 )
643 }
644
645 pub(crate) fn expected_joint_contracted_trace_hessian_for_specs(
646 &self,
647 block_states: &[ParameterBlockState],
648 specs: Option<&[ParameterBlockSpec]>,
649 trace_weight: &Array2<f64>,
650 ) -> Result<Option<Array2<f64>>, String> {
651 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
652 return Ok(None);
653 };
654 self.expected_joint_contracted_trace_hessian_from_designs(
655 block_states,
656 &x_t,
657 &x_ls,
658 trace_weight,
659 )
660 }
661
662 pub(crate) fn exact_newton_joint_psi_terms_for_specs(
663 &self,
664 block_states: &[ParameterBlockState],
665 specs: &[ParameterBlockSpec],
666 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
667 psi_index: usize,
668 ) -> Result<Option<gam_problem::ExactNewtonJointPsiTerms>, String> {
669 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
670 return Ok(None);
671 };
672 self.exact_newton_joint_psi_terms_from_designs(
673 block_states,
674 specs,
675 derivative_blocks,
676 psi_index,
677 &x_t,
678 &x_ls,
679 )
680 }
681
682 pub(crate) fn exact_newton_joint_psisecond_order_terms_for_specs(
683 &self,
684 block_states: &[ParameterBlockState],
685 specs: &[ParameterBlockSpec],
686 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
687 psi_i: usize,
688 psi_j: usize,
689 ) -> Result<Option<gam_problem::ExactNewtonJointPsiSecondOrderTerms>, String> {
690 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
691 return Ok(None);
692 };
693 self.exact_newton_joint_psisecond_order_terms_from_designs(
694 block_states,
695 derivative_blocks,
696 psi_i,
697 psi_j,
698 &x_t,
699 &x_ls,
700 )
701 }
702
703 pub(crate) fn exact_newton_joint_hessian_row_coefficients(
706 &self,
707 block_states: &[ParameterBlockState],
708 ) -> Result<(Array1<f64>, Array1<f64>, Array1<f64>), String> {
709 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
710 let n = self.y.len();
711 let eta_t = &block_states[Self::BLOCK_T].eta;
712 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
713 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
714 return Err(GamlssError::DimensionMismatch {
715 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
716 }
717 .into());
718 }
719
720 let core = binomial_location_scale_core(
721 &self.y,
722 &self.weights,
723 eta_t,
724 eta_ls,
725 None,
726 &self.link_kind,
727 )?;
728 let mut coeff_tt = vec![0.0_f64; n];
729 let mut coeff_tl = vec![0.0_f64; n];
730 let mut coeff_ll = vec![0.0_f64; n];
731 let y_slice = self.y.as_slice().expect("y must be contiguous");
732 let w_slice = self.weights.as_slice().expect("weights must be contiguous");
733 let q0_slice = core.q0.as_slice().expect("q0 must be contiguous");
734 let sigma_slice = core.sigma.as_slice().expect("sigma must be contiguous");
735 let dsigma_slice = core
736 .dsigma_deta
737 .as_slice()
738 .expect("dsigma_deta must be contiguous");
739 let mu_slice = core.mu.as_slice().expect("mu must be contiguous");
740 let dmu_slice = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
741 let d2mu_slice = core
742 .d2mu_dq2
743 .as_slice()
744 .expect("d2mu_dq2 must be contiguous");
745 let d3mu_slice = core
746 .d3mu_dq3
747 .as_slice()
748 .expect("d3mu_dq3 must be contiguous");
749 let link_kind = &self.link_kind;
750 coeff_tt
751 .par_iter_mut()
752 .zip(coeff_tl.par_iter_mut())
753 .zip(coeff_ll.par_iter_mut())
754 .enumerate()
755 .for_each(|(i, ((c_tt, c_tl), c_ll))| {
756 let q = q0_slice[i];
757 let r = 1.0 / sigma_slice[i];
758 let kappa = dsigma_slice[i] / sigma_slice[i];
759 let (m1, m2, _) = binomial_neglog_q_derivatives_dispatch(
760 y_slice[i],
761 w_slice[i],
762 q,
763 mu_slice[i],
764 dmu_slice[i],
765 d2mu_slice[i],
766 d3mu_slice[i],
767 link_kind,
768 );
769 *c_tt = m2 * r * r;
770 *c_tl = kappa * r * (m1 + q * m2);
771 *c_ll = kappa * kappa * q * (m1 + q * m2);
772 });
773 Ok((
774 Array1::from_vec(coeff_tt),
775 Array1::from_vec(coeff_tl),
776 Array1::from_vec(coeff_ll),
777 ))
778 }
779
780 pub(crate) fn exact_newton_block_diagonal_hessians_from_design_matrices(
784 &self,
785 block_states: &[ParameterBlockState],
786 x_t: &DesignMatrix,
787 x_ls: &DesignMatrix,
788 ) -> Result<(Array2<f64>, Array2<f64>), String> {
789 let (coeff_tt, _coeff_tl, coeff_ll) =
790 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
791 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
792 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
793 Ok((h_tt, h_ll))
794 }
795
796 pub(crate) fn exact_newton_joint_hessian_from_designs(
797 &self,
798 block_states: &[ParameterBlockState],
799 x_t: &Array2<f64>,
800 x_ls: &Array2<f64>,
801 ) -> Result<Option<Array2<f64>>, String> {
802 let (coeff_tt, coeff_tl, coeff_ll) =
859 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
860 let pt = x_t.ncols();
861 let pls = x_ls.ncols();
862
863 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
864 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
865 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
866 let total = pt + pls;
867 let mut h = Array2::<f64>::zeros((total, total));
868 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
869 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
870 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
871 mirror_upper_to_lower(&mut h);
872 Ok(Some(h))
873 }
874
875 pub(crate) fn exact_newton_joint_hessian_from_design_matrices(
876 &self,
877 block_states: &[ParameterBlockState],
878 x_t: &DesignMatrix,
879 x_ls: &DesignMatrix,
880 ) -> Result<Option<Array2<f64>>, String> {
881 if let (Some(x_t_dense), Some(x_ls_dense)) = (x_t.as_dense_ref(), x_ls.as_dense_ref()) {
882 return self.exact_newton_joint_hessian_from_designs(
883 block_states,
884 x_t_dense,
885 x_ls_dense,
886 );
887 }
888 let (coeff_tt, coeff_tl, coeff_ll) =
889 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
890 let pt = x_t.ncols();
891 let pls = x_ls.ncols();
892
893 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
894 let h_tl = xt_diag_y_design(x_t, &coeff_tl, x_ls)?;
895 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
896 let total = pt + pls;
897 let mut h = Array2::<f64>::zeros((total, total));
898 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
899 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
900 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
901 mirror_upper_to_lower(&mut h);
902 Ok(Some(h))
903 }
904
905 pub(crate) fn exact_newton_joint_hessian_directional_derivative_from_designs(
906 &self,
907 block_states: &[ParameterBlockState],
908 x_t: &Array2<f64>,
909 x_ls: &Array2<f64>,
910 d_beta_flat: &Array1<f64>,
911 ) -> Result<Option<Array2<f64>>, String> {
912 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
960 let n = self.y.len();
961 let eta_t = &block_states[Self::BLOCK_T].eta;
962 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
963 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
964 return Err(GamlssError::DimensionMismatch {
965 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
966 }
967 .into());
968 }
969
970 let pt = x_t.ncols();
971 let pls = x_ls.ncols();
972 if d_beta_flat.len() != pt + pls {
973 return Err(GamlssError::DimensionMismatch {
974 reason: format!(
975 "BinomialLocationScaleFamily joint d_beta length mismatch: got {}, expected {}",
976 d_beta_flat.len(),
977 pt + pls
978 ),
979 }
980 .into());
981 }
982 let d_eta_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
983 let d_eta_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
984 let core = binomial_location_scale_core(
985 &self.y,
986 &self.weights,
987 eta_t,
988 eta_ls,
989 None,
990 &self.link_kind,
991 )?;
992 let (coeff_tt, coeff_tl, coeff_ll) =
993 binomial_location_scale_first_directional_coefficients(
994 &self.y,
995 &self.weights,
996 &core,
997 &d_eta_t,
998 &d_eta_ls,
999 &self.link_kind,
1000 )?;
1001
1002 let d_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1003 let d_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1004 let d_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1005 let total = pt + pls;
1006 let mut d_h = Array2::<f64>::zeros((total, total));
1007 d_h.slice_mut(s![0..pt, 0..pt]).assign(&d_h_tt);
1008 d_h.slice_mut(s![0..pt, pt..total]).assign(&d_h_tl);
1009 d_h.slice_mut(s![pt..total, pt..total]).assign(&d_h_ll);
1010 mirror_upper_to_lower(&mut d_h);
1011 Ok(Some(d_h))
1012 }
1013
1014 pub(crate) fn exact_newton_joint_hessiansecond_directional_derivative_from_designs(
1015 &self,
1016 block_states: &[ParameterBlockState],
1017 x_t: &Array2<f64>,
1018 x_ls: &Array2<f64>,
1019 d_beta_u_flat: &Array1<f64>,
1020 d_betav_flat: &Array1<f64>,
1021 ) -> Result<Option<Array2<f64>>, String> {
1022 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1067 let n = self.y.len();
1068 let eta_t = &block_states[Self::BLOCK_T].eta;
1069 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1070 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1071 return Err(GamlssError::DimensionMismatch {
1072 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1073 }
1074 .into());
1075 }
1076
1077 let pt = x_t.ncols();
1078 let pls = x_ls.ncols();
1079 let total = pt + pls;
1080 if d_beta_u_flat.len() != total {
1081 return Err(GamlssError::DimensionMismatch { reason: format!(
1082 "BinomialLocationScaleFamily joint d_beta_u length mismatch: got {}, expected {}",
1083 d_beta_u_flat.len(),
1084 total
1085 ) }.into());
1086 }
1087 if d_betav_flat.len() != total {
1088 return Err(GamlssError::DimensionMismatch { reason: format!(
1089 "BinomialLocationScaleFamily joint d_betav length mismatch: got {}, expected {}",
1090 d_betav_flat.len(),
1091 total
1092 ) }.into());
1093 }
1094 let d_eta_t_u = fast_av(x_t, &d_beta_u_flat.slice(s![0..pt]));
1095 let d_eta_ls_u = fast_av(x_ls, &d_beta_u_flat.slice(s![pt..total]));
1096 let d_eta_tv = fast_av(x_t, &d_betav_flat.slice(s![0..pt]));
1097 let d_eta_lsv = fast_av(x_ls, &d_betav_flat.slice(s![pt..total]));
1098 let core = binomial_location_scale_core(
1099 &self.y,
1100 &self.weights,
1101 eta_t,
1102 eta_ls,
1103 None,
1104 &self.link_kind,
1105 )?;
1106 let (coeff_tt, coeff_tl, coeff_ll) =
1107 binomial_location_scalesecond_directional_coefficients(
1108 &self.y,
1109 &self.weights,
1110 &core,
1111 &d_eta_t_u,
1112 &d_eta_ls_u,
1113 &d_eta_tv,
1114 &d_eta_lsv,
1115 &self.link_kind,
1116 )?;
1117
1118 let d2_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1119 let d2_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1120 let d2_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1121 let mut d2_h = Array2::<f64>::zeros((total, total));
1122 d2_h.slice_mut(s![0..pt, 0..pt]).assign(&d2_h_tt);
1123 d2_h.slice_mut(s![0..pt, pt..total]).assign(&d2_h_tl);
1124 d2_h.slice_mut(s![pt..total, pt..total]).assign(&d2_h_ll);
1125 mirror_upper_to_lower(&mut d2_h);
1126 Ok(Some(d2_h))
1127 }
1128
1129 pub(crate) fn exact_newton_joint_psi_terms_from_designs(
1130 &self,
1131 block_states: &[ParameterBlockState],
1132 specs: &[ParameterBlockSpec],
1133 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1134 psi_index: usize,
1135 x_t: &Array2<f64>,
1136 x_ls: &Array2<f64>,
1137 ) -> Result<Option<gam_problem::ExactNewtonJointPsiTerms>, String> {
1138 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1139 if specs.len() != 2 || derivative_blocks.len() != 2 {
1140 return Err(GamlssError::DimensionMismatch { reason: format!(
1141 "BinomialLocationScaleFamily joint psi terms expect 2 specs and 2 derivative blocks, got {} and {}",
1142 specs.len(),
1143 derivative_blocks.len()
1144 ) }.into());
1145 }
1146 let n = self.y.len();
1147 let eta_t = &block_states[Self::BLOCK_T].eta;
1148 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1149 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1150 return Err(GamlssError::DimensionMismatch {
1151 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1152 }
1153 .into());
1154 }
1155
1156 let core = binomial_location_scale_core(
1252 &self.y,
1253 &self.weights,
1254 eta_t,
1255 eta_ls,
1256 None,
1257 &self.link_kind,
1258 )?;
1259 let pt = x_t.ncols();
1260 let pls = x_ls.ncols();
1261 let total = pt + pls;
1262 let Some(dir_a) = self.exact_newton_joint_psi_direction(
1263 block_states,
1264 derivative_blocks,
1265 psi_index,
1266 x_t,
1267 x_ls,
1268 &self.policy,
1269 )?
1270 else {
1271 return Ok(None);
1272 };
1273 let (z_t, z_ls) = (&dir_a.z_primary_psi, &dir_a.z_ls_psi);
1274
1275 struct PsiTermsRow {
1280 pub(crate) r_t: f64,
1281 pub(crate) r_ls: f64,
1282 pub(crate) dr_t: f64,
1283 pub(crate) dr_ls: f64,
1284 pub(crate) h_tt: f64,
1285 pub(crate) h_tl: f64,
1286 pub(crate) h_ll: f64,
1287 pub(crate) dh_tt: f64,
1288 pub(crate) dh_tl: f64,
1289 pub(crate) dh_ll: f64,
1290 pub(crate) obj: f64,
1291 }
1292 let y_p = self.y.as_slice().expect("y must be contiguous");
1293 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1294 let q0_p = core.q0.as_slice().expect("q0 must be contiguous");
1295 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1296 let dsigma_p = core
1297 .dsigma_deta
1298 .as_slice()
1299 .expect("dsigma_deta must be contiguous");
1300 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1301 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1302 let d2mu_p = core
1303 .d2mu_dq2
1304 .as_slice()
1305 .expect("d2mu_dq2 must be contiguous");
1306 let d3mu_p = core
1307 .d3mu_dq3
1308 .as_slice()
1309 .expect("d3mu_dq3 must be contiguous");
1310 let z_t_p = z_t.as_slice().expect("z_t must be contiguous");
1311 let z_ls_p = z_ls.as_slice().expect("z_ls must be contiguous");
1312 let link_kind_p = &self.link_kind;
1313 let rows: Vec<PsiTermsRow> = (0..n)
1314 .into_par_iter()
1315 .map(|i| {
1316 let q = q0_p[i];
1317 let r = 1.0 / sigma_p[i];
1318 let s = dsigma_p[i] / sigma_p[i];
1319 let sz = s * z_ls_p[i];
1320 let q_psi = -r * z_t_p[i] - q * sz;
1321 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1322 y_p[i],
1323 w_p[i],
1324 q,
1325 mu_p[i],
1326 dmu_p[i],
1327 d2mu_p[i],
1328 d3mu_p[i],
1329 link_kind_p,
1330 );
1331 let r_t = -a * r;
1332 let r_ls = -a * q * s;
1333 PsiTermsRow {
1334 r_t,
1335 r_ls,
1336 dr_t: -b * q_psi * r + a * r * sz,
1337 dr_ls: -(a + q * b) * q_psi,
1338 h_tt: b * r * r,
1339 h_tl: r * (a + q * b),
1340 h_ll: q * (a + q * b),
1341 dh_tt: r * r * (c * q_psi - 2.0 * b * sz),
1342 dh_tl: r * ((2.0 * b + c * q) * q_psi - (a + q * b) * sz),
1343 dh_ll: (a + 3.0 * q * b + q * q * c) * q_psi,
1344 obj: r_t * z_t_p[i] + r_ls * z_ls_p[i],
1345 }
1346 })
1347 .collect();
1348 let mut r_t = Array1::<f64>::zeros(n);
1349 let mut r_ls = Array1::<f64>::zeros(n);
1350 let mut dr_t = Array1::<f64>::zeros(n);
1351 let mut dr_ls = Array1::<f64>::zeros(n);
1352 let mut h_tt = Array1::<f64>::zeros(n);
1353 let mut h_tl = Array1::<f64>::zeros(n);
1354 let mut h_ll = Array1::<f64>::zeros(n);
1355 let mut dh_tt = Array1::<f64>::zeros(n);
1356 let mut dh_tl = Array1::<f64>::zeros(n);
1357 let mut dh_ll = Array1::<f64>::zeros(n);
1358 let mut objective_psi = 0.0_f64;
1359 for (i, row) in rows.into_iter().enumerate() {
1360 r_t[i] = row.r_t;
1361 r_ls[i] = row.r_ls;
1362 dr_t[i] = row.dr_t;
1363 dr_ls[i] = row.dr_ls;
1364 h_tt[i] = row.h_tt;
1365 h_tl[i] = row.h_tl;
1366 h_ll[i] = row.h_ll;
1367 dh_tt[i] = row.dh_tt;
1368 dh_tl[i] = row.dh_tl;
1369 dh_ll[i] = row.dh_ll;
1370 objective_psi += row.obj;
1371 }
1372
1373 let hessian_psi_operator = build_two_block_custom_family_joint_psi_operator_from_actions(
1374 dir_a.x_primary_psi.cloned_first_action(),
1375 dir_a.x_ls_psi.cloned_first_action(),
1376 0..pt,
1377 pt..pt + pls,
1378 x_t,
1379 x_ls,
1380 &h_tt,
1381 &h_tl,
1382 &h_ll,
1383 &dh_tt,
1384 &dh_tl,
1385 &dh_ll,
1386 )?;
1387 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
1388 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
1389 let score_t = x_t_map.transpose_mul(r_t.view()) + fast_atv(x_t, &dr_t);
1390 let score_ls = x_ls_map.transpose_mul(r_ls.view()) + fast_atv(x_ls, &dr_ls);
1391 let mut score_psi = Array1::<f64>::zeros(total);
1392 score_psi.slice_mut(s![0..pt]).assign(&score_t);
1393 score_psi.slice_mut(s![pt..pt + pls]).assign(&score_ls);
1394 let hessian_psi = if hessian_psi_operator.is_some() {
1395 Array2::zeros((0, 0))
1396 } else {
1397 let h_tt_block = weighted_crossprod_psi_maps(
1398 x_t_map,
1399 h_tt.view(),
1400 CustomFamilyPsiLinearMapRef::Dense(x_t),
1401 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
1402 CustomFamilyPsiLinearMapRef::Dense(x_t),
1403 h_tt.view(),
1404 x_t_map,
1405 ).map_err(|error| error.to_string())? + &xt_diag_x_dense(x_t, &dh_tt)?;
1406 let h_tl_block = weighted_crossprod_psi_maps(
1407 x_t_map,
1408 h_tl.view(),
1409 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1410 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
1411 CustomFamilyPsiLinearMapRef::Dense(x_t),
1412 h_tl.view(),
1413 x_ls_map,
1414 ).map_err(|error| error.to_string())? + &xt_diag_y_dense(x_t, &dh_tl, x_ls)?;
1415 let h_ll_block = weighted_crossprod_psi_maps(
1416 x_ls_map,
1417 h_ll.view(),
1418 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1419 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
1420 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1421 h_ll.view(),
1422 x_ls_map,
1423 ).map_err(|error| error.to_string())? + &xt_diag_x_dense(x_ls, &dh_ll)?;
1424
1425 let mut hessian_psi = Array2::<f64>::zeros((total, total));
1426 hessian_psi.slice_mut(s![0..pt, 0..pt]).assign(&h_tt_block);
1427 hessian_psi
1428 .slice_mut(s![0..pt, pt..pt + pls])
1429 .assign(&h_tl_block);
1430 hessian_psi
1431 .slice_mut(s![pt..pt + pls, pt..pt + pls])
1432 .assign(&h_ll_block);
1433 mirror_upper_to_lower(&mut hessian_psi);
1434 hessian_psi
1435 };
1436
1437 Ok(Some(gam_problem::ExactNewtonJointPsiTerms {
1438 objective_psi,
1439 score_psi,
1440 hessian_psi,
1441 hessian_psi_operator,
1442 }))
1443 }
1444
1445 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_designs(
1446 &self,
1447 block_states: &[ParameterBlockState],
1448 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1449 psi_i: usize,
1450 psi_j: usize,
1451 x_t: &Array2<f64>,
1452 x_ls: &Array2<f64>,
1453 ) -> Result<Option<gam_problem::ExactNewtonJointPsiSecondOrderTerms>, String> {
1454 let Some(dir_i) = self.exact_newton_joint_psi_direction(
1455 block_states,
1456 derivative_blocks,
1457 psi_i,
1458 x_t,
1459 x_ls,
1460 &self.policy,
1461 )?
1462 else {
1463 return Ok(None);
1464 };
1465 let Some(dir_j) = self.exact_newton_joint_psi_direction(
1466 block_states,
1467 derivative_blocks,
1468 psi_j,
1469 x_t,
1470 x_ls,
1471 &self.policy,
1472 )?
1473 else {
1474 return Ok(None);
1475 };
1476 Ok(Some(
1477 self.exact_newton_joint_psisecond_order_terms_from_parts(
1478 block_states,
1479 derivative_blocks,
1480 &dir_i,
1481 &dir_j,
1482 x_t,
1483 x_ls,
1484 )?,
1485 ))
1486 }
1487
1488 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_parts(
1489 &self,
1490 block_states: &[ParameterBlockState],
1491 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1492 dir_i: &LocationScaleJointPsiDirection,
1493 dir_j: &LocationScaleJointPsiDirection,
1494 x_t: &Array2<f64>,
1495 x_ls: &Array2<f64>,
1496 ) -> Result<gam_problem::ExactNewtonJointPsiSecondOrderTerms, String> {
1497 let second_drifts = self.exact_newton_joint_psisecond_design_drifts(
1498 block_states,
1499 derivative_blocks,
1500 dir_i,
1501 dir_j,
1502 x_t,
1503 x_ls,
1504 )?;
1505 let n = self.y.len();
1506 let eta_t = &block_states[Self::BLOCK_T].eta;
1507 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1508 let core = binomial_location_scale_core(
1509 &self.y,
1510 &self.weights,
1511 eta_t,
1512 eta_ls,
1513 None,
1514 &self.link_kind,
1515 )?;
1516 let pt = x_t.ncols();
1517 let pls = x_ls.ncols();
1518 let total = pt + pls;
1519 let x_t_i_map = dir_i.x_primary_psi.as_linear_map_ref();
1520 let x_t_j_map = dir_j.x_primary_psi.as_linear_map_ref();
1521 let x_ls_i_map = dir_i.x_ls_psi.as_linear_map_ref();
1522 let x_ls_j_map = dir_j.x_ls_psi.as_linear_map_ref();
1523 let x_t_ab_map = second_psi_linear_map(
1524 second_drifts.x_primary_ab_action.as_ref(),
1525 second_drifts.x_primary_ab.as_ref(),
1526 n,
1527 pt,
1528 );
1529 let x_ls_ab_map = second_psi_linear_map(
1530 second_drifts.x_ls_ab_action.as_ref(),
1531 second_drifts.x_ls_ab.as_ref(),
1532 n,
1533 pls,
1534 );
1535
1536 let mut r_t = Array1::<f64>::zeros(n);
1650 let mut r_ls = Array1::<f64>::zeros(n);
1651 let mut dr_t_i = Array1::<f64>::zeros(n);
1652 let mut dr_t_j = Array1::<f64>::zeros(n);
1653 let mut dr_ls_i = Array1::<f64>::zeros(n);
1654 let mut dr_ls_j = Array1::<f64>::zeros(n);
1655 let mut d2r_t = Array1::<f64>::zeros(n);
1656 let mut d2r_ls = Array1::<f64>::zeros(n);
1657 let mut h_tt = Array1::<f64>::zeros(n);
1658 let mut h_tl = Array1::<f64>::zeros(n);
1659 let mut h_ll = Array1::<f64>::zeros(n);
1660 let mut dh_tt_i = Array1::<f64>::zeros(n);
1661 let mut dh_tt_j = Array1::<f64>::zeros(n);
1662 let mut dh_tl_i = Array1::<f64>::zeros(n);
1663 let mut dh_tl_j = Array1::<f64>::zeros(n);
1664 let mut dh_ll_i = Array1::<f64>::zeros(n);
1665 let mut dh_ll_j = Array1::<f64>::zeros(n);
1666 let mut d2h_tt = Array1::<f64>::zeros(n);
1667 let mut d2h_tl = Array1::<f64>::zeros(n);
1668 let mut d2h_ll = Array1::<f64>::zeros(n);
1669 let mut objective_psi_psi = 0.0;
1670 struct PsiSecondRow {
1671 pub(crate) r_t: f64,
1672 pub(crate) r_ls: f64,
1673 pub(crate) dr_t_i: f64,
1674 pub(crate) dr_t_j: f64,
1675 pub(crate) dr_ls_i: f64,
1676 pub(crate) dr_ls_j: f64,
1677 pub(crate) d2r_t: f64,
1678 pub(crate) d2r_ls: f64,
1679 pub(crate) h_tt: f64,
1680 pub(crate) h_tl: f64,
1681 pub(crate) h_ll: f64,
1682 pub(crate) dh_tt_i: f64,
1683 pub(crate) dh_tt_j: f64,
1684 pub(crate) dh_tl_i: f64,
1685 pub(crate) dh_tl_j: f64,
1686 pub(crate) dh_ll_i: f64,
1687 pub(crate) dh_ll_j: f64,
1688 pub(crate) d2h_tt: f64,
1689 pub(crate) d2h_tl: f64,
1690 pub(crate) d2h_ll: f64,
1691 pub(crate) objective: f64,
1692 }
1693 let y_p = self.y.as_slice().expect("y must be contiguous");
1694 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1695 let q_p = core.q0.as_slice().expect("q0 must be contiguous");
1696 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1697 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1698 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1699 let d2mu_p = core
1700 .d2mu_dq2
1701 .as_slice()
1702 .expect("d2mu_dq2 must be contiguous");
1703 let d3mu_p = core
1704 .d3mu_dq3
1705 .as_slice()
1706 .expect("d3mu_dq3 must be contiguous");
1707 let z_t_i = dir_i
1708 .z_primary_psi
1709 .as_slice()
1710 .expect("z_t_psi_i must be contiguous");
1711 let z_t_j = dir_j
1712 .z_primary_psi
1713 .as_slice()
1714 .expect("z_t_psi_j must be contiguous");
1715 let z_ls_i = dir_i
1716 .z_ls_psi
1717 .as_slice()
1718 .expect("z_ls_psi_i must be contiguous");
1719 let z_ls_j = dir_j
1720 .z_ls_psi
1721 .as_slice()
1722 .expect("z_ls_psi_j must be contiguous");
1723 let z_t_ab = second_drifts
1724 .z_primary_ab
1725 .as_slice()
1726 .expect("z_t_ab must be contiguous");
1727 let z_ls_ab = second_drifts
1728 .z_ls_ab
1729 .as_slice()
1730 .expect("z_ls_ab must be contiguous");
1731 let link_kind_p = &self.link_kind;
1732 let rows: Result<Vec<PsiSecondRow>, String> = (0..n)
1733 .into_par_iter()
1734 .map(|row| {
1735 let q = q_p[row];
1736 let r = 1.0 / sigma_p[row];
1737 let q_i = -r * z_t_i[row] - q * z_ls_i[row];
1738 let q_j = -r * z_t_j[row] - q * z_ls_j[row];
1739 let q_ij = -r * z_t_ab[row]
1740 + r * (z_t_i[row] * z_ls_j[row] + z_t_j[row] * z_ls_i[row])
1741 + q * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row]);
1742 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1743 y_p[row],
1744 w_p[row],
1745 q,
1746 mu_p[row],
1747 dmu_p[row],
1748 d2mu_p[row],
1749 d3mu_p[row],
1750 link_kind_p,
1751 );
1752 let d = binomial_neglog_q_fourth_derivative_dispatch(
1753 y_p[row],
1754 w_p[row],
1755 q,
1756 mu_p[row],
1757 dmu_p[row],
1758 d2mu_p[row],
1759 d3mu_p[row],
1760 link_kind_p,
1761 )?;
1762 let u = a + q * b;
1763 let u_i = (2.0 * b + q * c) * q_i;
1764 let u_j = (2.0 * b + q * c) * q_j;
1765 Ok(PsiSecondRow {
1766 r_t: -a * r,
1767 r_ls: -a * q,
1768 dr_t_i: -b * q_i * r + a * r * z_ls_i[row],
1769 dr_t_j: -b * q_j * r + a * r * z_ls_j[row],
1770 dr_ls_i: -u * q_i,
1771 dr_ls_j: -u * q_j,
1772 d2r_t: r
1773 * (-c * q_i * q_j - b * q_ij + b * (q_i * z_ls_j[row] + q_j * z_ls_i[row])
1774 - a * z_ls_i[row] * z_ls_j[row]
1775 + a * z_ls_ab[row]),
1776 d2r_ls: -((2.0 * b + q * c) * q_i * q_j + u * q_ij),
1777 h_tt: b * r * r,
1778 h_tl: r * u,
1779 h_ll: q * u,
1780 dh_tt_i: r * r * (c * q_i - 2.0 * b * z_ls_i[row]),
1781 dh_tt_j: r * r * (c * q_j - 2.0 * b * z_ls_j[row]),
1782 dh_tl_i: r * (u_i - u * z_ls_i[row]),
1783 dh_tl_j: r * (u_j - u * z_ls_j[row]),
1784 dh_ll_i: (a + 3.0 * q * b + q * q * c) * q_i,
1785 dh_ll_j: (a + 3.0 * q * b + q * q * c) * q_j,
1786 d2h_tt: r
1787 * r
1788 * (d * q_i * q_j + c * q_ij
1789 - 2.0 * c * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1790 + 4.0 * b * z_ls_i[row] * z_ls_j[row]
1791 - 2.0 * b * z_ls_ab[row]),
1792 d2h_tl: r
1793 * (((3.0 * c + q * d) * q_j) * q_i + (2.0 * b + q * c) * q_ij
1794 - (2.0 * b + q * c) * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1795 + u * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row])),
1796 d2h_ll: (4.0 * b + 5.0 * q * c + q * q * d) * q_i * q_j
1797 + (a + 3.0 * q * b + q * q * c) * q_ij,
1798 objective: a * q_ij + b * q_i * q_j,
1799 })
1800 })
1801 .collect();
1802 for (row, vals) in rows?.into_iter().enumerate() {
1803 r_t[row] = vals.r_t;
1804 r_ls[row] = vals.r_ls;
1805 dr_t_i[row] = vals.dr_t_i;
1806 dr_t_j[row] = vals.dr_t_j;
1807 dr_ls_i[row] = vals.dr_ls_i;
1808 dr_ls_j[row] = vals.dr_ls_j;
1809 d2r_t[row] = vals.d2r_t;
1810 d2r_ls[row] = vals.d2r_ls;
1811 h_tt[row] = vals.h_tt;
1812 h_tl[row] = vals.h_tl;
1813 h_ll[row] = vals.h_ll;
1814 dh_tt_i[row] = vals.dh_tt_i;
1815 dh_tt_j[row] = vals.dh_tt_j;
1816 dh_tl_i[row] = vals.dh_tl_i;
1817 dh_tl_j[row] = vals.dh_tl_j;
1818 dh_ll_i[row] = vals.dh_ll_i;
1819 dh_ll_j[row] = vals.dh_ll_j;
1820 d2h_tt[row] = vals.d2h_tt;
1821 d2h_tl[row] = vals.d2h_tl;
1822 d2h_ll[row] = vals.d2h_ll;
1823 objective_psi_psi += vals.objective;
1824 }
1825 let mut score_psi_psi = Array1::<f64>::zeros(total);
1826 score_psi_psi.slice_mut(s![0..pt]).assign(
1827 &(x_t_ab_map.transpose_mul(r_t.view())
1828 + x_t_i_map.transpose_mul(dr_t_j.view())
1829 + x_t_j_map.transpose_mul(dr_t_i.view())
1830 + fast_atv(x_t, &d2r_t)),
1831 );
1832 score_psi_psi.slice_mut(s![pt..pt + pls]).assign(
1833 &(x_ls_ab_map.transpose_mul(r_ls.view())
1834 + x_ls_i_map.transpose_mul(dr_ls_j.view())
1835 + x_ls_j_map.transpose_mul(dr_ls_i.view())
1836 + fast_atv(x_ls, &d2r_ls)),
1837 );
1838
1839 let h_tt_block = weighted_crossprod_psi_maps(
1840 x_t_ab_map,
1841 h_tt.view(),
1842 CustomFamilyPsiLinearMapRef::Dense(x_t),
1843 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(x_t_i_map, h_tt.view(), x_t_j_map).map_err(|error| error.to_string())?
1844 + &weighted_crossprod_psi_maps(x_t_j_map, h_tt.view(), x_t_i_map).map_err(|error| error.to_string())?
1845 + &weighted_crossprod_psi_maps(
1846 x_t_i_map,
1847 dh_tt_j.view(),
1848 CustomFamilyPsiLinearMapRef::Dense(x_t),
1849 ).map_err(|error| error.to_string())?
1850 + &weighted_crossprod_psi_maps(
1851 x_t_j_map,
1852 dh_tt_i.view(),
1853 CustomFamilyPsiLinearMapRef::Dense(x_t),
1854 ).map_err(|error| error.to_string())?
1855 + &weighted_crossprod_psi_maps(
1856 CustomFamilyPsiLinearMapRef::Dense(x_t),
1857 dh_tt_i.view(),
1858 x_t_j_map,
1859 ).map_err(|error| error.to_string())?
1860 + &weighted_crossprod_psi_maps(
1861 CustomFamilyPsiLinearMapRef::Dense(x_t),
1862 dh_tt_j.view(),
1863 x_t_i_map,
1864 ).map_err(|error| error.to_string())?
1865 + &xt_diag_x_dense(x_t, &d2h_tt)?
1866 + &weighted_crossprod_psi_maps(
1867 CustomFamilyPsiLinearMapRef::Dense(x_t),
1868 h_tt.view(),
1869 x_t_ab_map,
1870 ).map_err(|error| error.to_string())?;
1871 let h_tl_block = weighted_crossprod_psi_maps(
1872 x_t_ab_map,
1873 h_tl.view(),
1874 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1875 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(x_t_i_map, h_tl.view(), x_ls_j_map).map_err(|error| error.to_string())?
1876 + &weighted_crossprod_psi_maps(x_t_j_map, h_tl.view(), x_ls_i_map).map_err(|error| error.to_string())?
1877 + &weighted_crossprod_psi_maps(
1878 x_t_i_map,
1879 dh_tl_j.view(),
1880 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1881 ).map_err(|error| error.to_string())?
1882 + &weighted_crossprod_psi_maps(
1883 x_t_j_map,
1884 dh_tl_i.view(),
1885 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1886 ).map_err(|error| error.to_string())?
1887 + &weighted_crossprod_psi_maps(
1888 CustomFamilyPsiLinearMapRef::Dense(x_t),
1889 dh_tl_i.view(),
1890 x_ls_j_map,
1891 ).map_err(|error| error.to_string())?
1892 + &weighted_crossprod_psi_maps(
1893 CustomFamilyPsiLinearMapRef::Dense(x_t),
1894 dh_tl_j.view(),
1895 x_ls_i_map,
1896 ).map_err(|error| error.to_string())?
1897 + &xt_diag_y_dense(x_t, &d2h_tl, x_ls)?
1898 + &weighted_crossprod_psi_maps(
1899 CustomFamilyPsiLinearMapRef::Dense(x_t),
1900 h_tl.view(),
1901 x_ls_ab_map,
1902 ).map_err(|error| error.to_string())?;
1903 let h_ll_block = weighted_crossprod_psi_maps(
1904 x_ls_ab_map,
1905 h_ll.view(),
1906 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1907 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(x_ls_i_map, h_ll.view(), x_ls_j_map).map_err(|error| error.to_string())?
1908 + &weighted_crossprod_psi_maps(x_ls_j_map, h_ll.view(), x_ls_i_map).map_err(|error| error.to_string())?
1909 + &weighted_crossprod_psi_maps(
1910 x_ls_i_map,
1911 dh_ll_j.view(),
1912 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1913 ).map_err(|error| error.to_string())?
1914 + &weighted_crossprod_psi_maps(
1915 x_ls_j_map,
1916 dh_ll_i.view(),
1917 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1918 ).map_err(|error| error.to_string())?
1919 + &weighted_crossprod_psi_maps(
1920 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1921 dh_ll_i.view(),
1922 x_ls_j_map,
1923 ).map_err(|error| error.to_string())?
1924 + &weighted_crossprod_psi_maps(
1925 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1926 dh_ll_j.view(),
1927 x_ls_i_map,
1928 ).map_err(|error| error.to_string())?
1929 + &xt_diag_x_dense(x_ls, &d2h_ll)?
1930 + &weighted_crossprod_psi_maps(
1931 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1932 h_ll.view(),
1933 x_ls_ab_map,
1934 ).map_err(|error| error.to_string())?;
1935
1936 let mut hessian_psi_psi = Array2::<f64>::zeros((total, total));
1937 hessian_psi_psi
1938 .slice_mut(s![0..pt, 0..pt])
1939 .assign(&h_tt_block);
1940 hessian_psi_psi
1941 .slice_mut(s![0..pt, pt..pt + pls])
1942 .assign(&h_tl_block);
1943 hessian_psi_psi
1944 .slice_mut(s![pt..pt + pls, pt..pt + pls])
1945 .assign(&h_ll_block);
1946 mirror_upper_to_lower(&mut hessian_psi_psi);
1947
1948 Ok(gam_problem::ExactNewtonJointPsiSecondOrderTerms {
1949 objective_psi_psi,
1950 score_psi_psi,
1951 hessian_psi_psi,
1952 hessian_psi_psi_operator: None,
1953 })
1954 }
1955
1956 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_parts(
1957 &self,
1958 block_states: &[ParameterBlockState],
1959 dir_a: &LocationScaleJointPsiDirection,
1960 d_beta_flat: &Array1<f64>,
1961 x_t: &Array2<f64>,
1962 x_ls: &Array2<f64>,
1963 ) -> Result<Array2<f64>, String> {
1964 let n = self.y.len();
1965 let eta_t = &block_states[Self::BLOCK_T].eta;
1966 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1967 let core = binomial_location_scale_core(
1968 &self.y,
1969 &self.weights,
1970 eta_t,
1971 eta_ls,
1972 None,
1973 &self.link_kind,
1974 )?;
1975 let pt = x_t.ncols();
1976 let pls = x_ls.ncols();
1977 let total = pt + pls;
1978 if d_beta_flat.len() != total {
1979 return Err(GamlssError::DimensionMismatch { reason: format!(
1980 "BinomialLocationScaleFamily joint psi hessian directional derivative length mismatch: got {}, expected {}",
1981 d_beta_flat.len(),
1982 total
1983 ) }.into());
1984 }
1985 let xi_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
1986 let xi_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
1987 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
1988 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
1989
1990 let mut dh_tt_u = Array1::<f64>::zeros(n);
2041 let mut dh_tl_u = Array1::<f64>::zeros(n);
2042 let mut dh_ll_u = Array1::<f64>::zeros(n);
2043 let mut h_tt_u = Array1::<f64>::zeros(n);
2044 let mut h_tl_u = Array1::<f64>::zeros(n);
2045 let mut h_ll_u = Array1::<f64>::zeros(n);
2046 for row in 0..n {
2047 let q = core.q0[row];
2048 let r = 1.0 / core.sigma[row];
2049 let s = core.dsigma_deta[row] / core.sigma[row];
2050 let xi_ls_s = s * xi_ls[row];
2051 let z_ls_psi_s = s * dir_a.z_ls_psi[row];
2052 let du = -r * xi_t[row] - q * xi_ls_s;
2053 let q_a = -r * dir_a.z_primary_psi[row] - q * z_ls_psi_s;
2054 let q_au = r * dir_a.z_primary_psi[row] * xi_ls_s - du * z_ls_psi_s;
2055 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
2056 self.y[row],
2057 self.weights[row],
2058 q,
2059 core.mu[row],
2060 core.dmu_dq[row],
2061 core.d2mu_dq2[row],
2062 core.d3mu_dq3[row],
2063 &self.link_kind,
2064 );
2065 let d = binomial_neglog_q_fourth_derivative_dispatch(
2066 self.y[row],
2067 self.weights[row],
2068 q,
2069 core.mu[row],
2070 core.dmu_dq[row],
2071 core.d2mu_dq2[row],
2072 core.d3mu_dq3[row],
2073 &self.link_kind,
2074 )?;
2075 let u = a + q * b;
2076 h_tt_u[row] = r * r * (c * du - 2.0 * b * xi_ls_s);
2077 h_tl_u[row] = r * ((2.0 * b + q * c) * du - u * xi_ls_s);
2078 h_ll_u[row] = (a + 3.0 * q * b + q * q * c) * du;
2079 dh_tt_u[row] = r
2080 * r
2081 * (d * du * q_a + c * q_au - 2.0 * c * (q_a * xi_ls_s + du * z_ls_psi_s)
2082 + 4.0 * b * xi_ls_s * z_ls_psi_s);
2083 dh_tl_u[row] = r
2084 * (((3.0 * c + q * d) * q_a) * du + (2.0 * b + q * c) * q_au
2085 - (2.0 * b + q * c) * (q_a * xi_ls_s + du * z_ls_psi_s)
2086 + u * xi_ls_s * z_ls_psi_s);
2087 dh_ll_u[row] = (4.0 * b + 5.0 * q * c + q * q * d) * du * q_a
2088 + (a + 3.0 * q * b + q * q * c) * q_au;
2089 }
2090
2091 let tt_block = weighted_crossprod_psi_maps(
2092 x_t_map,
2093 h_tt_u.view(),
2094 CustomFamilyPsiLinearMapRef::Dense(x_t),
2095 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
2096 CustomFamilyPsiLinearMapRef::Dense(x_t),
2097 h_tt_u.view(),
2098 x_t_map,
2099 ).map_err(|error| error.to_string())? + &xt_diag_x_dense(x_t, &dh_tt_u)?;
2100 let tl_block = weighted_crossprod_psi_maps(
2101 x_t_map,
2102 h_tl_u.view(),
2103 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2104 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
2105 CustomFamilyPsiLinearMapRef::Dense(x_t),
2106 h_tl_u.view(),
2107 x_ls_map,
2108 ).map_err(|error| error.to_string())? + &xt_diag_y_dense(x_t, &dh_tl_u, x_ls)?;
2109 let ll_block = weighted_crossprod_psi_maps(
2110 x_ls_map,
2111 h_ll_u.view(),
2112 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2113 ).map_err(|error| error.to_string())? + &weighted_crossprod_psi_maps(
2114 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2115 h_ll_u.view(),
2116 x_ls_map,
2117 ).map_err(|error| error.to_string())? + &xt_diag_x_dense(x_ls, &dh_ll_u)?;
2118 let mut out = Array2::<f64>::zeros((total, total));
2119 out.slice_mut(s![0..pt, 0..pt]).assign(&tt_block);
2120 out.slice_mut(s![0..pt, pt..pt + pls]).assign(&tl_block);
2121 out.slice_mut(s![pt..pt + pls, pt..pt + pls])
2122 .assign(&ll_block);
2123 mirror_upper_to_lower(&mut out);
2124 Ok(out)
2125 }
2126
2127 pub fn block_effective_jacobian(
2133 specs: &[ParameterBlockSpec],
2134 block_idx: usize,
2135 ) -> Result<Box<dyn BlockEffectiveJacobian>, String> {
2136 crate::block_layout::block_jacobian::AdditiveWiggleBlockLayout {
2137 family: "BinomialLocationScaleFamily",
2138 n_outputs: 2,
2139 additive_blocks: &[Self::BLOCK_T, Self::BLOCK_LOG_SIGMA],
2140 wiggle_block: None,
2141 }
2142 .block_effective_jacobian(specs, block_idx)
2143 }
2144}
2145
2146impl CustomFamily for BinomialLocationScaleFamily {
2147 fn joint_jeffreys_term_required(&self) -> bool {
2165 false
2166 }
2167
2168 fn exact_newton_joint_hessian_beta_dependent(&self) -> bool {
2172 true
2173 }
2174
2175 fn pseudo_logdet_mode(&self) -> crate::custom_family::PseudoLogdetMode {
2211 crate::custom_family::PseudoLogdetMode::HardPseudo
2212 }
2213
2214 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
2215 crate::location_scale_engine::location_scale_coefficient_hessian_cost(
2219 self.y.len() as u64,
2220 specs,
2221 )
2222 }
2223
2224 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
2225 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2226 let n = self.y.len();
2227 let eta_t = &block_states[Self::BLOCK_T].eta;
2228 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2229 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2230 return Err(GamlssError::DimensionMismatch {
2231 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2232 }
2233 .into());
2234 }
2235
2236 let core = binomial_location_scale_core(
2237 &self.y,
2238 &self.weights,
2239 eta_t,
2240 eta_ls,
2241 None,
2242 &self.link_kind,
2243 )?;
2244 if !self.exact_joint_supported() {
2245 return Err(
2246 "BinomialLocationScaleFamily requires exact curvature designs; diagonal fallback has been removed"
2247 .to_string(),
2248 );
2249 }
2250 let threshold_design = self.threshold_design.as_ref().ok_or_else(|| {
2251 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2252 })?;
2253 let log_sigma_design = self.log_sigma_design.as_ref().ok_or_else(|| {
2254 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2255 })?;
2256
2257 let mut grad_eta_t_v = vec![0.0_f64; n];
2263 let mut grad_eta_ls_v = vec![0.0_f64; n];
2264 let y_slice_e = self.y.as_slice().expect("y must be contiguous");
2265 let w_slice_e = self.weights.as_slice().expect("weights must be contiguous");
2266 let q0_slice_e = core.q0.as_slice().expect("q0 must be contiguous");
2267 let eta_t_slice_e = eta_t.as_slice().expect("eta_t must be contiguous");
2268 let eta_ls_slice_e = eta_ls.as_slice().expect("eta_ls must be contiguous");
2269 let link_kind_e = &self.link_kind;
2270 let gradient_pairs: Result<Vec<(f64, f64)>, String> = (0..n)
2271 .into_par_iter()
2272 .map(|i| {
2273 let gradient = binomial_location_scale_nll_gradient(
2274 y_slice_e[i],
2275 w_slice_e[i],
2276 eta_t_slice_e[i],
2277 eta_ls_slice_e[i],
2278 q0_slice_e[i],
2279 core.mu[i],
2280 core.dmu_dq[i],
2281 core.d2mu_dq2[i],
2282 core.d3mu_dq3[i],
2283 link_kind_e,
2284 )?;
2285 Ok((-gradient[0], -gradient[1]))
2286 })
2287 .collect();
2288 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
2289 grad_eta_t_v[i] = g_t;
2290 grad_eta_ls_v[i] = g_ls;
2291 }
2292 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
2293 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
2294 let grad_t = threshold_design.transpose_vector_multiply(&grad_eta_t);
2295 let grad_ls = log_sigma_design.transpose_vector_multiply(&grad_eta_ls);
2296
2297 let (h_tt, h_ll) = self.exact_newton_block_diagonal_hessians_from_design_matrices(
2302 block_states,
2303 threshold_design,
2304 log_sigma_design,
2305 )?;
2306 Ok(FamilyEvaluation {
2307 log_likelihood: core.log_likelihood,
2308 blockworking_sets: vec![
2309 BlockWorkingSet::ExactNewton {
2310 gradient: grad_t,
2311 hessian: SymmetricMatrix::Dense(h_tt),
2312 },
2313 BlockWorkingSet::ExactNewton {
2314 gradient: grad_ls,
2315 hessian: SymmetricMatrix::Dense(h_ll),
2316 },
2317 ],
2318 })
2319 }
2320
2321 fn log_likelihood_only(&self, block_states: &[ParameterBlockState]) -> Result<f64, String> {
2322 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2323 let n = self.y.len();
2324 let eta_t = &block_states[Self::BLOCK_T].eta;
2325 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2326 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2327 return Err(GamlssError::DimensionMismatch {
2328 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2329 }
2330 .into());
2331 }
2332 binomial_location_scale_ll_only(
2334 &self.y,
2335 &self.weights,
2336 eta_t,
2337 eta_ls,
2338 None,
2339 &self.link_kind,
2340 )
2341 }
2342
2343 fn log_likelihood_only_with_options(
2354 &self,
2355 block_states: &[ParameterBlockState],
2356 options: &BlockwiseFitOptions,
2357 ) -> Result<f64, String> {
2358 let Some(subsample) = options.outer_score_subsample.as_ref() else {
2359 return self.log_likelihood_only(block_states);
2360 };
2361 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2362 let n = self.y.len();
2363 let eta_t = &block_states[Self::BLOCK_T].eta;
2364 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2365 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2366 return Err(GamlssError::DimensionMismatch {
2367 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2368 }
2369 .into());
2370 }
2371 let link_kind = &self.link_kind;
2372 let rows = &subsample.rows;
2373 let ll = gam_linalg::pairwise_reduce::par_deterministic_try_block_fold(
2374 rows.len(),
2375 |range| -> Result<f64, String> {
2376 let mut acc = 0.0_f64;
2377 for k in range {
2378 let row = &rows[k];
2379 let i = row.index;
2380 let wi = self.weights[i];
2381 if wi == 0.0 {
2382 continue;
2383 }
2384 let SigmaJet1 { sigma, .. } = exp_sigma_jet1_scalar(eta_ls[i]);
2385 let q = binomial_location_scale_q0(eta_t[i], sigma);
2386 let mu = if matches!(link_kind, InverseLink::Standard(StandardLink::Probit)) {
2387 0.5
2388 } else {
2389 let jet = inverse_link_jet_for_inverse_link(link_kind, q).map_err(|e| {
2390 format!("location-scale inverse-link evaluation failed: {e}")
2391 })?;
2392 jet.mu
2393 };
2394 let term =
2395 binomial_location_scale_log_likelihood(self.y[i], wi, q, link_kind, mu)?;
2396 acc += row.weight * term;
2397 }
2398 Ok(acc)
2399 },
2400 |a, b| Ok(a + b),
2401 )?;
2402 Ok(ll.unwrap_or(0.0))
2403 }
2404
2405 fn requires_joint_outer_hyper_path(&self) -> bool {
2406 true
2407 }
2408
2409 fn diagonalworking_weights_directional_derivative(
2410 &self,
2411 block_states: &[ParameterBlockState],
2412 block_index: usize,
2413 d_eta: &Array1<f64>,
2414 ) -> Result<Option<Array1<f64>>, String> {
2415 assert!(
2420 block_index < block_states.len(),
2421 "diagonal working-weight directional derivative: block index {block_index} out of range for {} blocks",
2422 block_states.len()
2423 );
2424 assert_eq!(
2425 d_eta.len(),
2426 block_states[block_index].eta.len(),
2427 "diagonal working-weight directional derivative: direction is not in block {block_index}'s predictor space"
2428 );
2429 assert!(d_eta.iter().all(|v| !v.is_nan()));
2430 Err(format!(
2431 "BinomialLocationScaleFamily no longer supports diagonal working weights; exact \
2432 curvature is required (refused for block {block_index} of {})",
2433 block_states.len()
2434 ))
2435 }
2436
2437 impl_location_scale_joint_psi_custom_family_hooks!("BinomialLocationScaleFamily");
2438
2439 fn exact_newton_joint_psi_workspace(
2440 &self,
2441 block_states: &[ParameterBlockState],
2442 specs: &[ParameterBlockSpec],
2443 hyper_layout: &crate::custom_family::CustomFamilyHyperLayout,
2444 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
2445 if hyper_layout.family_axis_count() != 0 {
2446 return Err(
2447 "BinomialLocationScaleFamily does not declare family-owned hyper axes".to_string(),
2448 );
2449 }
2450 if !self.exact_joint_supported() {
2451 return Ok(None);
2452 }
2453 Ok(Some(Arc::new(
2454 BinomialLocationScaleExactNewtonJointPsiWorkspace::new(
2455 self.clone(),
2456 block_states.to_vec(),
2457 specs,
2458 hyper_layout.design_derivative_blocks().to_vec(),
2459 )?,
2460 )))
2461 }
2462
2463 fn exact_newton_hessian_directional_derivative(
2464 &self,
2465 block_states: &[ParameterBlockState],
2466 block_idx: usize,
2467 d_beta: &Array1<f64>,
2468 ) -> Result<Option<Array2<f64>>, String> {
2469 if !self.exact_joint_supported() {
2470 return Ok(None);
2471 }
2472 let pt = self
2473 .threshold_design
2474 .as_ref()
2475 .ok_or_else(|| {
2476 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2477 })?
2478 .ncols();
2479 let pls = self
2480 .log_sigma_design
2481 .as_ref()
2482 .ok_or_else(|| {
2483 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2484 })?
2485 .ncols();
2486 let total = pt + pls;
2487 let (start, end, joint_direction) = match block_idx {
2488 Self::BLOCK_T => {
2489 if d_beta.len() != pt {
2490 return Err(GamlssError::DimensionMismatch { reason: format!(
2491 "BinomialLocationScaleFamily threshold d_beta length mismatch: got {}, expected {}",
2492 d_beta.len(),
2493 pt
2494 ) }.into());
2495 }
2496 let mut dir = Array1::<f64>::zeros(total);
2497 dir.slice_mut(s![0..pt]).assign(d_beta);
2498 (0usize, pt, dir)
2499 }
2500 Self::BLOCK_LOG_SIGMA => {
2501 if d_beta.len() != pls {
2502 return Err(GamlssError::DimensionMismatch { reason: format!(
2503 "BinomialLocationScaleFamily log-sigma d_beta length mismatch: got {}, expected {}",
2504 d_beta.len(),
2505 pls
2506 ) }.into());
2507 }
2508 let mut dir = Array1::<f64>::zeros(total);
2509 dir.slice_mut(s![pt..pt + pls]).assign(d_beta);
2510 (pt, pt + pls, dir)
2511 }
2512 _ => return Ok(None),
2513 };
2514 let joint = self
2515 .exact_newton_joint_hessian_directional_derivative(block_states, &joint_direction)?
2516 .ok_or_else(|| {
2517 format!("missing joint exact-newton directional Hessian for block {block_idx}")
2518 })?;
2519 Ok(Some(joint.slice(s![start..end, start..end]).to_owned()))
2520 }
2521
2522 fn exact_newton_joint_hessian(
2523 &self,
2524 block_states: &[ParameterBlockState],
2525 ) -> Result<Option<Array2<f64>>, String> {
2526 self.exact_newton_joint_hessian_for_specs(block_states, None)
2527 }
2528
2529 fn has_explicit_joint_hessian(&self) -> bool {
2530 true
2531 }
2532
2533 fn exact_newton_joint_hessian_directional_derivative(
2534 &self,
2535 block_states: &[ParameterBlockState],
2536 d_beta_flat: &Array1<f64>,
2537 ) -> Result<Option<Array2<f64>>, String> {
2538 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2539 block_states,
2540 None,
2541 d_beta_flat,
2542 )
2543 }
2544
2545 fn exact_newton_joint_hessiansecond_directional_derivative(
2546 &self,
2547 block_states: &[ParameterBlockState],
2548 d_beta_u_flat: &Array1<f64>,
2549 d_betav_flat: &Array1<f64>,
2550 ) -> Result<Option<Array2<f64>>, String> {
2551 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2552 block_states,
2553 None,
2554 d_beta_u_flat,
2555 d_betav_flat,
2556 )
2557 }
2558
2559 fn exact_newton_joint_hessian_with_specs(
2560 &self,
2561 block_states: &[ParameterBlockState],
2562 specs: &[ParameterBlockSpec],
2563 ) -> Result<Option<Array2<f64>>, String> {
2564 self.exact_newton_joint_hessian_for_specs(block_states, Some(specs))
2565 }
2566
2567 fn exact_newton_joint_hessian_directional_derivative_with_specs(
2568 &self,
2569 block_states: &[ParameterBlockState],
2570 specs: &[ParameterBlockSpec],
2571 d_beta_flat: &Array1<f64>,
2572 ) -> Result<Option<Array2<f64>>, String> {
2573 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2574 block_states,
2575 Some(specs),
2576 d_beta_flat,
2577 )
2578 }
2579
2580 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
2581 &self,
2582 block_states: &[ParameterBlockState],
2583 specs: &[ParameterBlockSpec],
2584 d_beta_u_flat: &Array1<f64>,
2585 d_betav_flat: &Array1<f64>,
2586 ) -> Result<Option<Array2<f64>>, String> {
2587 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2588 block_states,
2589 Some(specs),
2590 d_beta_u_flat,
2591 d_betav_flat,
2592 )
2593 }
2594
2595 fn joint_jeffreys_information_with_specs(
2596 &self,
2597 block_states: &[ParameterBlockState],
2598 specs: &[ParameterBlockSpec],
2599 ) -> Result<Option<Array2<f64>>, String> {
2600 self.expected_joint_information_for_specs(block_states, Some(specs))
2601 }
2602
2603 fn joint_jeffreys_information_directional_derivative_with_specs(
2604 &self,
2605 block_states: &[ParameterBlockState],
2606 specs: &[ParameterBlockSpec],
2607 d_beta_flat: &Array1<f64>,
2608 ) -> Result<Option<Array2<f64>>, String> {
2609 self.expected_joint_information_directional_for_specs(
2610 block_states,
2611 Some(specs),
2612 d_beta_flat,
2613 )
2614 }
2615
2616 fn joint_jeffreys_information_second_directional_derivative_with_specs(
2617 &self,
2618 block_states: &[ParameterBlockState],
2619 specs: &[ParameterBlockSpec],
2620 d_beta_u_flat: &Array1<f64>,
2621 d_betav_flat: &Array1<f64>,
2622 ) -> Result<Option<Array2<f64>>, String> {
2623 self.expected_joint_information_second_directional_for_specs(
2624 block_states,
2625 Some(specs),
2626 d_beta_u_flat,
2627 d_betav_flat,
2628 )
2629 }
2630
2631 fn joint_jeffreys_information_contracted_trace_hessian_with_specs(
2632 &self,
2633 block_states: &[ParameterBlockState],
2634 specs: &[ParameterBlockSpec],
2635 weight: &Array2<f64>,
2636 ) -> Result<Option<Array2<f64>>, String> {
2637 self.expected_joint_contracted_trace_hessian_for_specs(block_states, Some(specs), weight)
2638 }
2639
2640 fn joint_jeffreys_information_contracted_trace_hessian_available(&self) -> bool {
2641 true
2642 }
2643
2644 fn joint_jeffreys_information_matches_observed_hessian(&self) -> bool {
2645 false
2653 }
2654
2655 fn exact_newton_joint_gradient_evaluation(
2656 &self,
2657 block_states: &[ParameterBlockState],
2658 specs: &[ParameterBlockSpec],
2659 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
2660 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2661 return Ok(None);
2662 };
2663 self.exact_newton_joint_gradient_from_designs(block_states, &x_t, &x_ls)
2664 .map(Some)
2665 }
2666
2667 fn exact_newton_joint_hessian_workspace(
2668 &self,
2669 block_states: &[ParameterBlockState],
2670 specs: &[ParameterBlockSpec],
2671 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2672 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2673 return Ok(None);
2674 };
2675 let workspace = BinomialLocationScaleHessianWorkspace::new(
2676 self.clone(),
2677 block_states.to_vec(),
2678 x_t,
2679 x_ls,
2680 )?;
2681 Ok(Some(Arc::new(workspace)))
2682 }
2683
2684 fn exact_newton_joint_hessian_workspace_with_options(
2698 &self,
2699 block_states: &[ParameterBlockState],
2700 specs: &[ParameterBlockSpec],
2701 options: &BlockwiseFitOptions,
2702 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2703 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2704 return Ok(None);
2705 };
2706 let mut workspace = BinomialLocationScaleHessianWorkspace::new(
2707 self.clone(),
2708 block_states.to_vec(),
2709 x_t,
2710 x_ls,
2711 )?;
2712 if let Some(subsample) = options.outer_score_subsample.as_ref() {
2713 workspace.apply_outer_subsample(subsample.rows.as_ref());
2714 }
2715 Ok(Some(Arc::new(workspace)))
2716 }
2717
2718 fn outer_derivative_subsample_capable(&self) -> bool {
2735 true
2736 }
2737
2738 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
2739 if specs.len() != 2 {
2743 return false;
2744 }
2745 let n = self.y.len();
2746 specs[Self::BLOCK_T].design.nrows() == n && specs[Self::BLOCK_LOG_SIGMA].design.nrows() == n
2747 }
2748}
2749
2750impl CustomFamilyGenerative for BinomialLocationScaleFamily {
2751 fn generativespec(
2752 &self,
2753 block_states: &[ParameterBlockState],
2754 ) -> Result<GenerativeSpec, String> {
2755 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2756 let eta_t = &block_states[Self::BLOCK_T].eta;
2757 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2758 if eta_t.len() != self.y.len() || eta_ls.len() != self.y.len() {
2759 return Err(GamlssError::DimensionMismatch {
2760 reason: "BinomialLocationScaleFamily generative size mismatch".to_string(),
2761 }
2762 .into());
2763 }
2764 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
2765 let sigma = exp_sigma_from_eta_scalar(eta_ls[i]);
2766 let q = binomial_location_scale_q0(eta_t[i], sigma);
2767 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
2768 .map_err(|e| format!("location-scale inverse-link evaluation failed: {e}"))?;
2769 Ok(jet.mu)
2770 })?;
2771 Ok(GenerativeSpec {
2772 mean,
2773 noise: NoiseModel::Bernoulli,
2774 })
2775 }
2776}