1use super::*;
6
7#[derive(Debug)]
13pub enum GamlssError {
14 DimensionMismatch { reason: String },
17 InvalidInput { reason: String },
21 NonFinite { reason: String },
24 UnsupportedConfiguration { reason: String },
28 ConstraintViolation { reason: String },
31 NumericalFailure { reason: String },
35}
36
37impl_reason_error_boilerplate! {
38 GamlssError {
39 DimensionMismatch,
40 InvalidInput,
41 NonFinite,
42 UnsupportedConfiguration,
43 ConstraintViolation,
44 NumericalFailure,
45 }
46}
47
48impl From<crate::block_layout::block_count::BlockCountMismatch> for GamlssError {
49 fn from(err: crate::block_layout::block_count::BlockCountMismatch) -> GamlssError {
50 GamlssError::DimensionMismatch {
51 reason: err.message(),
52 }
53 }
54}
55
56pub(crate) const MIN_PROB: f64 = 1e-10;
69
70pub(crate) const MIN_DERIV: f64 = 1e-8;
71
72use gam_problem::MIN_WEIGHT;
89
90pub(crate) const ETA_HARD_CLAMP: f64 = 30.0;
95
96#[inline]
103pub(crate) fn saturated_exp_eta(eta: f64) -> f64 {
104 eta.clamp(-ETA_HARD_CLAMP, ETA_HARD_CLAMP)
105 .exp()
106 .max(MIN_WEIGHT)
107}
108
109pub(crate) const WARMSTART_LOG_LAMBDA_FLOOR: f64 = 1e-12;
118
119pub(crate) const EXACT_DENSE_BLOCK_BUDGET_BYTES: usize = 512 * 1024 * 1024;
120
121pub(crate) const EXACT_DENSE_TOTAL_BUDGET_BYTES: usize = 2 * 1024 * 1024 * 1024;
122
123pub(crate) const GAMLSS_ROWWISE_PAR_MIN_N: usize = 4096;
124
125pub(crate) const GAMLSS_PROJECTED_TRACE_TARGET_BYTES: usize = 32 * 1024 * 1024;
126
127pub(crate) const GAMLSS_PROJECTED_TRACE_MIN_CHUNK_ROWS: usize = 64;
128
129pub(crate) const GAMLSS_PROJECTED_TRACE_MAX_CHUNK_ROWS: usize = 8192;
130
131pub(crate) fn gamlss_projected_trace_chunk_rows(
132 rank: usize,
133 projected_channel_count: usize,
134 gram_column_count: usize,
135) -> usize {
136 let per_row_values = rank
137 .saturating_mul(projected_channel_count.max(1))
138 .saturating_add(gram_column_count.max(1))
139 .max(1);
140 let per_row_bytes = per_row_values.saturating_mul(std::mem::size_of::<f64>());
141 let rows = GAMLSS_PROJECTED_TRACE_TARGET_BYTES / per_row_bytes.max(1);
142 rows.clamp(
143 GAMLSS_PROJECTED_TRACE_MIN_CHUNK_ROWS,
144 GAMLSS_PROJECTED_TRACE_MAX_CHUNK_ROWS,
145 )
146}
147
148pub(crate) fn gamlss_rowwise_map<F>(n: usize, f: F) -> Array1<f64>
149where
150 F: Fn(usize) -> f64 + Sync,
151{
152 if n >= GAMLSS_ROWWISE_PAR_MIN_N {
153 Array1::from((0..n).into_par_iter().map(&f).collect::<Vec<f64>>())
154 } else {
155 Array1::from_iter((0..n).map(f))
156 }
157}
158
159pub(crate) fn gamlss_rowwise_map_result<F>(n: usize, f: F) -> Result<Array1<f64>, String>
160where
161 F: Fn(usize) -> Result<f64, String> + Sync,
162{
163 if n >= GAMLSS_ROWWISE_PAR_MIN_N {
164 let values: Result<Vec<f64>, String> = (0..n).into_par_iter().map(&f).collect();
165 Ok(Array1::from(values?))
166 } else {
167 let mut out = Array1::<f64>::zeros(n);
168 for i in 0..n {
169 out[i] = f(i)?;
170 }
171 Ok(out)
172 }
173}
174
175pub(crate) enum DenseOrOperator<'a> {
176 Borrowed(&'a Array2<f64>),
177 Owned(Array2<f64>),
178 Operator(DesignMatrix),
179}
180
181impl DenseOrOperator<'_> {
182 pub(crate) fn nrows(&self) -> usize {
183 match self {
184 Self::Borrowed(dense) => dense.nrows(),
185 Self::Owned(dense) => dense.nrows(),
186 Self::Operator(design) => design.nrows(),
187 }
188 }
189
190 pub(crate) fn ncols(&self) -> usize {
191 match self {
192 Self::Borrowed(dense) => dense.ncols(),
193 Self::Owned(dense) => dense.ncols(),
194 Self::Operator(design) => design.ncols(),
195 }
196 }
197
198 pub(crate) fn row_chunk(&self, rows: std::ops::Range<usize>) -> Result<Array2<f64>, String> {
199 match self {
200 Self::Borrowed(dense) => Ok(dense.slice(s![rows, ..]).to_owned()),
201 Self::Owned(dense) => Ok(dense.slice(s![rows, ..]).to_owned()),
202 Self::Operator(design) => design.try_row_chunk(rows).map_err(|e| e.to_string()),
203 }
204 }
205
206 pub(crate) fn dot(&self, beta: ArrayView1<'_, f64>) -> Array1<f64> {
207 let n = self.nrows();
208 let p = self.ncols();
209 assert_eq!(beta.len(), p);
210 match self {
211 Self::Borrowed(dense) => fast_av(*dense, &beta),
212 Self::Owned(dense) => fast_av(dense, &beta),
213 Self::Operator(design) => {
214 let mut out = Array1::<f64>::zeros(n);
215 for rows in exact_design_row_chunks(n, p) {
216 let chunk = design
217 .try_row_chunk(rows.clone())
218 .expect("gamlss DesignSlot::dot: design row chunk materialization failed");
219 out.slice_mut(s![rows]).assign(&fast_av(&chunk, &beta));
220 }
221 out
222 }
223 }
224 }
225}
226
227pub(crate) fn dense_block_from_spec<'a>(
234 spec: &'a ParameterBlockSpec,
235 material_policy: &gam_runtime::resource::MaterializationPolicy,
236 materialization_label: &str,
237) -> Result<Cow<'a, Array2<f64>>, String> {
238 match spec.design.as_dense_ref() {
239 Some(d) => Ok(Cow::Borrowed(d)),
240 None => Ok(Cow::Owned(
241 spec.design
242 .try_to_dense_with_policy(material_policy, "gamlss dense_block_from_spec")
243 .map_err(|e| format!("{materialization_label}: {e}"))?
244 .as_ref()
245 .clone(),
246 )),
247 }
248}
249
250pub(crate) fn dense_locscale_block_designs_fromspecs<'a>(
257 specs: &'a [ParameterBlockSpec],
258 expected_count: usize,
259 family_name: &str,
260 short_family_name: &str,
261 primary_block_idx: usize,
262 log_sigma_block_idx: usize,
263 primary_label: &str,
264 material_policy: &gam_runtime::resource::MaterializationPolicy,
265) -> Result<(Cow<'a, Array2<f64>>, Cow<'a, Array2<f64>>), String> {
266 if specs.len() != expected_count {
267 return Err(GamlssError::DimensionMismatch {
268 reason: format!(
269 "{family_name} expects {expected_count} specs, got {}",
270 specs.len()
271 ),
272 }
273 .into());
274 }
275 let primary = dense_block_from_spec(
276 &specs[primary_block_idx],
277 material_policy,
278 &format!("{short_family_name} dense_block_designs_fromspecs {primary_label}"),
279 )?;
280 let log_sigma = dense_block_from_spec(
281 &specs[log_sigma_block_idx],
282 material_policy,
283 &format!("{short_family_name} dense_block_designs_fromspecs log_sigma"),
284 )?;
285 Ok((primary, log_sigma))
286}
287
288pub(crate) fn gamlss_joint_gradient_from_working_sets(
304 eval: &FamilyEvaluation,
305 specs: &[ParameterBlockSpec],
306 states: &[ParameterBlockState],
307) -> Result<ExactNewtonJointGradientEvaluation, String> {
308 if eval.blockworking_sets.len() != specs.len() || states.len() != specs.len() {
309 return Err(GamlssError::DimensionMismatch { reason: format!(
310 "gamlss joint gradient: block/spec/state count mismatch (working_sets={}, specs={}, states={})",
311 eval.blockworking_sets.len(),
312 specs.len(),
313 states.len()
314 ) }
315 .into());
316 }
317 let total: usize = specs.iter().map(|spec| spec.design.ncols()).sum();
318 let mut gradient = Array1::<f64>::zeros(total);
319 let mut offset = 0usize;
320 for ((spec, work), state) in specs
321 .iter()
322 .zip(eval.blockworking_sets.iter())
323 .zip(states.iter())
324 {
325 let width = spec.design.ncols();
326 let block_grad = match work {
327 BlockWorkingSet::Diagonal {
328 working_response,
329 working_weights,
330 } => {
331 let n = working_response.len();
332 if working_weights.len() != n || state.eta.len() != n || spec.design.nrows() != n {
333 return Err(GamlssError::DimensionMismatch { reason: format!(
334 "gamlss joint gradient: diagonal working-set length mismatch (z={}, w={}, η={}, X_rows={})",
335 n,
336 working_weights.len(),
337 state.eta.len(),
338 spec.design.nrows()
339 ) }
340 .into());
341 }
342 let mut weighted = Array1::<f64>::zeros(n);
343 for i in 0..n {
344 weighted[i] = working_weights[i] * (working_response[i] - state.eta[i]);
345 }
346 spec.design.transpose_vector_multiply(&weighted)
347 }
348 BlockWorkingSet::ExactNewton {
349 gradient: block_gradient,
350 ..
351 } => block_gradient.clone(),
352 };
353 if block_grad.len() != width {
354 return Err(GamlssError::DimensionMismatch { reason: format!(
355 "gamlss joint gradient: assembled block gradient length {} != design cols {width}",
356 block_grad.len()
357 ) }
358 .into());
359 }
360 gradient
361 .slice_mut(s![offset..offset + width])
362 .assign(&block_grad);
363 offset += width;
364 }
365 Ok(ExactNewtonJointGradientEvaluation {
366 log_likelihood: eval.log_likelihood,
367 gradient,
368 })
369}
370
371pub(crate) fn dense_locscale_block_designs_cached<'a>(
378 primary_design: Option<&'a DesignMatrix>,
379 log_sigma_design: Option<&'a DesignMatrix>,
380 family_name: &str,
381 short_family_name: &str,
382 primary_label: &str,
383 material_policy: &gam_runtime::resource::MaterializationPolicy,
384) -> Result<(Cow<'a, Array2<f64>>, Cow<'a, Array2<f64>>), String> {
385 let primary_design = primary_design
386 .ok_or_else(|| format!("{family_name} exact path is missing {primary_label} design"))?;
387 let log_sigma_design = log_sigma_design
388 .ok_or_else(|| format!("{family_name} exact path is missing log-sigma design"))?;
389 let primary = match primary_design.as_dense_ref() {
390 Some(d) => Cow::Borrowed(d),
391 None => Cow::Owned(
392 primary_design
393 .try_to_dense_with_policy(material_policy, "gamlss dense_locscale_block_designs")
394 .map_err(|e| {
395 format!("{short_family_name} dense_block_designs {primary_label}: {e}")
396 })?
397 .as_ref()
398 .clone(),
399 ),
400 };
401 let log_sigma = match log_sigma_design.as_dense_ref() {
402 Some(d) => Cow::Borrowed(d),
403 None => Cow::Owned(
404 log_sigma_design
405 .try_to_dense_with_policy(material_policy, "gamlss dense_locscale_block_designs")
406 .map_err(|e| format!("{short_family_name} dense_block_designs log_sigma: {e}"))?
407 .as_ref()
408 .clone(),
409 ),
410 };
411 Ok((primary, log_sigma))
412}
413
414pub(crate) struct LocScalePsiDirectionParts {
419 pub(crate) block_idx: usize,
420 pub(crate) local_idx: usize,
421 pub(crate) primary_psi: PsiDesignMap,
422 pub(crate) log_sigma_psi: PsiDesignMap,
423 pub(crate) primary_z: Array1<f64>,
424 pub(crate) log_sigma_z: Array1<f64>,
425}
426
427pub(crate) fn locscale_joint_psi_direction_parts(
437 block_states: &[ParameterBlockState],
438 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
439 psi_index: usize,
440 n: usize,
441 p_primary: usize,
442 p_log_sigma: usize,
443 primary_block_idx: usize,
444 log_sigma_block_idx: usize,
445 expected_blocks: usize,
446 family_name: &str,
447 primary_label: &str,
448 policy: &gam_runtime::resource::ResourcePolicy,
449) -> Result<Option<LocScalePsiDirectionParts>, String> {
450 validate_block_count::<GamlssError>(family_name, expected_blocks, block_states.len())?;
451 if derivative_blocks.len() != expected_blocks {
452 return Err(GamlssError::DimensionMismatch {
453 reason: format!(
454 "{family_name} joint psi direction expects {expected_blocks} derivative block lists, got {}",
455 derivative_blocks.len()
456 ),
457 }
458 .into());
459 }
460 let beta_primary = &block_states[primary_block_idx].beta;
461 let beta_log_sigma = &block_states[log_sigma_block_idx].beta;
462
463 let mut global = 0usize;
464 for (block_idx, block_derivs) in derivative_blocks.iter().enumerate() {
465 for (local_idx, deriv) in block_derivs.iter().enumerate() {
466 if global == psi_index {
467 let primary_psi;
468 let log_sigma_psi;
469 let primary_z;
470 let log_sigma_z;
471 if block_idx == primary_block_idx {
472 primary_psi = resolve_custom_family_x_psi_map(
473 deriv,
474 n,
475 p_primary,
476 0..n,
477 &format!("{family_name} {primary_label}"),
478 policy,
479 )?;
480 primary_z = primary_psi
481 .forward_mul(beta_primary.view())
482 .map_err(|e| format!("{family_name} {primary_label} forward_mul: {e}"))?;
483 log_sigma_psi = PsiDesignMap::Zero {
484 nrows: n,
485 ncols: p_log_sigma,
486 };
487 log_sigma_z = Array1::<f64>::zeros(n);
488 } else if block_idx == log_sigma_block_idx {
489 log_sigma_psi = resolve_custom_family_x_psi_map(
490 deriv,
491 n,
492 p_log_sigma,
493 0..n,
494 &format!("{family_name} log-sigma"),
495 policy,
496 )?;
497 log_sigma_z = log_sigma_psi
498 .forward_mul(beta_log_sigma.view())
499 .map_err(|e| format!("{family_name} log-sigma forward_mul: {e}"))?;
500 primary_psi = PsiDesignMap::Zero {
501 nrows: n,
502 ncols: p_primary,
503 };
504 primary_z = Array1::<f64>::zeros(n);
505 } else {
506 return Ok(None);
507 }
508 return Ok(Some(LocScalePsiDirectionParts {
509 block_idx,
510 local_idx,
511 primary_psi,
512 log_sigma_psi,
513 primary_z,
514 log_sigma_z,
515 }));
516 }
517 global += 1;
518 }
519 }
520 Ok(None)
521}
522
523pub(crate) struct LocScalePsiDriftConfig<'a> {
528 pub(crate) n: usize,
529 pub(crate) p_primary: usize,
530 pub(crate) p_log_sigma: usize,
531 pub(crate) primary_block_idx: usize,
532 pub(crate) log_sigma_block_idx: usize,
533 pub(crate) family_name: &'a str,
534 pub(crate) primary_label: &'a str,
535 pub(crate) policy: &'a gam_runtime::resource::ResourcePolicy,
536}
537
538pub(crate) fn locscale_joint_psisecond_design_drifts(
539 block_states: &[ParameterBlockState],
540 derivative_blocks: &[Vec<crate::custom_family::CustomFamilyBlockPsiDerivative>],
541 psi_a: &LocationScaleJointPsiDirection,
542 psi_b: &LocationScaleJointPsiDirection,
543 cfg: LocScalePsiDriftConfig<'_>,
544) -> Result<LocationScaleJointPsiSecondDrifts, String> {
545 let beta_primary = &block_states[cfg.primary_block_idx].beta;
546 let beta_log_sigma = &block_states[cfg.log_sigma_block_idx].beta;
547 let mut primary_ab_action = None;
548 let mut log_sigma_ab_action = None;
549 let mut primary_ab = None;
550 let mut log_sigma_ab = None;
551
552 if psi_a.block_idx == psi_b.block_idx {
556 let deriv = &derivative_blocks[psi_a.block_idx][psi_a.local_idx];
557 let deriv_b = &derivative_blocks[psi_b.block_idx][psi_b.local_idx];
558 if psi_a.block_idx == cfg.primary_block_idx {
559 let (action, matrix) = psi_psi_map_to_drift_slots(
560 deriv,
561 deriv_b,
562 psi_b.local_idx,
563 cfg.n,
564 cfg.p_primary,
565 &format!("{} {}", cfg.family_name, cfg.primary_label),
566 cfg.policy,
567 )?;
568 primary_ab_action = action;
569 primary_ab = matrix;
570 } else if psi_a.block_idx == cfg.log_sigma_block_idx {
571 let (action, matrix) = psi_psi_map_to_drift_slots(
572 deriv,
573 deriv_b,
574 psi_b.local_idx,
575 cfg.n,
576 cfg.p_log_sigma,
577 &format!("{} log-sigma", cfg.family_name),
578 cfg.policy,
579 )?;
580 log_sigma_ab_action = action;
581 log_sigma_ab = matrix;
582 }
583 }
584
585 let z_primary_ab = second_psi_linear_map(
586 primary_ab_action.as_ref(),
587 primary_ab.as_ref(),
588 cfg.n,
589 cfg.p_primary,
590 )
591 .forward_mul(beta_primary.view());
592 let z_ls_ab = second_psi_linear_map(
593 log_sigma_ab_action.as_ref(),
594 log_sigma_ab.as_ref(),
595 cfg.n,
596 cfg.p_log_sigma,
597 )
598 .forward_mul(beta_log_sigma.view());
599
600 Ok(LocationScaleJointPsiSecondDrifts {
601 x_primary_ab_action: primary_ab_action,
602 x_ls_ab_action: log_sigma_ab_action,
603 x_primary_ab: primary_ab,
604 x_ls_ab: log_sigma_ab,
605 z_primary_ab,
606 z_ls_ab,
607 })
608}
609
610pub(crate) fn psi_psi_map_to_drift_slots(
611 deriv: &crate::custom_family::CustomFamilyBlockPsiDerivative,
612 deriv_b: &crate::custom_family::CustomFamilyBlockPsiDerivative,
613 local_idx_b: usize,
614 n: usize,
615 p: usize,
616 label: &str,
617 policy: &gam_runtime::resource::ResourcePolicy,
618) -> Result<
619 (
620 Option<crate::custom_family::CustomFamilyPsiSecondDesignAction>,
621 Option<Array2<f64>>,
622 ),
623 String,
624> {
625 match resolve_custom_family_x_psi_psi_map(
626 deriv,
627 deriv_b,
628 local_idx_b,
629 n,
630 p,
631 0..n,
632 label,
633 policy,
634 )? {
635 crate::custom_family::PsiDesignMap::Second { action } => Ok((Some(action), None)),
636 crate::custom_family::PsiDesignMap::Dense { matrix } => Ok((None, Some((*matrix).clone()))),
637 crate::custom_family::PsiDesignMap::Zero { .. } => Ok((None, None)),
638 crate::custom_family::PsiDesignMap::First { .. } => {
639 Err(GamlssError::UnsupportedConfiguration {
640 reason: format!("{label}: unexpected First variant from _psi_psi_map"),
641 }
642 .into())
643 }
644 }
645}
646
647pub(crate) fn dense_block_or_operator<'a>(
648 design: &'a DesignMatrix,
649 n: usize,
650 p: usize,
651 budget_bytes: usize,
652 policy: &gam_runtime::resource::ResourcePolicy,
653) -> DenseOrOperator<'a> {
654 if let Some(dense) = design.as_dense_ref() {
655 return DenseOrOperator::Borrowed(dense);
656 }
657
658 let dense_bytes = 8usize.saturating_mul(n).saturating_mul(p);
659 if dense_bytes <= budget_bytes
660 && let Ok(arc) = design
661 .try_to_dense_with_policy(&policy.material_policy(), "gamlss dense_block_or_operator")
662 {
663 return DenseOrOperator::Owned(arc.as_ref().clone());
664 }
665
666 DenseOrOperator::Operator(design.clone())
667}
668
669pub(crate) fn dense_blocks_planned_budget(blocks: &[&DesignMatrix]) -> Vec<usize> {
670 let mut planned = vec![0; blocks.len()];
671 let mut total = 0usize;
672 for (idx, design) in blocks.iter().enumerate() {
673 if design.as_dense_ref().is_some() {
674 continue;
675 }
676 let bytes = 8usize
677 .saturating_mul(design.nrows())
678 .saturating_mul(design.ncols());
679 if bytes <= EXACT_DENSE_BLOCK_BUDGET_BYTES
680 && total.saturating_add(bytes) <= EXACT_DENSE_TOTAL_BUDGET_BYTES
681 {
682 planned[idx] = bytes;
683 total += bytes;
684 }
685 }
686 planned
687}
688
689pub(crate) fn exact_design_row_chunks(
690 n: usize,
691 p: usize,
692) -> impl Iterator<Item = std::ops::Range<usize>> {
693 const TARGET_BYTES: usize = 8 * 1024 * 1024;
694 const MIN_ROWS: usize = 512;
695 const MAX_ROWS: usize = 131_072;
696 let rows = (TARGET_BYTES / (p.max(1) * 8))
697 .clamp(MIN_ROWS, MAX_ROWS)
698 .min(n.max(1));
699 (0..n)
700 .step_by(rows)
701 .map(move |start| start..(start + rows).min(n))
702}
703
704pub(crate) fn design_weighted_column_squares(
705 design: &DesignMatrix,
706 weights: &Array1<f64>,
707) -> Result<Array1<f64>, String> {
708 let n = design.nrows();
709 let p = design.ncols();
710 if weights.len() != n {
711 return Err(GamlssError::DimensionMismatch {
712 reason: format!(
713 "design weighted column squares dimension mismatch: weights={}, rows={}",
714 weights.len(),
715 n
716 ),
717 }
718 .into());
719 }
720 let mut out = Array1::<f64>::zeros(p);
721 for rows in exact_design_row_chunks(n, p) {
722 let chunk = design.try_row_chunk(rows.clone()).map_err(|e| {
723 format!("design weighted column squares row chunk materialization failed: {e}")
724 })?;
725 for (local_i, row) in chunk.outer_iter().enumerate() {
726 let w = weights[rows.start + local_i];
727 if w == 0.0 {
728 continue;
729 }
730 for j in 0..p {
731 let x = row[j];
732 out[j] += w * x * x;
733 }
734 }
735 }
736 Ok(out)
737}
738
739#[inline]
740pub(crate) fn floor_positiveweight(rawweight: f64, minweight: f64) -> f64 {
741 if !rawweight.is_finite() || rawweight <= 0.0 {
742 0.0
743 } else {
744 rawweight.max(minweight)
745 }
746}
747
748#[inline]
749pub(crate) fn logb_dlog_sigma_deta(sigma: f64, d_sigma_deta: f64) -> f64 {
750 if d_sigma_deta.is_infinite() {
751 1.0
752 } else {
753 let value = d_sigma_deta / sigma;
754 if value.is_finite() {
755 value.clamp(0.0, 1.0)
756 } else {
757 0.0
758 }
759 }
760}
761
762#[inline]
763pub(crate) fn gaussian_log_sigma_irlsinfo_directional_derivative(
764 weight: f64,
765 sigma: f64,
766 d_sigma_deta: f64,
767 d_eta: f64,
768) -> f64 {
769 if weight == 0.0 || d_eta == 0.0 || !sigma.is_finite() || sigma <= 0.0 {
770 return 0.0;
771 }
772 let g = logb_dlog_sigma_deta(sigma, d_sigma_deta);
778 if !g.is_finite() || !(0.0..1.0).contains(&g) {
779 return 0.0;
780 }
781 let rawinfo = 2.0 * weight * g * g;
782 if !rawinfo.is_finite() || rawinfo <= MIN_WEIGHT {
783 return 0.0;
784 }
785 let dg_deta = g * (1.0 - g);
786 let dw = 4.0 * weight * g * dg_deta * d_eta;
787 if dw.is_finite() { dw } else { 0.0 }
788}
789
790#[derive(Clone, Copy)]
791pub(crate) struct GaussianDiagonalRowKernel {
792 pub(crate) log_likelihood: f64,
793 pub(crate) location_working_weight: f64,
794 pub(crate) location_working_shift: f64,
795 pub(crate) log_sigma_working_weight: f64,
796 pub(crate) log_sigma_working_response: f64,
797}
798
799#[inline]
800pub(crate) fn gaussian_diagonal_row_kernel(
801 y: f64,
802 location_eta: f64,
803 eta_log_sigma: f64,
804 obs_weight: f64,
805 ln2pi: f64,
806) -> GaussianDiagonalRowKernel {
807 if obs_weight == 0.0 {
808 return GaussianDiagonalRowKernel {
809 log_likelihood: 0.0,
810 location_working_weight: 0.0,
811 location_working_shift: 0.0,
812 log_sigma_working_weight: 0.0,
813 log_sigma_working_response: eta_log_sigma,
814 };
815 }
816
817 let SigmaJet1 { sigma, d1 } = logb_sigma_jet1_scalar(eta_log_sigma);
825 let inv_s2 = (sigma * sigma).recip();
826 let residual = y - location_eta;
827 let location_working_weight = floor_positiveweight(obs_weight * inv_s2, MIN_WEIGHT);
828 let dlog_sigma_deta = logb_dlog_sigma_deta(sigma, d1);
835 let log_sigma_working_weight = floor_positiveweight(
836 2.0 * obs_weight * dlog_sigma_deta * dlog_sigma_deta,
837 MIN_WEIGHT,
838 );
839 let log_sigma_score = obs_weight * (residual * residual * inv_s2 - 1.0) * dlog_sigma_deta;
840 let log_sigma_working_response = if log_sigma_working_weight == 0.0 {
841 eta_log_sigma
842 } else {
843 eta_log_sigma + log_sigma_score / log_sigma_working_weight
844 };
845
846 GaussianDiagonalRowKernel {
847 log_likelihood: obs_weight
848 * (-0.5 * (residual * residual * inv_s2 + ln2pi + 2.0 * sigma.ln())),
849 location_working_weight,
850 location_working_shift: residual,
851 log_sigma_working_weight,
852 log_sigma_working_response,
853 }
854}
855
856#[derive(Clone, Copy, Debug, PartialEq, Eq)]
858pub enum ParameterLink {
859 Identity,
860 Log,
861 Logit,
862 Probit,
863 InverseLink,
864 Wiggle,
866}