Skip to main content

gam_geometry/
lib.rs

1pub mod closure_family;
2pub mod curvature_estimand;
3pub mod integrator;
4pub mod latent_seed;
5pub mod manifold;
6pub mod manifolds;
7pub mod optimizer;
8pub mod response_geometry;
9pub mod sinkhorn_barycenter;
10
11// Re-export each manifold submodule at the crate root so the historical paths
12// (`gam_geometry::sphere::SphereManifold`, …) keep resolving after the
13// `manifolds/` regrouping.
14pub use manifolds::{circle, constant_curvature, euclidean, grassmann, lie_so, poincare, product, simplex, spd, sphere, stiefel, torus};
15
16pub use closure_family::{ClosureFamily, ClosureProfileCi};
17pub use curvature_estimand::{
18    CurvatureVerdict,
19    DesignCoordKappaJet,
20    FlatnessTest,
21    KappaEstimateSupport,
22    KappaProfileCi,
23    flatness_lr_test,
24    profile_ci_walk,
25    wald_half_width,
26};
27pub use integrator::GeodesicIntegrator;
28pub use latent_seed::laplacian_eigenmap_coords;
29pub use manifold::{GeometryError, GeometryResult, ManifoldSpec, RiemannianManifold};
30pub use manifolds::{
31    CircleManifold,
32    ConstantCurvature,
33    EuclideanManifold,
34    GrassmannManifold,
35    ProductManifold,
36    SpdManifold,
37    SphereManifold,
38    StiefelManifold,
39    TorusManifold,
40    constant_curvature_dirichlet_penalty,
41    constant_curvature_dirichlet_penalty_kappa_derivative,
42    distance_kappa_jet,
43};
44pub use optimizer::{
45    RiemannianLBFGS,
46    RiemannianObjective,
47    RiemannianTrustRegion,
48    TrustRegionTermination,
49};
50pub use response_geometry::{
51    ResponseCurvatureFit,
52    ResponseGeometryError,
53    ResponseManifold,
54    fit_response_curvature,
55    response_curvature_criterion,
56    response_exp_map,
57    response_frechet_mean,
58    response_log_map,
59};
60
61use ndarray::{Array1, ArrayView1};
62
63/// Validate and normalize per-row weights for a manifold barycenter computation.
64///
65/// With `None`, returns uniform weights `1/n`. With `Some(w)`, requires `w.len() == n`
66/// and every entry finite, non-negative, with positive total, then returns `w` divided
67/// by its total so the weights sum to one.
68pub(crate) fn normalize_weights(
69    n: usize,
70    weights: Option<ArrayView1<'_, f64>>,
71) -> Result<Array1<f64>, String> {
72    match weights {
73        None => Ok(Array1::from_elem(n, 1.0 / n as f64)),
74        Some(w) => {
75            if w.len() != n {
76                return Err("weights length must match the number of rows".to_string());
77            }
78            let mut total = 0.0_f64;
79            for value in w.iter() {
80                if !value.is_finite() || *value < 0.0 {
81                    return Err(
82                        "weights must be finite, non-negative, and have positive total".to_string(),
83                    );
84                }
85                total += *value;
86            }
87            if total <= 0.0 {
88                return Err(
89                    "weights must be finite, non-negative, and have positive total".to_string(),
90                );
91            }
92            Ok(w.mapv(|v| v / total))
93        }
94    }
95}