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 log_gradient_resolution,
170 precision_box,
171 IndexedLogStrengthDomainError,
172 LOG_STRENGTH_MAX,
173 LOG_STRENGTH_MIN,
174 LogStrengthDomainError,
175 PhysicalStrengthDomainError,
176 checked_exp_log_strength,
177 checked_exp_log_strengths,
178 checked_log_strength,
179 validate_log_strength,
180 validate_log_strengths,
181};
182pub use monotone_root_error::MonotoneRootError;
183pub use penalty_coordinate::{PenaltyCoordinate, project_block_root_out_of_null_directions};
184pub use penalty_matrix::PenaltyMatrix;
185pub use pseudo_logdet::PseudoLogdetMode;
186pub use psi_design_contract::{
187 CustomFamilyBlockPsiDerivative,
188 CustomFamilyHyperAxis,
189 CustomFamilyHyperLayout,
190 CustomFamilyPsiDerivativeOperator,
191 JointHessianSourcePreference,
192 MaterializablePsiDerivativeOperator,
193 MaterializationIntent,
194 SharedCustomFamilyHyperLayout,
195};
196pub use psi_terms::{
197 ExactNewtonJointPsiSecondOrderContracted,
198 ExactNewtonJointPsiSecondOrderTerms,
199 ExactNewtonJointPsiTerms,
200 ExactNewtonJointPsiWorkspace,
201};
202pub use row_metric::{
203 FisherFactorKind,
204 MetricProvenance,
205 RowMetric,
206 WeightField,
207 pack_probe_factors,
208};
209pub use schedule::{GumbelTemperatureSchedule, ScheduleKind};
210pub use seeding::{OrderedRhoBounds, SeedConfig, SeedRiskProfile};
211pub use solver_contract::{
212 DeclaredHessianForm,
213 Derivative,
214 EfsEval,
215 FixedPointCertificateEval,
216 FixedPointCoordinateCertificate,
217 HessianMaterialization,
218 HessianOperator,
219 HessianValue,
220 ObjectiveEvalError,
221 OuterEval,
222 OuterStrategyError,
223};
224pub use types::*;
225
226#[cold]
227fn reml_contract_panic(message: impl Into<String>) -> ! {
228 std::panic::panic_any(message.into())
229}
230
231#[derive(Clone, Copy, Debug, PartialEq, Eq)]
233pub enum EvalMode {
234 ValueOnly,
236 ValueAndGradient,
238 ValueGradientHessian,
240}
241
242struct NonDowncastableHyperOperator;
245
246static NON_DOWNCASTABLE_HYPER_OPERATOR: NonDowncastableHyperOperator = NonDowncastableHyperOperator;
247
248pub trait HyperOperator: Send + Sync {
249 fn dim(&self) -> usize;
252
253 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64>;
255
256 fn as_any(&self) -> &(dyn Any + 'static) {
260 &NON_DOWNCASTABLE_HYPER_OPERATOR
261 }
262
263 fn mul_vec_view(&self, v: ArrayView1<'_, f64>) -> Array1<f64> {
265 self.mul_vec(&v.to_owned())
266 }
267
268 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
270 out.assign(&self.mul_vec_view(v));
271 }
272
273 fn mul_mat(&self, factor: &Array2<f64>) -> Array2<f64> {
276 let p = factor.nrows();
277 let k = factor.ncols();
278 let mut out = Array2::<f64>::zeros((p, k));
279 if rayon::current_thread_index().is_some() {
280 for col in 0..k {
281 let bv = out.column_mut(col);
282 self.mul_vec_into(factor.column(col), bv);
283 }
284 return out;
285 }
286 let cols: Vec<Array1<f64>> = (0..k)
287 .into_par_iter()
288 .map(|col| {
289 let mut bv = Array1::<f64>::zeros(p);
290 self.mul_vec_into(factor.column(col), bv.view_mut());
291 bv
292 })
293 .collect();
294 for (col, bv) in cols.into_iter().enumerate() {
295 out.column_mut(col).assign(&bv);
296 }
297 out
298 }
299
300 fn trace_projected_factor(&self, factor: &Array2<f64>) -> f64 {
302 let op_factor = self.mul_mat(factor);
303 factor
304 .iter()
305 .zip(op_factor.iter())
306 .map(|(&f, &bf)| f * bf)
307 .sum()
308 }
309
310 fn projection_design_id(&self) -> Option<usize> {
319 None
320 }
321
322 fn trace_projected_factor_cached(
323 &self,
324 factor: &Array2<f64>,
325 factor_cache: &ProjectedFactorCache,
326 ) -> f64 {
327 assert!(std::mem::size_of_val(factor_cache) > 0);
331 match self.projection_design_id() {
332 Some(design_id) => {
333 let key = ProjectedFactorKey::from_factor_view(design_id, factor.view());
334 let projected = factor_cache.get_or_insert_with(key, || self.mul_mat(factor));
335 factor
336 .iter()
337 .zip(projected.iter())
338 .map(|(&f, &bf)| f * bf)
339 .sum()
340 }
341 None => self.trace_projected_factor(factor),
342 }
343 }
344
345 fn projected_matrix(&self, factor: &Array2<f64>) -> Array2<f64> {
347 let op_factor = self.mul_mat(factor);
348 gam_linalg::faer_ndarray::fast_atb(factor, &op_factor)
349 }
350
351 fn projected_matrix_cached(
354 &self,
355 factor: &Array2<f64>,
356 factor_cache: &ProjectedFactorCache,
357 ) -> Array2<f64> {
358 assert!(std::mem::size_of_val(factor_cache) > 0);
359 match self.projection_design_id() {
360 Some(design_id) => {
361 let key = ProjectedFactorKey::from_factor_view(design_id, factor.view());
362 let projected = factor_cache.get_or_insert_with(key, || self.mul_mat(factor));
363 gam_linalg::faer_ndarray::fast_atb(factor, projected.as_ref())
364 }
365 None => self.projected_matrix(factor),
366 }
367 }
368
369 fn mul_basis_columns_into(&self, start: usize, mut out: ArrayViewMut2<'_, f64>) {
371 let cols = out.ncols();
372 let dim = out.nrows();
373 assert!(start + cols <= dim);
374 let mut basis = Array1::<f64>::zeros(dim);
375 for local_col in 0..cols {
376 let global_col = start + local_col;
377 basis[global_col] = 1.0;
378 self.mul_vec_into(basis.view(), out.column_mut(local_col));
379 basis[global_col] = 0.0;
380 }
381 }
382
383 fn scaled_add_mul_vec(
385 &self,
386 v: ArrayView1<'_, f64>,
387 scale: f64,
388 mut out: ArrayViewMut1<'_, f64>,
389 ) {
390 if scale == 0.0 {
391 return;
392 }
393 let mut work = Array1::<f64>::zeros(out.len());
394 self.mul_vec_into(v, work.view_mut());
395 out.scaled_add(scale, &work);
396 }
397
398 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
400 let mut bv = Array1::<f64>::zeros(v.len());
401 self.mul_vec_into(v.view(), bv.view_mut());
402 u.dot(&bv)
403 }
404
405 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
407 let mut bv = Array1::<f64>::zeros(v.len());
408 self.mul_vec_into(v, bv.view_mut());
409 u.dot(&bv)
410 }
411
412 fn has_fast_bilinear_view(&self) -> bool {
414 false
415 }
416
417 fn to_dense(&self) -> Array2<f64> {
419 let p = self.dim();
420 let mut out = Array2::<f64>::zeros((p, p));
421 let mut basis = Array1::<f64>::zeros(p);
422 for j in 0..p {
423 basis[j] = 1.0;
424 self.mul_vec_into(basis.view(), out.column_mut(j));
425 basis[j] = 0.0;
426 }
427 out
428 }
429
430 fn is_implicit(&self) -> bool;
432
433 fn block_local_data(&self) -> Option<(&Array2<f64>, usize, usize)> {
435 None
436 }
437}
438
439#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
440pub struct ProjectedFactorKey {
441 pub(crate) design_id: usize,
442 pub(crate) factor_ptr: usize,
443 pub(crate) rows: usize,
444 pub(crate) cols: usize,
445 pub(crate) row_stride: isize,
446 pub(crate) col_stride: isize,
447 pub(crate) value_hash: u64,
448 pub(crate) value_hash2: u64,
449}
450
451impl ProjectedFactorKey {
452 pub fn from_factor_view(design_id: usize, factor: ArrayView2<'_, f64>) -> Self {
453 let strides = factor.strides();
454 let (value_hash, value_hash2) = projected_factor_value_fingerprint(factor);
455 Self {
456 design_id,
457 factor_ptr: factor.as_ptr() as usize,
458 rows: factor.nrows(),
459 cols: factor.ncols(),
460 row_stride: strides[0],
461 col_stride: strides[1],
462 value_hash,
463 value_hash2,
464 }
465 }
466
467 pub fn synthetic(seed: u64) -> Self {
472 Self {
473 design_id: 1,
474 factor_ptr: seed as usize,
475 rows: 1,
476 cols: 1,
477 row_stride: 1,
478 col_stride: 1,
479 value_hash: seed,
480 value_hash2: seed.wrapping_mul(31),
481 }
482 }
483}
484
485pub(crate) fn projected_factor_value_fingerprint(factor: ArrayView2<'_, f64>) -> (u64, u64) {
486 let mut h1 = 0xcbf2_9ce4_8422_2325_u64;
487 let mut h2 = 0x9e37_79b1_85eb_ca87_u64;
488 for (idx, value) in factor.iter().enumerate() {
489 let bits = value.to_bits();
490 let mixed = bits.wrapping_add((idx as u64).wrapping_mul(0x517c_c1b7_2722_0a95));
491 h1 ^= mixed;
492 h1 = h1.wrapping_mul(0x0000_0100_0000_01b3);
493 h2 ^= bits.rotate_left((idx & 63) as u32);
494 h2 = h2.wrapping_mul(0x94d0_49bb_1331_11eb).rotate_left(27);
495 }
496 (h1, h2)
497}
498
499pub struct ProjectedFactorCache {
501 pub(crate) inner: Mutex<ProjectedFactorCacheInner>,
502}
503
504pub(crate) struct ProjectedFactorCacheInner {
505 pub(crate) entries: HashMap<ProjectedFactorKey, ProjectedFactorEntry>,
506 pub(crate) in_progress: HashMap<ProjectedFactorKey, Arc<ProjectedFactorInProgress>>,
507 pub(crate) next_seq: u64,
508 pub(crate) total_bytes: usize,
509 pub(crate) budget_bytes: usize,
510}
511
512pub(crate) struct ProjectedFactorInProgress {
513 pub(crate) state: Mutex<Option<ProjectedFactorInProgressState>>,
514 pub(crate) ready: Condvar,
515 pub(crate) waiter_count: std::sync::atomic::AtomicUsize,
516 pub(crate) subscriber_arrived: (Mutex<()>, Condvar),
517}
518
519pub(crate) enum ProjectedFactorInProgressState {
520 Ready(Arc<Array2<f64>>),
521 Failed,
522}
523
524pub(crate) struct ProjectedFactorEntry {
525 pub(crate) value: Arc<Array2<f64>>,
526 pub(crate) bytes: usize,
527 pub(crate) last_used: u64,
528}
529
530impl Default for ProjectedFactorCache {
531 fn default() -> Self {
532 Self::with_budget(Self::DEFAULT_BUDGET_BYTES)
533 }
534}
535
536impl ProjectedFactorCache {
537 pub const DEFAULT_BUDGET_BYTES: usize = 2 * 1024 * 1024 * 1024;
538
539 pub fn with_budget(budget_bytes: usize) -> Self {
540 Self {
541 inner: Mutex::new(ProjectedFactorCacheInner {
542 entries: HashMap::new(),
543 in_progress: HashMap::new(),
544 next_seq: 0,
545 total_bytes: 0,
546 budget_bytes,
547 }),
548 }
549 }
550
551 pub fn get_or_insert_with(
552 &self,
553 key: ProjectedFactorKey,
554 compute: impl FnOnce() -> Array2<f64>,
555 ) -> Arc<Array2<f64>> {
556 enum CacheLookup {
557 Hit(Arc<Array2<f64>>),
558 Wait(Arc<ProjectedFactorInProgress>),
559 Compute(Arc<ProjectedFactorInProgress>),
560 }
561
562 let lookup = {
563 let mut inner = self
564 .inner
565 .lock()
566 .expect("projected factor cache lock poisoned");
567 inner.next_seq += 1;
568 let now = inner.next_seq;
569 if let Some(entry) = inner.entries.get_mut(&key) {
570 entry.last_used = now;
571 CacheLookup::Hit(entry.value.clone())
572 } else if let Some(waiter) = inner.in_progress.get(&key) {
573 CacheLookup::Wait(waiter.clone())
574 } else {
575 let marker = Arc::new(ProjectedFactorInProgress {
576 state: Mutex::new(None),
577 ready: Condvar::new(),
578 waiter_count: std::sync::atomic::AtomicUsize::new(0),
579 subscriber_arrived: (Mutex::new(()), Condvar::new()),
580 });
581 inner.in_progress.insert(key, marker.clone());
582 CacheLookup::Compute(marker)
583 }
584 };
585
586 match lookup {
587 CacheLookup::Hit(value) => value,
588 CacheLookup::Wait(marker) => {
589 marker
590 .waiter_count
591 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
592 let (lock, cv) = &marker.subscriber_arrived;
593 drop(
594 lock.lock()
595 .expect("subscriber-arrived notification lock poisoned"),
596 );
597 cv.notify_all();
598 let mut guard = marker
599 .state
600 .lock()
601 .expect("projected factor in-progress lock poisoned");
602 let result = loop {
603 match guard.as_ref() {
604 Some(ProjectedFactorInProgressState::Ready(value)) => {
605 break value.clone();
606 }
607 Some(ProjectedFactorInProgressState::Failed) => {
608 marker
609 .waiter_count
610 .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
611 reml_contract_panic("projected factor cache producer panicked")
612 }
613 None => {
614 guard = marker
615 .ready
616 .wait(guard)
617 .expect("projected factor in-progress wait poisoned");
618 }
619 }
620 };
621 marker
622 .waiter_count
623 .fetch_sub(1, std::sync::atomic::Ordering::AcqRel);
624 result
625 }
626 CacheLookup::Compute(marker) => {
627 let computed = match catch_unwind(AssertUnwindSafe(|| Arc::new(compute()))) {
628 Ok(value) => value,
629 Err(payload) => {
630 let mut inner = self
631 .inner
632 .lock()
633 .expect("projected factor cache lock poisoned");
634 inner.in_progress.remove(&key);
635 drop(inner);
636
637 let mut guard = marker
638 .state
639 .lock()
640 .expect("projected factor in-progress lock poisoned");
641 *guard = Some(ProjectedFactorInProgressState::Failed);
642 marker.ready.notify_all();
643 resume_unwind(payload);
644 }
645 };
646 let bytes = computed.len().saturating_mul(std::mem::size_of::<f64>());
647 let mut inner = self
648 .inner
649 .lock()
650 .expect("projected factor cache lock poisoned");
651 inner.next_seq += 1;
652 let now = inner.next_seq;
653
654 if inner.budget_bytes > 0 && bytes <= inner.budget_bytes {
655 while inner.total_bytes.saturating_add(bytes) > inner.budget_bytes
656 && !inner.entries.is_empty()
657 {
658 let Some(oldest_key) = inner
659 .entries
660 .iter()
661 .min_by_key(|(_, e)| e.last_used)
662 .map(|(k, _)| *k)
663 else {
664 break;
665 };
666 if let Some(removed) = inner.entries.remove(&oldest_key) {
667 inner.total_bytes = inner.total_bytes.saturating_sub(removed.bytes);
668 }
669 }
670 }
671
672 let value = if let Some(entry) = inner.entries.get_mut(&key) {
673 entry.last_used = now;
674 entry.value.clone()
675 } else {
676 inner.entries.insert(
677 key,
678 ProjectedFactorEntry {
679 value: computed.clone(),
680 bytes,
681 last_used: now,
682 },
683 );
684 inner.total_bytes = inner.total_bytes.saturating_add(bytes);
685 computed
686 };
687 inner.in_progress.remove(&key);
688 drop(inner);
689
690 let mut guard = marker
691 .state
692 .lock()
693 .expect("projected factor in-progress lock poisoned");
694 *guard = Some(ProjectedFactorInProgressState::Ready(value.clone()));
695 marker.ready.notify_all();
696 value
697 }
698 }
699 }
700
701 pub fn len(&self) -> usize {
702 self.inner
703 .lock()
704 .map(|inner| inner.entries.len())
705 .unwrap_or(0)
706 }
707
708 pub fn total_bytes(&self) -> usize {
709 self.inner
710 .lock()
711 .map(|inner| inner.total_bytes)
712 .unwrap_or(0)
713 }
714
715 pub fn is_empty(&self) -> bool {
716 self.len() == 0
717 }
718
719 pub fn wait_for_subscriber(
730 &self,
731 key: ProjectedFactorKey,
732 timeout: std::time::Duration,
733 ) -> bool {
734 let marker = {
735 let inner = self
736 .inner
737 .lock()
738 .expect("projected factor cache lock poisoned");
739 let Some(m) = inner.in_progress.get(&key) else {
740 return false;
741 };
742 Arc::clone(m)
743 };
744 if marker
745 .waiter_count
746 .load(std::sync::atomic::Ordering::Acquire)
747 > 0
748 {
749 return true;
750 }
751 let (lock, cv) = &marker.subscriber_arrived;
752 let mut guard = lock
753 .lock()
754 .expect("subscriber-arrived notification lock poisoned");
755 let deadline = std::time::Instant::now() + timeout;
756 loop {
757 if marker
758 .waiter_count
759 .load(std::sync::atomic::Ordering::Acquire)
760 > 0
761 {
762 return true;
763 }
764 let now = std::time::Instant::now();
765 if now >= deadline {
766 return false;
767 }
768 let (next_guard, result) = cv
769 .wait_timeout(guard, deadline - now)
770 .expect("subscriber-arrived wait poisoned");
771 guard = next_guard;
772 if result.timed_out()
773 && marker
774 .waiter_count
775 .load(std::sync::atomic::Ordering::Acquire)
776 == 0
777 {
778 return false;
779 }
780 }
781 }
782}
783
784#[derive(Clone)]
785pub struct DenseMatrixHyperOperator {
786 pub matrix: Array2<f64>,
787}
788
789impl HyperOperator for DenseMatrixHyperOperator {
790 fn dim(&self) -> usize {
791 self.matrix.nrows()
792 }
793
794 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64> {
795 self.matrix.dot(v)
796 }
797
798 fn as_any(&self) -> &(dyn Any + 'static) {
799 self
800 }
801
802 fn mul_vec_view(&self, v: ArrayView1<'_, f64>) -> Array1<f64> {
803 self.matrix.dot(&v)
804 }
805
806 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
807 assert_eq!(self.matrix.ncols(), v.len());
808 assert_eq!(self.matrix.nrows(), out.len());
809 for (row, out_value) in self.matrix.rows().into_iter().zip(out.iter_mut()) {
810 *out_value = row.dot(&v);
811 }
812 }
813
814 fn mul_basis_columns_into(&self, start: usize, mut out: ArrayViewMut2<'_, f64>) {
815 let end = start + out.ncols();
816 assert!(end <= self.matrix.ncols());
817 out.assign(&self.matrix.slice(ndarray::s![.., start..end]));
818 }
819
820 fn scaled_add_mul_vec(
821 &self,
822 v: ArrayView1<'_, f64>,
823 scale: f64,
824 mut out: ArrayViewMut1<'_, f64>,
825 ) {
826 assert_eq!(self.matrix.ncols(), v.len());
827 assert_eq!(self.matrix.nrows(), out.len());
828 if scale == 0.0 {
829 return;
830 }
831 for (row, out_value) in self.matrix.rows().into_iter().zip(out.iter_mut()) {
832 *out_value += scale * row.dot(&v);
833 }
834 }
835
836 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
837 dense::bilinear(&self.matrix, v.view(), u.view())
838 }
839
840 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
841 dense::bilinear(&self.matrix, v, u)
842 }
843
844 fn to_dense(&self) -> Array2<f64> {
845 self.matrix.clone()
846 }
847
848 fn is_implicit(&self) -> bool {
849 false
850 }
851}
852
853#[derive(Clone)]
854pub struct BlockLocalDrift {
855 pub local: Array2<f64>,
856 pub start: usize,
857 pub end: usize,
858 pub total_dim: usize,
859}
860
861impl HyperOperator for BlockLocalDrift {
862 fn dim(&self) -> usize {
863 self.total_dim
864 }
865
866 fn mul_vec(&self, v: &Array1<f64>) -> Array1<f64> {
867 assert_eq!(v.len(), self.total_dim);
868 let mut out = Array1::zeros(self.total_dim);
869 self.mul_vec_into(v.view(), out.view_mut());
870 out
871 }
872
873 fn as_any(&self) -> &(dyn Any + 'static) {
874 self
875 }
876
877 fn mul_vec_into(&self, v: ArrayView1<'_, f64>, mut out: ArrayViewMut1<'_, f64>) {
878 assert_eq!(v.len(), self.total_dim);
879 assert_eq!(out.len(), self.total_dim);
880 out.fill(0.0);
881 let v_block = v.slice(ndarray::s![self.start..self.end]);
882 let mut out_block = out.slice_mut(ndarray::s![self.start..self.end]);
883 dense::matvec_into(&self.local, v_block, out_block.view_mut());
884 }
885
886 fn scaled_add_mul_vec(
887 &self,
888 v: ArrayView1<'_, f64>,
889 scale: f64,
890 mut out: ArrayViewMut1<'_, f64>,
891 ) {
892 assert_eq!(v.len(), self.total_dim);
893 assert_eq!(out.len(), self.total_dim);
894 if scale == 0.0 {
895 return;
896 }
897 let v_block = v.slice(ndarray::s![self.start..self.end]);
898 let out_block = out.slice_mut(ndarray::s![self.start..self.end]);
899 dense::matvec_scaled_add_into(&self.local, v_block, scale, out_block);
900 }
901
902 fn bilinear(&self, v: &Array1<f64>, u: &Array1<f64>) -> f64 {
903 self.bilinear_view(v.view(), u.view())
904 }
905
906 fn bilinear_view(&self, v: ArrayView1<'_, f64>, u: ArrayView1<'_, f64>) -> f64 {
907 assert_eq!(v.len(), self.total_dim);
908 assert_eq!(u.len(), self.total_dim);
909 let v_block = v.slice(ndarray::s![self.start..self.end]);
910 let u_block = u.slice(ndarray::s![self.start..self.end]);
911 dense::bilinear(&self.local, v_block, u_block)
912 }
913
914 fn to_dense(&self) -> Array2<f64> {
915 let p = self.total_dim;
916 let mut out = Array2::zeros((p, p));
917 out.slice_mut(ndarray::s![self.start..self.end, self.start..self.end])
918 .assign(&self.local);
919 out
920 }
921
922 fn is_implicit(&self) -> bool {
923 false
924 }
925
926 fn block_local_data(&self) -> Option<(&Array2<f64>, usize, usize)> {
927 Some((&self.local, self.start, self.end))
928 }
929}
930
931#[derive(Clone)]
932pub struct HyperCoordDrift {
933 pub dense: Option<Array2<f64>>,
934 pub block_local: Option<BlockLocalDrift>,
935 pub operator: Option<Arc<dyn HyperOperator>>,
936}
937
938impl HyperCoordDrift {
939 pub fn none() -> Self {
940 Self {
941 dense: None,
942 block_local: None,
943 operator: None,
944 }
945 }
946
947 pub fn from_dense(dense: Array2<f64>) -> Self {
948 Self {
949 dense: Some(dense),
950 block_local: None,
951 operator: None,
952 }
953 }
954
955 pub fn from_operator(operator: Arc<dyn HyperOperator>) -> Self {
956 Self {
957 dense: None,
958 block_local: None,
959 operator: Some(operator),
960 }
961 }
962
963 pub fn from_parts(
964 dense: Option<Array2<f64>>,
965 operator: Option<Arc<dyn HyperOperator>>,
966 ) -> Self {
967 let dense = dense.filter(|mat| !(operator.is_some() && mat.is_empty()));
968 Self {
969 dense,
970 block_local: None,
971 operator,
972 }
973 }
974
975 pub fn from_block_local_and_operator(
976 local: Array2<f64>,
977 start: usize,
978 end: usize,
979 total_dim: usize,
980 operator: Option<Arc<dyn HyperOperator>>,
981 ) -> Self {
982 Self {
983 dense: None,
984 block_local: Some(BlockLocalDrift {
985 local,
986 start,
987 end,
988 total_dim,
989 }),
990 operator,
991 }
992 }
993
994 pub fn has_operator(&self) -> bool {
995 self.operator.is_some()
996 }
997
998 pub fn uses_operator_fast_path(&self) -> bool {
999 self.operator.is_some() || self.block_local.is_some()
1000 }
1001
1002 pub fn operator_ref(&self) -> Option<&dyn HyperOperator> {
1003 self.operator.as_ref().map(Arc::as_ref)
1004 }
1005
1006 pub fn materialize(&self) -> Array2<f64> {
1007 let p = self.infer_dim();
1008 if p == 0 {
1009 return Array2::zeros((0, 0));
1010 }
1011 let mut out = self.dense.clone().unwrap_or_else(|| Array2::zeros((p, p)));
1012 if let Some(bl) = &self.block_local {
1013 out.slice_mut(ndarray::s![bl.start..bl.end, bl.start..bl.end])
1014 .scaled_add(1.0, &bl.local);
1015 }
1016 if let Some(op) = &self.operator {
1017 out += &op.to_dense();
1018 }
1019 out
1020 }
1021
1022 pub fn apply(&self, v: &Array1<f64>) -> Array1<f64> {
1023 let mut out = Array1::zeros(v.len());
1024 self.scaled_add_apply(v.view(), 1.0, &mut out);
1025 out
1026 }
1027
1028 pub fn scaled_add_apply(&self, v: ArrayView1<'_, f64>, scale: f64, out: &mut Array1<f64>) {
1029 assert_eq!(v.len(), out.len());
1030 if scale == 0.0 {
1031 return;
1032 }
1033 if let Some(dense) = &self.dense {
1034 dense::matvec_scaled_add_into(dense, v, scale, out.view_mut());
1035 }
1036 if let Some(bl) = &self.block_local {
1037 let v_block = v.slice(ndarray::s![bl.start..bl.end]);
1038 let out_block = out.slice_mut(ndarray::s![bl.start..bl.end]);
1039 dense::matvec_scaled_add_into(&bl.local, v_block, scale, out_block);
1040 }
1041 if let Some(op) = &self.operator {
1042 op.scaled_add_mul_vec(v, scale, out.view_mut());
1043 }
1044 }
1045
1046 pub(crate) fn infer_dim(&self) -> usize {
1047 if let Some(d) = &self.dense {
1048 return d.nrows();
1049 }
1050 if let Some(op) = &self.operator {
1051 return op.dim();
1052 }
1053 if let Some(bl) = &self.block_local {
1054 return bl.total_dim;
1055 }
1056 0
1057 }
1058}
1059
1060#[derive(Clone)]
1061pub struct HyperCoord {
1062 pub a: f64,
1063 pub g: Array1<f64>,
1064 pub drift: HyperCoordDrift,
1065 pub ld_s: f64,
1066 pub b_depends_on_beta: bool,
1067 pub is_penalty_like: bool,
1068 pub firth_g: Option<Array1<f64>>,
1069 pub tk_eta_fixed: Option<Array1<f64>>,
1070 pub tk_x_fixed: Option<Array2<f64>>,
1071}
1072
1073#[derive(Clone)]
1074pub struct HyperCoordPair {
1075 pub a: f64,
1076 pub g: Array1<f64>,
1077 pub b_mat: Array2<f64>,
1078 pub b_operator: Option<Arc<dyn HyperOperator>>,
1079 pub ld_s: f64,
1080}
1081
1082pub type HyperCoordPairResult = Result<HyperCoordPair, String>;
1089
1090pub type HyperCoordPairFn = Arc<dyn Fn(usize, usize) -> HyperCoordPairResult + Send + Sync>;
1101
1102impl HyperCoordPair {
1103 pub fn zero() -> Self {
1104 Self {
1105 a: 0.0,
1106 g: Array1::zeros(0),
1107 b_mat: Array2::zeros((0, 0)),
1108 b_operator: None,
1109 ld_s: 0.0,
1110 }
1111 }
1112}
1113
1114#[derive(Clone)]
1115pub enum DriftDerivResult {
1116 Dense(Array2<f64>),
1117 Operator(Arc<dyn HyperOperator>),
1118}
1119
1120impl std::fmt::Debug for DriftDerivResult {
1121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1122 match self {
1123 Self::Dense(matrix) => f
1124 .debug_tuple("Dense")
1125 .field(&format_args!("{}x{}", matrix.nrows(), matrix.ncols()))
1126 .finish(),
1127 Self::Operator(_) => f
1128 .debug_tuple("Operator")
1129 .field(&"<hyper-operator>")
1130 .finish(),
1131 }
1132 }
1133}
1134
1135impl DriftDerivResult {
1136 pub fn into_operator(self) -> Arc<dyn HyperOperator> {
1137 match self {
1138 Self::Dense(matrix) => Arc::new(DenseMatrixHyperOperator { matrix }),
1139 Self::Operator(operator) => operator,
1140 }
1141 }
1142
1143 pub fn apply(&self, v: &Array1<f64>) -> Array1<f64> {
1144 match self {
1145 Self::Dense(matrix) => matrix.dot(v),
1146 Self::Operator(operator) => operator.mul_vec(v),
1147 }
1148 }
1149}
1150
1151pub type FixedDriftDerivFn =
1152 Box<dyn Fn(usize, &Array1<f64>) -> Result<Option<DriftDerivResult>, String> + Send + Sync>;
1153
1154pub type SharedFixedDriftDerivFn =
1162 Arc<dyn Fn(usize, &Array1<f64>) -> Result<Option<DriftDerivResult>, String> + Send + Sync>;
1163
1164pub struct ContractedPsiSecondOrder {
1165 pub objective: Array1<f64>,
1166 pub score: Array2<f64>,
1167 pub hessian: Vec<DriftDerivResult>,
1168 pub ld_s: Array1<f64>,
1169}
1170
1171pub type ContractedPsiSecondOrderFn =
1172 Arc<dyn Fn(&[f64]) -> Result<Option<ContractedPsiSecondOrder>, String> + Send + Sync>;