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::{
15    circle, constant_curvature, euclidean, grassmann, lie_so, poincare, product, simplex, spd,
16    sphere, stiefel, torus,
17};
18
19pub use closure_family::{
20    ClosureFamily, ClosureProfileCi, boundary_conductance, conductance_penalty_jet,
21    profile_ci_from_grid,
22};
23pub use curvature_estimand::{
24    CurvatureVerdict, DesignCoordKappaJet, FlatnessTest, KappaEstimateSupport, KappaProfileCi,
25    design_coord_kappa_derivative, flatness_lr_test, profile_ci_walk, 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, ConstantCurvature, EuclideanManifold, GrassmannManifold, ProductManifold,
32    SpdManifold, SphereManifold, StiefelManifold, TorusManifold,
33    constant_curvature_dirichlet_penalty, constant_curvature_dirichlet_penalty_kappa_derivative,
34    distance_kappa_jet, exp_map_kappa_jet, log_map_kappa_jet, spd_frechet_mean,
35};
36pub use optimizer::{
37    RiemannianLBFGS, RiemannianObjective, RiemannianTrustRegion, TrustRegionTermination,
38};
39pub use response_geometry::{
40    ResponseCurvatureFit, ResponseGeometryError, ResponseManifold, fit_response_curvature,
41    response_curvature_criterion, response_exp_map, response_frechet_mean, response_log_map,
42    response_projection_residual,
43};
44
45use ndarray::{Array1, ArrayView1};
46
47/// Validate and normalize per-row weights for a manifold barycenter computation.
48///
49/// With `None`, returns uniform weights `1/n`. With `Some(w)`, requires `w.len() == n`
50/// and every entry finite, non-negative, with positive total, then returns `w` divided
51/// by its total so the weights sum to one.
52pub(crate) fn normalize_weights(
53    n: usize,
54    weights: Option<ArrayView1<'_, f64>>,
55) -> Result<Array1<f64>, String> {
56    match weights {
57        None => Ok(Array1::from_elem(n, 1.0 / n as f64)),
58        Some(w) => {
59            if w.len() != n {
60                return Err("weights length must match the number of rows".to_string());
61            }
62            let mut total = 0.0_f64;
63            for value in w.iter() {
64                if !value.is_finite() || *value < 0.0 {
65                    return Err(
66                        "weights must be finite, non-negative, and have positive total".to_string(),
67                    );
68                }
69                total += *value;
70            }
71            if total <= 0.0 {
72                return Err(
73                    "weights must be finite, non-negative, and have positive total".to_string(),
74                );
75            }
76            Ok(w.mapv(|v| v / total))
77        }
78    }
79}