1use std::any::Any;
8use std::collections::HashMap;
9use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
10use std::sync::{Arc, Condvar, Mutex};
11
12use ndarray::{Array1, Array2, ArrayView1, ArrayView2, ArrayViewMut1, ArrayViewMut2};
13use rayon::iter::{IntoParallelIterator, ParallelIterator};
14
15#[macro_use]
16mod macros;
17
18pub mod basis_error;
19pub mod block_count_error;
20pub mod block_role;
21pub mod block_spec;
22pub mod coefficient_prior_mean;
23pub mod custom_family_blockwise;
24pub mod custom_family_error;
25pub mod diagnostics;
26pub mod dispersion;
27pub mod dispersion_cov;
28pub mod estimation_error;
29pub mod execution_path;
30pub mod family_options;
31pub mod finite_validation;
32pub mod fisher_rao;
33pub mod gauge;
34pub mod identifiability_audit;
35pub mod joint_penalty;
36mod linear_constraints;
37pub mod monotone_root_error;
38pub mod outer_subsample;
39pub mod penalty_coordinate;
40pub mod penalty_matrix;
41mod pseudo_logdet;
42pub mod psi_design_contract;
43pub mod psi_terms;
44pub mod riemannian_retraction;
45pub mod rho_posterior;
49pub mod row_measure;
50pub mod row_metric;
51pub mod schedule;
52pub mod laplace_sampler_contract;
56mod seeding;
57pub mod solver_contract;
58pub mod topology_certificates;
59pub mod types;
60
61pub use riemannian_retraction::LatentRetractionRegistry;
62pub use row_measure::RowSubsampleMask;
63
64pub use basis_error::BasisError;
65pub use block_count_error::BlockCountMismatch;
66pub use block_role::BlockRole;
67pub use block_spec::{
68 AdditiveBlockJacobian, BlockEffectiveJacobian, BlockGeometryDirectionalDerivative,
69 BlockWorkingSet, FamilyChannelHessian, FamilyLinearizationState, GaugeComposedJacobian,
70 ParameterBlockSpec, ParameterBlockState, RowScaledJacobian, TensorChannelHessian,
71};
72pub use coefficient_prior_mean::{CoefficientPriorMean, PriorMeanError};
73pub use custom_family_blockwise::{
74 CUSTOM_FAMILY_RIDGE_FLOOR, CUSTOM_FAMILY_WEIGHT_FLOOR, ExactNewtonOuterCurvature,
75 validate_blockspec_consistency,
76};
77pub use custom_family_error::CustomFamilyError;
78pub use dispersion::Dispersion;
79pub use dispersion_cov::{
80 DispersionExt, PhiScaledCovariance, UnscaledPrecision, se_from_covariance,
81};
82pub use estimation_error::{
83 EstimationError, FixedLambdaCheckpoint, FixedLambdaResidualKind, FixedLambdaSolverStage,
84 FixedLambdaStallReason, FixedLambdaStationarityEvidence,
85};
86pub use execution_path::ExecutionPath;
87pub use family_options::{ExactNewtonOuterObjective, ExactOuterDerivativeOrder};
88pub use finite_validation::{
89 bail_if_cached_beta_non_finite, ensure_finite_scalar, ensure_finite_scalar_estimation,
90 validate_all_finite, validate_all_finite_estimation,
91};
92pub use fisher_rao::{
93 FisherRaoDefiniteness, normalize_fisher_rao_blocks, normalize_fisher_rao_blocks_pd,
94};
95use gam_linalg::dense;
96pub use gam_linalg::faer_ndarray::{in_nested_parallel_region, with_nested_parallel};
97pub use gauge::Gauge;
98pub use identifiability_audit::{
99 AliasedPair, BlockIdentity, DroppedColumn, IdentifiabilityAudit, MapUniquenessError,
100};
101pub use joint_penalty::{JointPenaltyBundle, JointPenaltyError, JointPenaltySpec};
102pub use linear_constraints::LinearInequalityConstraints;
103pub use monotone_root_error::MonotoneRootError;
104pub use penalty_coordinate::PenaltyCoordinate;
105pub use penalty_matrix::PenaltyMatrix;
106pub use pseudo_logdet::PseudoLogdetMode;
107pub use psi_design_contract::{
108 CustomFamilyBlockPsiDerivative, CustomFamilyPsiDerivativeOperator,
109 JointHessianSourcePreference, MaterializablePsiDerivativeOperator, MaterializationIntent,
110 SharedDerivativeBlocks,
111};
112pub use psi_terms::{
113 ExactNewtonJointPsiSecondOrderContracted, ExactNewtonJointPsiSecondOrderTerms,
114 ExactNewtonJointPsiTerms, ExactNewtonJointPsiWorkspace,
115};
116pub use row_metric::{MetricProvenance, RowMetric, WeightField, pack_probe_factors};
117pub use schedule::{GumbelTemperatureSchedule, ScheduleKind, SearchStrategy};
118pub use seeding::{SeedConfig, SeedRiskProfile, clamp_seed_rho_to_bounds, normalize_seed_bounds};
119pub use solver_contract::{
120 DeclaredHessianForm, Derivative, EfsEval, FixedPointCertificateEval,
121 FixedPointCoordinateCertificate, HessianMaterialization, HessianOperator, HessianValue,
122 ObjectiveEvalError, OuterEval, OuterStrategyError,
123};
124pub use types::*;
125
126#[cold]
127fn reml_contract_panic(message: impl Into<String>) -> ! {
128 std::panic::panic_any(message.into())
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub enum EvalMode {
134 ValueOnly,
136 ValueAndGradient,
138 ValueGradientHessian,
140}
141
142struct NonDowncastableHyperOperator;
145
146static NON_DOWNCASTABLE_HYPER_OPERATOR: NonDowncastableHyperOperator = NonDowncastableHyperOperator;
147
148pub trait HyperOperator: Send + Sync {
149 fn dim(&self) -> usize;
152
153 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64>;
155
156 fn as_any(&self) -> &(dyn Any + 'static) {
160 &NON_DOWNCASTABLE_HYPER_OPERATOR
161 }
162
163 fn mul_vec_view(&self, v: ArrayView1<'_, f64>) -> Array1<f64> {
165 self.mul_vec(&v.to_owned())
166 }
167
168 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
170 out.assign(&self.mul_vec_view(v));
171 }
172
173 fn mul_mat(&self, factor: &Array2<f64>) -> Array2<f64> {
176 let p = factor.nrows();
177 let k = factor.ncols();
178 let mut out = Array2::<f64>::zeros((p, k));
179 if rayon::current_thread_index().is_some() {
180 for col in 0..k {
181 let bv = out.column_mut(col);
182 self.mul_vec_into(factor.column(col), bv);
183 }
184 return out;
185 }
186 let cols: Vec<Array1<f64>> = (0..k)
187 .into_par_iter()
188 .map(|col| {
189 let mut bv = Array1::<f64>::zeros(p);
190 self.mul_vec_into(factor.column(col), bv.view_mut());
191 bv
192 })
193 .collect();
194 for (col, bv) in cols.into_iter().enumerate() {
195 out.column_mut(col).assign(&bv);
196 }
197 out
198 }
199
200 fn trace_projected_factor(&self, factor: &Array2<f64>) -> f64 {
202 let op_factor = self.mul_mat(factor);
203 factor
204 .iter()
205 .zip(op_factor.iter())
206 .map(|(&f, &bf)| f * bf)
207 .sum()
208 }
209
210 fn projection_design_id(&self) -> Option<usize> {
219 None
220 }
221
222 fn trace_projected_factor_cached(
223 &self,
224 factor: &Array2<f64>,
225 factor_cache: &ProjectedFactorCache,
226 ) -> f64 {
227 assert!(std::mem::size_of_val(factor_cache) > 0);
231 match self.projection_design_id() {
232 Some(design_id) => {
233 let key = ProjectedFactorKey::from_factor_view(design_id, factor.view());
234 let projected = factor_cache.get_or_insert_with(key, || self.mul_mat(factor));
235 factor
236 .iter()
237 .zip(projected.iter())
238 .map(|(&f, &bf)| f * bf)
239 .sum()
240 }
241 None => self.trace_projected_factor(factor),
242 }
243 }
244
245 fn projected_matrix(&self, factor: &Array2<f64>) -> Array2<f64> {
247 let op_factor = self.mul_mat(factor);
248 gam_linalg::faer_ndarray::fast_atb(factor, &op_factor)
249 }
250
251 fn projected_matrix_cached(
254 &self,
255 factor: &Array2<f64>,
256 factor_cache: &ProjectedFactorCache,
257 ) -> Array2<f64> {
258 assert!(std::mem::size_of_val(factor_cache) > 0);
259 match self.projection_design_id() {
260 Some(design_id) => {
261 let key = ProjectedFactorKey::from_factor_view(design_id, factor.view());
262 let projected = factor_cache.get_or_insert_with(key, || self.mul_mat(factor));
263 gam_linalg::faer_ndarray::fast_atb(factor, projected.as_ref())
264 }
265 None => self.projected_matrix(factor),
266 }
267 }
268
269 fn mul_basis_columns_into(&self, start: usize, mut out: ArrayViewMut2<'_, f64>) {
271 let cols = out.ncols();
272 let dim = out.nrows();
273 assert!(start + cols <= dim);
274 let mut basis = Array1::<f64>::zeros(dim);
275 for local_col in 0..cols {
276 let global_col = start + local_col;
277 basis[global_col] = 1.0;
278 self.mul_vec_into(basis.view(), out.column_mut(local_col));
279 basis[global_col] = 0.0;
280 }
281 }
282
283 fn scaled_add_mul_vec(
285 &self,
286 v: ArrayView1<'_, f64>,
287 scale: f64,
288 mut out: ArrayViewMut1<'_, f64>,
289 ) {
290 if scale == 0.0 {
291 return;
292 }
293 let mut work = Array1::<f64>::zeros(out.len());
294 self.mul_vec_into(v, work.view_mut());
295 out.scaled_add(scale, &work);
296 }
297
298 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
300 let mut bv = Array1::<f64>::zeros(v.len());
301 self.mul_vec_into(v.view(), bv.view_mut());
302 u.dot(&bv)
303 }
304
305 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
307 let mut bv = Array1::<f64>::zeros(v.len());
308 self.mul_vec_into(v, bv.view_mut());
309 u.dot(&bv)
310 }
311
312 fn has_fast_bilinear_view(&self) -> bool {
314 false
315 }
316
317 fn to_dense(&self) -> Array2<f64> {
319 let p = self.dim();
320 let mut out = Array2::<f64>::zeros((p, p));
321 let mut basis = Array1::<f64>::zeros(p);
322 for j in 0..p {
323 basis[j] = 1.0;
324 self.mul_vec_into(basis.view(), out.column_mut(j));
325 basis[j] = 0.0;
326 }
327 out
328 }
329
330 fn is_implicit(&self) -> bool;
332
333 fn block_local_data(&self) -> Option<(&Array2<f64>, usize, usize)> {
335 None
336 }
337}
338
339#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
340pub struct ProjectedFactorKey {
341 pub(crate) design_id: usize,
342 pub(crate) factor_ptr: usize,
343 pub(crate) rows: usize,
344 pub(crate) cols: usize,
345 pub(crate) row_stride: isize,
346 pub(crate) col_stride: isize,
347 pub(crate) value_hash: u64,
348 pub(crate) value_hash2: u64,
349}
350
351impl ProjectedFactorKey {
352 pub fn from_factor_view(design_id: usize, factor: ArrayView2<'_, f64>) -> Self {
353 let strides = factor.strides();
354 let (value_hash, value_hash2) = projected_factor_value_fingerprint(factor);
355 Self {
356 design_id,
357 factor_ptr: factor.as_ptr() as usize,
358 rows: factor.nrows(),
359 cols: factor.ncols(),
360 row_stride: strides[0],
361 col_stride: strides[1],
362 value_hash,
363 value_hash2,
364 }
365 }
366
367 pub fn synthetic(seed: u64) -> Self {
372 Self {
373 design_id: 1,
374 factor_ptr: seed as usize,
375 rows: 1,
376 cols: 1,
377 row_stride: 1,
378 col_stride: 1,
379 value_hash: seed,
380 value_hash2: seed.wrapping_mul(31),
381 }
382 }
383}
384
385pub(crate) fn projected_factor_value_fingerprint(factor: ArrayView2<'_, f64>) -> (u64, u64) {
386 let mut h1 = 0xcbf2_9ce4_8422_2325_u64;
387 let mut h2 = 0x9e37_79b1_85eb_ca87_u64;
388 for (idx, value) in factor.iter().enumerate() {
389 let bits = value.to_bits();
390 let mixed = bits.wrapping_add((idx as u64).wrapping_mul(0x517c_c1b7_2722_0a95));
391 h1 ^= mixed;
392 h1 = h1.wrapping_mul(0x0000_0100_0000_01b3);
393 h2 ^= bits.rotate_left((idx & 63) as u32);
394 h2 = h2.wrapping_mul(0x94d0_49bb_1331_11eb).rotate_left(27);
395 }
396 (h1, h2)
397}
398
399pub struct ProjectedFactorCache {
401 pub(crate) inner: Mutex<ProjectedFactorCacheInner>,
402}
403
404pub(crate) struct ProjectedFactorCacheInner {
405 pub(crate) entries: HashMap<ProjectedFactorKey, ProjectedFactorEntry>,
406 pub(crate) in_progress: HashMap<ProjectedFactorKey, Arc<ProjectedFactorInProgress>>,
407 pub(crate) next_seq: u64,
408 pub(crate) total_bytes: usize,
409 pub(crate) budget_bytes: usize,
410}
411
412pub(crate) struct ProjectedFactorInProgress {
413 pub(crate) state: Mutex<Option<ProjectedFactorInProgressState>>,
414 pub(crate) ready: Condvar,
415 pub(crate) waiter_count: std::sync::atomic::AtomicUsize,
416 pub(crate) subscriber_arrived: (Mutex<()>, Condvar),
417}
418
419pub(crate) enum ProjectedFactorInProgressState {
420 Ready(Arc<Array2<f64>>),
421 Failed,
422}
423
424pub(crate) struct ProjectedFactorEntry {
425 pub(crate) value: Arc<Array2<f64>>,
426 pub(crate) bytes: usize,
427 pub(crate) last_used: u64,
428}
429
430impl Default for ProjectedFactorCache {
431 fn default() -> Self {
432 Self::with_budget(Self::DEFAULT_BUDGET_BYTES)
433 }
434}
435
436impl ProjectedFactorCache {
437 pub const DEFAULT_BUDGET_BYTES: usize = 2 * 1024 * 1024 * 1024;
438
439 pub fn with_budget(budget_bytes: usize) -> Self {
440 Self {
441 inner: Mutex::new(ProjectedFactorCacheInner {
442 entries: HashMap::new(),
443 in_progress: HashMap::new(),
444 next_seq: 0,
445 total_bytes: 0,
446 budget_bytes,
447 }),
448 }
449 }
450
451 pub fn get_or_insert_with(
452 &self,
453 key: ProjectedFactorKey,
454 compute: impl FnOnce() -> Array2<f64>,
455 ) -> Arc<Array2<f64>> {
456 enum CacheLookup {
457 Hit(Arc<Array2<f64>>),
458 Wait(Arc<ProjectedFactorInProgress>),
459 Compute(Arc<ProjectedFactorInProgress>),
460 }
461
462 let lookup = {
463 let mut inner = self
464 .inner
465 .lock()
466 .expect("projected factor cache lock poisoned");
467 inner.next_seq += 1;
468 let now = inner.next_seq;
469 if let Some(entry) = inner.entries.get_mut(&key) {
470 entry.last_used = now;
471 CacheLookup::Hit(entry.value.clone())
472 } else if let Some(waiter) = inner.in_progress.get(&key) {
473 CacheLookup::Wait(waiter.clone())
474 } else {
475 let marker = Arc::new(ProjectedFactorInProgress {
476 state: Mutex::new(None),
477 ready: Condvar::new(),
478 waiter_count: std::sync::atomic::AtomicUsize::new(0),
479 subscriber_arrived: (Mutex::new(()), Condvar::new()),
480 });
481 inner.in_progress.insert(key, marker.clone());
482 CacheLookup::Compute(marker)
483 }
484 };
485
486 match lookup {
487 CacheLookup::Hit(value) => value,
488 CacheLookup::Wait(marker) => {
489 marker
490 .waiter_count
491 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
492 let (lock, cv) = &marker.subscriber_arrived;
493 drop(
494 lock.lock()
495 .expect("subscriber-arrived notification lock poisoned"),
496 );
497 cv.notify_all();
498 let mut guard = marker
499 .state
500 .lock()
501 .expect("projected factor in-progress lock poisoned");
502 let result = loop {
503 match guard.as_ref() {
504 Some(ProjectedFactorInProgressState::Ready(value)) => {
505 break value.clone();
506 }
507 Some(ProjectedFactorInProgressState::Failed) => {
508 marker
509 .waiter_count
510 .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
511 reml_contract_panic("projected factor cache producer panicked")
512 }
513 None => {
514 guard = marker
515 .ready
516 .wait(guard)
517 .expect("projected factor in-progress wait poisoned");
518 }
519 }
520 };
521 marker
522 .waiter_count
523 .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
524 result
525 }
526 CacheLookup::Compute(marker) => {
527 let computed = match catch_unwind(AssertUnwindSafe(|| Arc::new(compute()))) {
528 Ok(value) => value,
529 Err(payload) => {
530 let mut inner = self
531 .inner
532 .lock()
533 .expect("projected factor cache lock poisoned");
534 inner.in_progress.remove(&key);
535 drop(inner);
536
537 let mut guard = marker
538 .state
539 .lock()
540 .expect("projected factor in-progress lock poisoned");
541 *guard = Some(ProjectedFactorInProgressState::Failed);
542 marker.ready.notify_all();
543 resume_unwind(payload);
544 }
545 };
546 let bytes = computed.len().saturating_mul(std::mem::size_of::<f64>());
547 let mut inner = self
548 .inner
549 .lock()
550 .expect("projected factor cache lock poisoned");
551 inner.next_seq += 1;
552 let now = inner.next_seq;
553
554 if inner.budget_bytes > 0 && bytes <= inner.budget_bytes {
555 while inner.total_bytes.saturating_add(bytes) > inner.budget_bytes
556 && !inner.entries.is_empty()
557 {
558 let Some(oldest_key) = inner
559 .entries
560 .iter()
561 .min_by_key(|(_, e)| e.last_used)
562 .map(|(k, _)| *k)
563 else {
564 break;
565 };
566 if let Some(removed) = inner.entries.remove(&oldest_key) {
567 inner.total_bytes = inner.total_bytes.saturating_sub(removed.bytes);
568 }
569 }
570 }
571
572 let value = if let Some(entry) = inner.entries.get_mut(&key) {
573 entry.last_used = now;
574 entry.value.clone()
575 } else {
576 inner.entries.insert(
577 key,
578 ProjectedFactorEntry {
579 value: computed.clone(),
580 bytes,
581 last_used: now,
582 },
583 );
584 inner.total_bytes = inner.total_bytes.saturating_add(bytes);
585 computed
586 };
587 inner.in_progress.remove(&key);
588 drop(inner);
589
590 let mut guard = marker
591 .state
592 .lock()
593 .expect("projected factor in-progress lock poisoned");
594 *guard = Some(ProjectedFactorInProgressState::Ready(value.clone()));
595 marker.ready.notify_all();
596 value
597 }
598 }
599 }
600
601 pub fn len(&self) -> usize {
602 self.inner
603 .lock()
604 .map(|inner| inner.entries.len())
605 .unwrap_or(0)
606 }
607
608 pub fn total_bytes(&self) -> usize {
609 self.inner
610 .lock()
611 .map(|inner| inner.total_bytes)
612 .unwrap_or(0)
613 }
614
615 pub fn is_empty(&self) -> bool {
616 self.len() == 0
617 }
618
619 pub fn wait_for_subscriber(
630 &self,
631 key: ProjectedFactorKey,
632 timeout: std::time::Duration,
633 ) -> bool {
634 let marker = {
635 let inner = self
636 .inner
637 .lock()
638 .expect("projected factor cache lock poisoned");
639 let Some(m) = inner.in_progress.get(&key) else {
640 return false;
641 };
642 Arc::clone(m)
643 };
644 if marker
645 .waiter_count
646 .load(std::sync::atomic::Ordering::Acquire)
647 > 0
648 {
649 return true;
650 }
651 let (lock, cv) = &marker.subscriber_arrived;
652 let mut guard = lock
653 .lock()
654 .expect("subscriber-arrived notification lock poisoned");
655 let deadline = std::time::Instant::now() + timeout;
656 loop {
657 if marker
658 .waiter_count
659 .load(std::sync::atomic::Ordering::Acquire)
660 > 0
661 {
662 return true;
663 }
664 let now = std::time::Instant::now();
665 if now >= deadline {
666 return false;
667 }
668 let (next_guard, result) = cv
669 .wait_timeout(guard, deadline - now)
670 .expect("subscriber-arrived wait poisoned");
671 guard = next_guard;
672 if result.timed_out()
673 && marker
674 .waiter_count
675 .load(std::sync::atomic::Ordering::Acquire)
676 == 0
677 {
678 return false;
679 }
680 }
681 }
682}
683
684#[derive(Clone)]
685pub struct DenseMatrixHyperOperator {
686 pub matrix: Array2<f64>,
687}
688
689impl HyperOperator for DenseMatrixHyperOperator {
690 fn dim(&self) -> usize {
691 self.matrix.nrows()
692 }
693
694 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64> {
695 self.matrix.dot(v)
696 }
697
698 fn as_any(&self) -> &(dyn Any + 'static) {
699 self
700 }
701
702 fn mul_vec_view(&self, v: ArrayView1<'_, f64>) -> Array1<f64> {
703 self.matrix.dot(&v)
704 }
705
706 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
707 assert_eq!(self.matrix.ncols(), v.len());
708 assert_eq!(self.matrix.nrows(), out.len());
709 for (row, out_value) in self.matrix.rows().into_iter().zip(out.iter_mut()) {
710 *out_value = row.dot(&v);
711 }
712 }
713
714 fn mul_basis_columns_into(&self, start: usize, mut out: ArrayViewMut2<'_, f64>) {
715 let end = start + out.ncols();
716 assert!(end <= self.matrix.ncols());
717 out.assign(&self.matrix.slice(ndarray::s![.., start..end]));
718 }
719
720 fn scaled_add_mul_vec(
721 &self,
722 v: ArrayView1<'_, f64>,
723 scale: f64,
724 mut out: ArrayViewMut1<'_, f64>,
725 ) {
726 assert_eq!(self.matrix.ncols(), v.len());
727 assert_eq!(self.matrix.nrows(), out.len());
728 if scale == 0.0 {
729 return;
730 }
731 for (row, out_value) in self.matrix.rows().into_iter().zip(out.iter_mut()) {
732 *out_value += scale * row.dot(&v);
733 }
734 }
735
736 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
737 dense::bilinear(&self.matrix, v.view(), u.view())
738 }
739
740 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
741 dense::bilinear(&self.matrix, v, u)
742 }
743
744 fn to_dense(&self) -> Array2<f64> {
745 self.matrix.clone()
746 }
747
748 fn is_implicit(&self) -> bool {
749 false
750 }
751}
752
753#[derive(Clone)]
754pub struct BlockLocalDrift {
755 pub local: Array2<f64>,
756 pub start: usize,
757 pub end: usize,
758 pub total_dim: usize,
759}
760
761impl HyperOperator for BlockLocalDrift {
762 fn dim(&self) -> usize {
763 self.total_dim
764 }
765
766 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64> {
767 assert_eq!(v.len(), self.total_dim);
768 let mut out = Array1::zeros(self.total_dim);
769 self.mul_vec_into(v.view(), out.view_mut());
770 out
771 }
772
773 fn as_any(&self) -> &(dyn Any + 'static) {
774 self
775 }
776
777 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
778 assert_eq!(v.len(), self.total_dim);
779 assert_eq!(out.len(), self.total_dim);
780 out.fill(0.0);
781 let v_block = v.slice(ndarray::s![self.start..self.end]);
782 let mut out_block = out.slice_mut(ndarray::s![self.start..self.end]);
783 dense::matvec_into(&self.local, v_block, out_block.view_mut());
784 }
785
786 fn scaled_add_mul_vec(
787 &self,
788 v: ArrayView1<'_, f64>,
789 scale: f64,
790 mut out: ArrayViewMut1<'_, f64>,
791 ) {
792 assert_eq!(v.len(), self.total_dim);
793 assert_eq!(out.len(), self.total_dim);
794 if scale == 0.0 {
795 return;
796 }
797 let v_block = v.slice(ndarray::s![self.start..self.end]);
798 let out_block = out.slice_mut(ndarray::s![self.start..self.end]);
799 dense::matvec_scaled_add_into(&self.local, v_block, scale, out_block);
800 }
801
802 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
803 self.bilinear_view(v.view(), u.view())
804 }
805
806 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
807 assert_eq!(v.len(), self.total_dim);
808 assert_eq!(u.len(), self.total_dim);
809 let v_block = v.slice(ndarray::s![self.start..self.end]);
810 let u_block = u.slice(ndarray::s![self.start..self.end]);
811 dense::bilinear(&self.local, v_block, u_block)
812 }
813
814 fn to_dense(&self) -> Array2<f64> {
815 let p = self.total_dim;
816 let mut out = Array2::zeros((p, p));
817 out.slice_mut(ndarray::s![self.start..self.end, self.start..self.end])
818 .assign(&self.local);
819 out
820 }
821
822 fn is_implicit(&self) -> bool {
823 false
824 }
825
826 fn block_local_data(&self) -> Option<(&Array2<f64>, usize, usize)> {
827 Some((&self.local, self.start, self.end))
828 }
829}
830
831#[derive(Clone)]
832pub struct HyperCoordDrift {
833 pub dense: Option<Array2<f64>>,
834 pub block_local: Option<BlockLocalDrift>,
835 pub operator: Option<Arc<dyn HyperOperator>>,
836}
837
838impl HyperCoordDrift {
839 pub fn none() -> Self {
840 Self {
841 dense: None,
842 block_local: None,
843 operator: None,
844 }
845 }
846
847 pub fn from_dense(dense: Array2<f64>) -> Self {
848 Self {
849 dense: Some(dense),
850 block_local: None,
851 operator: None,
852 }
853 }
854
855 pub fn from_operator(operator: Arc<dyn HyperOperator>) -> Self {
856 Self {
857 dense: None,
858 block_local: None,
859 operator: Some(operator),
860 }
861 }
862
863 pub fn from_parts(
864 dense: Option<Array2<f64>>,
865 operator: Option<Arc<dyn HyperOperator>>,
866 ) -> Self {
867 let dense = dense.filter(|mat| !(operator.is_some() && mat.is_empty()));
868 Self {
869 dense,
870 block_local: None,
871 operator,
872 }
873 }
874
875 pub fn from_block_local_and_operator(
876 local: Array2<f64>,
877 start: usize,
878 end: usize,
879 total_dim: usize,
880 operator: Option<Arc<dyn HyperOperator>>,
881 ) -> Self {
882 Self {
883 dense: None,
884 block_local: Some(BlockLocalDrift {
885 local,
886 start,
887 end,
888 total_dim,
889 }),
890 operator,
891 }
892 }
893
894 pub fn has_operator(&self) -> bool {
895 self.operator.is_some()
896 }
897
898 pub fn uses_operator_fast_path(&self) -> bool {
899 self.operator.is_some() || self.block_local.is_some()
900 }
901
902 pub fn operator_ref(&self) -> Option<&dyn HyperOperator> {
903 self.operator.as_ref().map(Arc::as_ref)
904 }
905
906 pub fn materialize(&self) -> Array2<f64> {
907 let p = self.infer_dim();
908 if p == 0 {
909 return Array2::zeros((0, 0));
910 }
911 let mut out = self.dense.clone().unwrap_or_else(|| Array2::zeros((p, p)));
912 if let Some(bl) = &self.block_local {
913 out.slice_mut(ndarray::s![bl.start..bl.end, bl.start..bl.end])
914 .scaled_add(1.0, &bl.local);
915 }
916 if let Some(op) = &self.operator {
917 out += &op.to_dense();
918 }
919 out
920 }
921
922 pub fn apply(&self, v: &Array1<f64>) -> Array1<f64> {
923 let mut out = Array1::zeros(v.len());
924 self.scaled_add_apply(v.view(), 1.0, &mut out);
925 out
926 }
927
928 pub fn scaled_add_apply(&self, v: ArrayView1<'_, f64>, scale: f64, out: &mut Array1<f64>) {
929 assert_eq!(v.len(), out.len());
930 if scale == 0.0 {
931 return;
932 }
933 if let Some(dense) = &self.dense {
934 dense::matvec_scaled_add_into(dense, v, scale, out.view_mut());
935 }
936 if let Some(bl) = &self.block_local {
937 let v_block = v.slice(ndarray::s![bl.start..bl.end]);
938 let out_block = out.slice_mut(ndarray::s![bl.start..bl.end]);
939 dense::matvec_scaled_add_into(&bl.local, v_block, scale, out_block);
940 }
941 if let Some(op) = &self.operator {
942 op.scaled_add_mul_vec(v, scale, out.view_mut());
943 }
944 }
945
946 pub(crate) fn infer_dim(&self) -> usize {
947 if let Some(d) = &self.dense {
948 return d.nrows();
949 }
950 if let Some(op) = &self.operator {
951 return op.dim();
952 }
953 if let Some(bl) = &self.block_local {
954 return bl.total_dim;
955 }
956 0
957 }
958}
959
960#[derive(Clone)]
961pub struct HyperCoord {
962 pub a: f64,
963 pub g: Array1<f64>,
964 pub drift: HyperCoordDrift,
965 pub ld_s: f64,
966 pub b_depends_on_beta: bool,
967 pub is_penalty_like: bool,
968 pub firth_g: Option<Array1<f64>>,
969 pub tk_eta_fixed: Option<Array1<f64>>,
970 pub tk_x_fixed: Option<Array2<f64>>,
971}
972
973pub struct HyperCoordPair {
974 pub a: f64,
975 pub g: Array1<f64>,
976 pub b_mat: Array2<f64>,
977 pub b_operator: Option<Box<dyn HyperOperator>>,
978 pub ld_s: f64,
979}
980
981pub type HyperCoordPairFn = Arc<dyn Fn(usize, usize) -> HyperCoordPair + Send + Sync>;
992
993impl HyperCoordPair {
994 pub fn zero() -> Self {
995 Self {
996 a: 0.0,
997 g: Array1::zeros(0),
998 b_mat: Array2::zeros((0, 0)),
999 b_operator: None,
1000 ld_s: 0.0,
1001 }
1002 }
1003}
1004
1005#[derive(Clone)]
1006pub enum DriftDerivResult {
1007 Dense(Array2<f64>),
1008 Operator(Arc<dyn HyperOperator>),
1009}
1010
1011impl std::fmt::Debug for DriftDerivResult {
1012 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013 match self {
1014 Self::Dense(matrix) => f
1015 .debug_tuple("Dense")
1016 .field(&format_args!("{}x{}", matrix.nrows(), matrix.ncols()))
1017 .finish(),
1018 Self::Operator(_) => f
1019 .debug_tuple("Operator")
1020 .field(&"<hyper-operator>")
1021 .finish(),
1022 }
1023 }
1024}
1025
1026impl DriftDerivResult {
1027 pub fn into_operator(self) -> Arc<dyn HyperOperator> {
1028 match self {
1029 Self::Dense(matrix) => Arc::new(DenseMatrixHyperOperator { matrix }),
1030 Self::Operator(operator) => operator,
1031 }
1032 }
1033
1034 pub fn apply(&self, v: &Array1<f64>) -> Array1<f64> {
1035 match self {
1036 Self::Dense(matrix) => matrix.dot(v),
1037 Self::Operator(operator) => operator.mul_vec(v),
1038 }
1039 }
1040}
1041
1042pub type FixedDriftDerivFn =
1043 Box<dyn Fn(usize, &Array1<f64>) -> Option<DriftDerivResult> + Send + Sync>;
1044
1045pub type SharedFixedDriftDerivFn =
1053 Arc<dyn Fn(usize, &Array1<f64>) -> Option<DriftDerivResult> + Send + Sync>;
1054
1055pub struct ContractedPsiSecondOrder {
1056 pub objective: Array1<f64>,
1057 pub score: Array2<f64>,
1058 pub hessian: Vec<DriftDerivResult>,
1059 pub ld_s: Array1<f64>,
1060}
1061
1062pub type ContractedPsiSecondOrderFn =
1063 Arc<dyn Fn(&[f64]) -> Result<Option<ContractedPsiSecondOrder>, String> + Send + Sync>;