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