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 tower = binomial_location_scale_nll_tower(
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 false,
178 )?;
179 Ok((-tower.g[0], -tower.g[1]))
180 })
181 .collect();
182 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
183 grad_eta_t_v[i] = g_t;
184 grad_eta_ls_v[i] = g_ls;
185 }
186 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
187 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
188 let grad_t = x_t.transpose_vector_multiply(&grad_eta_t);
189 let grad_ls = x_ls.transpose_vector_multiply(&grad_eta_ls);
190 let total = grad_t.len() + grad_ls.len();
191 let mut gradient = Array1::<f64>::zeros(total);
192 gradient.slice_mut(s![0..grad_t.len()]).assign(&grad_t);
193 gradient.slice_mut(s![grad_t.len()..total]).assign(&grad_ls);
194 Ok(ExactNewtonJointGradientEvaluation {
195 log_likelihood: core.log_likelihood,
196 gradient,
197 })
198 }
199
200 pub(crate) fn exact_newton_joint_hessian_for_specs(
201 &self,
202 block_states: &[ParameterBlockState],
203 specs: Option<&[ParameterBlockSpec]>,
204 ) -> Result<Option<Array2<f64>>, String> {
205 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(specs)? else {
206 return Ok(None);
207 };
208 self.exact_newton_joint_hessian_from_design_matrices(block_states, &x_t, &x_ls)
209 }
210
211 pub(crate) fn exact_newton_joint_hessian_directional_derivative_for_specs(
212 &self,
213 block_states: &[ParameterBlockState],
214 specs: Option<&[ParameterBlockSpec]>,
215 d_beta_flat: &Array1<f64>,
216 ) -> Result<Option<Array2<f64>>, String> {
217 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
218 return Ok(None);
219 };
220 self.exact_newton_joint_hessian_directional_derivative_from_designs(
221 block_states,
222 &x_t,
223 &x_ls,
224 d_beta_flat,
225 )
226 }
227
228 pub(crate) fn exact_newton_joint_hessian_second_directional_derivative_for_specs(
229 &self,
230 block_states: &[ParameterBlockState],
231 specs: Option<&[ParameterBlockSpec]>,
232 d_beta_u_flat: &Array1<f64>,
233 d_betav_flat: &Array1<f64>,
234 ) -> Result<Option<Array2<f64>>, String> {
235 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
236 return Ok(None);
237 };
238 self.exact_newton_joint_hessiansecond_directional_derivative_from_designs(
239 block_states,
240 &x_t,
241 &x_ls,
242 d_beta_u_flat,
243 d_betav_flat,
244 )
245 }
246
247 pub(crate) fn expected_joint_information_from_designs(
248 &self,
249 block_states: &[ParameterBlockState],
250 x_t: &Array2<f64>,
251 x_ls: &Array2<f64>,
252 ) -> Result<Option<Array2<f64>>, String> {
253 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
254 let n = self.y.len();
255 let eta_t = &block_states[Self::BLOCK_T].eta;
256 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
257 if eta_t.len() != n
258 || eta_ls.len() != n
259 || self.weights.len() != n
260 || x_t.nrows() != n
261 || x_ls.nrows() != n
262 {
263 return Err(GamlssError::DimensionMismatch {
264 reason: "BinomialLocationScaleFamily expected information input size mismatch"
265 .to_string(),
266 }
267 .into());
268 }
269 let core = binomial_location_scale_core(
270 &self.y,
271 &self.weights,
272 eta_t,
273 eta_ls,
274 None,
275 &self.link_kind,
276 )?;
277 let rows: Vec<(f64, f64, f64)> = (0..n)
278 .into_par_iter()
279 .map(|i| {
280 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
281 let (f, _, _) = binomial_expected_q_information_derivatives(
282 self.weights[i],
283 core.mu[i],
284 core.dmu_dq[i],
285 core.d2mu_dq2[i],
286 core.d3mu_dq3[i],
287 );
288 (f * q.q_t * q.q_t, f * q.q_t * q.q_ls, f * q.q_ls * q.q_ls)
289 })
290 .collect();
291 let mut coeff_tt = Array1::<f64>::zeros(n);
292 let mut coeff_tl = Array1::<f64>::zeros(n);
293 let mut coeff_ll = Array1::<f64>::zeros(n);
294 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
295 coeff_tt[i] = tt;
296 coeff_tl[i] = tl;
297 coeff_ll[i] = ll;
298 }
299 let pt = x_t.ncols();
300 let pls = x_ls.ncols();
301 let total = pt + pls;
302 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
303 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
304 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
305 let mut h = Array2::<f64>::zeros((total, total));
306 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
307 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
308 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
309 mirror_upper_to_lower(&mut h);
310 Ok(Some(h))
311 }
312
313 pub(crate) fn expected_joint_information_directional_from_designs(
314 &self,
315 block_states: &[ParameterBlockState],
316 x_t: &Array2<f64>,
317 x_ls: &Array2<f64>,
318 d_beta_flat: &Array1<f64>,
319 ) -> Result<Option<Array2<f64>>, String> {
320 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
321 let n = self.y.len();
322 let eta_t = &block_states[Self::BLOCK_T].eta;
323 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
324 if eta_t.len() != n
325 || eta_ls.len() != n
326 || self.weights.len() != n
327 || x_t.nrows() != n
328 || x_ls.nrows() != n
329 {
330 return Err(GamlssError::DimensionMismatch {
331 reason: "BinomialLocationScaleFamily expected dI input size mismatch".to_string(),
332 }
333 .into());
334 }
335 let pt = x_t.ncols();
336 let pls = x_ls.ncols();
337 let total = pt + pls;
338 if d_beta_flat.len() != total {
339 return Err(GamlssError::DimensionMismatch {
340 reason: format!(
341 "BinomialLocationScaleFamily expected dI direction length mismatch: got {}, expected {}",
342 d_beta_flat.len(),
343 total
344 ),
345 }
346 .into());
347 }
348 let d_eta_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
349 let d_eta_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..total]));
350 let core = binomial_location_scale_core(
351 &self.y,
352 &self.weights,
353 eta_t,
354 eta_ls,
355 None,
356 &self.link_kind,
357 )?;
358 let rows: Vec<(f64, f64, f64)> = (0..n)
359 .into_par_iter()
360 .map(|i| {
361 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
362 let u = nonwiggle_q_directional(q, d_eta_t[i], d_eta_ls[i]);
363 let (f, f1, _) = binomial_expected_q_information_derivatives(
364 self.weights[i],
365 core.mu[i],
366 core.dmu_dq[i],
367 core.d2mu_dq2[i],
368 core.d3mu_dq3[i],
369 );
370 let tt = f1 * u.delta_q * q.q_t * q.q_t + 2.0 * f * q.q_t * u.delta_q_t;
371 let tl = f1 * u.delta_q * q.q_t * q.q_ls
372 + f * (u.delta_q_t * q.q_ls + q.q_t * u.delta_q_ls);
373 let ll = f1 * u.delta_q * q.q_ls * q.q_ls + 2.0 * f * q.q_ls * u.delta_q_ls;
374 (tt, tl, ll)
375 })
376 .collect();
377 let mut coeff_tt = Array1::<f64>::zeros(n);
378 let mut coeff_tl = Array1::<f64>::zeros(n);
379 let mut coeff_ll = Array1::<f64>::zeros(n);
380 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
381 coeff_tt[i] = tt;
382 coeff_tl[i] = tl;
383 coeff_ll[i] = ll;
384 }
385 let d_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
386 let d_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
387 let d_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
388 let mut d_h = Array2::<f64>::zeros((total, total));
389 d_h.slice_mut(s![0..pt, 0..pt]).assign(&d_h_tt);
390 d_h.slice_mut(s![0..pt, pt..total]).assign(&d_h_tl);
391 d_h.slice_mut(s![pt..total, pt..total]).assign(&d_h_ll);
392 mirror_upper_to_lower(&mut d_h);
393 Ok(Some(d_h))
394 }
395
396 pub(crate) fn expected_joint_information_second_directional_from_designs(
397 &self,
398 block_states: &[ParameterBlockState],
399 x_t: &Array2<f64>,
400 x_ls: &Array2<f64>,
401 d_beta_u_flat: &Array1<f64>,
402 d_betav_flat: &Array1<f64>,
403 ) -> Result<Option<Array2<f64>>, String> {
404 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
405 let n = self.y.len();
406 let eta_t = &block_states[Self::BLOCK_T].eta;
407 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
408 if eta_t.len() != n
409 || eta_ls.len() != n
410 || self.weights.len() != n
411 || x_t.nrows() != n
412 || x_ls.nrows() != n
413 {
414 return Err(GamlssError::DimensionMismatch {
415 reason: "BinomialLocationScaleFamily expected d2I input size mismatch".to_string(),
416 }
417 .into());
418 }
419 let pt = x_t.ncols();
420 let pls = x_ls.ncols();
421 let total = pt + pls;
422 if d_beta_u_flat.len() != total {
423 return Err(GamlssError::DimensionMismatch { reason: format!(
424 "BinomialLocationScaleFamily expected d2I u direction length mismatch: got {}, expected {}",
425 d_beta_u_flat.len(),
426 total
427 ) }.into());
428 }
429 if d_betav_flat.len() != total {
430 return Err(GamlssError::DimensionMismatch { reason: format!(
431 "BinomialLocationScaleFamily expected d2I v direction length mismatch: got {}, expected {}",
432 d_betav_flat.len(),
433 total
434 ) }.into());
435 }
436 let d_eta_t_u = fast_av(x_t, &d_beta_u_flat.slice(s![0..pt]));
437 let d_eta_ls_u = fast_av(x_ls, &d_beta_u_flat.slice(s![pt..total]));
438 let d_eta_t_v = fast_av(x_t, &d_betav_flat.slice(s![0..pt]));
439 let d_eta_ls_v = fast_av(x_ls, &d_betav_flat.slice(s![pt..total]));
440 let core = binomial_location_scale_core(
441 &self.y,
442 &self.weights,
443 eta_t,
444 eta_ls,
445 None,
446 &self.link_kind,
447 )?;
448 let rows: Vec<(f64, f64, f64)> = (0..n)
449 .into_par_iter()
450 .map(|i| {
451 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
452 let (f, f1, f2) = binomial_expected_q_information_derivatives(
453 self.weights[i],
454 core.mu[i],
455 core.dmu_dq[i],
456 core.d2mu_dq2[i],
457 core.d3mu_dq3[i],
458 );
459 binomial_expected_location_scale_second_coefficients(
460 q,
461 f,
462 f1,
463 f2,
464 d_eta_t_u[i],
465 d_eta_ls_u[i],
466 d_eta_t_v[i],
467 d_eta_ls_v[i],
468 )
469 })
470 .collect();
471 let mut coeff_tt = Array1::<f64>::zeros(n);
472 let mut coeff_tl = Array1::<f64>::zeros(n);
473 let mut coeff_ll = Array1::<f64>::zeros(n);
474 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
475 coeff_tt[i] = tt;
476 coeff_tl[i] = tl;
477 coeff_ll[i] = ll;
478 }
479 let d2_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
480 let d2_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
481 let d2_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
482 let mut d2_h = Array2::<f64>::zeros((total, total));
483 d2_h.slice_mut(s![0..pt, 0..pt]).assign(&d2_h_tt);
484 d2_h.slice_mut(s![0..pt, pt..total]).assign(&d2_h_tl);
485 d2_h.slice_mut(s![pt..total, pt..total]).assign(&d2_h_ll);
486 mirror_upper_to_lower(&mut d2_h);
487 Ok(Some(d2_h))
488 }
489
490 pub(crate) fn expected_joint_contracted_trace_hessian_from_designs(
491 &self,
492 block_states: &[ParameterBlockState],
493 x_t: &Array2<f64>,
494 x_ls: &Array2<f64>,
495 trace_weight: &Array2<f64>,
496 ) -> Result<Option<Array2<f64>>, String> {
497 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
498 let n = self.y.len();
499 let eta_t = &block_states[Self::BLOCK_T].eta;
500 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
501 if eta_t.len() != n
502 || eta_ls.len() != n
503 || self.weights.len() != n
504 || x_t.nrows() != n
505 || x_ls.nrows() != n
506 {
507 return Err(GamlssError::DimensionMismatch {
508 reason: "BinomialLocationScaleFamily expected contracted trace input size mismatch"
509 .to_string(),
510 }
511 .into());
512 }
513 let pt = x_t.ncols();
514 let pls = x_ls.ncols();
515 let total = pt + pls;
516 if trace_weight.dim() != (total, total) {
517 return Err(GamlssError::DimensionMismatch {
518 reason: format!(
519 "BinomialLocationScaleFamily expected contracted trace weight shape {:?} == ({total}, {total})",
520 trace_weight.dim()
521 ),
522 }
523 .into());
524 }
525 let core = binomial_location_scale_core(
526 &self.y,
527 &self.weights,
528 eta_t,
529 eta_ls,
530 None,
531 &self.link_kind,
532 )?;
533 let rows: Vec<(f64, f64, f64)> = (0..n)
534 .into_par_iter()
535 .map(|i| {
536 let mut trace_tt = 0.0;
537 for a in 0..pt {
538 for b in 0..pt {
539 trace_tt += x_t[[i, a]] * trace_weight[[a, b]] * x_t[[i, b]];
540 }
541 }
542 let mut trace_tl = 0.0;
543 for a in 0..pt {
544 for b in 0..pls {
545 trace_tl += x_t[[i, a]]
546 * (trace_weight[[a, pt + b]] + trace_weight[[pt + b, a]])
547 * x_ls[[i, b]];
548 }
549 }
550 let mut trace_ll = 0.0;
551 for a in 0..pls {
552 for b in 0..pls {
553 trace_ll += x_ls[[i, a]] * trace_weight[[pt + a, pt + b]] * x_ls[[i, b]];
554 }
555 }
556 let q = nonwiggle_q_derivs(eta_t[i], core.sigma[i]);
557 let (f, f1, f2) = binomial_expected_q_information_derivatives(
558 self.weights[i],
559 core.mu[i],
560 core.dmu_dq[i],
561 core.d2mu_dq2[i],
562 core.d3mu_dq3[i],
563 );
564 let (tt_tt, tt_tl, tt_ll) = binomial_expected_location_scale_second_coefficients(
565 q, f, f1, f2, 1.0, 0.0, 1.0, 0.0,
566 );
567 let (tl_tt, tl_tl, tl_ll) = binomial_expected_location_scale_second_coefficients(
568 q, f, f1, f2, 1.0, 0.0, 0.0, 1.0,
569 );
570 let (ll_tt, ll_tl, ll_ll) = binomial_expected_location_scale_second_coefficients(
571 q, f, f1, f2, 0.0, 1.0, 0.0, 1.0,
572 );
573 (
574 trace_tt * tt_tt + trace_tl * tt_tl + trace_ll * tt_ll,
575 trace_tt * tl_tt + trace_tl * tl_tl + trace_ll * tl_ll,
576 trace_tt * ll_tt + trace_tl * ll_tl + trace_ll * ll_ll,
577 )
578 })
579 .collect();
580 let mut coeff_tt = Array1::<f64>::zeros(n);
581 let mut coeff_tl = Array1::<f64>::zeros(n);
582 let mut coeff_ll = Array1::<f64>::zeros(n);
583 for (i, (tt, tl, ll)) in rows.into_iter().enumerate() {
584 coeff_tt[i] = tt;
585 coeff_tl[i] = tl;
586 coeff_ll[i] = ll;
587 }
588 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
589 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
590 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
591 let mut h = Array2::<f64>::zeros((total, total));
592 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
593 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
594 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
595 mirror_upper_to_lower(&mut h);
596 Ok(Some(h))
597 }
598
599 pub(crate) fn expected_joint_information_for_specs(
600 &self,
601 block_states: &[ParameterBlockState],
602 specs: Option<&[ParameterBlockSpec]>,
603 ) -> Result<Option<Array2<f64>>, String> {
604 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
605 return Ok(None);
606 };
607 self.expected_joint_information_from_designs(block_states, &x_t, &x_ls)
608 }
609
610 pub(crate) fn expected_joint_information_directional_for_specs(
611 &self,
612 block_states: &[ParameterBlockState],
613 specs: Option<&[ParameterBlockSpec]>,
614 d_beta_flat: &Array1<f64>,
615 ) -> Result<Option<Array2<f64>>, String> {
616 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
617 return Ok(None);
618 };
619 self.expected_joint_information_directional_from_designs(
620 block_states,
621 &x_t,
622 &x_ls,
623 d_beta_flat,
624 )
625 }
626
627 pub(crate) fn expected_joint_information_second_directional_for_specs(
628 &self,
629 block_states: &[ParameterBlockState],
630 specs: Option<&[ParameterBlockSpec]>,
631 d_beta_u_flat: &Array1<f64>,
632 d_betav_flat: &Array1<f64>,
633 ) -> Result<Option<Array2<f64>>, String> {
634 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
635 return Ok(None);
636 };
637 self.expected_joint_information_second_directional_from_designs(
638 block_states,
639 &x_t,
640 &x_ls,
641 d_beta_u_flat,
642 d_betav_flat,
643 )
644 }
645
646 pub(crate) fn expected_joint_contracted_trace_hessian_for_specs(
647 &self,
648 block_states: &[ParameterBlockState],
649 specs: Option<&[ParameterBlockSpec]>,
650 trace_weight: &Array2<f64>,
651 ) -> Result<Option<Array2<f64>>, String> {
652 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(specs)? else {
653 return Ok(None);
654 };
655 self.expected_joint_contracted_trace_hessian_from_designs(
656 block_states,
657 &x_t,
658 &x_ls,
659 trace_weight,
660 )
661 }
662
663 pub(crate) fn exact_newton_joint_psi_terms_for_specs(
664 &self,
665 block_states: &[ParameterBlockState],
666 specs: &[ParameterBlockSpec],
667 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
668 psi_index: usize,
669 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
670 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
671 return Ok(None);
672 };
673 self.exact_newton_joint_psi_terms_from_designs(
674 block_states,
675 specs,
676 derivative_blocks,
677 psi_index,
678 &x_t,
679 &x_ls,
680 )
681 }
682
683 pub(crate) fn exact_newton_joint_psisecond_order_terms_for_specs(
684 &self,
685 block_states: &[ParameterBlockState],
686 specs: &[ParameterBlockSpec],
687 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
688 psi_i: usize,
689 psi_j: usize,
690 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
691 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
692 return Ok(None);
693 };
694 self.exact_newton_joint_psisecond_order_terms_from_designs(
695 block_states,
696 derivative_blocks,
697 psi_i,
698 psi_j,
699 &x_t,
700 &x_ls,
701 )
702 }
703
704 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_for_specs(
705 &self,
706 block_states: &[ParameterBlockState],
707 specs: &[ParameterBlockSpec],
708 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
709 psi_index: usize,
710 d_beta_flat: &Array1<f64>,
711 ) -> Result<Option<Array2<f64>>, String> {
712 let Some((x_t, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
713 return Ok(None);
714 };
715 self.exact_newton_joint_psihessian_directional_derivative_from_designs(
716 block_states,
717 derivative_blocks,
718 psi_index,
719 d_beta_flat,
720 &x_t,
721 &x_ls,
722 )
723 }
724
725 pub(crate) fn exact_newton_joint_hessian_row_coefficients(
728 &self,
729 block_states: &[ParameterBlockState],
730 ) -> Result<(Array1<f64>, Array1<f64>, Array1<f64>), String> {
731 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
732 let n = self.y.len();
733 let eta_t = &block_states[Self::BLOCK_T].eta;
734 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
735 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
736 return Err(GamlssError::DimensionMismatch {
737 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
738 }
739 .into());
740 }
741
742 let core = binomial_location_scale_core(
743 &self.y,
744 &self.weights,
745 eta_t,
746 eta_ls,
747 None,
748 &self.link_kind,
749 )?;
750 let mut coeff_tt = vec![0.0_f64; n];
751 let mut coeff_tl = vec![0.0_f64; n];
752 let mut coeff_ll = vec![0.0_f64; n];
753 let y_slice = self.y.as_slice().expect("y must be contiguous");
754 let w_slice = self.weights.as_slice().expect("weights must be contiguous");
755 let q0_slice = core.q0.as_slice().expect("q0 must be contiguous");
756 let sigma_slice = core.sigma.as_slice().expect("sigma must be contiguous");
757 let dsigma_slice = core
758 .dsigma_deta
759 .as_slice()
760 .expect("dsigma_deta must be contiguous");
761 let mu_slice = core.mu.as_slice().expect("mu must be contiguous");
762 let dmu_slice = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
763 let d2mu_slice = core
764 .d2mu_dq2
765 .as_slice()
766 .expect("d2mu_dq2 must be contiguous");
767 let d3mu_slice = core
768 .d3mu_dq3
769 .as_slice()
770 .expect("d3mu_dq3 must be contiguous");
771 let link_kind = &self.link_kind;
772 coeff_tt
773 .par_iter_mut()
774 .zip(coeff_tl.par_iter_mut())
775 .zip(coeff_ll.par_iter_mut())
776 .enumerate()
777 .for_each(|(i, ((c_tt, c_tl), c_ll))| {
778 let q = q0_slice[i];
779 let r = 1.0 / sigma_slice[i];
780 let kappa = dsigma_slice[i] / sigma_slice[i];
781 let (m1, m2, _) = binomial_neglog_q_derivatives_dispatch(
782 y_slice[i],
783 w_slice[i],
784 q,
785 mu_slice[i],
786 dmu_slice[i],
787 d2mu_slice[i],
788 d3mu_slice[i],
789 link_kind,
790 );
791 *c_tt = m2 * r * r;
792 *c_tl = kappa * r * (m1 + q * m2);
793 *c_ll = kappa * kappa * q * (m1 + q * m2);
794 });
795 Ok((
796 Array1::from_vec(coeff_tt),
797 Array1::from_vec(coeff_tl),
798 Array1::from_vec(coeff_ll),
799 ))
800 }
801
802 pub(crate) fn exact_newton_block_diagonal_hessians_from_design_matrices(
806 &self,
807 block_states: &[ParameterBlockState],
808 x_t: &DesignMatrix,
809 x_ls: &DesignMatrix,
810 ) -> Result<(Array2<f64>, Array2<f64>), String> {
811 let (coeff_tt, _coeff_tl, coeff_ll) =
812 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
813 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
814 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
815 Ok((h_tt, h_ll))
816 }
817
818 pub(crate) fn exact_newton_joint_hessian_from_designs(
819 &self,
820 block_states: &[ParameterBlockState],
821 x_t: &Array2<f64>,
822 x_ls: &Array2<f64>,
823 ) -> Result<Option<Array2<f64>>, String> {
824 let (coeff_tt, coeff_tl, coeff_ll) =
881 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
882 let pt = x_t.ncols();
883 let pls = x_ls.ncols();
884
885 let h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
886 let h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
887 let h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
888 let total = pt + pls;
889 let mut h = Array2::<f64>::zeros((total, total));
890 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
891 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
892 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
893 mirror_upper_to_lower(&mut h);
894 Ok(Some(h))
895 }
896
897 pub(crate) fn exact_newton_joint_hessian_from_design_matrices(
898 &self,
899 block_states: &[ParameterBlockState],
900 x_t: &DesignMatrix,
901 x_ls: &DesignMatrix,
902 ) -> Result<Option<Array2<f64>>, String> {
903 if let (Some(x_t_dense), Some(x_ls_dense)) = (x_t.as_dense_ref(), x_ls.as_dense_ref()) {
904 return self.exact_newton_joint_hessian_from_designs(
905 block_states,
906 x_t_dense,
907 x_ls_dense,
908 );
909 }
910 let (coeff_tt, coeff_tl, coeff_ll) =
911 self.exact_newton_joint_hessian_row_coefficients(block_states)?;
912 let pt = x_t.ncols();
913 let pls = x_ls.ncols();
914
915 let h_tt = xt_diag_x_design(x_t, &coeff_tt)?;
916 let h_tl = xt_diag_y_design(x_t, &coeff_tl, x_ls)?;
917 let h_ll = xt_diag_x_design(x_ls, &coeff_ll)?;
918 let total = pt + pls;
919 let mut h = Array2::<f64>::zeros((total, total));
920 h.slice_mut(s![0..pt, 0..pt]).assign(&h_tt);
921 h.slice_mut(s![0..pt, pt..total]).assign(&h_tl);
922 h.slice_mut(s![pt..total, pt..total]).assign(&h_ll);
923 mirror_upper_to_lower(&mut h);
924 Ok(Some(h))
925 }
926
927 pub(crate) fn exact_newton_joint_hessian_directional_derivative_from_designs(
928 &self,
929 block_states: &[ParameterBlockState],
930 x_t: &Array2<f64>,
931 x_ls: &Array2<f64>,
932 d_beta_flat: &Array1<f64>,
933 ) -> Result<Option<Array2<f64>>, String> {
934 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
982 let n = self.y.len();
983 let eta_t = &block_states[Self::BLOCK_T].eta;
984 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
985 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
986 return Err(GamlssError::DimensionMismatch {
987 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
988 }
989 .into());
990 }
991
992 let pt = x_t.ncols();
993 let pls = x_ls.ncols();
994 if d_beta_flat.len() != pt + pls {
995 return Err(GamlssError::DimensionMismatch {
996 reason: format!(
997 "BinomialLocationScaleFamily joint d_beta length mismatch: got {}, expected {}",
998 d_beta_flat.len(),
999 pt + pls
1000 ),
1001 }
1002 .into());
1003 }
1004 let d_eta_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
1005 let d_eta_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
1006 let core = binomial_location_scale_core(
1007 &self.y,
1008 &self.weights,
1009 eta_t,
1010 eta_ls,
1011 None,
1012 &self.link_kind,
1013 )?;
1014 let (coeff_tt, coeff_tl, coeff_ll) =
1015 binomial_location_scale_first_directional_coefficients(
1016 &self.y,
1017 &self.weights,
1018 &core,
1019 &d_eta_t,
1020 &d_eta_ls,
1021 &self.link_kind,
1022 )?;
1023
1024 let d_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1025 let d_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1026 let d_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1027 let total = pt + pls;
1028 let mut d_h = Array2::<f64>::zeros((total, total));
1029 d_h.slice_mut(s![0..pt, 0..pt]).assign(&d_h_tt);
1030 d_h.slice_mut(s![0..pt, pt..total]).assign(&d_h_tl);
1031 d_h.slice_mut(s![pt..total, pt..total]).assign(&d_h_ll);
1032 mirror_upper_to_lower(&mut d_h);
1033 Ok(Some(d_h))
1034 }
1035
1036 pub(crate) fn exact_newton_joint_hessiansecond_directional_derivative_from_designs(
1037 &self,
1038 block_states: &[ParameterBlockState],
1039 x_t: &Array2<f64>,
1040 x_ls: &Array2<f64>,
1041 d_beta_u_flat: &Array1<f64>,
1042 d_betav_flat: &Array1<f64>,
1043 ) -> Result<Option<Array2<f64>>, String> {
1044 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1089 let n = self.y.len();
1090 let eta_t = &block_states[Self::BLOCK_T].eta;
1091 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1092 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1093 return Err(GamlssError::DimensionMismatch {
1094 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1095 }
1096 .into());
1097 }
1098
1099 let pt = x_t.ncols();
1100 let pls = x_ls.ncols();
1101 let total = pt + pls;
1102 if d_beta_u_flat.len() != total {
1103 return Err(GamlssError::DimensionMismatch { reason: format!(
1104 "BinomialLocationScaleFamily joint d_beta_u length mismatch: got {}, expected {}",
1105 d_beta_u_flat.len(),
1106 total
1107 ) }.into());
1108 }
1109 if d_betav_flat.len() != total {
1110 return Err(GamlssError::DimensionMismatch { reason: format!(
1111 "BinomialLocationScaleFamily joint d_betav length mismatch: got {}, expected {}",
1112 d_betav_flat.len(),
1113 total
1114 ) }.into());
1115 }
1116 let d_eta_t_u = fast_av(x_t, &d_beta_u_flat.slice(s![0..pt]));
1117 let d_eta_ls_u = fast_av(x_ls, &d_beta_u_flat.slice(s![pt..total]));
1118 let d_eta_tv = fast_av(x_t, &d_betav_flat.slice(s![0..pt]));
1119 let d_eta_lsv = fast_av(x_ls, &d_betav_flat.slice(s![pt..total]));
1120 let core = binomial_location_scale_core(
1121 &self.y,
1122 &self.weights,
1123 eta_t,
1124 eta_ls,
1125 None,
1126 &self.link_kind,
1127 )?;
1128 let (coeff_tt, coeff_tl, coeff_ll) =
1129 binomial_location_scalesecond_directional_coefficients(
1130 &self.y,
1131 &self.weights,
1132 &core,
1133 &d_eta_t_u,
1134 &d_eta_ls_u,
1135 &d_eta_tv,
1136 &d_eta_lsv,
1137 &self.link_kind,
1138 )?;
1139
1140 let d2_h_tt = xt_diag_x_dense(x_t, &coeff_tt)?;
1141 let d2_h_tl = xt_diag_y_dense(x_t, &coeff_tl, x_ls)?;
1142 let d2_h_ll = xt_diag_x_dense(x_ls, &coeff_ll)?;
1143 let mut d2_h = Array2::<f64>::zeros((total, total));
1144 d2_h.slice_mut(s![0..pt, 0..pt]).assign(&d2_h_tt);
1145 d2_h.slice_mut(s![0..pt, pt..total]).assign(&d2_h_tl);
1146 d2_h.slice_mut(s![pt..total, pt..total]).assign(&d2_h_ll);
1147 mirror_upper_to_lower(&mut d2_h);
1148 Ok(Some(d2_h))
1149 }
1150
1151 pub(crate) fn exact_newton_joint_psi_direction(
1152 &self,
1153 block_states: &[ParameterBlockState],
1154 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1155 psi_index: usize,
1156 x_t: &Array2<f64>,
1157 x_ls: &Array2<f64>,
1158 policy: &gam_runtime::resource::ResourcePolicy,
1159 ) -> Result<Option<LocationScaleJointPsiDirection>, String> {
1160 let Some(parts) = locscale_joint_psi_direction_parts(
1161 block_states,
1162 derivative_blocks,
1163 psi_index,
1164 self.y.len(),
1165 x_t.ncols(),
1166 x_ls.ncols(),
1167 Self::BLOCK_T,
1168 Self::BLOCK_LOG_SIGMA,
1169 2,
1170 "BinomialLocationScaleFamily",
1171 "threshold",
1172 policy,
1173 )?
1174 else {
1175 return Ok(None);
1176 };
1177 Ok(Some(LocationScaleJointPsiDirection {
1178 block_idx: parts.block_idx,
1179 local_idx: parts.local_idx,
1180 x_primary_psi: parts.primary_psi,
1181 x_ls_psi: parts.log_sigma_psi,
1182 z_primary_psi: parts.primary_z,
1183 z_ls_psi: parts.log_sigma_z,
1184 }))
1185 }
1186
1187 pub(crate) fn exact_newton_joint_psisecond_design_drifts(
1188 &self,
1189 block_states: &[ParameterBlockState],
1190 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1191 psi_a: &LocationScaleJointPsiDirection,
1192 psi_b: &LocationScaleJointPsiDirection,
1193 x_t: &Array2<f64>,
1194 x_ls: &Array2<f64>,
1195 ) -> Result<LocationScaleJointPsiSecondDrifts, String> {
1196 locscale_joint_psisecond_design_drifts(
1197 block_states,
1198 derivative_blocks,
1199 psi_a,
1200 psi_b,
1201 LocScalePsiDriftConfig {
1202 n: self.y.len(),
1203 p_primary: x_t.ncols(),
1204 p_log_sigma: x_ls.ncols(),
1205 primary_block_idx: Self::BLOCK_T,
1206 log_sigma_block_idx: Self::BLOCK_LOG_SIGMA,
1207 family_name: "BinomialLocationScaleFamily",
1208 primary_label: "threshold",
1209 policy: &self.policy,
1210 },
1211 )
1212 }
1213
1214 pub(crate) fn exact_newton_joint_psi_terms_from_designs(
1215 &self,
1216 block_states: &[ParameterBlockState],
1217 specs: &[ParameterBlockSpec],
1218 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1219 psi_index: usize,
1220 x_t: &Array2<f64>,
1221 x_ls: &Array2<f64>,
1222 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
1223 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
1224 if specs.len() != 2 || derivative_blocks.len() != 2 {
1225 return Err(GamlssError::DimensionMismatch { reason: format!(
1226 "BinomialLocationScaleFamily joint psi terms expect 2 specs and 2 derivative blocks, got {} and {}",
1227 specs.len(),
1228 derivative_blocks.len()
1229 ) }.into());
1230 }
1231 let n = self.y.len();
1232 let eta_t = &block_states[Self::BLOCK_T].eta;
1233 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1234 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
1235 return Err(GamlssError::DimensionMismatch {
1236 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
1237 }
1238 .into());
1239 }
1240
1241 let core = binomial_location_scale_core(
1337 &self.y,
1338 &self.weights,
1339 eta_t,
1340 eta_ls,
1341 None,
1342 &self.link_kind,
1343 )?;
1344 let pt = x_t.ncols();
1345 let pls = x_ls.ncols();
1346 let total = pt + pls;
1347 let Some(dir_a) = self.exact_newton_joint_psi_direction(
1348 block_states,
1349 derivative_blocks,
1350 psi_index,
1351 x_t,
1352 x_ls,
1353 &self.policy,
1354 )?
1355 else {
1356 return Ok(None);
1357 };
1358 let (z_t, z_ls) = (&dir_a.z_primary_psi, &dir_a.z_ls_psi);
1359
1360 struct PsiTermsRow {
1365 pub(crate) r_t: f64,
1366 pub(crate) r_ls: f64,
1367 pub(crate) dr_t: f64,
1368 pub(crate) dr_ls: f64,
1369 pub(crate) h_tt: f64,
1370 pub(crate) h_tl: f64,
1371 pub(crate) h_ll: f64,
1372 pub(crate) dh_tt: f64,
1373 pub(crate) dh_tl: f64,
1374 pub(crate) dh_ll: f64,
1375 pub(crate) obj: f64,
1376 }
1377 let y_p = self.y.as_slice().expect("y must be contiguous");
1378 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1379 let q0_p = core.q0.as_slice().expect("q0 must be contiguous");
1380 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1381 let dsigma_p = core
1382 .dsigma_deta
1383 .as_slice()
1384 .expect("dsigma_deta must be contiguous");
1385 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1386 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1387 let d2mu_p = core
1388 .d2mu_dq2
1389 .as_slice()
1390 .expect("d2mu_dq2 must be contiguous");
1391 let d3mu_p = core
1392 .d3mu_dq3
1393 .as_slice()
1394 .expect("d3mu_dq3 must be contiguous");
1395 let z_t_p = z_t.as_slice().expect("z_t must be contiguous");
1396 let z_ls_p = z_ls.as_slice().expect("z_ls must be contiguous");
1397 let link_kind_p = &self.link_kind;
1398 let rows: Vec<PsiTermsRow> = (0..n)
1399 .into_par_iter()
1400 .map(|i| {
1401 let q = q0_p[i];
1402 let r = 1.0 / sigma_p[i];
1403 let s = dsigma_p[i] / sigma_p[i];
1404 let sz = s * z_ls_p[i];
1405 let q_psi = -r * z_t_p[i] - q * sz;
1406 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1407 y_p[i],
1408 w_p[i],
1409 q,
1410 mu_p[i],
1411 dmu_p[i],
1412 d2mu_p[i],
1413 d3mu_p[i],
1414 link_kind_p,
1415 );
1416 let r_t = -a * r;
1417 let r_ls = -a * q * s;
1418 PsiTermsRow {
1419 r_t,
1420 r_ls,
1421 dr_t: -b * q_psi * r + a * r * sz,
1422 dr_ls: -(a + q * b) * q_psi,
1423 h_tt: b * r * r,
1424 h_tl: r * (a + q * b),
1425 h_ll: q * (a + q * b),
1426 dh_tt: r * r * (c * q_psi - 2.0 * b * sz),
1427 dh_tl: r * ((2.0 * b + c * q) * q_psi - (a + q * b) * sz),
1428 dh_ll: (a + 3.0 * q * b + q * q * c) * q_psi,
1429 obj: r_t * z_t_p[i] + r_ls * z_ls_p[i],
1430 }
1431 })
1432 .collect();
1433 let mut r_t = Array1::<f64>::zeros(n);
1434 let mut r_ls = Array1::<f64>::zeros(n);
1435 let mut dr_t = Array1::<f64>::zeros(n);
1436 let mut dr_ls = Array1::<f64>::zeros(n);
1437 let mut h_tt = Array1::<f64>::zeros(n);
1438 let mut h_tl = Array1::<f64>::zeros(n);
1439 let mut h_ll = Array1::<f64>::zeros(n);
1440 let mut dh_tt = Array1::<f64>::zeros(n);
1441 let mut dh_tl = Array1::<f64>::zeros(n);
1442 let mut dh_ll = Array1::<f64>::zeros(n);
1443 let mut objective_psi = 0.0_f64;
1444 for (i, row) in rows.into_iter().enumerate() {
1445 r_t[i] = row.r_t;
1446 r_ls[i] = row.r_ls;
1447 dr_t[i] = row.dr_t;
1448 dr_ls[i] = row.dr_ls;
1449 h_tt[i] = row.h_tt;
1450 h_tl[i] = row.h_tl;
1451 h_ll[i] = row.h_ll;
1452 dh_tt[i] = row.dh_tt;
1453 dh_tl[i] = row.dh_tl;
1454 dh_ll[i] = row.dh_ll;
1455 objective_psi += row.obj;
1456 }
1457
1458 let hessian_psi_operator = build_two_block_custom_family_joint_psi_operator_from_actions(
1459 dir_a.x_primary_psi.cloned_first_action(),
1460 dir_a.x_ls_psi.cloned_first_action(),
1461 0..pt,
1462 pt..pt + pls,
1463 x_t,
1464 x_ls,
1465 &h_tt,
1466 &h_tl,
1467 &h_ll,
1468 &dh_tt,
1469 &dh_tl,
1470 &dh_ll,
1471 )?;
1472 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
1473 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
1474 let score_t = x_t_map.transpose_mul(r_t.view()) + fast_atv(x_t, &dr_t);
1475 let score_ls = x_ls_map.transpose_mul(r_ls.view()) + fast_atv(x_ls, &dr_ls);
1476 let mut score_psi = Array1::<f64>::zeros(total);
1477 score_psi.slice_mut(s![0..pt]).assign(&score_t);
1478 score_psi.slice_mut(s![pt..pt + pls]).assign(&score_ls);
1479 let hessian_psi = if hessian_psi_operator.is_some() {
1480 Array2::zeros((0, 0))
1481 } else {
1482 let h_tt_block = weighted_crossprod_psi_maps(
1483 x_t_map,
1484 h_tt.view(),
1485 CustomFamilyPsiLinearMapRef::Dense(x_t),
1486 )? + &weighted_crossprod_psi_maps(
1487 CustomFamilyPsiLinearMapRef::Dense(x_t),
1488 h_tt.view(),
1489 x_t_map,
1490 )? + &xt_diag_x_dense(x_t, &dh_tt)?;
1491 let h_tl_block = weighted_crossprod_psi_maps(
1492 x_t_map,
1493 h_tl.view(),
1494 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1495 )? + &weighted_crossprod_psi_maps(
1496 CustomFamilyPsiLinearMapRef::Dense(x_t),
1497 h_tl.view(),
1498 x_ls_map,
1499 )? + &xt_diag_y_dense(x_t, &dh_tl, x_ls)?;
1500 let h_ll_block = weighted_crossprod_psi_maps(
1501 x_ls_map,
1502 h_ll.view(),
1503 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1504 )? + &weighted_crossprod_psi_maps(
1505 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1506 h_ll.view(),
1507 x_ls_map,
1508 )? + &xt_diag_x_dense(x_ls, &dh_ll)?;
1509
1510 let mut hessian_psi = Array2::<f64>::zeros((total, total));
1511 hessian_psi.slice_mut(s![0..pt, 0..pt]).assign(&h_tt_block);
1512 hessian_psi
1513 .slice_mut(s![0..pt, pt..pt + pls])
1514 .assign(&h_tl_block);
1515 hessian_psi
1516 .slice_mut(s![pt..pt + pls, pt..pt + pls])
1517 .assign(&h_ll_block);
1518 mirror_upper_to_lower(&mut hessian_psi);
1519 hessian_psi
1520 };
1521
1522 Ok(Some(crate::custom_family::ExactNewtonJointPsiTerms {
1523 objective_psi,
1524 score_psi,
1525 hessian_psi,
1526 hessian_psi_operator,
1527 }))
1528 }
1529
1530 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_designs(
1531 &self,
1532 block_states: &[ParameterBlockState],
1533 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1534 psi_i: usize,
1535 psi_j: usize,
1536 x_t: &Array2<f64>,
1537 x_ls: &Array2<f64>,
1538 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
1539 let Some(dir_i) = self.exact_newton_joint_psi_direction(
1540 block_states,
1541 derivative_blocks,
1542 psi_i,
1543 x_t,
1544 x_ls,
1545 &self.policy,
1546 )?
1547 else {
1548 return Ok(None);
1549 };
1550 let Some(dir_j) = self.exact_newton_joint_psi_direction(
1551 block_states,
1552 derivative_blocks,
1553 psi_j,
1554 x_t,
1555 x_ls,
1556 &self.policy,
1557 )?
1558 else {
1559 return Ok(None);
1560 };
1561 Ok(Some(
1562 self.exact_newton_joint_psisecond_order_terms_from_parts(
1563 block_states,
1564 derivative_blocks,
1565 &dir_i,
1566 &dir_j,
1567 x_t,
1568 x_ls,
1569 )?,
1570 ))
1571 }
1572
1573 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_parts(
1574 &self,
1575 block_states: &[ParameterBlockState],
1576 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1577 dir_i: &LocationScaleJointPsiDirection,
1578 dir_j: &LocationScaleJointPsiDirection,
1579 x_t: &Array2<f64>,
1580 x_ls: &Array2<f64>,
1581 ) -> Result<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms, String> {
1582 let second_drifts = self.exact_newton_joint_psisecond_design_drifts(
1583 block_states,
1584 derivative_blocks,
1585 dir_i,
1586 dir_j,
1587 x_t,
1588 x_ls,
1589 )?;
1590 let n = self.y.len();
1591 let eta_t = &block_states[Self::BLOCK_T].eta;
1592 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1593 let core = binomial_location_scale_core(
1594 &self.y,
1595 &self.weights,
1596 eta_t,
1597 eta_ls,
1598 None,
1599 &self.link_kind,
1600 )?;
1601 let pt = x_t.ncols();
1602 let pls = x_ls.ncols();
1603 let total = pt + pls;
1604 let x_t_i_map = dir_i.x_primary_psi.as_linear_map_ref();
1605 let x_t_j_map = dir_j.x_primary_psi.as_linear_map_ref();
1606 let x_ls_i_map = dir_i.x_ls_psi.as_linear_map_ref();
1607 let x_ls_j_map = dir_j.x_ls_psi.as_linear_map_ref();
1608 let x_t_ab_map = second_psi_linear_map(
1609 second_drifts.x_primary_ab_action.as_ref(),
1610 second_drifts.x_primary_ab.as_ref(),
1611 n,
1612 pt,
1613 );
1614 let x_ls_ab_map = second_psi_linear_map(
1615 second_drifts.x_ls_ab_action.as_ref(),
1616 second_drifts.x_ls_ab.as_ref(),
1617 n,
1618 pls,
1619 );
1620
1621 let mut r_t = Array1::<f64>::zeros(n);
1735 let mut r_ls = Array1::<f64>::zeros(n);
1736 let mut dr_t_i = Array1::<f64>::zeros(n);
1737 let mut dr_t_j = Array1::<f64>::zeros(n);
1738 let mut dr_ls_i = Array1::<f64>::zeros(n);
1739 let mut dr_ls_j = Array1::<f64>::zeros(n);
1740 let mut d2r_t = Array1::<f64>::zeros(n);
1741 let mut d2r_ls = Array1::<f64>::zeros(n);
1742 let mut h_tt = Array1::<f64>::zeros(n);
1743 let mut h_tl = Array1::<f64>::zeros(n);
1744 let mut h_ll = Array1::<f64>::zeros(n);
1745 let mut dh_tt_i = Array1::<f64>::zeros(n);
1746 let mut dh_tt_j = Array1::<f64>::zeros(n);
1747 let mut dh_tl_i = Array1::<f64>::zeros(n);
1748 let mut dh_tl_j = Array1::<f64>::zeros(n);
1749 let mut dh_ll_i = Array1::<f64>::zeros(n);
1750 let mut dh_ll_j = Array1::<f64>::zeros(n);
1751 let mut d2h_tt = Array1::<f64>::zeros(n);
1752 let mut d2h_tl = Array1::<f64>::zeros(n);
1753 let mut d2h_ll = Array1::<f64>::zeros(n);
1754 let mut objective_psi_psi = 0.0;
1755 struct PsiSecondRow {
1756 pub(crate) r_t: f64,
1757 pub(crate) r_ls: f64,
1758 pub(crate) dr_t_i: f64,
1759 pub(crate) dr_t_j: f64,
1760 pub(crate) dr_ls_i: f64,
1761 pub(crate) dr_ls_j: f64,
1762 pub(crate) d2r_t: f64,
1763 pub(crate) d2r_ls: f64,
1764 pub(crate) h_tt: f64,
1765 pub(crate) h_tl: f64,
1766 pub(crate) h_ll: f64,
1767 pub(crate) dh_tt_i: f64,
1768 pub(crate) dh_tt_j: f64,
1769 pub(crate) dh_tl_i: f64,
1770 pub(crate) dh_tl_j: f64,
1771 pub(crate) dh_ll_i: f64,
1772 pub(crate) dh_ll_j: f64,
1773 pub(crate) d2h_tt: f64,
1774 pub(crate) d2h_tl: f64,
1775 pub(crate) d2h_ll: f64,
1776 pub(crate) objective: f64,
1777 }
1778 let y_p = self.y.as_slice().expect("y must be contiguous");
1779 let w_p = self.weights.as_slice().expect("weights must be contiguous");
1780 let q_p = core.q0.as_slice().expect("q0 must be contiguous");
1781 let sigma_p = core.sigma.as_slice().expect("sigma must be contiguous");
1782 let mu_p = core.mu.as_slice().expect("mu must be contiguous");
1783 let dmu_p = core.dmu_dq.as_slice().expect("dmu_dq must be contiguous");
1784 let d2mu_p = core
1785 .d2mu_dq2
1786 .as_slice()
1787 .expect("d2mu_dq2 must be contiguous");
1788 let d3mu_p = core
1789 .d3mu_dq3
1790 .as_slice()
1791 .expect("d3mu_dq3 must be contiguous");
1792 let z_t_i = dir_i
1793 .z_primary_psi
1794 .as_slice()
1795 .expect("z_t_psi_i must be contiguous");
1796 let z_t_j = dir_j
1797 .z_primary_psi
1798 .as_slice()
1799 .expect("z_t_psi_j must be contiguous");
1800 let z_ls_i = dir_i
1801 .z_ls_psi
1802 .as_slice()
1803 .expect("z_ls_psi_i must be contiguous");
1804 let z_ls_j = dir_j
1805 .z_ls_psi
1806 .as_slice()
1807 .expect("z_ls_psi_j must be contiguous");
1808 let z_t_ab = second_drifts
1809 .z_primary_ab
1810 .as_slice()
1811 .expect("z_t_ab must be contiguous");
1812 let z_ls_ab = second_drifts
1813 .z_ls_ab
1814 .as_slice()
1815 .expect("z_ls_ab must be contiguous");
1816 let link_kind_p = &self.link_kind;
1817 let rows: Result<Vec<PsiSecondRow>, String> = (0..n)
1818 .into_par_iter()
1819 .map(|row| {
1820 let q = q_p[row];
1821 let r = 1.0 / sigma_p[row];
1822 let q_i = -r * z_t_i[row] - q * z_ls_i[row];
1823 let q_j = -r * z_t_j[row] - q * z_ls_j[row];
1824 let q_ij = -r * z_t_ab[row]
1825 + r * (z_t_i[row] * z_ls_j[row] + z_t_j[row] * z_ls_i[row])
1826 + q * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row]);
1827 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
1828 y_p[row],
1829 w_p[row],
1830 q,
1831 mu_p[row],
1832 dmu_p[row],
1833 d2mu_p[row],
1834 d3mu_p[row],
1835 link_kind_p,
1836 );
1837 let d = binomial_neglog_q_fourth_derivative_dispatch(
1838 y_p[row],
1839 w_p[row],
1840 q,
1841 mu_p[row],
1842 dmu_p[row],
1843 d2mu_p[row],
1844 d3mu_p[row],
1845 link_kind_p,
1846 )?;
1847 let u = a + q * b;
1848 let u_i = (2.0 * b + q * c) * q_i;
1849 let u_j = (2.0 * b + q * c) * q_j;
1850 Ok(PsiSecondRow {
1851 r_t: -a * r,
1852 r_ls: -a * q,
1853 dr_t_i: -b * q_i * r + a * r * z_ls_i[row],
1854 dr_t_j: -b * q_j * r + a * r * z_ls_j[row],
1855 dr_ls_i: -u * q_i,
1856 dr_ls_j: -u * q_j,
1857 d2r_t: r
1858 * (-c * q_i * q_j - b * q_ij + b * (q_i * z_ls_j[row] + q_j * z_ls_i[row])
1859 - a * z_ls_i[row] * z_ls_j[row]
1860 + a * z_ls_ab[row]),
1861 d2r_ls: -((2.0 * b + q * c) * q_i * q_j + u * q_ij),
1862 h_tt: b * r * r,
1863 h_tl: r * u,
1864 h_ll: q * u,
1865 dh_tt_i: r * r * (c * q_i - 2.0 * b * z_ls_i[row]),
1866 dh_tt_j: r * r * (c * q_j - 2.0 * b * z_ls_j[row]),
1867 dh_tl_i: r * (u_i - u * z_ls_i[row]),
1868 dh_tl_j: r * (u_j - u * z_ls_j[row]),
1869 dh_ll_i: (a + 3.0 * q * b + q * q * c) * q_i,
1870 dh_ll_j: (a + 3.0 * q * b + q * q * c) * q_j,
1871 d2h_tt: r
1872 * r
1873 * (d * q_i * q_j + c * q_ij
1874 - 2.0 * c * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1875 + 4.0 * b * z_ls_i[row] * z_ls_j[row]
1876 - 2.0 * b * z_ls_ab[row]),
1877 d2h_tl: r
1878 * (((3.0 * c + q * d) * q_j) * q_i + (2.0 * b + q * c) * q_ij
1879 - (2.0 * b + q * c) * (q_j * z_ls_i[row] + q_i * z_ls_j[row])
1880 + u * (z_ls_i[row] * z_ls_j[row] - z_ls_ab[row])),
1881 d2h_ll: (4.0 * b + 5.0 * q * c + q * q * d) * q_i * q_j
1882 + (a + 3.0 * q * b + q * q * c) * q_ij,
1883 objective: a * q_ij + b * q_i * q_j,
1884 })
1885 })
1886 .collect();
1887 for (row, vals) in rows?.into_iter().enumerate() {
1888 r_t[row] = vals.r_t;
1889 r_ls[row] = vals.r_ls;
1890 dr_t_i[row] = vals.dr_t_i;
1891 dr_t_j[row] = vals.dr_t_j;
1892 dr_ls_i[row] = vals.dr_ls_i;
1893 dr_ls_j[row] = vals.dr_ls_j;
1894 d2r_t[row] = vals.d2r_t;
1895 d2r_ls[row] = vals.d2r_ls;
1896 h_tt[row] = vals.h_tt;
1897 h_tl[row] = vals.h_tl;
1898 h_ll[row] = vals.h_ll;
1899 dh_tt_i[row] = vals.dh_tt_i;
1900 dh_tt_j[row] = vals.dh_tt_j;
1901 dh_tl_i[row] = vals.dh_tl_i;
1902 dh_tl_j[row] = vals.dh_tl_j;
1903 dh_ll_i[row] = vals.dh_ll_i;
1904 dh_ll_j[row] = vals.dh_ll_j;
1905 d2h_tt[row] = vals.d2h_tt;
1906 d2h_tl[row] = vals.d2h_tl;
1907 d2h_ll[row] = vals.d2h_ll;
1908 objective_psi_psi += vals.objective;
1909 }
1910 let mut score_psi_psi = Array1::<f64>::zeros(total);
1911 score_psi_psi.slice_mut(s![0..pt]).assign(
1912 &(x_t_ab_map.transpose_mul(r_t.view())
1913 + x_t_i_map.transpose_mul(dr_t_j.view())
1914 + x_t_j_map.transpose_mul(dr_t_i.view())
1915 + fast_atv(x_t, &d2r_t)),
1916 );
1917 score_psi_psi.slice_mut(s![pt..pt + pls]).assign(
1918 &(x_ls_ab_map.transpose_mul(r_ls.view())
1919 + x_ls_i_map.transpose_mul(dr_ls_j.view())
1920 + x_ls_j_map.transpose_mul(dr_ls_i.view())
1921 + fast_atv(x_ls, &d2r_ls)),
1922 );
1923
1924 let h_tt_block = weighted_crossprod_psi_maps(
1925 x_t_ab_map,
1926 h_tt.view(),
1927 CustomFamilyPsiLinearMapRef::Dense(x_t),
1928 )? + &weighted_crossprod_psi_maps(x_t_i_map, h_tt.view(), x_t_j_map)?
1929 + &weighted_crossprod_psi_maps(x_t_j_map, h_tt.view(), x_t_i_map)?
1930 + &weighted_crossprod_psi_maps(
1931 x_t_i_map,
1932 dh_tt_j.view(),
1933 CustomFamilyPsiLinearMapRef::Dense(x_t),
1934 )?
1935 + &weighted_crossprod_psi_maps(
1936 x_t_j_map,
1937 dh_tt_i.view(),
1938 CustomFamilyPsiLinearMapRef::Dense(x_t),
1939 )?
1940 + &weighted_crossprod_psi_maps(
1941 CustomFamilyPsiLinearMapRef::Dense(x_t),
1942 dh_tt_i.view(),
1943 x_t_j_map,
1944 )?
1945 + &weighted_crossprod_psi_maps(
1946 CustomFamilyPsiLinearMapRef::Dense(x_t),
1947 dh_tt_j.view(),
1948 x_t_i_map,
1949 )?
1950 + &xt_diag_x_dense(x_t, &d2h_tt)?
1951 + &weighted_crossprod_psi_maps(
1952 CustomFamilyPsiLinearMapRef::Dense(x_t),
1953 h_tt.view(),
1954 x_t_ab_map,
1955 )?;
1956 let h_tl_block = weighted_crossprod_psi_maps(
1957 x_t_ab_map,
1958 h_tl.view(),
1959 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1960 )? + &weighted_crossprod_psi_maps(x_t_i_map, h_tl.view(), x_ls_j_map)?
1961 + &weighted_crossprod_psi_maps(x_t_j_map, h_tl.view(), x_ls_i_map)?
1962 + &weighted_crossprod_psi_maps(
1963 x_t_i_map,
1964 dh_tl_j.view(),
1965 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1966 )?
1967 + &weighted_crossprod_psi_maps(
1968 x_t_j_map,
1969 dh_tl_i.view(),
1970 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1971 )?
1972 + &weighted_crossprod_psi_maps(
1973 CustomFamilyPsiLinearMapRef::Dense(x_t),
1974 dh_tl_i.view(),
1975 x_ls_j_map,
1976 )?
1977 + &weighted_crossprod_psi_maps(
1978 CustomFamilyPsiLinearMapRef::Dense(x_t),
1979 dh_tl_j.view(),
1980 x_ls_i_map,
1981 )?
1982 + &xt_diag_y_dense(x_t, &d2h_tl, x_ls)?
1983 + &weighted_crossprod_psi_maps(
1984 CustomFamilyPsiLinearMapRef::Dense(x_t),
1985 h_tl.view(),
1986 x_ls_ab_map,
1987 )?;
1988 let h_ll_block = weighted_crossprod_psi_maps(
1989 x_ls_ab_map,
1990 h_ll.view(),
1991 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1992 )? + &weighted_crossprod_psi_maps(x_ls_i_map, h_ll.view(), x_ls_j_map)?
1993 + &weighted_crossprod_psi_maps(x_ls_j_map, h_ll.view(), x_ls_i_map)?
1994 + &weighted_crossprod_psi_maps(
1995 x_ls_i_map,
1996 dh_ll_j.view(),
1997 CustomFamilyPsiLinearMapRef::Dense(x_ls),
1998 )?
1999 + &weighted_crossprod_psi_maps(
2000 x_ls_j_map,
2001 dh_ll_i.view(),
2002 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2003 )?
2004 + &weighted_crossprod_psi_maps(
2005 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2006 dh_ll_i.view(),
2007 x_ls_j_map,
2008 )?
2009 + &weighted_crossprod_psi_maps(
2010 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2011 dh_ll_j.view(),
2012 x_ls_i_map,
2013 )?
2014 + &xt_diag_x_dense(x_ls, &d2h_ll)?
2015 + &weighted_crossprod_psi_maps(
2016 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2017 h_ll.view(),
2018 x_ls_ab_map,
2019 )?;
2020
2021 let mut hessian_psi_psi = Array2::<f64>::zeros((total, total));
2022 hessian_psi_psi
2023 .slice_mut(s![0..pt, 0..pt])
2024 .assign(&h_tt_block);
2025 hessian_psi_psi
2026 .slice_mut(s![0..pt, pt..pt + pls])
2027 .assign(&h_tl_block);
2028 hessian_psi_psi
2029 .slice_mut(s![pt..pt + pls, pt..pt + pls])
2030 .assign(&h_ll_block);
2031 mirror_upper_to_lower(&mut hessian_psi_psi);
2032
2033 Ok(crate::custom_family::ExactNewtonJointPsiSecondOrderTerms {
2034 objective_psi_psi,
2035 score_psi_psi,
2036 hessian_psi_psi,
2037 hessian_psi_psi_operator: None,
2038 })
2039 }
2040
2041 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_designs(
2042 &self,
2043 block_states: &[ParameterBlockState],
2044 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2045 psi_index: usize,
2046 d_beta_flat: &Array1<f64>,
2047 x_t: &Array2<f64>,
2048 x_ls: &Array2<f64>,
2049 ) -> Result<Option<Array2<f64>>, String> {
2050 let Some(dir_a) = self.exact_newton_joint_psi_direction(
2051 block_states,
2052 derivative_blocks,
2053 psi_index,
2054 x_t,
2055 x_ls,
2056 &self.policy,
2057 )?
2058 else {
2059 return Ok(None);
2060 };
2061 Ok(Some(
2062 self.exact_newton_joint_psihessian_directional_derivative_from_parts(
2063 block_states,
2064 &dir_a,
2065 d_beta_flat,
2066 x_t,
2067 x_ls,
2068 )?,
2069 ))
2070 }
2071
2072 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_parts(
2073 &self,
2074 block_states: &[ParameterBlockState],
2075 dir_a: &LocationScaleJointPsiDirection,
2076 d_beta_flat: &Array1<f64>,
2077 x_t: &Array2<f64>,
2078 x_ls: &Array2<f64>,
2079 ) -> Result<Array2<f64>, String> {
2080 let n = self.y.len();
2081 let eta_t = &block_states[Self::BLOCK_T].eta;
2082 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2083 let core = binomial_location_scale_core(
2084 &self.y,
2085 &self.weights,
2086 eta_t,
2087 eta_ls,
2088 None,
2089 &self.link_kind,
2090 )?;
2091 let pt = x_t.ncols();
2092 let pls = x_ls.ncols();
2093 let total = pt + pls;
2094 if d_beta_flat.len() != total {
2095 return Err(GamlssError::DimensionMismatch { reason: format!(
2096 "BinomialLocationScaleFamily joint psi hessian directional derivative length mismatch: got {}, expected {}",
2097 d_beta_flat.len(),
2098 total
2099 ) }.into());
2100 }
2101 let xi_t = fast_av(x_t, &d_beta_flat.slice(s![0..pt]));
2102 let xi_ls = fast_av(x_ls, &d_beta_flat.slice(s![pt..pt + pls]));
2103 let x_t_map = dir_a.x_primary_psi.as_linear_map_ref();
2104 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
2105
2106 let mut dh_tt_u = Array1::<f64>::zeros(n);
2157 let mut dh_tl_u = Array1::<f64>::zeros(n);
2158 let mut dh_ll_u = Array1::<f64>::zeros(n);
2159 let mut h_tt_u = Array1::<f64>::zeros(n);
2160 let mut h_tl_u = Array1::<f64>::zeros(n);
2161 let mut h_ll_u = Array1::<f64>::zeros(n);
2162 for row in 0..n {
2163 let q = core.q0[row];
2164 let r = 1.0 / core.sigma[row];
2165 let s = core.dsigma_deta[row] / core.sigma[row];
2166 let xi_ls_s = s * xi_ls[row];
2167 let z_ls_psi_s = s * dir_a.z_ls_psi[row];
2168 let du = -r * xi_t[row] - q * xi_ls_s;
2169 let q_a = -r * dir_a.z_primary_psi[row] - q * z_ls_psi_s;
2170 let q_au = r * dir_a.z_primary_psi[row] * xi_ls_s - du * z_ls_psi_s;
2171 let (a, b, c) = binomial_neglog_q_derivatives_dispatch(
2172 self.y[row],
2173 self.weights[row],
2174 q,
2175 core.mu[row],
2176 core.dmu_dq[row],
2177 core.d2mu_dq2[row],
2178 core.d3mu_dq3[row],
2179 &self.link_kind,
2180 );
2181 let d = binomial_neglog_q_fourth_derivative_dispatch(
2182 self.y[row],
2183 self.weights[row],
2184 q,
2185 core.mu[row],
2186 core.dmu_dq[row],
2187 core.d2mu_dq2[row],
2188 core.d3mu_dq3[row],
2189 &self.link_kind,
2190 )?;
2191 let u = a + q * b;
2192 h_tt_u[row] = r * r * (c * du - 2.0 * b * xi_ls_s);
2193 h_tl_u[row] = r * ((2.0 * b + q * c) * du - u * xi_ls_s);
2194 h_ll_u[row] = (a + 3.0 * q * b + q * q * c) * du;
2195 dh_tt_u[row] = r
2196 * r
2197 * (d * du * q_a + c * q_au - 2.0 * c * (q_a * xi_ls_s + du * z_ls_psi_s)
2198 + 4.0 * b * xi_ls_s * z_ls_psi_s);
2199 dh_tl_u[row] = r
2200 * (((3.0 * c + q * d) * q_a) * du + (2.0 * b + q * c) * q_au
2201 - (2.0 * b + q * c) * (q_a * xi_ls_s + du * z_ls_psi_s)
2202 + u * xi_ls_s * z_ls_psi_s);
2203 dh_ll_u[row] = (4.0 * b + 5.0 * q * c + q * q * d) * du * q_a
2204 + (a + 3.0 * q * b + q * q * c) * q_au;
2205 }
2206
2207 let tt_block = weighted_crossprod_psi_maps(
2208 x_t_map,
2209 h_tt_u.view(),
2210 CustomFamilyPsiLinearMapRef::Dense(x_t),
2211 )? + &weighted_crossprod_psi_maps(
2212 CustomFamilyPsiLinearMapRef::Dense(x_t),
2213 h_tt_u.view(),
2214 x_t_map,
2215 )? + &xt_diag_x_dense(x_t, &dh_tt_u)?;
2216 let tl_block = weighted_crossprod_psi_maps(
2217 x_t_map,
2218 h_tl_u.view(),
2219 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2220 )? + &weighted_crossprod_psi_maps(
2221 CustomFamilyPsiLinearMapRef::Dense(x_t),
2222 h_tl_u.view(),
2223 x_ls_map,
2224 )? + &xt_diag_y_dense(x_t, &dh_tl_u, x_ls)?;
2225 let ll_block = weighted_crossprod_psi_maps(
2226 x_ls_map,
2227 h_ll_u.view(),
2228 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2229 )? + &weighted_crossprod_psi_maps(
2230 CustomFamilyPsiLinearMapRef::Dense(x_ls),
2231 h_ll_u.view(),
2232 x_ls_map,
2233 )? + &xt_diag_x_dense(x_ls, &dh_ll_u)?;
2234 let mut out = Array2::<f64>::zeros((total, total));
2235 out.slice_mut(s![0..pt, 0..pt]).assign(&tt_block);
2236 out.slice_mut(s![0..pt, pt..pt + pls]).assign(&tl_block);
2237 out.slice_mut(s![pt..pt + pls, pt..pt + pls])
2238 .assign(&ll_block);
2239 mirror_upper_to_lower(&mut out);
2240 Ok(out)
2241 }
2242
2243 pub fn block_effective_jacobian(
2249 specs: &[ParameterBlockSpec],
2250 block_idx: usize,
2251 ) -> Result<Box<dyn BlockEffectiveJacobian>, String> {
2252 crate::block_layout::block_jacobian::AdditiveWiggleBlockLayout {
2253 family: "BinomialLocationScaleFamily",
2254 n_outputs: 2,
2255 additive_blocks: &[Self::BLOCK_T, Self::BLOCK_LOG_SIGMA],
2256 wiggle_block: None,
2257 }
2258 .block_effective_jacobian(specs, block_idx)
2259 }
2260}
2261
2262impl CustomFamily for BinomialLocationScaleFamily {
2263 fn joint_jeffreys_term_required(&self) -> bool {
2267 true
2268 }
2269
2270 fn exact_newton_joint_hessian_beta_dependent(&self) -> bool {
2274 true
2275 }
2276
2277 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
2278 crate::location_scale_engine::location_scale_coefficient_hessian_cost(
2282 self.y.len() as u64,
2283 specs,
2284 )
2285 }
2286
2287 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
2288 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2289 let n = self.y.len();
2290 let eta_t = &block_states[Self::BLOCK_T].eta;
2291 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2292 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2293 return Err(GamlssError::DimensionMismatch {
2294 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2295 }
2296 .into());
2297 }
2298
2299 let core = binomial_location_scale_core(
2300 &self.y,
2301 &self.weights,
2302 eta_t,
2303 eta_ls,
2304 None,
2305 &self.link_kind,
2306 )?;
2307 if !self.exact_joint_supported() {
2308 return Err(
2309 "BinomialLocationScaleFamily requires exact curvature designs; diagonal fallback has been removed"
2310 .to_string(),
2311 );
2312 }
2313 let threshold_design = self.threshold_design.as_ref().ok_or_else(|| {
2314 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2315 })?;
2316 let log_sigma_design = self.log_sigma_design.as_ref().ok_or_else(|| {
2317 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2318 })?;
2319
2320 let mut grad_eta_t_v = vec![0.0_f64; n];
2326 let mut grad_eta_ls_v = vec![0.0_f64; n];
2327 let y_slice_e = self.y.as_slice().expect("y must be contiguous");
2328 let w_slice_e = self.weights.as_slice().expect("weights must be contiguous");
2329 let q0_slice_e = core.q0.as_slice().expect("q0 must be contiguous");
2330 let eta_t_slice_e = eta_t.as_slice().expect("eta_t must be contiguous");
2331 let eta_ls_slice_e = eta_ls.as_slice().expect("eta_ls must be contiguous");
2332 let link_kind_e = &self.link_kind;
2333 let gradient_pairs: Result<Vec<(f64, f64)>, String> = (0..n)
2334 .into_par_iter()
2335 .map(|i| {
2336 let tower = binomial_location_scale_nll_tower(
2337 y_slice_e[i],
2338 w_slice_e[i],
2339 eta_t_slice_e[i],
2340 eta_ls_slice_e[i],
2341 q0_slice_e[i],
2342 core.mu[i],
2343 core.dmu_dq[i],
2344 core.d2mu_dq2[i],
2345 core.d3mu_dq3[i],
2346 link_kind_e,
2347 false,
2348 )?;
2349 Ok((-tower.g[0], -tower.g[1]))
2350 })
2351 .collect();
2352 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
2353 grad_eta_t_v[i] = g_t;
2354 grad_eta_ls_v[i] = g_ls;
2355 }
2356 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
2357 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
2358 let grad_t = threshold_design.transpose_vector_multiply(&grad_eta_t);
2359 let grad_ls = log_sigma_design.transpose_vector_multiply(&grad_eta_ls);
2360
2361 let (h_tt, h_ll) = self.exact_newton_block_diagonal_hessians_from_design_matrices(
2366 block_states,
2367 threshold_design,
2368 log_sigma_design,
2369 )?;
2370 Ok(FamilyEvaluation {
2371 log_likelihood: core.log_likelihood,
2372 blockworking_sets: vec![
2373 BlockWorkingSet::ExactNewton {
2374 gradient: grad_t,
2375 hessian: SymmetricMatrix::Dense(h_tt),
2376 },
2377 BlockWorkingSet::ExactNewton {
2378 gradient: grad_ls,
2379 hessian: SymmetricMatrix::Dense(h_ll),
2380 },
2381 ],
2382 })
2383 }
2384
2385 fn log_likelihood_only(&self, block_states: &[ParameterBlockState]) -> Result<f64, String> {
2386 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2387 let n = self.y.len();
2388 let eta_t = &block_states[Self::BLOCK_T].eta;
2389 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2390 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2391 return Err(GamlssError::DimensionMismatch {
2392 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2393 }
2394 .into());
2395 }
2396 binomial_location_scale_ll_only(
2398 &self.y,
2399 &self.weights,
2400 eta_t,
2401 eta_ls,
2402 None,
2403 &self.link_kind,
2404 )
2405 }
2406
2407 fn log_likelihood_only_with_options(
2418 &self,
2419 block_states: &[ParameterBlockState],
2420 options: &BlockwiseFitOptions,
2421 ) -> Result<f64, String> {
2422 let Some(subsample) = options.outer_score_subsample.as_ref() else {
2423 return self.log_likelihood_only(block_states);
2424 };
2425 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2426 let n = self.y.len();
2427 let eta_t = &block_states[Self::BLOCK_T].eta;
2428 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2429 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2430 return Err(GamlssError::DimensionMismatch {
2431 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2432 }
2433 .into());
2434 }
2435 use rayon::iter::ParallelIterator;
2436 let link_kind = &self.link_kind;
2437 let ll: Result<f64, String> = subsample
2438 .rows
2439 .par_iter()
2440 .try_fold(
2441 || 0.0_f64,
2442 |acc, row| -> Result<f64, String> {
2443 let i = row.index;
2444 let wi = self.weights[i];
2445 if wi == 0.0 {
2446 return Ok(acc);
2447 }
2448 let SigmaJet1 { sigma, .. } = exp_sigma_jet1_scalar(eta_ls[i]);
2449 let q = binomial_location_scale_q0(eta_t[i], sigma);
2450 let mu = if matches!(link_kind, InverseLink::Standard(StandardLink::Probit)) {
2451 0.5
2452 } else {
2453 let jet = inverse_link_jet_for_inverse_link(link_kind, q).map_err(|e| {
2454 format!("location-scale inverse-link evaluation failed: {e}")
2455 })?;
2456 jet.mu
2457 };
2458 let term =
2459 binomial_location_scale_log_likelihood(self.y[i], wi, q, link_kind, mu)?;
2460 Ok(acc + row.weight * term)
2461 },
2462 )
2463 .try_reduce(|| 0.0_f64, |a, b| Ok(a + b));
2464 ll
2465 }
2466
2467 fn requires_joint_outer_hyper_path(&self) -> bool {
2468 true
2469 }
2470
2471 fn diagonalworking_weights_directional_derivative(
2472 &self,
2473 _: &[ParameterBlockState],
2474 _: usize,
2475 arr: &Array1<f64>,
2476 ) -> Result<Option<Array1<f64>>, String> {
2477 assert!(arr.iter().all(|v| !v.is_nan()));
2479 Err(
2480 "BinomialLocationScaleFamily no longer supports diagonal working weights; exact curvature is required"
2481 .to_string(),
2482 )
2483 }
2484
2485 fn exact_newton_joint_psi_terms(
2486 &self,
2487 block_states: &[ParameterBlockState],
2488 specs: &[ParameterBlockSpec],
2489 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2490 psi_index: usize,
2491 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
2492 self.exact_newton_joint_psi_terms_for_specs(
2493 block_states,
2494 specs,
2495 derivative_blocks,
2496 psi_index,
2497 )
2498 }
2499
2500 fn exact_newton_joint_psisecond_order_terms(
2501 &self,
2502 block_states: &[ParameterBlockState],
2503 specs: &[ParameterBlockSpec],
2504 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2505 psi_i: usize,
2506 psi_j: usize,
2507 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
2508 self.exact_newton_joint_psisecond_order_terms_for_specs(
2509 block_states,
2510 specs,
2511 derivative_blocks,
2512 psi_i,
2513 psi_j,
2514 )
2515 }
2516
2517 fn exact_newton_joint_psihessian_directional_derivative(
2518 &self,
2519 block_states: &[ParameterBlockState],
2520 specs: &[ParameterBlockSpec],
2521 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2522 psi_index: usize,
2523 d_beta_flat: &Array1<f64>,
2524 ) -> Result<Option<Array2<f64>>, String> {
2525 self.exact_newton_joint_psihessian_directional_derivative_for_specs(
2526 block_states,
2527 specs,
2528 derivative_blocks,
2529 psi_index,
2530 d_beta_flat,
2531 )
2532 }
2533
2534 fn exact_newton_joint_psi_workspace(
2535 &self,
2536 block_states: &[ParameterBlockState],
2537 specs: &[ParameterBlockSpec],
2538 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2539 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
2540 if !self.exact_joint_supported() {
2541 return Ok(None);
2542 }
2543 Ok(Some(Arc::new(
2544 BinomialLocationScaleExactNewtonJointPsiWorkspace::new(
2545 self.clone(),
2546 block_states.to_vec(),
2547 specs,
2548 derivative_blocks.to_vec(),
2549 )?,
2550 )))
2551 }
2552
2553 fn exact_newton_hessian_directional_derivative(
2554 &self,
2555 block_states: &[ParameterBlockState],
2556 block_idx: usize,
2557 d_beta: &Array1<f64>,
2558 ) -> Result<Option<Array2<f64>>, String> {
2559 if !self.exact_joint_supported() {
2560 return Ok(None);
2561 }
2562 let pt = self
2563 .threshold_design
2564 .as_ref()
2565 .ok_or_else(|| {
2566 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2567 })?
2568 .ncols();
2569 let pls = self
2570 .log_sigma_design
2571 .as_ref()
2572 .ok_or_else(|| {
2573 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2574 })?
2575 .ncols();
2576 let total = pt + pls;
2577 let (start, end, joint_direction) = match block_idx {
2578 Self::BLOCK_T => {
2579 if d_beta.len() != pt {
2580 return Err(GamlssError::DimensionMismatch { reason: format!(
2581 "BinomialLocationScaleFamily threshold d_beta length mismatch: got {}, expected {}",
2582 d_beta.len(),
2583 pt
2584 ) }.into());
2585 }
2586 let mut dir = Array1::<f64>::zeros(total);
2587 dir.slice_mut(s![0..pt]).assign(d_beta);
2588 (0usize, pt, dir)
2589 }
2590 Self::BLOCK_LOG_SIGMA => {
2591 if d_beta.len() != pls {
2592 return Err(GamlssError::DimensionMismatch { reason: format!(
2593 "BinomialLocationScaleFamily log-sigma d_beta length mismatch: got {}, expected {}",
2594 d_beta.len(),
2595 pls
2596 ) }.into());
2597 }
2598 let mut dir = Array1::<f64>::zeros(total);
2599 dir.slice_mut(s![pt..pt + pls]).assign(d_beta);
2600 (pt, pt + pls, dir)
2601 }
2602 _ => return Ok(None),
2603 };
2604 let joint = self
2605 .exact_newton_joint_hessian_directional_derivative(block_states, &joint_direction)?
2606 .ok_or_else(|| {
2607 format!("missing joint exact-newton directional Hessian for block {block_idx}")
2608 })?;
2609 Ok(Some(joint.slice(s![start..end, start..end]).to_owned()))
2610 }
2611
2612 fn exact_newton_joint_hessian(
2613 &self,
2614 block_states: &[ParameterBlockState],
2615 ) -> Result<Option<Array2<f64>>, String> {
2616 self.exact_newton_joint_hessian_for_specs(block_states, None)
2617 }
2618
2619 fn has_explicit_joint_hessian(&self) -> bool {
2620 true
2621 }
2622
2623 fn exact_newton_joint_hessian_directional_derivative(
2624 &self,
2625 block_states: &[ParameterBlockState],
2626 d_beta_flat: &Array1<f64>,
2627 ) -> Result<Option<Array2<f64>>, String> {
2628 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2629 block_states,
2630 None,
2631 d_beta_flat,
2632 )
2633 }
2634
2635 fn exact_newton_joint_hessiansecond_directional_derivative(
2636 &self,
2637 block_states: &[ParameterBlockState],
2638 d_beta_u_flat: &Array1<f64>,
2639 d_betav_flat: &Array1<f64>,
2640 ) -> Result<Option<Array2<f64>>, String> {
2641 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2642 block_states,
2643 None,
2644 d_beta_u_flat,
2645 d_betav_flat,
2646 )
2647 }
2648
2649 fn exact_newton_joint_hessian_with_specs(
2650 &self,
2651 block_states: &[ParameterBlockState],
2652 specs: &[ParameterBlockSpec],
2653 ) -> Result<Option<Array2<f64>>, String> {
2654 self.exact_newton_joint_hessian_for_specs(block_states, Some(specs))
2655 }
2656
2657 fn exact_newton_joint_hessian_directional_derivative_with_specs(
2658 &self,
2659 block_states: &[ParameterBlockState],
2660 specs: &[ParameterBlockSpec],
2661 d_beta_flat: &Array1<f64>,
2662 ) -> Result<Option<Array2<f64>>, String> {
2663 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2664 block_states,
2665 Some(specs),
2666 d_beta_flat,
2667 )
2668 }
2669
2670 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
2671 &self,
2672 block_states: &[ParameterBlockState],
2673 specs: &[ParameterBlockSpec],
2674 d_beta_u_flat: &Array1<f64>,
2675 d_betav_flat: &Array1<f64>,
2676 ) -> Result<Option<Array2<f64>>, String> {
2677 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2678 block_states,
2679 Some(specs),
2680 d_beta_u_flat,
2681 d_betav_flat,
2682 )
2683 }
2684
2685 fn joint_jeffreys_information_with_specs(
2686 &self,
2687 block_states: &[ParameterBlockState],
2688 specs: &[ParameterBlockSpec],
2689 ) -> Result<Option<Array2<f64>>, String> {
2690 self.expected_joint_information_for_specs(block_states, Some(specs))
2691 }
2692
2693 fn joint_jeffreys_information_directional_derivative_with_specs(
2694 &self,
2695 block_states: &[ParameterBlockState],
2696 specs: &[ParameterBlockSpec],
2697 d_beta_flat: &Array1<f64>,
2698 ) -> Result<Option<Array2<f64>>, String> {
2699 self.expected_joint_information_directional_for_specs(
2700 block_states,
2701 Some(specs),
2702 d_beta_flat,
2703 )
2704 }
2705
2706 fn joint_jeffreys_information_second_directional_derivative_with_specs(
2707 &self,
2708 block_states: &[ParameterBlockState],
2709 specs: &[ParameterBlockSpec],
2710 d_beta_u_flat: &Array1<f64>,
2711 d_betav_flat: &Array1<f64>,
2712 ) -> Result<Option<Array2<f64>>, String> {
2713 self.expected_joint_information_second_directional_for_specs(
2714 block_states,
2715 Some(specs),
2716 d_beta_u_flat,
2717 d_betav_flat,
2718 )
2719 }
2720
2721 fn joint_jeffreys_information_contracted_trace_hessian_with_specs(
2722 &self,
2723 block_states: &[ParameterBlockState],
2724 specs: &[ParameterBlockSpec],
2725 weight: &Array2<f64>,
2726 ) -> Result<Option<Array2<f64>>, String> {
2727 self.expected_joint_contracted_trace_hessian_for_specs(block_states, Some(specs), weight)
2728 }
2729
2730 fn joint_jeffreys_information_contracted_trace_hessian_available(&self) -> bool {
2731 true
2732 }
2733
2734 fn joint_jeffreys_information_matches_observed_hessian(&self) -> bool {
2735 false
2743 }
2744
2745 fn exact_newton_joint_gradient_evaluation(
2746 &self,
2747 block_states: &[ParameterBlockState],
2748 specs: &[ParameterBlockSpec],
2749 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
2750 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2751 return Ok(None);
2752 };
2753 self.exact_newton_joint_gradient_from_designs(block_states, &x_t, &x_ls)
2754 .map(Some)
2755 }
2756
2757 fn exact_newton_joint_hessian_workspace(
2758 &self,
2759 block_states: &[ParameterBlockState],
2760 specs: &[ParameterBlockSpec],
2761 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2762 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2763 return Ok(None);
2764 };
2765 let workspace = BinomialLocationScaleHessianWorkspace::new(
2766 self.clone(),
2767 block_states.to_vec(),
2768 x_t,
2769 x_ls,
2770 )?;
2771 Ok(Some(Arc::new(workspace)))
2772 }
2773
2774 fn exact_newton_joint_hessian_workspace_with_options(
2788 &self,
2789 block_states: &[ParameterBlockState],
2790 specs: &[ParameterBlockSpec],
2791 options: &BlockwiseFitOptions,
2792 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2793 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2794 return Ok(None);
2795 };
2796 let mut workspace = BinomialLocationScaleHessianWorkspace::new(
2797 self.clone(),
2798 block_states.to_vec(),
2799 x_t,
2800 x_ls,
2801 )?;
2802 if let Some(subsample) = options.outer_score_subsample.as_ref() {
2803 workspace.apply_outer_subsample(subsample.rows.as_ref());
2804 }
2805 Ok(Some(Arc::new(workspace)))
2806 }
2807
2808 fn outer_derivative_subsample_capable(&self) -> bool {
2825 true
2826 }
2827
2828 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
2829 if specs.len() != 2 {
2833 return false;
2834 }
2835 let n = self.y.len();
2836 specs[Self::BLOCK_T].design.nrows() == n && specs[Self::BLOCK_LOG_SIGMA].design.nrows() == n
2837 }
2838}
2839
2840impl CustomFamilyGenerative for BinomialLocationScaleFamily {
2841 fn generativespec(
2842 &self,
2843 block_states: &[ParameterBlockState],
2844 ) -> Result<GenerativeSpec, String> {
2845 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2846 let eta_t = &block_states[Self::BLOCK_T].eta;
2847 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2848 if eta_t.len() != self.y.len() || eta_ls.len() != self.y.len() {
2849 return Err(GamlssError::DimensionMismatch {
2850 reason: "BinomialLocationScaleFamily generative size mismatch".to_string(),
2851 }
2852 .into());
2853 }
2854 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
2855 let sigma = exp_sigma_from_eta_scalar(eta_ls[i]);
2856 let q = binomial_location_scale_q0(eta_t[i], sigma);
2857 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
2858 .map_err(|e| format!("location-scale inverse-link evaluation failed: {e}"))?;
2859 Ok(jet.mu)
2860 })?;
2861 Ok(GenerativeSpec {
2862 mean,
2863 noise: NoiseModel::Bernoulli,
2864 })
2865 }
2866}