1use super::*;
6
7pub struct GaussianLocationScaleFamily {
8 pub y: Array1<f64>,
9 pub weights: Array1<f64>,
10 pub mu_design: Option<DesignMatrix>,
11 pub log_sigma_design: Option<DesignMatrix>,
12 pub policy: gam_runtime::resource::ResourcePolicy,
17 pub cached_row_scalars:
27 std::sync::RwLock<Option<(Array1<f64>, Array1<f64>, Arc<GaussianJointRowScalars>)>>,
28}
29
30impl Clone for GaussianLocationScaleFamily {
31 fn clone(&self) -> Self {
32 Self {
33 y: self.y.clone(),
34 weights: self.weights.clone(),
35 mu_design: self.mu_design.clone(),
36 log_sigma_design: self.log_sigma_design.clone(),
37 policy: self.policy.clone(),
38 cached_row_scalars: std::sync::RwLock::new(
39 self.cached_row_scalars
40 .read()
41 .expect("lock poisoned")
42 .clone(),
43 ),
44 }
45 }
46}
47
48impl GaussianLocationScaleFamily {
49 pub const BLOCK_MU: usize = 0;
50 pub const BLOCK_LOG_SIGMA: usize = 1;
51
52 #[inline]
64 fn eta_keys_match(stored: &Array1<f64>, query: &Array1<f64>) -> bool {
65 stored.len() == query.len()
66 && stored
67 .iter()
68 .zip(query.iter())
69 .all(|(a, b)| a.to_bits() == b.to_bits())
70 }
71
72 pub(crate) fn get_or_compute_row_scalars(
73 &self,
74 etamu: &Array1<f64>,
75 eta_ls: &Array1<f64>,
76 ) -> Result<Arc<GaussianJointRowScalars>, String> {
77 if let Ok(guard) = self.cached_row_scalars.read() {
81 if let Some((cmu, cls, rows)) = guard.as_ref() {
82 if Self::eta_keys_match(cmu, etamu) && Self::eta_keys_match(cls, eta_ls) {
83 return Ok(Arc::clone(rows));
84 }
85 }
86 }
87 let rows = Arc::new(gaussian_jointrow_scalars(
92 &self.y,
93 etamu,
94 eta_ls,
95 &self.weights,
96 )?);
97 if let Ok(mut guard) = self.cached_row_scalars.write() {
98 *guard = Some((etamu.clone(), eta_ls.clone(), Arc::clone(&rows)));
99 }
100 Ok(rows)
101 }
102
103 pub fn parameternames() -> &'static [&'static str] {
104 &["mu", "log_sigma"]
105 }
106
107 pub fn parameter_links() -> &'static [ParameterLink] {
108 &[ParameterLink::Identity, ParameterLink::Log]
109 }
110
111 pub fn metadata() -> FamilyMetadata {
112 FamilyMetadata {
113 name: "gaussian_location_scale",
114 parameternames: Self::parameternames(),
115 parameter_links: Self::parameter_links(),
116 }
117 }
118
119 pub(crate) fn exact_joint_supported(&self) -> bool {
120 self.mu_design.is_some() && self.log_sigma_design.is_some()
121 }
122
123 pub(crate) fn exact_block_designs(
124 &self,
125 ) -> Result<(DenseOrOperator<'_>, DenseOrOperator<'_>), String> {
126 let mu_design = self.mu_design.as_ref().ok_or_else(|| {
127 "GaussianLocationScaleFamily exact path is missing mu design".to_string()
128 })?;
129 let log_sigma_design = self.log_sigma_design.as_ref().ok_or_else(|| {
130 "GaussianLocationScaleFamily exact path is missing log-sigma design".to_string()
131 })?;
132 let planned = dense_blocks_planned_budget(&[mu_design, log_sigma_design]);
133 let xmu = dense_block_or_operator(
134 mu_design,
135 mu_design.nrows(),
136 mu_design.ncols(),
137 planned[0],
138 &self.policy,
139 );
140 let x_ls = dense_block_or_operator(
141 log_sigma_design,
142 log_sigma_design.nrows(),
143 log_sigma_design.ncols(),
144 planned[1],
145 &self.policy,
146 );
147 Ok((xmu, x_ls))
148 }
149
150 pub(crate) fn exact_block_designs_fromspecs<'a>(
151 &self,
152 specs: &'a [ParameterBlockSpec],
153 ) -> Result<(DenseOrOperator<'a>, DenseOrOperator<'a>), String> {
154 if specs.len() != 2 {
155 return Err(GamlssError::DimensionMismatch {
156 reason: format!(
157 "GaussianLocationScaleFamily spec-aware exact path expects 2 specs, got {}",
158 specs.len()
159 ),
160 }
161 .into());
162 }
163 let mu_design = &specs[Self::BLOCK_MU].design;
164 let log_sigma_design = &specs[Self::BLOCK_LOG_SIGMA].design;
165 let planned = dense_blocks_planned_budget(&[mu_design, log_sigma_design]);
166 let xmu = dense_block_or_operator(
167 mu_design,
168 mu_design.nrows(),
169 mu_design.ncols(),
170 planned[0],
171 &self.policy,
172 );
173 let x_ls = dense_block_or_operator(
174 log_sigma_design,
175 log_sigma_design.nrows(),
176 log_sigma_design.ncols(),
177 planned[1],
178 &self.policy,
179 );
180 Ok((xmu, x_ls))
181 }
182
183 pub(crate) fn exact_joint_block_designs<'a>(
184 &'a self,
185 specs: Option<&'a [ParameterBlockSpec]>,
186 ) -> Result<Option<(DenseOrOperator<'a>, DenseOrOperator<'a>)>, String> {
187 if let Some(specs) = specs {
201 return self.exact_block_designs_fromspecs(specs).map(Some);
202 }
203 if self.exact_joint_supported() {
204 return self.exact_block_designs().map(Some);
205 }
206 Ok(None)
207 }
208
209 pub(crate) fn exact_joint_dense_block_designs<'a>(
210 &'a self,
211 specs: Option<&'a [ParameterBlockSpec]>,
212 ) -> Result<Option<(Cow<'a, Array2<f64>>, Cow<'a, Array2<f64>>)>, String> {
213 let Some((xmu, x_ls)) = self.exact_joint_block_designs(specs)? else {
214 return Ok(None);
215 };
216 let xmu = match xmu {
217 DenseOrOperator::Borrowed(dense) => Cow::Borrowed(dense),
218 DenseOrOperator::Owned(dense) => Cow::Owned(dense),
219 DenseOrOperator::Operator(_) => {
220 return Err(
221 "GaussianLocationScaleFamily exact psi path requires chunked operator support for oversized designs"
222 .to_string(),
223 );
224 }
225 };
226 let x_ls = match x_ls {
227 DenseOrOperator::Borrowed(dense) => Cow::Borrowed(dense),
228 DenseOrOperator::Owned(dense) => Cow::Owned(dense),
229 DenseOrOperator::Operator(_) => {
230 return Err(
231 "GaussianLocationScaleFamily exact psi path requires chunked operator support for oversized designs"
232 .to_string(),
233 );
234 }
235 };
236 Ok(Some((xmu, x_ls)))
237 }
238
239 pub(crate) fn exact_newton_joint_hessian_for_specs(
240 &self,
241 block_states: &[ParameterBlockState],
242 specs: Option<&[ParameterBlockSpec]>,
243 ) -> Result<Option<Array2<f64>>, String> {
244 let Some((xmu, x_ls)) = self.exact_joint_block_designs(specs)? else {
245 return Ok(None);
246 };
247 self.exact_newton_joint_hessian_from_designs(block_states, &xmu, &x_ls)
248 }
249
250 pub(crate) fn exact_newton_joint_hessian_directional_derivative_for_specs(
251 &self,
252 block_states: &[ParameterBlockState],
253 specs: Option<&[ParameterBlockSpec]>,
254 d_beta_flat: &Array1<f64>,
255 ) -> Result<Option<Array2<f64>>, String> {
256 let Some((xmu, x_ls)) = self.exact_joint_block_designs(specs)? else {
257 return Ok(None);
258 };
259 self.exact_newton_joint_hessian_directional_derivative_from_designs(
260 block_states,
261 &xmu,
262 &x_ls,
263 d_beta_flat,
264 )
265 }
266
267 pub(crate) fn exact_newton_joint_hessian_second_directional_derivative_for_specs(
268 &self,
269 block_states: &[ParameterBlockState],
270 specs: Option<&[ParameterBlockSpec]>,
271 d_beta_u_flat: &Array1<f64>,
272 d_betav_flat: &Array1<f64>,
273 ) -> Result<Option<Array2<f64>>, String> {
274 let Some((xmu, x_ls)) = self.exact_joint_block_designs(specs)? else {
275 return Ok(None);
276 };
277 self.exact_newton_joint_hessiansecond_directional_derivative_from_designs(
278 block_states,
279 &xmu,
280 &x_ls,
281 d_beta_u_flat,
282 d_betav_flat,
283 )
284 }
285
286 pub(crate) fn exact_newton_joint_psi_terms_for_specs(
287 &self,
288 block_states: &[ParameterBlockState],
289 specs: &[ParameterBlockSpec],
290 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
291 psi_index: usize,
292 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
293 let Some((xmu, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
294 return Ok(None);
295 };
296 self.exact_newton_joint_psi_terms_from_designs(
297 block_states,
298 specs,
299 derivative_blocks,
300 psi_index,
301 &xmu,
302 &x_ls,
303 )
304 }
305
306 pub(crate) fn exact_newton_joint_psisecond_order_terms_for_specs(
307 &self,
308 block_states: &[ParameterBlockState],
309 specs: &[ParameterBlockSpec],
310 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
311 psi_i: usize,
312 psi_j: usize,
313 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
314 let Some((xmu, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
315 return Ok(None);
316 };
317 self.exact_newton_joint_psisecond_order_terms_from_designs(
318 block_states,
319 derivative_blocks,
320 psi_i,
321 psi_j,
322 &xmu,
323 &x_ls,
324 )
325 }
326
327 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_for_specs(
328 &self,
329 block_states: &[ParameterBlockState],
330 specs: &[ParameterBlockSpec],
331 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
332 psi_index: usize,
333 d_beta_flat: &Array1<f64>,
334 ) -> Result<Option<Array2<f64>>, String> {
335 let Some((xmu, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
336 return Ok(None);
337 };
338 self.exact_newton_joint_psihessian_directional_derivative_from_designs(
339 block_states,
340 derivative_blocks,
341 psi_index,
342 d_beta_flat,
343 &xmu,
344 &x_ls,
345 )
346 }
347
348 pub(crate) fn exact_newton_joint_hessian_from_designs(
349 &self,
350 block_states: &[ParameterBlockState],
351 xmu: &DenseOrOperator<'_>,
352 x_ls: &DenseOrOperator<'_>,
353 ) -> Result<Option<Array2<f64>>, String> {
354 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
355 let n = self.y.len();
356 let etamu = &block_states[Self::BLOCK_MU].eta;
357 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
358 if etamu.len() != n || eta_ls.len() != n || self.weights.len() != n {
359 return Err(GamlssError::DimensionMismatch {
360 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
361 }
362 .into());
363 }
364
365 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
366 let (mm, cross, scale) = gaussian_locscale_fisher_joint_row_coeffs(&rows);
371 Ok(Some(gaussian_joint_hessian_from_designs(
372 xmu, x_ls, &mm, &cross, &scale,
373 )?))
374 }
375
376 pub(crate) fn exact_newton_joint_hessian_directional_derivative_from_designs(
377 &self,
378 block_states: &[ParameterBlockState],
379 xmu: &DenseOrOperator<'_>,
380 x_ls: &DenseOrOperator<'_>,
381 d_beta_flat: &Array1<f64>,
382 ) -> Result<Option<Array2<f64>>, String> {
383 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
384 let n = self.y.len();
385 let etamu = &block_states[Self::BLOCK_MU].eta;
386 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
387 if etamu.len() != n || eta_ls.len() != n || self.weights.len() != n {
388 return Err(GamlssError::DimensionMismatch {
389 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
390 }
391 .into());
392 }
393
394 let pmu = xmu.ncols();
395 let p_ls = x_ls.ncols();
396 let total = pmu + p_ls;
397 if d_beta_flat.len() != total {
398 return Err(GamlssError::DimensionMismatch {
399 reason: format!(
400 "GaussianLocationScaleFamily joint d_beta length mismatch: got {}, expected {}",
401 d_beta_flat.len(),
402 total
403 ),
404 }
405 .into());
406 }
407 let ximu = xmu.dot(d_beta_flat.slice(s![0..pmu]));
408 let xi_ls = x_ls.dot(d_beta_flat.slice(s![pmu..pmu + p_ls]));
409 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
410 let directional = gaussian_joint_first_directionalweights(&rows, &ximu, &xi_ls);
411 let dhmumu = directional.0;
412 let dh_ls_ls = directional.2;
413 let dhmu_ls = Array1::<f64>::zeros(dhmumu.len());
420
421 Ok(Some(gaussian_joint_hessian_from_designs(
422 xmu, x_ls, &dhmumu, &dhmu_ls, &dh_ls_ls,
423 )?))
424 }
425
426 pub(crate) fn exact_newton_joint_hessiansecond_directional_derivative_from_designs(
427 &self,
428 block_states: &[ParameterBlockState],
429 xmu: &DenseOrOperator<'_>,
430 x_ls: &DenseOrOperator<'_>,
431 d_beta_u_flat: &Array1<f64>,
432 d_betav_flat: &Array1<f64>,
433 ) -> Result<Option<Array2<f64>>, String> {
434 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
435 let n = self.y.len();
436 let etamu = &block_states[Self::BLOCK_MU].eta;
437 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
438 if etamu.len() != n || eta_ls.len() != n || self.weights.len() != n {
439 return Err(GamlssError::DimensionMismatch {
440 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
441 }
442 .into());
443 }
444
445 let pmu = xmu.ncols();
446 let p_ls = x_ls.ncols();
447 let total = pmu + p_ls;
448 if d_beta_u_flat.len() != total || d_betav_flat.len() != total {
449 return Err(GamlssError::DimensionMismatch { reason: format!(
450 "GaussianLocationScaleFamily joint second directional derivative length mismatch: got {} and {}, expected {}",
451 d_beta_u_flat.len(),
452 d_betav_flat.len(),
453 total
454 ) }.into());
455 }
456 let ximu_u = xmu.dot(d_beta_u_flat.slice(s![0..pmu]));
457 let xi_ls_u = x_ls.dot(d_beta_u_flat.slice(s![pmu..pmu + p_ls]));
458 let ximuv = xmu.dot(d_betav_flat.slice(s![0..pmu]));
459 let xi_lsv = x_ls.dot(d_betav_flat.slice(s![pmu..pmu + p_ls]));
460 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
461 let second =
462 gaussian_jointsecond_directionalweights(&rows, &ximu_u, &xi_ls_u, &ximuv, &xi_lsv);
463 let d2hmumu = second.0;
464 let d2h_ls_ls = second.2;
465 let d2hmu_ls = Array1::<f64>::zeros(d2hmumu.len());
469
470 Ok(Some(gaussian_joint_hessian_from_designs(
471 xmu, x_ls, &d2hmumu, &d2hmu_ls, &d2h_ls_ls,
472 )?))
473 }
474
475 pub(crate) fn exact_newton_joint_psi_direction(
476 &self,
477 block_states: &[ParameterBlockState],
478 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
479 psi_index: usize,
480 xmu: &Array2<f64>,
481 x_ls: &Array2<f64>,
482 policy: &gam_runtime::resource::ResourcePolicy,
483 ) -> Result<Option<LocationScaleJointPsiDirection>, String> {
484 let Some(parts) = locscale_joint_psi_direction_parts(
485 block_states,
486 derivative_blocks,
487 psi_index,
488 self.y.len(),
489 xmu.ncols(),
490 x_ls.ncols(),
491 Self::BLOCK_MU,
492 Self::BLOCK_LOG_SIGMA,
493 2,
494 "GaussianLocationScaleFamily",
495 "mu",
496 policy,
497 )?
498 else {
499 return Ok(None);
500 };
501 Ok(Some(LocationScaleJointPsiDirection {
502 block_idx: parts.block_idx,
503 local_idx: parts.local_idx,
504 z_primary_psi: parts.primary_z,
505 z_ls_psi: parts.log_sigma_z,
506 x_primary_psi: parts.primary_psi,
507 x_ls_psi: parts.log_sigma_psi,
508 }))
509 }
510
511 pub(crate) fn exact_newton_joint_psisecond_design_drifts(
512 &self,
513 block_states: &[ParameterBlockState],
514 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
515 psi_a: &LocationScaleJointPsiDirection,
516 psi_b: &LocationScaleJointPsiDirection,
517 xmu: &Array2<f64>,
518 x_ls: &Array2<f64>,
519 ) -> Result<LocationScaleJointPsiSecondDrifts, String> {
520 locscale_joint_psisecond_design_drifts(
521 block_states,
522 derivative_blocks,
523 psi_a,
524 psi_b,
525 LocScalePsiDriftConfig {
526 n: self.y.len(),
527 p_primary: xmu.ncols(),
528 p_log_sigma: x_ls.ncols(),
529 primary_block_idx: Self::BLOCK_MU,
530 log_sigma_block_idx: Self::BLOCK_LOG_SIGMA,
531 family_name: "GaussianLocationScaleFamily",
532 primary_label: "mu",
533 policy: &self.policy,
534 },
535 )
536 }
537
538 pub(crate) fn exact_newton_joint_psi_terms_from_designs(
539 &self,
540 block_states: &[ParameterBlockState],
541 specs: &[ParameterBlockSpec],
542 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
543 psi_index: usize,
544 xmu: &Array2<f64>,
545 x_ls: &Array2<f64>,
546 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
547 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
548 if specs.len() != 2 || derivative_blocks.len() != 2 {
549 return Err(GamlssError::DimensionMismatch { reason: format!(
550 "GaussianLocationScaleFamily joint psi terms expect 2 specs and 2 derivative blocks, got {} and {}",
551 specs.len(),
552 derivative_blocks.len()
553 ) }.into());
554 }
555 let Some(dir_a) = self.exact_newton_joint_psi_direction(
556 block_states,
557 derivative_blocks,
558 psi_index,
559 xmu,
560 x_ls,
561 &self.policy,
562 )?
563 else {
564 return Ok(None);
565 };
566 let etamu = &block_states[Self::BLOCK_MU].eta;
598 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
599 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
600 let weights_a =
601 gaussian_joint_psi_firstweights(&rows, &dir_a.z_primary_psi, &dir_a.z_ls_psi);
602 let objective_psi = weights_a.objective_psirow.sum();
603 let xmu_map = dir_a.x_primary_psi.as_linear_map_ref();
604 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
605 let score_mu =
606 xmu_map.transpose_mul(weights_a.scoremu.view()) + fast_atv(xmu, &weights_a.dscoremu);
607 let score_ls = x_ls_map.transpose_mul(weights_a.score_ls.view())
608 + fast_atv(x_ls, &weights_a.dscore_ls);
609 let score_psi = gaussian_pack_joint_score(&score_mu, &score_ls);
610 let hessian_psi_operator = build_two_block_custom_family_joint_psi_operator_from_actions(
611 dir_a.x_primary_psi.cloned_first_action(),
612 dir_a.x_ls_psi.cloned_first_action(),
613 0..xmu.ncols(),
614 xmu.ncols()..xmu.ncols() + x_ls.ncols(),
615 xmu,
616 x_ls,
617 &weights_a.hmumu,
618 &weights_a.hmu_ls,
619 &weights_a.h_ls_ls,
620 &weights_a.dhmumu,
621 &weights_a.dhmu_ls,
622 &weights_a.dh_ls_ls,
623 )?;
624 let hessian_psi = if hessian_psi_operator.is_some() {
625 Array2::zeros((0, 0))
626 } else {
627 gaussian_joint_psihessian_fromweights(xmu, x_ls, xmu_map, x_ls_map, &weights_a)?
628 };
629
630 Ok(Some(crate::custom_family::ExactNewtonJointPsiTerms {
631 objective_psi,
632 score_psi,
633 hessian_psi,
634 hessian_psi_operator,
635 }))
636 }
637
638 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_designs(
639 &self,
640 block_states: &[ParameterBlockState],
641 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
642 psi_i: usize,
643 psi_j: usize,
644 xmu: &Array2<f64>,
645 x_ls: &Array2<f64>,
646 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
647 let Some(dir_i) = self.exact_newton_joint_psi_direction(
648 block_states,
649 derivative_blocks,
650 psi_i,
651 xmu,
652 x_ls,
653 &self.policy,
654 )?
655 else {
656 return Ok(None);
657 };
658 let Some(dir_j) = self.exact_newton_joint_psi_direction(
659 block_states,
660 derivative_blocks,
661 psi_j,
662 xmu,
663 x_ls,
664 &self.policy,
665 )?
666 else {
667 return Ok(None);
668 };
669 Ok(Some(
670 self.exact_newton_joint_psisecond_order_terms_from_parts(
671 block_states,
672 derivative_blocks,
673 &dir_i,
674 &dir_j,
675 xmu,
676 x_ls,
677 None,
678 )?,
679 ))
680 }
681
682 pub(crate) fn exact_newton_joint_psisecond_order_terms_from_parts(
683 &self,
684 block_states: &[ParameterBlockState],
685 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
686 dir_i: &LocationScaleJointPsiDirection,
687 dir_j: &LocationScaleJointPsiDirection,
688 xmu: &Array2<f64>,
689 x_ls: &Array2<f64>,
690 subsample: Option<&[crate::outer_subsample::WeightedOuterRow]>,
691 ) -> Result<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms, String> {
692 let second_drifts = self.exact_newton_joint_psisecond_design_drifts(
693 block_states,
694 derivative_blocks,
695 dir_i,
696 dir_j,
697 xmu,
698 x_ls,
699 )?;
700 let n = self.y.len();
701 let xmu_i_map = dir_i.x_primary_psi.as_linear_map_ref();
702 let x_ls_i_map = dir_i.x_ls_psi.as_linear_map_ref();
703 let xmu_j_map = dir_j.x_primary_psi.as_linear_map_ref();
704 let x_ls_j_map = dir_j.x_ls_psi.as_linear_map_ref();
705 let xmu_ab_map = second_psi_linear_map(
706 second_drifts.x_primary_ab_action.as_ref(),
707 second_drifts.x_primary_ab.as_ref(),
708 n,
709 xmu.ncols(),
710 );
711 let x_ls_ab_map = second_psi_linear_map(
712 second_drifts.x_ls_ab_action.as_ref(),
713 second_drifts.x_ls_ab.as_ref(),
714 n,
715 x_ls.ncols(),
716 );
717 let etamu = &block_states[Self::BLOCK_MU].eta;
741 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
742 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
743 let mut weights_i =
744 gaussian_joint_psi_firstweights(&rows, &dir_i.z_primary_psi, &dir_i.z_ls_psi);
745 let mut weights_j =
746 gaussian_joint_psi_firstweights(&rows, &dir_j.z_primary_psi, &dir_j.z_ls_psi);
747 let mut secondweights = gaussian_joint_psisecondweights(
748 &rows,
749 &dir_i.z_primary_psi,
750 &dir_i.z_ls_psi,
751 &dir_j.z_primary_psi,
752 &dir_j.z_ls_psi,
753 &second_drifts.z_primary_ab,
754 &second_drifts.z_ls_ab,
755 );
756 if let Some(sub_rows) = subsample {
757 apply_ht_mask_first(&mut weights_i, sub_rows);
763 apply_ht_mask_first(&mut weights_j, sub_rows);
764 apply_ht_mask_second(&mut secondweights, sub_rows);
765 }
766 let objective_psi_psi = secondweights.objective_psi_psirow.sum();
767
768 let score_psi_psi = gaussian_pack_joint_score(
769 &(xmu_ab_map.transpose_mul(weights_i.scoremu.view())
770 + xmu_i_map.transpose_mul(weights_j.dscoremu.view())
771 + xmu_j_map.transpose_mul(weights_i.dscoremu.view())
772 + fast_atv(xmu, &secondweights.d2scoremu)),
773 &(x_ls_ab_map.transpose_mul(weights_i.score_ls.view())
774 + x_ls_i_map.transpose_mul(weights_j.dscore_ls.view())
775 + x_ls_j_map.transpose_mul(weights_i.dscore_ls.view())
776 + fast_atv(x_ls, &secondweights.d2score_ls)),
777 );
778 let hessian_psi_psi = gaussian_joint_psisecondhessian_fromweights(
779 xmu,
780 x_ls,
781 xmu_i_map,
782 x_ls_i_map,
783 xmu_j_map,
784 x_ls_j_map,
785 xmu_ab_map,
786 x_ls_ab_map,
787 &weights_i,
788 &weights_j,
789 &secondweights,
790 )?;
791
792 Ok(crate::custom_family::ExactNewtonJointPsiSecondOrderTerms {
793 objective_psi_psi,
794 score_psi_psi,
795 hessian_psi_psi,
796 hessian_psi_psi_operator: None,
797 })
798 }
799
800 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_designs(
801 &self,
802 block_states: &[ParameterBlockState],
803 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
804 psi_index: usize,
805 d_beta_flat: &Array1<f64>,
806 xmu: &Array2<f64>,
807 x_ls: &Array2<f64>,
808 ) -> Result<Option<Array2<f64>>, String> {
809 let Some(dir_a) = self.exact_newton_joint_psi_direction(
810 block_states,
811 derivative_blocks,
812 psi_index,
813 xmu,
814 x_ls,
815 &self.policy,
816 )?
817 else {
818 return Ok(None);
819 };
820 Ok(Some(
821 self.exact_newton_joint_psihessian_directional_derivative_from_parts(
822 block_states,
823 &dir_a,
824 d_beta_flat,
825 xmu,
826 x_ls,
827 None,
828 )?,
829 ))
830 }
831
832 pub(crate) fn exact_newton_joint_psihessian_directional_derivative_from_parts(
833 &self,
834 block_states: &[ParameterBlockState],
835 dir_a: &LocationScaleJointPsiDirection,
836 d_beta_flat: &Array1<f64>,
837 xmu: &Array2<f64>,
838 x_ls: &Array2<f64>,
839 subsample: Option<&[crate::outer_subsample::WeightedOuterRow]>,
840 ) -> Result<Array2<f64>, String> {
841 let etamu = &block_states[Self::BLOCK_MU].eta;
842 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
843 let pmu = xmu.ncols();
844 let p_ls = x_ls.ncols();
845 let xmu_map = dir_a.x_primary_psi.as_linear_map_ref();
846 let x_ls_map = dir_a.x_ls_psi.as_linear_map_ref();
847 let total = pmu + p_ls;
848 if d_beta_flat.len() != total {
849 return Err(GamlssError::DimensionMismatch { reason: format!(
850 "GaussianLocationScaleFamily joint psi hessian directional derivative length mismatch: got {}, expected {}",
851 d_beta_flat.len(),
852 total
853 ) }.into());
854 }
855 let u_ls = d_beta_flat.slice(s![pmu..pmu + p_ls]);
859 let xi_ls = fast_av(x_ls, &u_ls);
860 let uza_ls = x_ls_map.forward_mul(u_ls);
861 let rows = self.get_or_compute_row_scalars(etamu, eta_ls)?;
903 let mut mixedweights =
904 gaussian_joint_psi_mixed_driftweights(&rows, &xi_ls, &dir_a.z_ls_psi, &uza_ls);
905 if let Some(sub_rows) = subsample {
906 apply_ht_mask_mixed(&mut mixedweights, sub_rows);
911 }
912
913 gaussian_joint_psi_mixedhessian_drift_fromweights(
914 xmu,
915 x_ls,
916 xmu_map,
917 x_ls_map,
918 &mixedweights,
919 )
920 }
921
922 pub fn block_effective_jacobian(
929 specs: &[ParameterBlockSpec],
930 block_idx: usize,
931 ) -> Result<Box<dyn BlockEffectiveJacobian>, String> {
932 crate::block_layout::block_jacobian::AdditiveWiggleBlockLayout {
933 family: "GaussianLocationScaleFamily",
934 n_outputs: 2,
935 additive_blocks: &[Self::BLOCK_MU, Self::BLOCK_LOG_SIGMA],
936 wiggle_block: None,
937 }
938 .block_effective_jacobian(specs, block_idx)
939 }
940}
941
942pub struct GaussianLocationScaleChannelHessian {
962 pub(crate) h: ndarray::Array3<f64>,
964}
965
966impl GaussianLocationScaleChannelHessian {
967 pub fn from_pilot_observed_unclamped(
980 y: &ndarray::Array1<f64>,
981 w: &ndarray::Array1<f64>,
982 eta_mu: &ndarray::Array1<f64>,
983 eta_log_sigma: &ndarray::Array1<f64>,
984 ) -> Result<Self, String> {
985 let n = y.len();
986 if w.len() != n || eta_mu.len() != n || eta_log_sigma.len() != n {
987 return Err(format!(
988 "GaussianLocationScaleChannelHessian::from_pilot_observed_unclamped: \
989 length mismatch y={n} w={} eta_mu={} eta_log_sigma={}",
990 w.len(),
991 eta_mu.len(),
992 eta_log_sigma.len(),
993 ));
994 }
995 let mut h = ndarray::Array3::<f64>::zeros((n, 2, 2));
996 for i in 0..n {
997 let wi = w[i];
998 let mu_i = eta_mu[i];
999 let s_i = eta_log_sigma[i];
1000 let inv_sigma2 = (-2.0 * s_i).exp();
1001 let resid = y[i] - mu_i;
1002 h[[i, 0, 0]] = wi * inv_sigma2;
1003 h[[i, 1, 1]] = wi * 2.0 * resid * resid * inv_sigma2;
1004 h[[i, 0, 1]] = wi * 2.0 * resid * inv_sigma2;
1005 h[[i, 1, 0]] = h[[i, 0, 1]];
1006 }
1007 Ok(Self { h })
1008 }
1009
1010 pub fn from_pilot(
1018 y: &ndarray::Array1<f64>,
1019 w: &ndarray::Array1<f64>,
1020 eta_mu: &ndarray::Array1<f64>,
1021 eta_log_sigma: &ndarray::Array1<f64>,
1022 ) -> Result<Self, String> {
1023 let n = y.len();
1024 if w.len() != n || eta_mu.len() != n || eta_log_sigma.len() != n {
1025 return Err(format!(
1026 "GaussianLocationScaleChannelHessian::from_pilot: \
1027 length mismatch y={n} w={} eta_mu={} eta_log_sigma={}",
1028 w.len(),
1029 eta_mu.len(),
1030 eta_log_sigma.len(),
1031 ));
1032 }
1033 let mut h = ndarray::Array3::<f64>::zeros((n, 2, 2));
1034 for i in 0..n {
1035 let wi = w[i];
1036 let mu_i = eta_mu[i];
1037 let s_i = eta_log_sigma[i];
1038 let inv_sigma2 = (-2.0 * s_i).exp(); let resid = y[i] - mu_i;
1040 let h00 = wi * inv_sigma2;
1042 let h11 = wi * 2.0 * resid * resid * inv_sigma2;
1043 let h01 = wi * 2.0 * resid * inv_sigma2;
1044 let (e0, e1, u1_0, u1_1, u2_0, u2_1) = psd_clamp_2x2(h00, h01, h11);
1049 h[[i, 0, 0]] = e0 * u1_0 * u1_0 + e1 * u2_0 * u2_0;
1050 h[[i, 0, 1]] = e0 * u1_0 * u1_1 + e1 * u2_0 * u2_1;
1051 h[[i, 1, 0]] = h[[i, 0, 1]];
1052 h[[i, 1, 1]] = e0 * u1_1 * u1_1 + e1 * u2_1 * u2_1;
1053 }
1054 Ok(Self { h })
1055 }
1056}
1057
1058impl FamilyChannelHessian for GaussianLocationScaleChannelHessian {
1059 fn n_outputs(&self) -> usize {
1060 2
1061 }
1062
1063 fn n_subjects(&self) -> usize {
1064 self.h.shape()[0]
1065 }
1066
1067 fn fill_subject(&self, i: usize, out: &mut [f64]) {
1068 assert_eq!(out.len(), 4);
1069 out[0] = self.h[[i, 0, 0]];
1070 out[1] = self.h[[i, 0, 1]];
1071 out[2] = self.h[[i, 1, 0]];
1072 out[3] = self.h[[i, 1, 1]];
1073 }
1074
1075 fn evaluate_full(&self) -> ndarray::Array3<f64> {
1076 self.h.clone()
1077 }
1078}
1079
1080impl CustomFamily for GaussianLocationScaleFamily {
1081 fn exact_newton_joint_hessian_beta_dependent(&self) -> bool {
1095 true
1096 }
1097
1098 fn outer_seed_config(&self, n_params: usize) -> crate::seeding::SeedConfig {
1121 if n_params == 0 {
1122 return crate::seeding::SeedConfig::default();
1123 }
1124 let mut config = crate::seeding::SeedConfig::default();
1125 config.risk_profile = crate::seeding::SeedRiskProfile::GaussianLocationScale;
1126 config.max_seeds = 8;
1149 config.seed_budget = 4;
1150 config.screen_max_inner_iterations = 16;
1151 config
1152 }
1153
1154 fn output_channel_assignment(&self, specs: &[ParameterBlockSpec]) -> Option<Vec<usize>> {
1161 Some(
1164 (0..specs.len())
1165 .map(|i| usize::from(i == Self::BLOCK_LOG_SIGMA))
1166 .collect(),
1167 )
1168 }
1169
1170 fn coefficient_hessian_cost(&self, specs: &[ParameterBlockSpec]) -> u64 {
1171 crate::location_scale_engine::location_scale_coefficient_hessian_cost(
1179 self.y.len() as u64,
1180 specs,
1181 )
1182 }
1183
1184 fn evaluate(&self, block_states: &[ParameterBlockState]) -> Result<FamilyEvaluation, String> {
1185 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1186 let n = self.y.len();
1187 let etamu = &block_states[Self::BLOCK_MU].eta;
1188 let eta_log_sigma = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1189 if etamu.len() != n || eta_log_sigma.len() != n || self.weights.len() != n {
1190 return Err(GamlssError::DimensionMismatch {
1191 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
1192 }
1193 .into());
1194 }
1195
1196 let mut zmu = Array1::<f64>::zeros(n);
1208 let mut wmu = Array1::<f64>::zeros(n);
1209 let mut z_ls = Array1::<f64>::zeros(n);
1210 let mut w_ls = Array1::<f64>::zeros(n);
1211 let ln2pi = (2.0 * std::f64::consts::PI).ln();
1212 let mut ll = 0.0;
1213
1214 const CHUNK: usize = 1024;
1215 if let (
1216 Some(y_s),
1217 Some(w_s),
1218 Some(mu_s),
1219 Some(ls_s),
1220 Some(zmu_s),
1221 Some(wmu_s),
1222 Some(zls_s),
1223 Some(wls_s),
1224 ) = (
1225 self.y.as_slice_memory_order(),
1226 self.weights.as_slice_memory_order(),
1227 etamu.as_slice_memory_order(),
1228 eta_log_sigma.as_slice_memory_order(),
1229 zmu.as_slice_memory_order_mut(),
1230 wmu.as_slice_memory_order_mut(),
1231 z_ls.as_slice_memory_order_mut(),
1232 w_ls.as_slice_memory_order_mut(),
1233 ) {
1234 ll += zmu_s
1238 .par_chunks_mut(CHUNK)
1239 .zip(wmu_s.par_chunks_mut(CHUNK))
1240 .zip(zls_s.par_chunks_mut(CHUNK))
1241 .zip(wls_s.par_chunks_mut(CHUNK))
1242 .enumerate()
1243 .map(|(chunk_idx, (((zmu_c, wmu_c), zls_c), wls_c))| {
1244 let start = chunk_idx * CHUNK;
1245 let mut local_ll = 0.0;
1246 for local in 0..zmu_c.len() {
1247 let i = start + local;
1248 let row =
1249 gaussian_diagonal_row_kernel(y_s[i], mu_s[i], ls_s[i], w_s[i], ln2pi);
1250 zmu_c[local] = mu_s[i] + row.location_working_shift;
1251 wmu_c[local] = row.location_working_weight;
1252 zls_c[local] = row.log_sigma_working_response;
1253 wls_c[local] = row.log_sigma_working_weight;
1254 local_ll += row.log_likelihood;
1255 }
1256 local_ll
1257 })
1258 .sum::<f64>();
1259 } else {
1260 let y_view = self.y.view();
1263 let w_view = self.weights.view();
1264 let mu_view = etamu.view();
1265 let ls_view = eta_log_sigma.view();
1266 let zmu_s = zmu
1267 .as_slice_memory_order_mut()
1268 .expect("zeros is contiguous");
1269 let wmu_s = wmu
1270 .as_slice_memory_order_mut()
1271 .expect("zeros is contiguous");
1272 let zls_s = z_ls
1273 .as_slice_memory_order_mut()
1274 .expect("zeros is contiguous");
1275 let wls_s = w_ls
1276 .as_slice_memory_order_mut()
1277 .expect("zeros is contiguous");
1278 ll += zmu_s
1279 .par_chunks_mut(CHUNK)
1280 .zip(wmu_s.par_chunks_mut(CHUNK))
1281 .zip(zls_s.par_chunks_mut(CHUNK))
1282 .zip(wls_s.par_chunks_mut(CHUNK))
1283 .enumerate()
1284 .map(|(chunk_idx, (((zmu_c, wmu_c), zls_c), wls_c))| {
1285 let start = chunk_idx * CHUNK;
1286 let mut local_ll = 0.0;
1287 for local in 0..zmu_c.len() {
1288 let i = start + local;
1289 let row = gaussian_diagonal_row_kernel(
1290 y_view[i], mu_view[i], ls_view[i], w_view[i], ln2pi,
1291 );
1292 zmu_c[local] = mu_view[i] + row.location_working_shift;
1293 wmu_c[local] = row.location_working_weight;
1294 zls_c[local] = row.log_sigma_working_response;
1295 wls_c[local] = row.log_sigma_working_weight;
1296 local_ll += row.log_likelihood;
1297 }
1298 local_ll
1299 })
1300 .sum::<f64>();
1301 }
1302
1303 Ok(FamilyEvaluation {
1304 log_likelihood: ll,
1305 blockworking_sets: vec![
1306 BlockWorkingSet::diagonal_checked(zmu, wmu)?,
1307 BlockWorkingSet::diagonal_checked(z_ls, w_ls)?,
1308 ],
1309 })
1310 }
1311
1312 fn log_likelihood_only(&self, block_states: &[ParameterBlockState]) -> Result<f64, String> {
1313 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1314 let n = self.y.len();
1315 let etamu = &block_states[Self::BLOCK_MU].eta;
1316 let eta_log_sigma = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1317 if etamu.len() != n || eta_log_sigma.len() != n || self.weights.len() != n {
1318 return Err(GamlssError::DimensionMismatch {
1319 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
1320 }
1321 .into());
1322 }
1323 let ln2pi = (2.0 * std::f64::consts::PI).ln();
1327 let mut ll = 0.0;
1328 if let (Some(y_s), Some(w_s), Some(mu_s), Some(ls_s)) = (
1329 self.y.as_slice_memory_order(),
1330 self.weights.as_slice_memory_order(),
1331 etamu.as_slice_memory_order(),
1332 eta_log_sigma.as_slice_memory_order(),
1333 ) {
1334 use rayon::iter::{IntoParallelIterator, ParallelIterator};
1335 ll += (0..n)
1336 .into_par_iter()
1337 .map(|i| {
1338 let wi = w_s[i];
1339 if wi == 0.0 {
1340 return 0.0;
1341 }
1342 let sigma_i = logb_sigma_from_eta_scalar(ls_s[i]);
1343 let inv_s2 = (sigma_i * sigma_i).recip();
1344 let r = y_s[i] - mu_s[i];
1345 wi * (-0.5 * (r * r * inv_s2 + ln2pi + 2.0 * sigma_i.ln()))
1346 })
1347 .sum::<f64>();
1348 } else {
1349 use rayon::iter::{IntoParallelIterator, ParallelIterator};
1350 ll += (0..n)
1351 .into_par_iter()
1352 .map(|i| {
1353 let wi = self.weights[i];
1354 if wi == 0.0 {
1355 return 0.0;
1356 }
1357 let sigma_i = logb_sigma_from_eta_scalar(eta_log_sigma[i]);
1358 let inv_s2 = (sigma_i * sigma_i).recip();
1359 let r = self.y[i] - etamu[i];
1360 wi * (-0.5 * (r * r * inv_s2 + ln2pi + 2.0 * sigma_i.ln()))
1361 })
1362 .sum::<f64>();
1363 }
1364 Ok(ll)
1365 }
1366
1367 fn log_likelihood_only_with_options(
1378 &self,
1379 block_states: &[ParameterBlockState],
1380 options: &BlockwiseFitOptions,
1381 ) -> Result<f64, String> {
1382 let Some(subsample) = options.outer_score_subsample.as_ref() else {
1383 return self.log_likelihood_only(block_states);
1384 };
1385 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1386 let n = self.y.len();
1387 let etamu = &block_states[Self::BLOCK_MU].eta;
1388 let eta_log_sigma = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1389 if etamu.len() != n || eta_log_sigma.len() != n || self.weights.len() != n {
1390 return Err(GamlssError::DimensionMismatch {
1391 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
1392 }
1393 .into());
1394 }
1395 let ln2pi = (2.0 * std::f64::consts::PI).ln();
1396 use rayon::iter::ParallelIterator;
1397 let ll: f64 = subsample
1398 .rows
1399 .par_iter()
1400 .map(|row| {
1401 let i = row.index;
1402 let wi = self.weights[i];
1403 if wi == 0.0 {
1404 return 0.0;
1405 }
1406 let sigma_i = logb_sigma_from_eta_scalar(eta_log_sigma[i]);
1407 let inv_s2 = (sigma_i * sigma_i).recip();
1408 let r = self.y[i] - etamu[i];
1409 row.weight * wi * (-0.5 * (r * r * inv_s2 + ln2pi + 2.0 * sigma_i.ln()))
1410 })
1411 .sum();
1412 Ok(ll)
1413 }
1414
1415 fn exact_newton_joint_hessian(
1416 &self,
1417 block_states: &[ParameterBlockState],
1418 ) -> Result<Option<Array2<f64>>, String> {
1419 self.exact_newton_joint_hessian_for_specs(block_states, None)
1420 }
1421
1422 fn has_explicit_joint_hessian(&self) -> bool {
1423 true
1424 }
1425
1426 fn joint_jeffreys_term_required(&self) -> bool {
1442 false
1443 }
1444
1445 fn exact_newton_joint_hessian_directional_derivative(
1446 &self,
1447 block_states: &[ParameterBlockState],
1448 d_beta_flat: &Array1<f64>,
1449 ) -> Result<Option<Array2<f64>>, String> {
1450 self.exact_newton_joint_hessian_directional_derivative_for_specs(
1451 block_states,
1452 None,
1453 d_beta_flat,
1454 )
1455 }
1456
1457 fn exact_newton_joint_hessiansecond_directional_derivative(
1458 &self,
1459 block_states: &[ParameterBlockState],
1460 d_beta_u_flat: &Array1<f64>,
1461 d_betav_flat: &Array1<f64>,
1462 ) -> Result<Option<Array2<f64>>, String> {
1463 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
1464 block_states,
1465 None,
1466 d_beta_u_flat,
1467 d_betav_flat,
1468 )
1469 }
1470
1471 fn diagonalworking_weights_directional_derivative(
1472 &self,
1473 block_states: &[ParameterBlockState],
1474 block_idx: usize,
1475 d_eta: &Array1<f64>,
1476 ) -> Result<Option<Array1<f64>>, String> {
1477 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1478 let n = self.y.len();
1479 let eta_t = &block_states[Self::BLOCK_MU].eta;
1480 let eta_ls = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1481 if eta_t.len() != n || eta_ls.len() != n || self.weights.len() != n || d_eta.len() != n {
1482 return Err(GamlssError::DimensionMismatch {
1483 reason: "GaussianLocationScaleFamily input size mismatch".to_string(),
1484 }
1485 .into());
1486 }
1487
1488 let sigma = eta_ls.mapv(logb_sigma_from_eta_scalar);
1489 let mut dw = Array1::<f64>::zeros(n);
1490 match block_idx {
1491 Self::BLOCK_MU => {
1492 Ok(Some(dw))
1500 }
1501 Self::BLOCK_LOG_SIGMA => {
1502 use rayon::iter::{IntoParallelIterator, ParallelIterator};
1525 let dw_vec: Vec<f64> = (0..n)
1526 .into_par_iter()
1527 .map(|i| {
1528 let d1 = crate::sigma_link::logb_sigma_jet1_scalar(eta_ls[i]).d1;
1529 gaussian_log_sigma_irlsinfo_directional_derivative(
1530 self.weights[i],
1531 sigma[i],
1532 d1,
1533 d_eta[i],
1534 )
1535 })
1536 .collect();
1537 for (i, v) in dw_vec.into_iter().enumerate() {
1538 dw[i] = v;
1539 }
1540 Ok(Some(dw))
1541 }
1542 _ => Ok(None),
1543 }
1544 }
1545
1546 fn exact_newton_joint_hessian_with_specs(
1547 &self,
1548 block_states: &[ParameterBlockState],
1549 specs: &[ParameterBlockSpec],
1550 ) -> Result<Option<Array2<f64>>, String> {
1551 self.exact_newton_joint_hessian_for_specs(block_states, Some(specs))
1552 }
1553
1554 fn exact_newton_joint_hessian_directional_derivative_with_specs(
1555 &self,
1556 block_states: &[ParameterBlockState],
1557 specs: &[ParameterBlockSpec],
1558 d_beta_flat: &Array1<f64>,
1559 ) -> Result<Option<Array2<f64>>, String> {
1560 self.exact_newton_joint_hessian_directional_derivative_for_specs(
1561 block_states,
1562 Some(specs),
1563 d_beta_flat,
1564 )
1565 }
1566
1567 fn exact_newton_joint_hessian_second_directional_derivative_with_specs(
1568 &self,
1569 block_states: &[ParameterBlockState],
1570 specs: &[ParameterBlockSpec],
1571 d_beta_u_flat: &Array1<f64>,
1572 d_betav_flat: &Array1<f64>,
1573 ) -> Result<Option<Array2<f64>>, String> {
1574 self.exact_newton_joint_hessian_second_directional_derivative_for_specs(
1575 block_states,
1576 Some(specs),
1577 d_beta_u_flat,
1578 d_betav_flat,
1579 )
1580 }
1581
1582 fn exact_newton_joint_psi_terms(
1583 &self,
1584 block_states: &[ParameterBlockState],
1585 specs: &[ParameterBlockSpec],
1586 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1587 psi_index: usize,
1588 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiTerms>, String> {
1589 self.exact_newton_joint_psi_terms_for_specs(
1590 block_states,
1591 specs,
1592 derivative_blocks,
1593 psi_index,
1594 )
1595 }
1596
1597 fn exact_newton_joint_psisecond_order_terms(
1598 &self,
1599 block_states: &[ParameterBlockState],
1600 specs: &[ParameterBlockSpec],
1601 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1602 psi_i: usize,
1603 psi_j: usize,
1604 ) -> Result<Option<crate::custom_family::ExactNewtonJointPsiSecondOrderTerms>, String> {
1605 self.exact_newton_joint_psisecond_order_terms_for_specs(
1606 block_states,
1607 specs,
1608 derivative_blocks,
1609 psi_i,
1610 psi_j,
1611 )
1612 }
1613
1614 fn exact_newton_joint_psihessian_directional_derivative(
1615 &self,
1616 block_states: &[ParameterBlockState],
1617 specs: &[ParameterBlockSpec],
1618 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1619 psi_index: usize,
1620 d_beta_flat: &Array1<f64>,
1621 ) -> Result<Option<Array2<f64>>, String> {
1622 self.exact_newton_joint_psihessian_directional_derivative_for_specs(
1623 block_states,
1624 specs,
1625 derivative_blocks,
1626 psi_index,
1627 d_beta_flat,
1628 )
1629 }
1630
1631 fn exact_newton_joint_psi_workspace(
1632 &self,
1633 block_states: &[ParameterBlockState],
1634 specs: &[ParameterBlockSpec],
1635 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1636 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
1637 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1638 if specs.len() != 2 || derivative_blocks.len() != 2 {
1639 return Err(GamlssError::DimensionMismatch { reason: format!(
1640 "GaussianLocationScaleFamily joint psi workspace expects 2 specs and 2 derivative block lists, got {} / {}",
1641 specs.len(),
1642 derivative_blocks.len()
1643 ) }.into());
1644 }
1645 Ok(Some(Arc::new(
1646 GaussianLocationScaleExactNewtonJointPsiWorkspace::new(
1647 self.clone(),
1648 block_states.to_vec(),
1649 specs,
1650 derivative_blocks.to_vec(),
1651 )?,
1652 )))
1653 }
1654
1655 fn exact_newton_joint_psi_workspace_with_options(
1673 &self,
1674 block_states: &[ParameterBlockState],
1675 specs: &[ParameterBlockSpec],
1676 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
1677 options: &BlockwiseFitOptions,
1678 ) -> Result<Option<Arc<dyn ExactNewtonJointPsiWorkspace>>, String> {
1679 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1680 if specs.len() != 2 || derivative_blocks.len() != 2 {
1681 return Err(GamlssError::DimensionMismatch { reason: format!(
1682 "GaussianLocationScaleFamily joint psi workspace expects 2 specs and 2 derivative block lists, got {} / {}",
1683 specs.len(),
1684 derivative_blocks.len()
1685 ) }.into());
1686 }
1687 Ok(Some(Arc::new(
1688 GaussianLocationScaleExactNewtonJointPsiWorkspace::new_with_subsample(
1689 self.clone(),
1690 block_states.to_vec(),
1691 specs,
1692 derivative_blocks.to_vec(),
1693 options.outer_score_subsample.clone(),
1694 )?,
1695 )))
1696 }
1697
1698 fn exact_newton_joint_hessian_workspace(
1699 &self,
1700 block_states: &[ParameterBlockState],
1701 specs: &[ParameterBlockSpec],
1702 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
1703 let Some((xmu, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
1704 return Ok(None);
1705 };
1706 let workspace = GaussianLocationScaleHessianWorkspace::new(
1707 self.clone(),
1708 block_states.to_vec(),
1709 xmu.into_owned(),
1710 x_ls.into_owned(),
1711 )?;
1712 Ok(Some(Arc::new(workspace)))
1713 }
1714
1715 fn exact_newton_joint_hessian_workspace_with_options(
1729 &self,
1730 block_states: &[ParameterBlockState],
1731 specs: &[ParameterBlockSpec],
1732 options: &BlockwiseFitOptions,
1733 ) -> Result<Option<Arc<dyn ExactNewtonJointHessianWorkspace>>, String> {
1734 let Some((xmu, x_ls)) = self.exact_joint_dense_block_designs(Some(specs))? else {
1735 return Ok(None);
1736 };
1737 let mut workspace = GaussianLocationScaleHessianWorkspace::new(
1738 self.clone(),
1739 block_states.to_vec(),
1740 xmu.into_owned(),
1741 x_ls.into_owned(),
1742 )?;
1743 if let Some(subsample) = options.outer_score_subsample.as_ref() {
1744 workspace.apply_outer_subsample(subsample.rows.as_ref());
1745 }
1746 Ok(Some(Arc::new(workspace)))
1747 }
1748
1749 fn inner_coefficient_hessian_hvp_available(&self, specs: &[ParameterBlockSpec]) -> bool {
1750 self.exact_joint_supported()
1756 && matches!(
1757 self.exact_joint_dense_block_designs(Some(specs)),
1758 Ok(Some(_))
1759 )
1760 }
1761
1762 fn outer_derivative_subsample_capable(&self) -> bool {
1784 true
1785 }
1786}
1787
1788impl CustomFamilyGenerative for GaussianLocationScaleFamily {
1789 fn generativespec(
1790 &self,
1791 block_states: &[ParameterBlockState],
1792 ) -> Result<GenerativeSpec, String> {
1793 validate_block_count::<GamlssError>("GaussianLocationScaleFamily", 2, block_states.len())?;
1794 let mu = block_states[Self::BLOCK_MU].eta.clone();
1795 let eta_log_sigma = &block_states[Self::BLOCK_LOG_SIGMA].eta;
1796 let sigma = gamlss_rowwise_map(eta_log_sigma.len(), |i| {
1797 logb_sigma_from_eta_scalar(eta_log_sigma[i])
1798 });
1799 Ok(GenerativeSpec {
1800 mean: mu,
1801 noise: NoiseModel::Gaussian { sigma },
1802 })
1803 }
1804}