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