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_psihessian_directional_derivative_for_specs(
704 &self,
705 block_states: &[ParameterBlockState],
706 specs: &[ParameterBlockSpec],
707 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
708 psi_index: usize,
709 d_beta_flat: &Array1<f64>,
710 ) -> Result<Option<Array2<f64>>, String> {
711 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
712 return Ok(None);
713 };
714 self.exact_newton_joint_psihessian_directional_derivative_from_designs(
715 block_states,
716 derivative_blocks,
717 psi_index,
718 d_beta_flat,
719 &x_t,
720 &x_ls,
721 )
722 }
723
724 pub(crate) fn exact_newton_joint_hessian_row_coefficients(
727 &self,
728 block_states: &[ParameterBlockState],
729 ) -> Result<(Array1<f64>, Array1<f64>, Array1<f64>), String> {
730 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
731 let n = self.y.len();
732 let eta_t = &block_states[Self::BLOCK_T].eta;
733 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
734 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
735 return Err(GamlssError::DimensionMismatch {
736 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
737 }
738 .into());
739 }
740
741 let core = binomial_location_scale_core(
742 &self.y,
743 &self.weights,
744 eta_t,
745 eta_ls,
746 None,
747 &self.link_kind,
748 )?;
749 let mut coeff_tt = vec![0.0_f64; n];
750 let mut coeff_tl = vec![0.0_f64; n];
751 let mut coeff_ll = vec![0.0_f64; n];
752 let y_slice = self.y.as_slice().expect("y must be contiguous");
753 let w_slice = self.weights.as_slice().expect("weights must be contiguous");
754 let q0_slice = core.q0.as_slice().expect("q0 must be contiguous");
755 let sigma_slice = core.sigma.as_slice().expect("sigma must be contiguous");
756 let dsigma_slice = core
757 .dsigma_deta
758 .as_slice()
759 .expect("dsigma_deta must be contiguous");
760 let mu_slice = core.mu.as_slice().expect("mu must be contiguous");
761 let dmu_slice = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
762 let d2mu_slice = core
763 .d2mu_dq2
764 .as_slice()
765 .expect("d2mu_dq2 must be contiguous");
766 let d3mu_slice = core
767 .d3mu_dq3
768 .as_slice()
769 .expect("d3mu_dq3 must be contiguous");
770 let link_kind = &self.link_kind;
771 coeff_tt
772 .par_iter_mut()
773 .zip(coeff_tl.par_iter_mut())
774 .zip(coeff_ll.par_iter_mut())
775 .enumerate()
776 .for_each(|(i, ((c_tt, c_tl), c_ll))| {
777 let q = q0_slice[i];
778 let r = 1.0 / sigma_slice[i];
779 let kappa = dsigma_slice[i] / sigma_slice[i];
780 let (m1, m2, _) = binomial_neglog_q_derivatives_dispatch(
781 y_slice[i],
782 w_slice[i],
783 q,
784 mu_slice[i],
785 dmu_slice[i],
786 d2mu_slice[i],
787 d3mu_slice[i],
788 link_kind,
789 );
790 *c_tt = m2 * r * r;
791 *c_tl = kappa * r * (m1 + q * m2);
792 *c_ll = kappa * kappa * q * (m1 + q * m2);
793 });
794 Ok((
795 Array1::from_vec(coeff_tt),
796 Array1::from_vec(coeff_tl),
797 Array1::from_vec(coeff_ll),
798 ))
799 }
800
801 pub(crate) fn exact_newton_block_diagonal_hessians_from_design_matrices(
805 &self,
806 block_states: &[ParameterBlockState],
807 x_t: &DesignMatrix,
808 x_ls: &DesignMatrix,
809 ) -> Result<(Array2<f64>, Array2<f64>), String> {
810 let (coeff_tt, _coeff_tl, coeff_ll) =
811 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
812 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
813 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
814 Ok((h_tt, h_ll))
815 }
816
817 pub(crate) fn exact_newton_joint_hessian_from_designs(
818 &self,
819 block_states: &[ParameterBlockState],
820 x_t: &Array2<f64>,
821 x_ls: &Array2<f64>,
822 ) -> Result<Option<Array2<f64>>, String> {
823 let (coeff_tt, coeff_tl, coeff_ll) =
880 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
881 let pt = x_t.ncols();
882 let pls = x_ls.ncols();
883
884 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
885 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
886 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
887 let total = pt + pls;
888 let mut h = Array2::<f64>::zeros((total, total));
889 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
890 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
891 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
892 mirror_upper_to_lower(&mut h);
893 Ok(Some(h))
894 }
895
896 pub(crate) fn exact_newton_joint_hessian_from_design_matrices(
897 &self,
898 block_states: &[ParameterBlockState],
899 x_t: &DesignMatrix,
900 x_ls: &DesignMatrix,
901 ) -> Result<Option<Array2<f64>>, String> {
902 if let (Some(x_t_dense), Some(x_ls_dense)) = (x_t.as_dense_ref(), x_ls.as_dense_ref()) {
903 return self.exact_newton_joint_hessian_from_designs(
904 block_states,
905 x_t_dense,
906 x_ls_dense,
907 );
908 }
909 let (coeff_tt, coeff_tl, coeff_ll) =
910 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
911 let pt = x_t.ncols();
912 let pls = x_ls.ncols();
913
914 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
915 let h_tl = xt_diag_y_design(x_t, &coeff_tl, x_ls)?;
916 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
917 let total = pt + pls;
918 let mut h = Array2::<f64>::zeros((total, total));
919 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
920 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
921 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
922 mirror_upper_to_lower(&mut h);
923 Ok(Some(h))
924 }
925
926 pub(crate) fn exact_newton_joint_hessian_directional_derivative_from_designs(
927 &self,
928 block_states: &[ParameterBlockState],
929 x_t: &Array2<f64>,
930 x_ls: &Array2<f64>,
931 d_beta_flat: &Array1<f64>,
932 ) -> Result<Option<Array2<f64>>, String> {
933 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
981 let n = self.y.len();
982 let eta_t = &block_states[Self::BLOCK_T].eta;
983 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
984 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
985 return Err(GamlssError::DimensionMismatch {
986 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
987 }
988 .into());
989 }
990
991 let pt = x_t.ncols();
992 let pls = x_ls.ncols();
993 if d_beta_flat.len() != pt + pls {
994 return Err(GamlssError::DimensionMismatch {
995 reason: format!(
996 "BinomialLocationScaleFamily joint d_beta length mismatch: got {}, expected {}",
997 d_beta_flat.len(),
998 pt + pls
999 ),
1000 }
1001 .into());
1002 }
1003 let d_eta_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
1004 let d_eta_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
1005 let core = binomial_location_scale_core(
1006 &self.y,
1007 &self.weights,
1008 eta_t,
1009 eta_ls,
1010 None,
1011 &self.link_kind,
1012 )?;
1013 let (coeff_tt, coeff_tl, coeff_ll) =
1014 binomial_location_scale_first_directional_coefficients(
1015 &self.y,
1016 &self.weights,
1017 &core,
1018 &d_eta_t,
1019 &d_eta_ls,
1020 &self.link_kind,
1021 )?;
1022
1023 let d_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1024 let d_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1025 let d_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1026 let total = pt + pls;
1027 let mut d_h = Array2::<f64>::zeros((total, total));
1028 d_h.slice_mut(s![0..pt, 0..pt]).assign(&d_h_tt);
1029 d_h.slice_mut(s![0..pt, pt..total]).assign(&d_h_tl);
1030 d_h.slice_mut(s![pt..total, pt..total]).assign(&d_h_ll);
1031 mirror_upper_to_lower(&mut d_h);
1032 Ok(Some(d_h))
1033 }
1034
1035 pub(crate) fn exact_newton_joint_hessiansecond_directional_derivative_from_designs(
1036 &self,
1037 block_states: &[ParameterBlockState],
1038 x_t: &Array2<f64>,
1039 x_ls: &Array2<f64>,
1040 d_beta_u_flat: &Array1<f64>,
1041 d_betav_flat: &Array1<f64>,
1042 ) -> Result<Option<Array2<f64>>, String> {
1043 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1088 let n = self.y.len();
1089 let eta_t = &block_states[Self::BLOCK_T].eta;
1090 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1091 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1092 return Err(GamlssError::DimensionMismatch {
1093 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1094 }
1095 .into());
1096 }
1097
1098 let pt = x_t.ncols();
1099 let pls = x_ls.ncols();
1100 let total = pt + pls;
1101 if d_beta_u_flat.len() != total {
1102 return Err(GamlssError::DimensionMismatch { reason: format!(
1103 "BinomialLocationScaleFamily joint d_beta_u length mismatch: got {}, expected {}",
1104 d_beta_u_flat.len(),
1105 total
1106 ) }.into());
1107 }
1108 if d_betav_flat.len() != total {
1109 return Err(GamlssError::DimensionMismatch { reason: format!(
1110 "BinomialLocationScaleFamily joint d_betav length mismatch: got {}, expected {}",
1111 d_betav_flat.len(),
1112 total
1113 ) }.into());
1114 }
1115 let d_eta_t_u = fast_av(x_t, &d_beta_u_flat.slice(s![0..pt]));
1116 let d_eta_ls_u = fast_av(x_ls, &d_beta_u_flat.slice(s![pt..total]));
1117 let d_eta_tv = fast_av(x_t, &d_betav_flat.slice(s![0..pt]));
1118 let d_eta_lsv = fast_av(x_ls, &d_betav_flat.slice(s![pt..total]));
1119 let core = binomial_location_scale_core(
1120 &self.y,
1121 &self.weights,
1122 eta_t,
1123 eta_ls,
1124 None,
1125 &self.link_kind,
1126 )?;
1127 let (coeff_tt, coeff_tl, coeff_ll) =
1128 binomial_location_scalesecond_directional_coefficients(
1129 &self.y,
1130 &self.weights,
1131 &core,
1132 &d_eta_t_u,
1133 &d_eta_ls_u,
1134 &d_eta_tv,
1135 &d_eta_lsv,
1136 &self.link_kind,
1137 )?;
1138
1139 let d2_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1140 let d2_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1141 let d2_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1142 let mut d2_h = Array2::<f64>::zeros((total, total));
1143 d2_h.slice_mut(s![0..pt, 0..pt]).assign(&d2_h_tt);
1144 d2_h.slice_mut(s![0..pt, pt..total]).assign(&d2_h_tl);
1145 d2_h.slice_mut(s![pt..total, pt..total]).assign(&d2_h_ll);
1146 mirror_upper_to_lower(&mut d2_h);
1147 Ok(Some(d2_h))
1148 }
1149
1150 pub(crate) fn exact_newton_joint_psi_direction(
1151 &self,
1152 block_states: &[ParameterBlockState],
1153 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1154 psi_index: usize,
1155 x_t: &Array2<f64>,
1156 x_ls: &Array2<f64>,
1157 policy: &gam_runtime::resource::ResourcePolicy,
1158 ) -> Result<Option<LocationScaleJointPsiDirection>, String> {
1159 let Some(parts) = locscale_joint_psi_direction_parts(
1160 block_states,
1161 derivative_blocks,
1162 psi_index,
1163 self.y.len(),
1164 x_t.ncols(),
1165 x_ls.ncols(),
1166 Self::BLOCK_T,
1167 Self::BLOCK_LOG_SIGMA,
1168 2,
1169 "BinomialLocationScaleFamily",
1170 "threshold",
1171 policy,
1172 )?
1173 else {
1174 return Ok(None);
1175 };
1176 Ok(Some(LocationScaleJointPsiDirection {
1177 block_idx: parts.block_idx,
1178 local_idx: parts.local_idx,
1179 x_primary_psi: parts.primary_psi,
1180 x_ls_psi: parts.log_sigma_psi,
1181 z_primary_psi: parts.primary_z,
1182 z_ls_psi: parts.log_sigma_z,
1183 }))
1184 }
1185
1186 pub(crate) fn exact_newton_joint_psisecond_design_drifts(
1187 &self,
1188 block_states: &[ParameterBlockState],
1189 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1190 psi_a: &LocationScaleJointPsiDirection,
1191 psi_b: &LocationScaleJointPsiDirection,
1192 x_t: &Array2<f64>,
1193 x_ls: &Array2<f64>,
1194 ) -> Result<LocationScaleJointPsiSecondDrifts, String> {
1195 locscale_joint_psisecond_design_drifts(
1196 block_states,
1197 derivative_blocks,
1198 psi_a,
1199 psi_b,
1200 LocScalePsiDriftConfig {
1201 n: self.y.len(),
1202 p_primary: x_t.ncols(),
1203 p_log_sigma: x_ls.ncols(),
1204 primary_block_idx: Self::BLOCK_T,
1205 log_sigma_block_idx: Self::BLOCK_LOG_SIGMA,
1206 family_name: "BinomialLocationScaleFamily",
1207 primary_label: "threshold",
1208 policy: &self.policy,
1209 },
1210 )
1211 }
1212
1213 pub(crate) fn exact_newton_joint_psi_terms_from_designs(
1214 &self,
1215 block_states: &[ParameterBlockState],
1216 specs: &[ParameterBlockSpec],
1217 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1218 psi_index: usize,
1219 x_t: &Array2<f64>,
1220 x_ls: &Array2<f64>,
1221 ) -> Result<Option<gam_problem::ExactNewtonJointPsiTerms>, String> {
1222 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1223 if specs.len() != 2 || derivative_blocks.len() != 2 {
1224 return Err(GamlssError::DimensionMismatch { reason: format!(
1225 "BinomialLocationScaleFamily joint psi terms expect 2 specs and 2 derivative blocks, got {} and {}",
1226 specs.len(),
1227 derivative_blocks.len()
1228 ) }.into());
1229 }
1230 let n = self.y.len();
1231 let eta_t = &block_states[Self::BLOCK_T].eta;
1232 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1233 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1234 return Err(GamlssError::DimensionMismatch {
1235 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1236 }
1237 .into());
1238 }
1239
1240 let core = binomial_location_scale_core(
1336 &self.y,
1337 &self.weights,
1338 eta_t,
1339 eta_ls,
1340 None,
1341 &self.link_kind,
1342 )?;
1343 let pt = x_t.ncols();
1344 let pls = x_ls.ncols();
1345 let total = pt + pls;
1346 let Some(dir_a) = self.exact_newton_joint_psi_direction(
1347 block_states,
1348 derivative_blocks,
1349 psi_index,
1350 x_t,
1351 x_ls,
1352 &self.policy,
1353 )?
1354 else {
1355 return Ok(None);
1356 };
1357 let (z_t, z_ls) = (&dir_a.z_primary_psi, &dir_a.z_ls_psi);
1358
1359 struct PsiTermsRow {
1364 pub(crate) r_t: f64,
1365 pub(crate) r_ls: f64,
1366 pub(crate) dr_t: f64,
1367 pub(crate) dr_ls: f64,
1368 pub(crate) h_tt: f64,
1369 pub(crate) h_tl: f64,
1370 pub(crate) h_ll: f64,
1371 pub(crate) dh_tt: f64,
1372 pub(crate) dh_tl: f64,
1373 pub(crate) dh_ll: f64,
1374 pub(crate) obj: f64,
1375 }
1376 let y_p = self.y.as_slice().expect("y must be contiguous");
1377 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1378 let q0_p = core.q0.as_slice().expect("q0 must be contiguous");
1379 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1380 let dsigma_p = core
1381 .dsigma_deta
1382 .as_slice()
1383 .expect("dsigma_deta must be contiguous");
1384 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1385 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1386 let d2mu_p = core
1387 .d2mu_dq2
1388 .as_slice()
1389 .expect("d2mu_dq2 must be contiguous");
1390 let d3mu_p = core
1391 .d3mu_dq3
1392 .as_slice()
1393 .expect("d3mu_dq3 must be contiguous");
1394 let z_t_p = z_t.as_slice().expect("z_t must be contiguous");
1395 let z_ls_p = z_ls.as_slice().expect("z_ls must be contiguous");
1396 let link_kind_p = &self.link_kind;
1397 let rows: Vec<PsiTermsRow> = (0..n)
1398 .into_par_iter()
1399 .map(|i| {
1400 let q = q0_p[i];
1401 let r = 1.0 / sigma_p[i];
1402 let s = dsigma_p[i] / sigma_p[i];
1403 let sz = s * z_ls_p[i];
1404 let q_psi = -r * z_t_p[i] - q * sz;
1405 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1406 y_p[i],
1407 w_p[i],
1408 q,
1409 mu_p[i],
1410 dmu_p[i],
1411 d2mu_p[i],
1412 d3mu_p[i],
1413 link_kind_p,
1414 );
1415 let r_t = -a * r;
1416 let r_ls = -a * q * s;
1417 PsiTermsRow {
1418 r_t,
1419 r_ls,
1420 dr_t: -b * q_psi * r + a * r * sz,
1421 dr_ls: -(a + q * b) * q_psi,
1422 h_tt: b * r * r,
1423 h_tl: r * (a + q * b),
1424 h_ll: q * (a + q * b),
1425 dh_tt: r * r * (c * q_psi - 2.0 * b * sz),
1426 dh_tl: r * ((2.0 * b + c * q) * q_psi - (a + q * b) * sz),
1427 dh_ll: (a + 3.0 * q * b + q * q * c) * q_psi,
1428 obj: r_t * z_t_p[i] + r_ls * z_ls_p[i],
1429 }
1430 })
1431 .collect();
1432 let mut r_t = Array1::<f64>::zeros(n);
1433 let mut r_ls = Array1::<f64>::zeros(n);
1434 let mut dr_t = Array1::<f64>::zeros(n);
1435 let mut dr_ls = Array1::<f64>::zeros(n);
1436 let mut h_tt = Array1::<f64>::zeros(n);
1437 let mut h_tl = Array1::<f64>::zeros(n);
1438 let mut h_ll = Array1::<f64>::zeros(n);
1439 let mut dh_tt = Array1::<f64>::zeros(n);
1440 let mut dh_tl = Array1::<f64>::zeros(n);
1441 let mut dh_ll = Array1::<f64>::zeros(n);
1442 let mut objective_psi = 0.0_f64;
1443 for (i, row) in rows.into_iter().enumerate() {
1444 r_t[i] = row.r_t;
1445 r_ls[i] = row.r_ls;
1446 dr_t[i] = row.dr_t;
1447 dr_ls[i] = row.dr_ls;
1448 h_tt[i] = row.h_tt;
1449 h_tl[i] = row.h_tl;
1450 h_ll[i] = row.h_ll;
1451 dh_tt[i] = row.dh_tt;
1452 dh_tl[i] = row.dh_tl;
1453 dh_ll[i] = row.dh_ll;
1454 objective_psi += row.obj;
1455 }
1456
1457 let hessian_psi_operator = build_two_block_custom_family_joint_psi_operator_from_actions(
1458 dir_a.x_primary_psi.cloned_first_action(),
1459 dir_a.x_ls_psi.cloned_first_action(),
1460 0..pt,
1461 pt..pt + pls,
1462 x_t,
1463 x_ls,
1464 &h_tt,
1465 &h_tl,
1466 &h_ll,
1467 &dh_tt,
1468 &dh_tl,
1469 &dh_ll,
1470 )?;
1471 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
1472 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
1473 let score_t = x_t_map.transpose_mul(r_t.view()) + fast_atv(x_t, &dr_t);
1474 let score_ls = x_ls_map.transpose_mul(r_ls.view()) + fast_atv(x_ls, &dr_ls);
1475 let mut score_psi = Array1::<f64>::zeros(total);
1476 score_psi.slice_mut(s![0..pt]).assign(&score_t);
1477 score_psi.slice_mut(s![pt..pt + pls]).assign(&score_ls);
1478 let hessian_psi = if hessian_psi_operator.is_some() {
1479 Array2::zeros((0, 0))
1480 } else {
1481 let h_tt_block = weighted_crossprod_psi_maps(
1482 x_t_map,
1483 h_tt.view(),
1484 CustomFamilyPsiLinearMapRef::Dense(x_t),
1485 )? + &weighted_crossprod_psi_maps(
1486 CustomFamilyPsiLinearMapRef::Dense(x_t),
1487 h_tt.view(),
1488 x_t_map,
1489 )? + &xt_diag_x_dense(x_t, &dh_tt)?;
1490 let h_tl_block = weighted_crossprod_psi_maps(
1491 x_t_map,
1492 h_tl.view(),
1493 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1494 )? + &weighted_crossprod_psi_maps(
1495 CustomFamilyPsiLinearMapRef::Dense(x_t),
1496 h_tl.view(),
1497 x_ls_map,
1498 )? + &xt_diag_y_dense(x_t, &dh_tl, x_ls)?;
1499 let h_ll_block = weighted_crossprod_psi_maps(
1500 x_ls_map,
1501 h_ll.view(),
1502 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1503 )? + &weighted_crossprod_psi_maps(
1504 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1505 h_ll.view(),
1506 x_ls_map,
1507 )? + &xt_diag_x_dense(x_ls, &dh_ll)?;
1508
1509 let mut hessian_psi = Array2::<f64>::zeros((total, total));
1510 hessian_psi.slice_mut(s![0..pt, 0..pt]).assign(&h_tt_block);
1511 hessian_psi
1512 .slice_mut(s![0..pt, pt..pt + pls])
1513 .assign(&h_tl_block);
1514 hessian_psi
1515 .slice_mut(s![pt..pt + pls, pt..pt + pls])
1516 .assign(&h_ll_block);
1517 mirror_upper_to_lower(&mut hessian_psi);
1518 hessian_psi
1519 };
1520
1521 Ok(Some(gam_problem::ExactNewtonJointPsiTerms {
1522 objective_psi,
1523 score_psi,
1524 hessian_psi,
1525 hessian_psi_operator,
1526 }))
1527 }
1528
1529 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_designs(
1530 &self,
1531 block_states: &[ParameterBlockState],
1532 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1533 psi_i: usize,
1534 psi_j: usize,
1535 x_t: &Array2<f64>,
1536 x_ls: &Array2<f64>,
1537 ) -> Result<Option<gam_problem::ExactNewtonJointPsiSecondOrderTerms>, String> {
1538 let Some(dir_i) = self.exact_newton_joint_psi_direction(
1539 block_states,
1540 derivative_blocks,
1541 psi_i,
1542 x_t,
1543 x_ls,
1544 &self.policy,
1545 )?
1546 else {
1547 return Ok(None);
1548 };
1549 let Some(dir_j) = self.exact_newton_joint_psi_direction(
1550 block_states,
1551 derivative_blocks,
1552 psi_j,
1553 x_t,
1554 x_ls,
1555 &self.policy,
1556 )?
1557 else {
1558 return Ok(None);
1559 };
1560 Ok(Some(
1561 self.exact_newton_joint_psisecond_order_terms_from_parts(
1562 block_states,
1563 derivative_blocks,
1564 &dir_i,
1565 &dir_j,
1566 x_t,
1567 x_ls,
1568 )?,
1569 ))
1570 }
1571
1572 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_parts(
1573 &self,
1574 block_states: &[ParameterBlockState],
1575 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1576 dir_i: &LocationScaleJointPsiDirection,
1577 dir_j: &LocationScaleJointPsiDirection,
1578 x_t: &Array2<f64>,
1579 x_ls: &Array2<f64>,
1580 ) -> Result<gam_problem::ExactNewtonJointPsiSecondOrderTerms, String> {
1581 let second_drifts = self.exact_newton_joint_psisecond_design_drifts(
1582 block_states,
1583 derivative_blocks,
1584 dir_i,
1585 dir_j,
1586 x_t,
1587 x_ls,
1588 )?;
1589 let n = self.y.len();
1590 let eta_t = &block_states[Self::BLOCK_T].eta;
1591 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1592 let core = binomial_location_scale_core(
1593 &self.y,
1594 &self.weights,
1595 eta_t,
1596 eta_ls,
1597 None,
1598 &self.link_kind,
1599 )?;
1600 let pt = x_t.ncols();
1601 let pls = x_ls.ncols();
1602 let total = pt + pls;
1603 let x_t_i_map = dir_i.x_primary_psi.as_linear_map_ref();
1604 let x_t_j_map = dir_j.x_primary_psi.as_linear_map_ref();
1605 let x_ls_i_map = dir_i.x_ls_psi.as_linear_map_ref();
1606 let x_ls_j_map = dir_j.x_ls_psi.as_linear_map_ref();
1607 let x_t_ab_map = second_psi_linear_map(
1608 second_drifts.x_primary_ab_action.as_ref(),
1609 second_drifts.x_primary_ab.as_ref(),
1610 n,
1611 pt,
1612 );
1613 let x_ls_ab_map = second_psi_linear_map(
1614 second_drifts.x_ls_ab_action.as_ref(),
1615 second_drifts.x_ls_ab.as_ref(),
1616 n,
1617 pls,
1618 );
1619
1620 let mut r_t = Array1::<f64>::zeros(n);
1734 let mut r_ls = Array1::<f64>::zeros(n);
1735 let mut dr_t_i = Array1::<f64>::zeros(n);
1736 let mut dr_t_j = Array1::<f64>::zeros(n);
1737 let mut dr_ls_i = Array1::<f64>::zeros(n);
1738 let mut dr_ls_j = Array1::<f64>::zeros(n);
1739 let mut d2r_t = Array1::<f64>::zeros(n);
1740 let mut d2r_ls = Array1::<f64>::zeros(n);
1741 let mut h_tt = Array1::<f64>::zeros(n);
1742 let mut h_tl = Array1::<f64>::zeros(n);
1743 let mut h_ll = Array1::<f64>::zeros(n);
1744 let mut dh_tt_i = Array1::<f64>::zeros(n);
1745 let mut dh_tt_j = Array1::<f64>::zeros(n);
1746 let mut dh_tl_i = Array1::<f64>::zeros(n);
1747 let mut dh_tl_j = Array1::<f64>::zeros(n);
1748 let mut dh_ll_i = Array1::<f64>::zeros(n);
1749 let mut dh_ll_j = Array1::<f64>::zeros(n);
1750 let mut d2h_tt = Array1::<f64>::zeros(n);
1751 let mut d2h_tl = Array1::<f64>::zeros(n);
1752 let mut d2h_ll = Array1::<f64>::zeros(n);
1753 let mut objective_psi_psi = 0.0;
1754 struct PsiSecondRow {
1755 pub(crate) r_t: f64,
1756 pub(crate) r_ls: f64,
1757 pub(crate) dr_t_i: f64,
1758 pub(crate) dr_t_j: f64,
1759 pub(crate) dr_ls_i: f64,
1760 pub(crate) dr_ls_j: f64,
1761 pub(crate) d2r_t: f64,
1762 pub(crate) d2r_ls: f64,
1763 pub(crate) h_tt: f64,
1764 pub(crate) h_tl: f64,
1765 pub(crate) h_ll: f64,
1766 pub(crate) dh_tt_i: f64,
1767 pub(crate) dh_tt_j: f64,
1768 pub(crate) dh_tl_i: f64,
1769 pub(crate) dh_tl_j: f64,
1770 pub(crate) dh_ll_i: f64,
1771 pub(crate) dh_ll_j: f64,
1772 pub(crate) d2h_tt: f64,
1773 pub(crate) d2h_tl: f64,
1774 pub(crate) d2h_ll: f64,
1775 pub(crate) objective: f64,
1776 }
1777 let y_p = self.y.as_slice().expect("y must be contiguous");
1778 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1779 let q_p = core.q0.as_slice().expect("q0 must be contiguous");
1780 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1781 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1782 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1783 let d2mu_p = core
1784 .d2mu_dq2
1785 .as_slice()
1786 .expect("d2mu_dq2 must be contiguous");
1787 let d3mu_p = core
1788 .d3mu_dq3
1789 .as_slice()
1790 .expect("d3mu_dq3 must be contiguous");
1791 let z_t_i = dir_i
1792 .z_primary_psi
1793 .as_slice()
1794 .expect("z_t_psi_i must be contiguous");
1795 let z_t_j = dir_j
1796 .z_primary_psi
1797 .as_slice()
1798 .expect("z_t_psi_j must be contiguous");
1799 let z_ls_i = dir_i
1800 .z_ls_psi
1801 .as_slice()
1802 .expect("z_ls_psi_i must be contiguous");
1803 let z_ls_j = dir_j
1804 .z_ls_psi
1805 .as_slice()
1806 .expect("z_ls_psi_j must be contiguous");
1807 let z_t_ab = second_drifts
1808 .z_primary_ab
1809 .as_slice()
1810 .expect("z_t_ab must be contiguous");
1811 let z_ls_ab = second_drifts
1812 .z_ls_ab
1813 .as_slice()
1814 .expect("z_ls_ab must be contiguous");
1815 let link_kind_p = &self.link_kind;
1816 let rows: Result<Vec<PsiSecondRow>, String> = (0..n)
1817 .into_par_iter()
1818 .map(|row| {
1819 let q = q_p[row];
1820 let r = 1.0 / sigma_p[row];
1821 let q_i = -r * z_t_i[row] - q * z_ls_i[row];
1822 let q_j = -r * z_t_j[row] - q * z_ls_j[row];
1823 let q_ij = -r * z_t_ab[row]
1824 + r * (z_t_i[row] * z_ls_j[row] + z_t_j[row] * z_ls_i[row])
1825 + q * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row]);
1826 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1827 y_p[row],
1828 w_p[row],
1829 q,
1830 mu_p[row],
1831 dmu_p[row],
1832 d2mu_p[row],
1833 d3mu_p[row],
1834 link_kind_p,
1835 );
1836 let d = binomial_neglog_q_fourth_derivative_dispatch(
1837 y_p[row],
1838 w_p[row],
1839 q,
1840 mu_p[row],
1841 dmu_p[row],
1842 d2mu_p[row],
1843 d3mu_p[row],
1844 link_kind_p,
1845 )?;
1846 let u = a + q * b;
1847 let u_i = (2.0 * b + q * c) * q_i;
1848 let u_j = (2.0 * b + q * c) * q_j;
1849 Ok(PsiSecondRow {
1850 r_t: -a * r,
1851 r_ls: -a * q,
1852 dr_t_i: -b * q_i * r + a * r * z_ls_i[row],
1853 dr_t_j: -b * q_j * r + a * r * z_ls_j[row],
1854 dr_ls_i: -u * q_i,
1855 dr_ls_j: -u * q_j,
1856 d2r_t: r
1857 * (-c * q_i * q_j - b * q_ij + b * (q_i * z_ls_j[row] + q_j * z_ls_i[row])
1858 - a * z_ls_i[row] * z_ls_j[row]
1859 + a * z_ls_ab[row]),
1860 d2r_ls: -((2.0 * b + q * c) * q_i * q_j + u * q_ij),
1861 h_tt: b * r * r,
1862 h_tl: r * u,
1863 h_ll: q * u,
1864 dh_tt_i: r * r * (c * q_i - 2.0 * b * z_ls_i[row]),
1865 dh_tt_j: r * r * (c * q_j - 2.0 * b * z_ls_j[row]),
1866 dh_tl_i: r * (u_i - u * z_ls_i[row]),
1867 dh_tl_j: r * (u_j - u * z_ls_j[row]),
1868 dh_ll_i: (a + 3.0 * q * b + q * q * c) * q_i,
1869 dh_ll_j: (a + 3.0 * q * b + q * q * c) * q_j,
1870 d2h_tt: r
1871 * r
1872 * (d * q_i * q_j + c * q_ij
1873 - 2.0 * c * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1874 + 4.0 * b * z_ls_i[row] * z_ls_j[row]
1875 - 2.0 * b * z_ls_ab[row]),
1876 d2h_tl: r
1877 * (((3.0 * c + q * d) * q_j) * q_i + (2.0 * b + q * c) * q_ij
1878 - (2.0 * b + q * c) * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1879 + u * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row])),
1880 d2h_ll: (4.0 * b + 5.0 * q * c + q * q * d) * q_i * q_j
1881 + (a + 3.0 * q * b + q * q * c) * q_ij,
1882 objective: a * q_ij + b * q_i * q_j,
1883 })
1884 })
1885 .collect();
1886 for (row, vals) in rows?.into_iter().enumerate() {
1887 r_t[row] = vals.r_t;
1888 r_ls[row] = vals.r_ls;
1889 dr_t_i[row] = vals.dr_t_i;
1890 dr_t_j[row] = vals.dr_t_j;
1891 dr_ls_i[row] = vals.dr_ls_i;
1892 dr_ls_j[row] = vals.dr_ls_j;
1893 d2r_t[row] = vals.d2r_t;
1894 d2r_ls[row] = vals.d2r_ls;
1895 h_tt[row] = vals.h_tt;
1896 h_tl[row] = vals.h_tl;
1897 h_ll[row] = vals.h_ll;
1898 dh_tt_i[row] = vals.dh_tt_i;
1899 dh_tt_j[row] = vals.dh_tt_j;
1900 dh_tl_i[row] = vals.dh_tl_i;
1901 dh_tl_j[row] = vals.dh_tl_j;
1902 dh_ll_i[row] = vals.dh_ll_i;
1903 dh_ll_j[row] = vals.dh_ll_j;
1904 d2h_tt[row] = vals.d2h_tt;
1905 d2h_tl[row] = vals.d2h_tl;
1906 d2h_ll[row] = vals.d2h_ll;
1907 objective_psi_psi += vals.objective;
1908 }
1909 let mut score_psi_psi = Array1::<f64>::zeros(total);
1910 score_psi_psi.slice_mut(s![0..pt]).assign(
1911 &(x_t_ab_map.transpose_mul(r_t.view())
1912 + x_t_i_map.transpose_mul(dr_t_j.view())
1913 + x_t_j_map.transpose_mul(dr_t_i.view())
1914 + fast_atv(x_t, &d2r_t)),
1915 );
1916 score_psi_psi.slice_mut(s![pt..pt + pls]).assign(
1917 &(x_ls_ab_map.transpose_mul(r_ls.view())
1918 + x_ls_i_map.transpose_mul(dr_ls_j.view())
1919 + x_ls_j_map.transpose_mul(dr_ls_i.view())
1920 + fast_atv(x_ls, &d2r_ls)),
1921 );
1922
1923 let h_tt_block = weighted_crossprod_psi_maps(
1924 x_t_ab_map,
1925 h_tt.view(),
1926 CustomFamilyPsiLinearMapRef::Dense(x_t),
1927 )? + &weighted_crossprod_psi_maps(x_t_i_map, h_tt.view(), x_t_j_map)?
1928 + &weighted_crossprod_psi_maps(x_t_j_map, h_tt.view(), x_t_i_map)?
1929 + &weighted_crossprod_psi_maps(
1930 x_t_i_map,
1931 dh_tt_j.view(),
1932 CustomFamilyPsiLinearMapRef::Dense(x_t),
1933 )?
1934 + &weighted_crossprod_psi_maps(
1935 x_t_j_map,
1936 dh_tt_i.view(),
1937 CustomFamilyPsiLinearMapRef::Dense(x_t),
1938 )?
1939 + &weighted_crossprod_psi_maps(
1940 CustomFamilyPsiLinearMapRef::Dense(x_t),
1941 dh_tt_i.view(),
1942 x_t_j_map,
1943 )?
1944 + &weighted_crossprod_psi_maps(
1945 CustomFamilyPsiLinearMapRef::Dense(x_t),
1946 dh_tt_j.view(),
1947 x_t_i_map,
1948 )?
1949 + &xt_diag_x_dense(x_t, &d2h_tt)?
1950 + &weighted_crossprod_psi_maps(
1951 CustomFamilyPsiLinearMapRef::Dense(x_t),
1952 h_tt.view(),
1953 x_t_ab_map,
1954 )?;
1955 let h_tl_block = weighted_crossprod_psi_maps(
1956 x_t_ab_map,
1957 h_tl.view(),
1958 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1959 )? + &weighted_crossprod_psi_maps(x_t_i_map, h_tl.view(), x_ls_j_map)?
1960 + &weighted_crossprod_psi_maps(x_t_j_map, h_tl.view(), x_ls_i_map)?
1961 + &weighted_crossprod_psi_maps(
1962 x_t_i_map,
1963 dh_tl_j.view(),
1964 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1965 )?
1966 + &weighted_crossprod_psi_maps(
1967 x_t_j_map,
1968 dh_tl_i.view(),
1969 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1970 )?
1971 + &weighted_crossprod_psi_maps(
1972 CustomFamilyPsiLinearMapRef::Dense(x_t),
1973 dh_tl_i.view(),
1974 x_ls_j_map,
1975 )?
1976 + &weighted_crossprod_psi_maps(
1977 CustomFamilyPsiLinearMapRef::Dense(x_t),
1978 dh_tl_j.view(),
1979 x_ls_i_map,
1980 )?
1981 + &xt_diag_y_dense(x_t, &d2h_tl, x_ls)?
1982 + &weighted_crossprod_psi_maps(
1983 CustomFamilyPsiLinearMapRef::Dense(x_t),
1984 h_tl.view(),
1985 x_ls_ab_map,
1986 )?;
1987 let h_ll_block = weighted_crossprod_psi_maps(
1988 x_ls_ab_map,
1989 h_ll.view(),
1990 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1991 )? + &weighted_crossprod_psi_maps(x_ls_i_map, h_ll.view(), x_ls_j_map)?
1992 + &weighted_crossprod_psi_maps(x_ls_j_map, h_ll.view(), x_ls_i_map)?
1993 + &weighted_crossprod_psi_maps(
1994 x_ls_i_map,
1995 dh_ll_j.view(),
1996 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1997 )?
1998 + &weighted_crossprod_psi_maps(
1999 x_ls_j_map,
2000 dh_ll_i.view(),
2001 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2002 )?
2003 + &weighted_crossprod_psi_maps(
2004 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2005 dh_ll_i.view(),
2006 x_ls_j_map,
2007 )?
2008 + &weighted_crossprod_psi_maps(
2009 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2010 dh_ll_j.view(),
2011 x_ls_i_map,
2012 )?
2013 + &xt_diag_x_dense(x_ls, &d2h_ll)?
2014 + &weighted_crossprod_psi_maps(
2015 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2016 h_ll.view(),
2017 x_ls_ab_map,
2018 )?;
2019
2020 let mut hessian_psi_psi = Array2::<f64>::zeros((total, total));
2021 hessian_psi_psi
2022 .slice_mut(s![0..pt, 0..pt])
2023 .assign(&h_tt_block);
2024 hessian_psi_psi
2025 .slice_mut(s![0..pt, pt..pt + pls])
2026 .assign(&h_tl_block);
2027 hessian_psi_psi
2028 .slice_mut(s![pt..pt + pls, pt..pt + pls])
2029 .assign(&h_ll_block);
2030 mirror_upper_to_lower(&mut hessian_psi_psi);
2031
2032 Ok(gam_problem::ExactNewtonJointPsiSecondOrderTerms {
2033 objective_psi_psi,
2034 score_psi_psi,
2035 hessian_psi_psi,
2036 hessian_psi_psi_operator: None,
2037 })
2038 }
2039
2040 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_designs(
2041 &self,
2042 block_states: &[ParameterBlockState],
2043 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2044 psi_index: usize,
2045 d_beta_flat: &Array1<f64>,
2046 x_t: &Array2<f64>,
2047 x_ls: &Array2<f64>,
2048 ) -> Result<Option<Array2<f64>>, String> {
2049 let Some(dir_a) = self.exact_newton_joint_psi_direction(
2050 block_states,
2051 derivative_blocks,
2052 psi_index,
2053 x_t,
2054 x_ls,
2055 &self.policy,
2056 )?
2057 else {
2058 return Ok(None);
2059 };
2060 Ok(Some(
2061 self.exact_newton_joint_psihessian_directional_derivative_from_parts(
2062 block_states,
2063 &dir_a,
2064 d_beta_flat,
2065 x_t,
2066 x_ls,
2067 )?,
2068 ))
2069 }
2070
2071 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_parts(
2072 &self,
2073 block_states: &[ParameterBlockState],
2074 dir_a: &LocationScaleJointPsiDirection,
2075 d_beta_flat: &Array1<f64>,
2076 x_t: &Array2<f64>,
2077 x_ls: &Array2<f64>,
2078 ) -> Result<Array2<f64>, String> {
2079 let n = self.y.len();
2080 let eta_t = &block_states[Self::BLOCK_T].eta;
2081 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2082 let core = binomial_location_scale_core(
2083 &self.y,
2084 &self.weights,
2085 eta_t,
2086 eta_ls,
2087 None,
2088 &self.link_kind,
2089 )?;
2090 let pt = x_t.ncols();
2091 let pls = x_ls.ncols();
2092 let total = pt + pls;
2093 if d_beta_flat.len() != total {
2094 return Err(GamlssError::DimensionMismatch { reason: format!(
2095 "BinomialLocationScaleFamily joint psi hessian directional derivative length mismatch: got {}, expected {}",
2096 d_beta_flat.len(),
2097 total
2098 ) }.into());
2099 }
2100 let xi_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
2101 let xi_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
2102 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
2103 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
2104
2105 let mut dh_tt_u = Array1::<f64>::zeros(n);
2156 let mut dh_tl_u = Array1::<f64>::zeros(n);
2157 let mut dh_ll_u = Array1::<f64>::zeros(n);
2158 let mut h_tt_u = Array1::<f64>::zeros(n);
2159 let mut h_tl_u = Array1::<f64>::zeros(n);
2160 let mut h_ll_u = Array1::<f64>::zeros(n);
2161 for row in 0..n {
2162 let q = core.q0[row];
2163 let r = 1.0 / core.sigma[row];
2164 let s = core.dsigma_deta[row] / core.sigma[row];
2165 let xi_ls_s = s * xi_ls[row];
2166 let z_ls_psi_s = s * dir_a.z_ls_psi[row];
2167 let du = -r * xi_t[row] - q * xi_ls_s;
2168 let q_a = -r * dir_a.z_primary_psi[row] - q * z_ls_psi_s;
2169 let q_au = r * dir_a.z_primary_psi[row] * xi_ls_s - du * z_ls_psi_s;
2170 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
2171 self.y[row],
2172 self.weights[row],
2173 q,
2174 core.mu[row],
2175 core.dmu_dq[row],
2176 core.d2mu_dq2[row],
2177 core.d3mu_dq3[row],
2178 &self.link_kind,
2179 );
2180 let d = binomial_neglog_q_fourth_derivative_dispatch(
2181 self.y[row],
2182 self.weights[row],
2183 q,
2184 core.mu[row],
2185 core.dmu_dq[row],
2186 core.d2mu_dq2[row],
2187 core.d3mu_dq3[row],
2188 &self.link_kind,
2189 )?;
2190 let u = a + q * b;
2191 h_tt_u[row] = r * r * (c * du - 2.0 * b * xi_ls_s);
2192 h_tl_u[row] = r * ((2.0 * b + q * c) * du - u * xi_ls_s);
2193 h_ll_u[row] = (a + 3.0 * q * b + q * q * c) * du;
2194 dh_tt_u[row] = r
2195 * r
2196 * (d * du * q_a + c * q_au - 2.0 * c * (q_a * xi_ls_s + du * z_ls_psi_s)
2197 + 4.0 * b * xi_ls_s * z_ls_psi_s);
2198 dh_tl_u[row] = r
2199 * (((3.0 * c + q * d) * q_a) * du + (2.0 * b + q * c) * q_au
2200 - (2.0 * b + q * c) * (q_a * xi_ls_s + du * z_ls_psi_s)
2201 + u * xi_ls_s * z_ls_psi_s);
2202 dh_ll_u[row] = (4.0 * b + 5.0 * q * c + q * q * d) * du * q_a
2203 + (a + 3.0 * q * b + q * q * c) * q_au;
2204 }
2205
2206 let tt_block = weighted_crossprod_psi_maps(
2207 x_t_map,
2208 h_tt_u.view(),
2209 CustomFamilyPsiLinearMapRef::Dense(x_t),
2210 )? + &weighted_crossprod_psi_maps(
2211 CustomFamilyPsiLinearMapRef::Dense(x_t),
2212 h_tt_u.view(),
2213 x_t_map,
2214 )? + &xt_diag_x_dense(x_t, &dh_tt_u)?;
2215 let tl_block = weighted_crossprod_psi_maps(
2216 x_t_map,
2217 h_tl_u.view(),
2218 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2219 )? + &weighted_crossprod_psi_maps(
2220 CustomFamilyPsiLinearMapRef::Dense(x_t),
2221 h_tl_u.view(),
2222 x_ls_map,
2223 )? + &xt_diag_y_dense(x_t, &dh_tl_u, x_ls)?;
2224 let ll_block = weighted_crossprod_psi_maps(
2225 x_ls_map,
2226 h_ll_u.view(),
2227 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2228 )? + &weighted_crossprod_psi_maps(
2229 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2230 h_ll_u.view(),
2231 x_ls_map,
2232 )? + &xt_diag_x_dense(x_ls, &dh_ll_u)?;
2233 let mut out = Array2::<f64>::zeros((total, total));
2234 out.slice_mut(s![0..pt, 0..pt]).assign(&tt_block);
2235 out.slice_mut(s![0..pt, pt..pt + pls]).assign(&tl_block);
2236 out.slice_mut(s![pt..pt + pls, pt..pt + pls])
2237 .assign(&ll_block);
2238 mirror_upper_to_lower(&mut out);
2239 Ok(out)
2240 }
2241
2242 pub fn block_effective_jacobian(
2248 specs: &[ParameterBlockSpec],
2249 block_idx: usize,
2250 ) -> Result<Box<dyn BlockEffectiveJacobian>, String> {
2251 crate::block_layout::block_jacobian::AdditiveWiggleBlockLayout {
2252 family: "BinomialLocationScaleFamily",
2253 n_outputs: 2,
2254 additive_blocks: &[Self::BLOCK_T, Self::BLOCK_LOG_SIGMA],
2255 wiggle_block: None,
2256 }
2257 .block_effective_jacobian(specs, block_idx)
2258 }
2259}
2260
2261impl CustomFamily for BinomialLocationScaleFamily {
2262 fn joint_jeffreys_term_required(&self) -> bool {
2280 false
2281 }
2282
2283 fn exact_newton_joint_hessian_beta_dependent(&self) -> bool {
2287 true
2288 }
2289
2290 fn pseudo_logdet_mode(&self) -> crate::custom_family::PseudoLogdetMode {
2326 crate::custom_family::PseudoLogdetMode::HardPseudo
2327 }
2328
2329 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
2330 crate::location_scale_engine::location_scale_coefficient_hessian_cost(
2334 self.y.len() as u64,
2335 specs,
2336 )
2337 }
2338
2339 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
2340 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2341 let n = self.y.len();
2342 let eta_t = &block_states[Self::BLOCK_T].eta;
2343 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2344 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2345 return Err(GamlssError::DimensionMismatch {
2346 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2347 }
2348 .into());
2349 }
2350
2351 let core = binomial_location_scale_core(
2352 &self.y,
2353 &self.weights,
2354 eta_t,
2355 eta_ls,
2356 None,
2357 &self.link_kind,
2358 )?;
2359 if !self.exact_joint_supported() {
2360 return Err(
2361 "BinomialLocationScaleFamily requires exact curvature designs; diagonal fallback has been removed"
2362 .to_string(),
2363 );
2364 }
2365 let threshold_design = self.threshold_design.as_ref().ok_or_else(|| {
2366 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2367 })?;
2368 let log_sigma_design = self.log_sigma_design.as_ref().ok_or_else(|| {
2369 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2370 })?;
2371
2372 let mut grad_eta_t_v = vec![0.0_f64; n];
2378 let mut grad_eta_ls_v = vec![0.0_f64; n];
2379 let y_slice_e = self.y.as_slice().expect("y must be contiguous");
2380 let w_slice_e = self.weights.as_slice().expect("weights must be contiguous");
2381 let q0_slice_e = core.q0.as_slice().expect("q0 must be contiguous");
2382 let eta_t_slice_e = eta_t.as_slice().expect("eta_t must be contiguous");
2383 let eta_ls_slice_e = eta_ls.as_slice().expect("eta_ls must be contiguous");
2384 let link_kind_e = &self.link_kind;
2385 let gradient_pairs: Result<Vec<(f64, f64)>, String> = (0..n)
2386 .into_par_iter()
2387 .map(|i| {
2388 let gradient = binomial_location_scale_nll_gradient(
2389 y_slice_e[i],
2390 w_slice_e[i],
2391 eta_t_slice_e[i],
2392 eta_ls_slice_e[i],
2393 q0_slice_e[i],
2394 core.mu[i],
2395 core.dmu_dq[i],
2396 core.d2mu_dq2[i],
2397 core.d3mu_dq3[i],
2398 link_kind_e,
2399 )?;
2400 Ok((-gradient[0], -gradient[1]))
2401 })
2402 .collect();
2403 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
2404 grad_eta_t_v[i] = g_t;
2405 grad_eta_ls_v[i] = g_ls;
2406 }
2407 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
2408 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
2409 let grad_t = threshold_design.transpose_vector_multiply(&grad_eta_t);
2410 let grad_ls = log_sigma_design.transpose_vector_multiply(&grad_eta_ls);
2411
2412 let (h_tt, h_ll) = self.exact_newton_block_diagonal_hessians_from_design_matrices(
2417 block_states,
2418 threshold_design,
2419 log_sigma_design,
2420 )?;
2421 Ok(FamilyEvaluation {
2422 log_likelihood: core.log_likelihood,
2423 blockworking_sets: vec![
2424 BlockWorkingSet::ExactNewton {
2425 gradient: grad_t,
2426 hessian: SymmetricMatrix::Dense(h_tt),
2427 },
2428 BlockWorkingSet::ExactNewton {
2429 gradient: grad_ls,
2430 hessian: SymmetricMatrix::Dense(h_ll),
2431 },
2432 ],
2433 })
2434 }
2435
2436 fn log_likelihood_only(&self, block_states: &[ParameterBlockState]) -> Result<f64, String> {
2437 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2438 let n = self.y.len();
2439 let eta_t = &block_states[Self::BLOCK_T].eta;
2440 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2441 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2442 return Err(GamlssError::DimensionMismatch {
2443 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2444 }
2445 .into());
2446 }
2447 binomial_location_scale_ll_only(
2449 &self.y,
2450 &self.weights,
2451 eta_t,
2452 eta_ls,
2453 None,
2454 &self.link_kind,
2455 )
2456 }
2457
2458 fn log_likelihood_only_with_options(
2469 &self,
2470 block_states: &[ParameterBlockState],
2471 options: &BlockwiseFitOptions,
2472 ) -> Result<f64, String> {
2473 let Some(subsample) = options.outer_score_subsample.as_ref() else {
2474 return self.log_likelihood_only(block_states);
2475 };
2476 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2477 let n = self.y.len();
2478 let eta_t = &block_states[Self::BLOCK_T].eta;
2479 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2480 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2481 return Err(GamlssError::DimensionMismatch {
2482 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2483 }
2484 .into());
2485 }
2486 let link_kind = &self.link_kind;
2487 let rows = &subsample.rows;
2488 let ll = gam_linalg::pairwise_reduce::par_deterministic_try_block_fold(
2489 rows.len(),
2490 |range| -> Result<f64, String> {
2491 let mut acc = 0.0_f64;
2492 for k in range {
2493 let row = &rows[k];
2494 let i = row.index;
2495 let wi = self.weights[i];
2496 if wi == 0.0 {
2497 continue;
2498 }
2499 let SigmaJet1 { sigma, .. } = exp_sigma_jet1_scalar(eta_ls[i]);
2500 let q = binomial_location_scale_q0(eta_t[i], sigma);
2501 let mu = if matches!(link_kind, InverseLink::Standard(StandardLink::Probit)) {
2502 0.5
2503 } else {
2504 let jet = inverse_link_jet_for_inverse_link(link_kind, q).map_err(|e| {
2505 format!("location-scale inverse-link evaluation failed: {e}")
2506 })?;
2507 jet.mu
2508 };
2509 let term =
2510 binomial_location_scale_log_likelihood(self.y[i], wi, q, link_kind, mu)?;
2511 acc += row.weight * term;
2512 }
2513 Ok(acc)
2514 },
2515 |a, b| Ok(a + b),
2516 )?;
2517 Ok(ll.unwrap_or(0.0))
2518 }
2519
2520 fn requires_joint_outer_hyper_path(&self) -> bool {
2521 true
2522 }
2523
2524 fn diagonalworking_weights_directional_derivative(
2525 &self,
2526 _: &[ParameterBlockState],
2527 _: usize,
2528 arr: &Array1<f64>,
2529 ) -> Result<Option<Array1<f64>>, String> {
2530 assert!(arr.iter().all(|v| !v.is_nan()));
2532 Err(
2533 "BinomialLocationScaleFamily no longer supports diagonal working weights; exact curvature is required"
2534 .to_string(),
2535 )
2536 }
2537
2538 fn exact_newton_joint_psi_terms(
2539 &self,
2540 block_states: &[ParameterBlockState],
2541 specs: &[ParameterBlockSpec],
2542 hyper_layout: &crate::custom_family::CustomFamilyHyperLayout,
2543 psi_index: usize,
2544 ) -> Result<Option<gam_problem::ExactNewtonJointPsiTerms>, String> {
2545 if hyper_layout.family_axis_count() != 0 {
2546 return Err(
2547 "BinomialLocationScaleFamily does not declare family-owned hyper axes".to_string(),
2548 );
2549 }
2550 self.exact_newton_joint_psi_terms_for_specs(
2551 block_states,
2552 specs,
2553 hyper_layout.design_derivative_blocks(),
2554 psi_index,
2555 )
2556 }
2557
2558 fn exact_newton_joint_psisecond_order_terms(
2559 &self,
2560 block_states: &[ParameterBlockState],
2561 specs: &[ParameterBlockSpec],
2562 hyper_layout: &crate::custom_family::CustomFamilyHyperLayout,
2563 psi_i: usize,
2564 psi_j: usize,
2565 ) -> Result<Option<gam_problem::ExactNewtonJointPsiSecondOrderTerms>, String> {
2566 if hyper_layout.family_axis_count() != 0 {
2567 return Err(
2568 "BinomialLocationScaleFamily does not declare family-owned hyper axes".to_string(),
2569 );
2570 }
2571 self.exact_newton_joint_psisecond_order_terms_for_specs(
2572 block_states,
2573 specs,
2574 hyper_layout.design_derivative_blocks(),
2575 psi_i,
2576 psi_j,
2577 )
2578 }
2579
2580 fn exact_newton_joint_psihessian_directional_derivative(
2581 &self,
2582 block_states: &[ParameterBlockState],
2583 specs: &[ParameterBlockSpec],
2584 hyper_layout: &crate::custom_family::CustomFamilyHyperLayout,
2585 psi_index: usize,
2586 d_beta_flat: &Array1<f64>,
2587 ) -> Result<Option<Array2<f64>>, String> {
2588 if hyper_layout.family_axis_count() != 0 {
2589 return Err(
2590 "BinomialLocationScaleFamily does not declare family-owned hyper axes".to_string(),
2591 );
2592 }
2593 self.exact_newton_joint_psihessian_directional_derivative_for_specs(
2594 block_states,
2595 specs,
2596 hyper_layout.design_derivative_blocks(),
2597 psi_index,
2598 d_beta_flat,
2599 )
2600 }
2601
2602 fn exact_newton_joint_psi_workspace(
2603 &self,
2604 block_states: &[ParameterBlockState],
2605 specs: &[ParameterBlockSpec],
2606 hyper_layout: &crate::custom_family::CustomFamilyHyperLayout,
2607 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
2608 if hyper_layout.family_axis_count() != 0 {
2609 return Err(
2610 "BinomialLocationScaleFamily does not declare family-owned hyper axes".to_string(),
2611 );
2612 }
2613 if !self.exact_joint_supported() {
2614 return Ok(None);
2615 }
2616 Ok(Some(Arc::new(
2617 BinomialLocationScaleExactNewtonJointPsiWorkspace::new(
2618 self.clone(),
2619 block_states.to_vec(),
2620 specs,
2621 hyper_layout.design_derivative_blocks().to_vec(),
2622 )?,
2623 )))
2624 }
2625
2626 fn exact_newton_hessian_directional_derivative(
2627 &self,
2628 block_states: &[ParameterBlockState],
2629 block_idx: usize,
2630 d_beta: &Array1<f64>,
2631 ) -> Result<Option<Array2<f64>>, String> {
2632 if !self.exact_joint_supported() {
2633 return Ok(None);
2634 }
2635 let pt = self
2636 .threshold_design
2637 .as_ref()
2638 .ok_or_else(|| {
2639 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2640 })?
2641 .ncols();
2642 let pls = self
2643 .log_sigma_design
2644 .as_ref()
2645 .ok_or_else(|| {
2646 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2647 })?
2648 .ncols();
2649 let total = pt + pls;
2650 let (start, end, joint_direction) = match block_idx {
2651 Self::BLOCK_T => {
2652 if d_beta.len() != pt {
2653 return Err(GamlssError::DimensionMismatch { reason: format!(
2654 "BinomialLocationScaleFamily threshold d_beta length mismatch: got {}, expected {}",
2655 d_beta.len(),
2656 pt
2657 ) }.into());
2658 }
2659 let mut dir = Array1::<f64>::zeros(total);
2660 dir.slice_mut(s![0..pt]).assign(d_beta);
2661 (0usize, pt, dir)
2662 }
2663 Self::BLOCK_LOG_SIGMA => {
2664 if d_beta.len() != pls {
2665 return Err(GamlssError::DimensionMismatch { reason: format!(
2666 "BinomialLocationScaleFamily log-sigma d_beta length mismatch: got {}, expected {}",
2667 d_beta.len(),
2668 pls
2669 ) }.into());
2670 }
2671 let mut dir = Array1::<f64>::zeros(total);
2672 dir.slice_mut(s![pt..pt + pls]).assign(d_beta);
2673 (pt, pt + pls, dir)
2674 }
2675 _ => return Ok(None),
2676 };
2677 let joint = self
2678 .exact_newton_joint_hessian_directional_derivative(block_states, &joint_direction)?
2679 .ok_or_else(|| {
2680 format!("missing joint exact-newton directional Hessian for block {block_idx}")
2681 })?;
2682 Ok(Some(joint.slice(s![start..end, start..end]).to_owned()))
2683 }
2684
2685 fn exact_newton_joint_hessian(
2686 &self,
2687 block_states: &[ParameterBlockState],
2688 ) -> Result<Option<Array2<f64>>, String> {
2689 self.exact_newton_joint_hessian_for_specs(block_states, None)
2690 }
2691
2692 fn has_explicit_joint_hessian(&self) -> bool {
2693 true
2694 }
2695
2696 fn exact_newton_joint_hessian_directional_derivative(
2697 &self,
2698 block_states: &[ParameterBlockState],
2699 d_beta_flat: &Array1<f64>,
2700 ) -> Result<Option<Array2<f64>>, String> {
2701 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2702 block_states,
2703 None,
2704 d_beta_flat,
2705 )
2706 }
2707
2708 fn exact_newton_joint_hessiansecond_directional_derivative(
2709 &self,
2710 block_states: &[ParameterBlockState],
2711 d_beta_u_flat: &Array1<f64>,
2712 d_betav_flat: &Array1<f64>,
2713 ) -> Result<Option<Array2<f64>>, String> {
2714 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2715 block_states,
2716 None,
2717 d_beta_u_flat,
2718 d_betav_flat,
2719 )
2720 }
2721
2722 fn exact_newton_joint_hessian_with_specs(
2723 &self,
2724 block_states: &[ParameterBlockState],
2725 specs: &[ParameterBlockSpec],
2726 ) -> Result<Option<Array2<f64>>, String> {
2727 self.exact_newton_joint_hessian_for_specs(block_states, Some(specs))
2728 }
2729
2730 fn exact_newton_joint_hessian_directional_derivative_with_specs(
2731 &self,
2732 block_states: &[ParameterBlockState],
2733 specs: &[ParameterBlockSpec],
2734 d_beta_flat: &Array1<f64>,
2735 ) -> Result<Option<Array2<f64>>, String> {
2736 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2737 block_states,
2738 Some(specs),
2739 d_beta_flat,
2740 )
2741 }
2742
2743 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
2744 &self,
2745 block_states: &[ParameterBlockState],
2746 specs: &[ParameterBlockSpec],
2747 d_beta_u_flat: &Array1<f64>,
2748 d_betav_flat: &Array1<f64>,
2749 ) -> Result<Option<Array2<f64>>, String> {
2750 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2751 block_states,
2752 Some(specs),
2753 d_beta_u_flat,
2754 d_betav_flat,
2755 )
2756 }
2757
2758 fn joint_jeffreys_information_with_specs(
2759 &self,
2760 block_states: &[ParameterBlockState],
2761 specs: &[ParameterBlockSpec],
2762 ) -> Result<Option<Array2<f64>>, String> {
2763 self.expected_joint_information_for_specs(block_states, Some(specs))
2764 }
2765
2766 fn joint_jeffreys_information_directional_derivative_with_specs(
2767 &self,
2768 block_states: &[ParameterBlockState],
2769 specs: &[ParameterBlockSpec],
2770 d_beta_flat: &Array1<f64>,
2771 ) -> Result<Option<Array2<f64>>, String> {
2772 self.expected_joint_information_directional_for_specs(
2773 block_states,
2774 Some(specs),
2775 d_beta_flat,
2776 )
2777 }
2778
2779 fn joint_jeffreys_information_second_directional_derivative_with_specs(
2780 &self,
2781 block_states: &[ParameterBlockState],
2782 specs: &[ParameterBlockSpec],
2783 d_beta_u_flat: &Array1<f64>,
2784 d_betav_flat: &Array1<f64>,
2785 ) -> Result<Option<Array2<f64>>, String> {
2786 self.expected_joint_information_second_directional_for_specs(
2787 block_states,
2788 Some(specs),
2789 d_beta_u_flat,
2790 d_betav_flat,
2791 )
2792 }
2793
2794 fn joint_jeffreys_information_contracted_trace_hessian_with_specs(
2795 &self,
2796 block_states: &[ParameterBlockState],
2797 specs: &[ParameterBlockSpec],
2798 weight: &Array2<f64>,
2799 ) -> Result<Option<Array2<f64>>, String> {
2800 self.expected_joint_contracted_trace_hessian_for_specs(block_states, Some(specs), weight)
2801 }
2802
2803 fn joint_jeffreys_information_contracted_trace_hessian_available(&self) -> bool {
2804 true
2805 }
2806
2807 fn joint_jeffreys_information_matches_observed_hessian(&self) -> bool {
2808 false
2816 }
2817
2818 fn exact_newton_joint_gradient_evaluation(
2819 &self,
2820 block_states: &[ParameterBlockState],
2821 specs: &[ParameterBlockSpec],
2822 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
2823 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2824 return Ok(None);
2825 };
2826 self.exact_newton_joint_gradient_from_designs(block_states, &x_t, &x_ls)
2827 .map(Some)
2828 }
2829
2830 fn exact_newton_joint_hessian_workspace(
2831 &self,
2832 block_states: &[ParameterBlockState],
2833 specs: &[ParameterBlockSpec],
2834 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2835 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2836 return Ok(None);
2837 };
2838 let workspace = BinomialLocationScaleHessianWorkspace::new(
2839 self.clone(),
2840 block_states.to_vec(),
2841 x_t,
2842 x_ls,
2843 )?;
2844 Ok(Some(Arc::new(workspace)))
2845 }
2846
2847 fn exact_newton_joint_hessian_workspace_with_options(
2861 &self,
2862 block_states: &[ParameterBlockState],
2863 specs: &[ParameterBlockSpec],
2864 options: &BlockwiseFitOptions,
2865 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2866 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2867 return Ok(None);
2868 };
2869 let mut workspace = BinomialLocationScaleHessianWorkspace::new(
2870 self.clone(),
2871 block_states.to_vec(),
2872 x_t,
2873 x_ls,
2874 )?;
2875 if let Some(subsample) = options.outer_score_subsample.as_ref() {
2876 workspace.apply_outer_subsample(subsample.rows.as_ref());
2877 }
2878 Ok(Some(Arc::new(workspace)))
2879 }
2880
2881 fn outer_derivative_subsample_capable(&self) -> bool {
2898 true
2899 }
2900
2901 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
2902 if specs.len() != 2 {
2906 return false;
2907 }
2908 let n = self.y.len();
2909 specs[Self::BLOCK_T].design.nrows() == n && specs[Self::BLOCK_LOG_SIGMA].design.nrows() == n
2910 }
2911}
2912
2913impl CustomFamilyGenerative for BinomialLocationScaleFamily {
2914 fn generativespec(
2915 &self,
2916 block_states: &[ParameterBlockState],
2917 ) -> Result<GenerativeSpec, String> {
2918 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2919 let eta_t = &block_states[Self::BLOCK_T].eta;
2920 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2921 if eta_t.len() != self.y.len() || eta_ls.len() != self.y.len() {
2922 return Err(GamlssError::DimensionMismatch {
2923 reason: "BinomialLocationScaleFamily generative size mismatch".to_string(),
2924 }
2925 .into());
2926 }
2927 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
2928 let sigma = exp_sigma_from_eta_scalar(eta_ls[i]);
2929 let q = binomial_location_scale_q0(eta_t[i], sigma);
2930 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
2931 .map_err(|e| format!("location-scale inverse-link evaluation failed: {e}"))?;
2932 Ok(jet.mu)
2933 })?;
2934 Ok(GenerativeSpec {
2935 mean,
2936 noise: NoiseModel::Bernoulli,
2937 })
2938 }
2939}