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 exact_newton_outer_curvature(
2305 &self,
2306 block_states: &[ParameterBlockState],
2307 ) -> Result<Option<crate::custom_family::ExactNewtonOuterCurvature>, String> {
2308 Ok(self.expected_joint_information_for_specs(block_states, None)?.map(
2309 |hessian| crate::custom_family::ExactNewtonOuterCurvature {
2310 hessian,
2311 rho_curvature_scale: 1.0,
2312 hessian_logdet_correction: 0.0,
2313 },
2314 ))
2315 }
2316
2317 fn exact_newton_outer_curvature_directional_derivative_with_specs(
2318 &self,
2319 block_states: &[ParameterBlockState],
2320 specs: &[ParameterBlockSpec],
2321 d_beta_flat: &Array1<f64>,
2322 ) -> Result<Option<Array2<f64>>, String> {
2323 self.expected_joint_information_directional_for_specs(block_states, Some(specs), d_beta_flat)
2324 }
2325
2326 fn exact_newton_outer_curvature_second_directional_derivative_with_specs(
2327 &self,
2328 block_states: &[ParameterBlockState],
2329 specs: &[ParameterBlockSpec],
2330 d_beta_u_flat: &Array1<f64>,
2331 d_beta_v_flat: &Array1<f64>,
2332 ) -> Result<Option<Array2<f64>>, String> {
2333 self.expected_joint_information_second_directional_for_specs(
2334 block_states,
2335 Some(specs),
2336 d_beta_u_flat,
2337 d_beta_v_flat,
2338 )
2339 }
2340
2341 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
2342 crate::location_scale_engine::location_scale_coefficient_hessian_cost(
2346 self.y.len() as u64,
2347 specs,
2348 )
2349 }
2350
2351 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
2352 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2353 let n = self.y.len();
2354 let eta_t = &block_states[Self::BLOCK_T].eta;
2355 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2356 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2357 return Err(GamlssError::DimensionMismatch {
2358 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2359 }
2360 .into());
2361 }
2362
2363 let core = binomial_location_scale_core(
2364 &self.y,
2365 &self.weights,
2366 eta_t,
2367 eta_ls,
2368 None,
2369 &self.link_kind,
2370 )?;
2371 if !self.exact_joint_supported() {
2372 return Err(
2373 "BinomialLocationScaleFamily requires exact curvature designs; diagonal fallback has been removed"
2374 .to_string(),
2375 );
2376 }
2377 let threshold_design = self.threshold_design.as_ref().ok_or_else(|| {
2378 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2379 })?;
2380 let log_sigma_design = self.log_sigma_design.as_ref().ok_or_else(|| {
2381 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2382 })?;
2383
2384 let mut grad_eta_t_v = vec![0.0_f64; n];
2390 let mut grad_eta_ls_v = vec![0.0_f64; n];
2391 let y_slice_e = self.y.as_slice().expect("y must be contiguous");
2392 let w_slice_e = self.weights.as_slice().expect("weights must be contiguous");
2393 let q0_slice_e = core.q0.as_slice().expect("q0 must be contiguous");
2394 let eta_t_slice_e = eta_t.as_slice().expect("eta_t must be contiguous");
2395 let eta_ls_slice_e = eta_ls.as_slice().expect("eta_ls must be contiguous");
2396 let link_kind_e = &self.link_kind;
2397 let gradient_pairs: Result<Vec<(f64, f64)>, String> = (0..n)
2398 .into_par_iter()
2399 .map(|i| {
2400 let tower = binomial_location_scale_nll_tower(
2401 y_slice_e[i],
2402 w_slice_e[i],
2403 eta_t_slice_e[i],
2404 eta_ls_slice_e[i],
2405 q0_slice_e[i],
2406 core.mu[i],
2407 core.dmu_dq[i],
2408 core.d2mu_dq2[i],
2409 core.d3mu_dq3[i],
2410 link_kind_e,
2411 false,
2412 )?;
2413 Ok((-tower.g[0], -tower.g[1]))
2414 })
2415 .collect();
2416 for (i, (g_t, g_ls)) in gradient_pairs?.into_iter().enumerate() {
2417 grad_eta_t_v[i] = g_t;
2418 grad_eta_ls_v[i] = g_ls;
2419 }
2420 let grad_eta_t = Array1::from_vec(grad_eta_t_v);
2421 let grad_eta_ls = Array1::from_vec(grad_eta_ls_v);
2422 let grad_t = threshold_design.transpose_vector_multiply(&grad_eta_t);
2423 let grad_ls = log_sigma_design.transpose_vector_multiply(&grad_eta_ls);
2424
2425 let (h_tt, h_ll) = self.exact_newton_block_diagonal_hessians_from_design_matrices(
2430 block_states,
2431 threshold_design,
2432 log_sigma_design,
2433 )?;
2434 Ok(FamilyEvaluation {
2435 log_likelihood: core.log_likelihood,
2436 blockworking_sets: vec![
2437 BlockWorkingSet::ExactNewton {
2438 gradient: grad_t,
2439 hessian: SymmetricMatrix::Dense(h_tt),
2440 },
2441 BlockWorkingSet::ExactNewton {
2442 gradient: grad_ls,
2443 hessian: SymmetricMatrix::Dense(h_ll),
2444 },
2445 ],
2446 })
2447 }
2448
2449 fn log_likelihood_only(&self, block_states: &[ParameterBlockState]) -> Result<f64, String> {
2450 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2451 let n = self.y.len();
2452 let eta_t = &block_states[Self::BLOCK_T].eta;
2453 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2454 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2455 return Err(GamlssError::DimensionMismatch {
2456 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2457 }
2458 .into());
2459 }
2460 binomial_location_scale_ll_only(
2462 &self.y,
2463 &self.weights,
2464 eta_t,
2465 eta_ls,
2466 None,
2467 &self.link_kind,
2468 )
2469 }
2470
2471 fn log_likelihood_only_with_options(
2482 &self,
2483 block_states: &[ParameterBlockState],
2484 options: &BlockwiseFitOptions,
2485 ) -> Result<f64, String> {
2486 let Some(subsample) = options.outer_score_subsample.as_ref() else {
2487 return self.log_likelihood_only(block_states);
2488 };
2489 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2490 let n = self.y.len();
2491 let eta_t = &block_states[Self::BLOCK_T].eta;
2492 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2493 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n {
2494 return Err(GamlssError::DimensionMismatch {
2495 reason: "BinomialLocationScaleFamily input size mismatch".to_string(),
2496 }
2497 .into());
2498 }
2499 use rayon::iter::ParallelIterator;
2500 let link_kind = &self.link_kind;
2501 let ll: Result<f64, String> = subsample
2502 .rows
2503 .par_iter()
2504 .try_fold(
2505 || 0.0_f64,
2506 |acc, row| -> Result<f64, String> {
2507 let i = row.index;
2508 let wi = self.weights[i];
2509 if wi == 0.0 {
2510 return Ok(acc);
2511 }
2512 let SigmaJet1 { sigma, .. } = exp_sigma_jet1_scalar(eta_ls[i]);
2513 let q = binomial_location_scale_q0(eta_t[i], sigma);
2514 let mu = if matches!(link_kind, InverseLink::Standard(StandardLink::Probit)) {
2515 0.5
2516 } else {
2517 let jet = inverse_link_jet_for_inverse_link(link_kind, q).map_err(|e| {
2518 format!("location-scale inverse-link evaluation failed: {e}")
2519 })?;
2520 jet.mu
2521 };
2522 let term =
2523 binomial_location_scale_log_likelihood(self.y[i], wi, q, link_kind, mu)?;
2524 Ok(acc + row.weight * term)
2525 },
2526 )
2527 .try_reduce(|| 0.0_f64, |a, b| Ok(a + b));
2528 ll
2529 }
2530
2531 fn requires_joint_outer_hyper_path(&self) -> bool {
2532 true
2533 }
2534
2535 fn diagonalworking_weights_directional_derivative(
2536 &self,
2537 _: &[ParameterBlockState],
2538 _: usize,
2539 arr: &Array1<f64>,
2540 ) -> Result<Option<Array1<f64>>, String> {
2541 assert!(arr.iter().all(|v| !v.is_nan()));
2543 Err(
2544 "BinomialLocationScaleFamily no longer supports diagonal working weights; exact curvature is required"
2545 .to_string(),
2546 )
2547 }
2548
2549 fn exact_newton_joint_psi_terms(
2550 &self,
2551 block_states: &[ParameterBlockState],
2552 specs: &[ParameterBlockSpec],
2553 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2554 psi_index: usize,
2555 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
2556 self.exact_newton_joint_psi_terms_for_specs(
2557 block_states,
2558 specs,
2559 derivative_blocks,
2560 psi_index,
2561 )
2562 }
2563
2564 fn exact_newton_joint_psisecond_order_terms(
2565 &self,
2566 block_states: &[ParameterBlockState],
2567 specs: &[ParameterBlockSpec],
2568 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2569 psi_i: usize,
2570 psi_j: usize,
2571 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
2572 self.exact_newton_joint_psisecond_order_terms_for_specs(
2573 block_states,
2574 specs,
2575 derivative_blocks,
2576 psi_i,
2577 psi_j,
2578 )
2579 }
2580
2581 fn exact_newton_joint_psihessian_directional_derivative(
2582 &self,
2583 block_states: &[ParameterBlockState],
2584 specs: &[ParameterBlockSpec],
2585 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2586 psi_index: usize,
2587 d_beta_flat: &Array1<f64>,
2588 ) -> Result<Option<Array2<f64>>, String> {
2589 self.exact_newton_joint_psihessian_directional_derivative_for_specs(
2590 block_states,
2591 specs,
2592 derivative_blocks,
2593 psi_index,
2594 d_beta_flat,
2595 )
2596 }
2597
2598 fn exact_newton_joint_psi_workspace(
2599 &self,
2600 block_states: &[ParameterBlockState],
2601 specs: &[ParameterBlockSpec],
2602 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
2603 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
2604 if !self.exact_joint_supported() {
2605 return Ok(None);
2606 }
2607 Ok(Some(Arc::new(
2608 BinomialLocationScaleExactNewtonJointPsiWorkspace::new(
2609 self.clone(),
2610 block_states.to_vec(),
2611 specs,
2612 derivative_blocks.to_vec(),
2613 )?,
2614 )))
2615 }
2616
2617 fn exact_newton_hessian_directional_derivative(
2618 &self,
2619 block_states: &[ParameterBlockState],
2620 block_idx: usize,
2621 d_beta: &Array1<f64>,
2622 ) -> Result<Option<Array2<f64>>, String> {
2623 if !self.exact_joint_supported() {
2624 return Ok(None);
2625 }
2626 let pt = self
2627 .threshold_design
2628 .as_ref()
2629 .ok_or_else(|| {
2630 "BinomialLocationScaleFamily exact path is missing threshold design".to_string()
2631 })?
2632 .ncols();
2633 let pls = self
2634 .log_sigma_design
2635 .as_ref()
2636 .ok_or_else(|| {
2637 "BinomialLocationScaleFamily exact path is missing log-sigma design".to_string()
2638 })?
2639 .ncols();
2640 let total = pt + pls;
2641 let (start, end, joint_direction) = match block_idx {
2642 Self::BLOCK_T => {
2643 if d_beta.len() != pt {
2644 return Err(GamlssError::DimensionMismatch { reason: format!(
2645 "BinomialLocationScaleFamily threshold d_beta length mismatch: got {}, expected {}",
2646 d_beta.len(),
2647 pt
2648 ) }.into());
2649 }
2650 let mut dir = Array1::<f64>::zeros(total);
2651 dir.slice_mut(s![0..pt]).assign(d_beta);
2652 (0usize, pt, dir)
2653 }
2654 Self::BLOCK_LOG_SIGMA => {
2655 if d_beta.len() != pls {
2656 return Err(GamlssError::DimensionMismatch { reason: format!(
2657 "BinomialLocationScaleFamily log-sigma d_beta length mismatch: got {}, expected {}",
2658 d_beta.len(),
2659 pls
2660 ) }.into());
2661 }
2662 let mut dir = Array1::<f64>::zeros(total);
2663 dir.slice_mut(s![pt..pt + pls]).assign(d_beta);
2664 (pt, pt + pls, dir)
2665 }
2666 _ => return Ok(None),
2667 };
2668 let joint = self
2669 .exact_newton_joint_hessian_directional_derivative(block_states, &joint_direction)?
2670 .ok_or_else(|| {
2671 format!("missing joint exact-newton directional Hessian for block {block_idx}")
2672 })?;
2673 Ok(Some(joint.slice(s![start..end, start..end]).to_owned()))
2674 }
2675
2676 fn exact_newton_joint_hessian(
2677 &self,
2678 block_states: &[ParameterBlockState],
2679 ) -> Result<Option<Array2<f64>>, String> {
2680 self.exact_newton_joint_hessian_for_specs(block_states, None)
2681 }
2682
2683 fn has_explicit_joint_hessian(&self) -> bool {
2684 true
2685 }
2686
2687 fn exact_newton_joint_hessian_directional_derivative(
2688 &self,
2689 block_states: &[ParameterBlockState],
2690 d_beta_flat: &Array1<f64>,
2691 ) -> Result<Option<Array2<f64>>, String> {
2692 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2693 block_states,
2694 None,
2695 d_beta_flat,
2696 )
2697 }
2698
2699 fn exact_newton_joint_hessiansecond_directional_derivative(
2700 &self,
2701 block_states: &[ParameterBlockState],
2702 d_beta_u_flat: &Array1<f64>,
2703 d_betav_flat: &Array1<f64>,
2704 ) -> Result<Option<Array2<f64>>, String> {
2705 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2706 block_states,
2707 None,
2708 d_beta_u_flat,
2709 d_betav_flat,
2710 )
2711 }
2712
2713 fn exact_newton_joint_hessian_with_specs(
2714 &self,
2715 block_states: &[ParameterBlockState],
2716 specs: &[ParameterBlockSpec],
2717 ) -> Result<Option<Array2<f64>>, String> {
2718 self.exact_newton_joint_hessian_for_specs(block_states, Some(specs))
2719 }
2720
2721 fn exact_newton_joint_hessian_directional_derivative_with_specs(
2722 &self,
2723 block_states: &[ParameterBlockState],
2724 specs: &[ParameterBlockSpec],
2725 d_beta_flat: &Array1<f64>,
2726 ) -> Result<Option<Array2<f64>>, String> {
2727 self.exact_newton_joint_hessian_directional_derivative_for_specs(
2728 block_states,
2729 Some(specs),
2730 d_beta_flat,
2731 )
2732 }
2733
2734 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
2735 &self,
2736 block_states: &[ParameterBlockState],
2737 specs: &[ParameterBlockSpec],
2738 d_beta_u_flat: &Array1<f64>,
2739 d_betav_flat: &Array1<f64>,
2740 ) -> Result<Option<Array2<f64>>, String> {
2741 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
2742 block_states,
2743 Some(specs),
2744 d_beta_u_flat,
2745 d_betav_flat,
2746 )
2747 }
2748
2749 fn joint_jeffreys_information_with_specs(
2750 &self,
2751 block_states: &[ParameterBlockState],
2752 specs: &[ParameterBlockSpec],
2753 ) -> Result<Option<Array2<f64>>, String> {
2754 self.expected_joint_information_for_specs(block_states, Some(specs))
2755 }
2756
2757 fn joint_jeffreys_information_directional_derivative_with_specs(
2758 &self,
2759 block_states: &[ParameterBlockState],
2760 specs: &[ParameterBlockSpec],
2761 d_beta_flat: &Array1<f64>,
2762 ) -> Result<Option<Array2<f64>>, String> {
2763 self.expected_joint_information_directional_for_specs(
2764 block_states,
2765 Some(specs),
2766 d_beta_flat,
2767 )
2768 }
2769
2770 fn joint_jeffreys_information_second_directional_derivative_with_specs(
2771 &self,
2772 block_states: &[ParameterBlockState],
2773 specs: &[ParameterBlockSpec],
2774 d_beta_u_flat: &Array1<f64>,
2775 d_betav_flat: &Array1<f64>,
2776 ) -> Result<Option<Array2<f64>>, String> {
2777 self.expected_joint_information_second_directional_for_specs(
2778 block_states,
2779 Some(specs),
2780 d_beta_u_flat,
2781 d_betav_flat,
2782 )
2783 }
2784
2785 fn joint_jeffreys_information_contracted_trace_hessian_with_specs(
2786 &self,
2787 block_states: &[ParameterBlockState],
2788 specs: &[ParameterBlockSpec],
2789 weight: &Array2<f64>,
2790 ) -> Result<Option<Array2<f64>>, String> {
2791 self.expected_joint_contracted_trace_hessian_for_specs(block_states, Some(specs), weight)
2792 }
2793
2794 fn joint_jeffreys_information_contracted_trace_hessian_available(&self) -> bool {
2795 true
2796 }
2797
2798 fn joint_jeffreys_information_matches_observed_hessian(&self) -> bool {
2799 false
2807 }
2808
2809 fn exact_newton_joint_gradient_evaluation(
2810 &self,
2811 block_states: &[ParameterBlockState],
2812 specs: &[ParameterBlockSpec],
2813 ) -> Result<Option<ExactNewtonJointGradientEvaluation>, String> {
2814 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2815 return Ok(None);
2816 };
2817 self.exact_newton_joint_gradient_from_designs(block_states, &x_t, &x_ls)
2818 .map(Some)
2819 }
2820
2821 fn exact_newton_joint_hessian_workspace(
2822 &self,
2823 block_states: &[ParameterBlockState],
2824 specs: &[ParameterBlockSpec],
2825 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2826 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2827 return Ok(None);
2828 };
2829 let workspace = BinomialLocationScaleHessianWorkspace::new(
2830 self.clone(),
2831 block_states.to_vec(),
2832 x_t,
2833 x_ls,
2834 )?;
2835 Ok(Some(Arc::new(workspace)))
2836 }
2837
2838 fn exact_newton_joint_hessian_workspace_with_options(
2852 &self,
2853 block_states: &[ParameterBlockState],
2854 specs: &[ParameterBlockSpec],
2855 options: &BlockwiseFitOptions,
2856 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
2857 let Some((x_t, x_ls)) = self.exact_joint_block_designs_owned(Some(specs))? else {
2858 return Ok(None);
2859 };
2860 let mut workspace = BinomialLocationScaleHessianWorkspace::new(
2861 self.clone(),
2862 block_states.to_vec(),
2863 x_t,
2864 x_ls,
2865 )?;
2866 if let Some(subsample) = options.outer_score_subsample.as_ref() {
2867 workspace.apply_outer_subsample(subsample.rows.as_ref());
2868 }
2869 Ok(Some(Arc::new(workspace)))
2870 }
2871
2872 fn outer_derivative_subsample_capable(&self) -> bool {
2889 true
2890 }
2891
2892 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
2893 if specs.len() != 2 {
2897 return false;
2898 }
2899 let n = self.y.len();
2900 specs[Self::BLOCK_T].design.nrows() == n && specs[Self::BLOCK_LOG_SIGMA].design.nrows() == n
2901 }
2902}
2903
2904impl CustomFamilyGenerative for BinomialLocationScaleFamily {
2905 fn generativespec(
2906 &self,
2907 block_states: &[ParameterBlockState],
2908 ) -> Result<GenerativeSpec, String> {
2909 validate_block_count::<GamlssError>("BinomialLocationScaleFamily", 2, block_states.len())?;
2910 let eta_t = &block_states[Self::BLOCK_T].eta;
2911 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
2912 if eta_t.len() != self.y.len() || eta_ls.len() != self.y.len() {
2913 return Err(GamlssError::DimensionMismatch {
2914 reason: "BinomialLocationScaleFamily generative size mismatch".to_string(),
2915 }
2916 .into());
2917 }
2918 let mean = gamlss_rowwise_map_result(self.y.len(), |i| {
2919 let sigma = exp_sigma_from_eta_scalar(eta_ls[i]);
2920 let q = binomial_location_scale_q0(eta_t[i], sigma);
2921 let jet = inverse_link_jet_for_inverse_link(&self.link_kind, q)
2922 .map_err(|e| format!("location-scale inverse-link evaluation failed: {e}"))?;
2923 Ok(jet.mu)
2924 })?;
2925 Ok(GenerativeSpec {
2926 mean,
2927 noise: NoiseModel::Bernoulli,
2928 })
2929 }
2930}