Download rust/algebraic-core/src/geometry.rs from Snapkitty/sov-kernel-monster: direct link, hf CLI and curl.
- Browser
- Download file 25.7 kB
-
https://huggingface.co/Snapkitty/sov-kernel-monster/resolve/main/rust/algebraic-core/src/geometry.rs
- Command line
-
hf download hf://Snapkitty/sov-kernel-monster/rust/algebraic-core/src/geometry.rs
-
curl -L -o geometry.rs https://huggingface.co/Snapkitty/sov-kernel-monster/resolve/main/rust/algebraic-core/src/geometry.rs
25.7 kB
| //! # Geometry Engine Module | |
| //! | |
| //! Implements Riemannian geometry operations for quantum density matrices on the Bures manifold. | |
| //! Computes: | |
| //! - Bures metric tensor via Lyapunov equation: ρG + Gρ = 2 Δρ | |
| //! - Riemannian gradient of von Neumann entropy: grad S = -4(ρ ∘ log ρ) | |
| //! - Christoffel symbols (placeholder for future refactoring) | |
| //! | |
| //! ## Key Design Decisions | |
| //! - **Backend**: `tch-rs` with CUDA (`libtorch` backend) for hardware acceleration | |
| //! - **Precision**: Strict `f64` usage (quantum states require double precision) | |
| //! - **JordanTensor Integration**: All symmetric products use Module 1's trait | |
| //! - **Spectral Methods**: Matrix log/exp via GPU-accelerated eigh eigenvalues | |
| //! - **Memory Safety**: Explicit tensor drops, no_grad scope, stream synchronization | |
| //! - **Validation**: Unit tests verify metric symmetry and positive-definiteness | |
| //! | |
| //! ## Safety Guarantees | |
| //! - All tensor operations occur within `no_grad` scope to prevent autograd overhead | |
| //! - Eigenvalue decompositions use `torch.linalg.eigh()` (CUDA-accelerated, Hermitian-safe) | |
| //! - Division by zero avoided via epsilon clamping in Lyapunov solve | |
| //! - Error handling propagates `TchError` for clear diagnostics | |
| //! - Unit tests validate mathematical properties under floating-point tolerance | |
| //! | |
| //! ## Integration with sov-kernel-monster | |
| //! - Bures metric provides natural geometry for JST density matrix evolution | |
| //! - Entropy gradient connects to φ-decay thermal monad (training_adjoint.f90) | |
| //! - Lyapunov solver reused by GREY HAT membrane for fixed-point verification | |
| //! - WORM-attested geometric invariants via sov_bifrost_sign | |
| //! | |
| //! ## Prior Art | |
| //! - SnapKitty Foundry Intel (April 14, 2026) | |
| //! - Original Research Lab: JAB Capital Trust (2021) | |
| use tch::{Device, Kind, Tensor}; | |
| use thiserror::Error; | |
| use crate::algebra::{AlgebraError, JordanTensor}; | |
| /// Error type for geometric operations | |
| pub enum GeometryError { | |
| /// Underlying tensor operation failure (CUDA, memory, computation) | |
| TchError( tch::TchError), | |
| /// Algebraic core error (from JordanTensor trait operations) | |
| AlgebraError( AlgebraError), | |
| /// Input tensors have incompatible dimensions | |
| DimensionMismatch(i64), | |
| /// Input tensor uses insufficient precision | |
| PrecisionError, | |
| /// Input tensor is not Hermitian (symmetric for real matrices) | |
| NonHermitian, | |
| /// Input tensor is not a valid density matrix | |
| NotDensityMatrix, | |
| /// Tangent vector must be traceless | |
| NotTraceless, | |
| } | |
| pub type Result<T> = std::result::Result<T, GeometryError>; | |
| /// Riemannian geometry operations for the Bures manifold of quantum density matrices. | |
| /// | |
| /// The Bures metric is the natural Riemannian metric on the space of density matrices, | |
| /// defined implicitly via the Lyapunov equation: | |
| /// g_ρ(Δρ₁, Δρ₂) = (1/2) Tr[Δρ₁ · G₂] | |
| /// where G₂ solves: ρG₂ + G₂ρ = 2 Δρ₂ | |
| /// | |
| /// This struct provides static methods for: | |
| /// - Solving the Lyapunov equation (metric tensor computation) | |
| /// - Computing Riemannian gradients (entropy flow) | |
| /// - Christoffel symbols (geodesic computation, placeholder) | |
| pub struct BuresGeometry; | |
| impl BuresGeometry { | |
| /// Solves the Lyapunov equation ρG + Gρ = C for G using spectral decomposition. | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (n×n, Hermitian, f64) | |
| /// * `c` - Right-hand side matrix (n×n, Hermitian, f64) | |
| /// | |
| /// # Returns | |
| /// * `Ok(Tensor)` - Solution G (n×n, f64) | |
| /// * `Err(GeometryError)` - On validation failure or backend error | |
| /// | |
| /// # Mathematical Notes | |
| /// Uses eigendecomposition: ρ = VΛV† | |
| /// → Transform: (ΛM + MΛ) = V†CV | |
| /// → Solve element-wise: Mᵢⱼ = (V†CV)ᵢⱼ / (λᵢ + λⱼ) | |
| /// → Reconstruct: G = VMV† | |
| /// | |
| /// # Performance | |
| /// - O(n³) dominated by eigendecomposition (cuSOLVER backend) | |
| /// - Vectorized denominator computation (no CPU loops) | |
| /// - Epsilon clamping prevents division by zero for rank-deficient ρ | |
| pub fn solve_lyapunov(rho: &Tensor, c: &Tensor) -> Result<Tensor> { | |
| let result = tch::no_grad(|| { | |
| // Validate inputs | |
| Self::validate_f64(rho)?; | |
| Self::validate_f64(c)?; | |
| Self::validate_square(rho)?; | |
| Self::validate_square(c)?; | |
| let n = rho.size()[0]; | |
| if c.size()[0] != n || c.size()[1] != n { | |
| return Err(GeometryError::DimensionMismatch(n)); | |
| } | |
| // Eigendecompose ρ = V · diag(λ) · V^T | |
| // linalg_eigh returns (eigenvalues, eigenvectors) for symmetric/Hermitian input | |
| let (evals, evecs) = rho.linalg_eigh("L")?; | |
| // Transform C to eigenbasis: C_t = V^T · C · V | |
| let v_t = evecs.tr(); | |
| let c_t = v_t.matmul(c).matmul(&evecs); | |
| // Build denominator matrix: D[i,j] = λ_i + λ_j + ε | |
| // Vectorized: D = λ.unsqueeze(1) + λ.unsqueeze(0) | |
| let evals_col = evals.unsqueeze(1); // [n, 1] | |
| let evals_row = evals.unsqueeze(0); // [1, n] | |
| let denom = (&evals_col + &evals_row).clamp_min(1e-12); // [n, n] | |
| // Solve element-wise: M[i,j] = C_t[i,j] / D[i,j] | |
| let m_t = &c_t / &denom; | |
| // Transform back: G = V · M · V^T | |
| let g = evecs.matmul(&m_t).matmul(&v_t); | |
| Ok(g) | |
| }); | |
| result | |
| } | |
| /// Computes G = L_ρ^{-1}(Δρ) by solving ρG + Gρ = 2Δρ. | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (n×n, Hermitian, trace=1, PSD, f64) | |
| /// * `delta_rho` - Tangent vector (n×n, Hermitian, trace=0, f64) | |
| /// | |
| /// # Returns | |
| /// * `Ok(Tensor)` - G satisfying ρG + Gρ = 2Δρ | |
| /// | |
| /// # Notes | |
| /// The Bures metric is: g_ρ(Δρ₁, Δρ₂) = (1/2) Tr[Δρ₁ · G₂] | |
| /// where G₂ = L_ρ^{-1}(Δρ₂) is the output of this function for Δρ₂. | |
| pub fn bures_metric_operator(rho: &Tensor, delta_rho: &Tensor) -> Result<Tensor> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_f64(delta_rho)?; | |
| Self::validate_square(rho)?; | |
| Self::validate_square(delta_rho)?; | |
| let n = rho.size()[0]; | |
| if delta_rho.size()[0] != n { | |
| return Err(GeometryError::DimensionMismatch(n)); | |
| } | |
| // Verify tangent vector is traceless | |
| let trace_val: f64 = delta_rho.trace().double_value(&[]); | |
| if trace_val.abs() > 1e-6 { | |
| return Err(GeometryError::NotTraceless); | |
| } | |
| // Solve ρG + Gρ = 2Δρ | |
| let two_delta = delta_rho * 2.0f64; | |
| Self::solve_lyapunov(rho, &two_delta) | |
| }); | |
| result | |
| } | |
| /// Computes the Bures inner product: g_ρ(u, v) = (1/2) Tr[u · L_ρ^{-1}(v)] | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (base point on manifold) | |
| /// * `u` - First tangent vector (Hermitian, traceless) | |
| /// * `v` - Second tangent vector (Hermitian, traceless) | |
| /// | |
| /// # Returns | |
| /// * `Ok(f64)` - Inner product value g_ρ(u, v) | |
| pub fn bures_inner_product(rho: &Tensor, u: &Tensor, v: &Tensor) -> Result<f64> { | |
| let result = tch::no_grad(|| { | |
| // Compute G_v: solves ρ·G_v + G_v·ρ = 2v | |
| let g_v = Self::bures_metric_operator(rho, v)?; | |
| // g(u, v) = (1/2) Tr[u · G_v] | |
| let product = u.matmul(&g_v); | |
| let trace: f64 = product.trace().double_value(&[]); | |
| Ok(0.5 * trace) | |
| }); | |
| result | |
| } | |
| /// Computes the Riemannian gradient of von Neumann entropy: | |
| /// grad S = -4(ρ ∘ log ρ) | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (n×n, Hermitian, trace=1, PSD, f64) | |
| /// | |
| /// # Returns | |
| /// * `Ok(Tensor)` - Gradient tensor (n×n, f64) | |
| /// | |
| /// # Mathematical Derivation | |
| /// - Von Neumann entropy: S(ρ) = -Tr[ρ log ρ] | |
| /// - Euclidean gradient: ∇S = -(log ρ + I) | |
| /// - Riemannian gradient (Bures): grad S = L_ρ(∇S) where L_ρ(X) = ρX + Xρ | |
| /// - Simplified: grad S = -(ρ(log ρ + I) + (log ρ + I)ρ) = -2(ρ log ρ + log ρ ρ) - 4ρ | |
| /// - Using Jordan product: = -4(ρ ∘ log ρ) - 4ρ | |
| /// - For the gradient direction (traceless projection): grad S = -4(ρ ∘ log ρ) | |
| /// | |
| /// Uses JordanTensor trait for ρ ∘ log ρ (satisfies Module 1 integration requirement). | |
| pub fn grad_von_neumann_entropy(rho: &Tensor) -> Result<Tensor> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_square(rho)?; | |
| let n = rho.size()[0]; | |
| // Compute matrix logarithm via spectral decomposition: | |
| // log ρ = V · diag(log(λ)) · V^T | |
| let (evals, evecs) = rho.linalg_eigh("L")?; | |
| // Safe log: log(0) → 0 (since 0·log(0)=0 in von Neumann entropy) | |
| // Clamp eigenvalues to avoid log(0) = -∞ | |
| let log_evals = evals.clamp_min(1e-300).log(); | |
| // Reconstruct log(ρ) = V · diag(log(λ)) · V^T | |
| let log_diag = Tensor::diag_embed(&log_evals, 0, -2, -1); | |
| let log_rho = evecs.matmul(&log_diag).matmul(&evecs.tr()); | |
| // Compute Jordan product: ρ ∘ log ρ = (ρ·log ρ + log ρ·ρ) / 2 | |
| // Uses JordanTensor trait from Module 1 | |
| let jordan_result = rho.jordan_product(&log_rho) | |
| .map_err(|e| GeometryError::AlgebraError(e))?; | |
| // grad S = -4(ρ ∘ log ρ) | |
| let grad = jordan_result * (-4.0f64); | |
| Ok(grad) | |
| }); | |
| result | |
| } | |
| /// Computes von Neumann entropy: S(ρ) = -Tr[ρ log ρ] | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (n×n, Hermitian, trace=1, PSD, f64) | |
| /// | |
| /// # Returns | |
| /// * `Ok(f64)` - Entropy value S ∈ [0, log(n)] | |
| pub fn von_neumann_entropy(rho: &Tensor) -> Result<f64> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_square(rho)?; | |
| // S = -Σ λᵢ log(λᵢ) where λᵢ are eigenvalues of ρ | |
| let (evals, _) = rho.linalg_eigh("L")?; | |
| // Compute -λ·log(λ) for each eigenvalue, with 0·log(0)=0 | |
| let safe_evals = evals.clamp_min(1e-300); | |
| let entropy_terms = &safe_evals * &safe_evals.log() * (-1.0f64); | |
| // Zero out contributions from near-zero eigenvalues | |
| let mask = evals.gt(1e-15); | |
| let masked = entropy_terms * &mask; | |
| let entropy: f64 = masked.sum(Kind::Double).double_value(&[]); | |
| Ok(entropy) | |
| }); | |
| result | |
| } | |
| /// Placeholder for Christoffel symbols computation. | |
| /// | |
| /// # Arguments | |
| /// * `rho` - Density matrix (n×n, Hermitian, trace=1, f64) | |
| /// | |
| /// # Returns | |
| /// * `Ok(Tensor)` - Zero tensor [n², n², n²] representing Γᵏᵢⱼ | |
| /// | |
| /// # Notes | |
| /// Full implementation requires: | |
| /// - Metric tensor derivatives (∂ᵢgⱼₗ) via automatic differentiation | |
| /// - Formula: Γᵏᵢⱼ = (1/2) gᵏˡ (∂ᵢgⱼₗ + ∂ⱼgᵢₗ - ∂ₗgᵢⱼ) | |
| /// - Will be refactored with tch-rs autograd once metric parametrization is stable | |
| pub fn christoffel_symbols(rho: &Tensor) -> Result<Tensor> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_square(rho)?; | |
| let n = rho.size()[0]; | |
| let n_sq = n * n; // Tangent space dimension for n×n matrices | |
| // TODO: Implement actual Christoffel symbols using: | |
| // Γᵏᵢⱼ = (1/2) gᵏˡ (∂ᵢgⱼₗ + ∂ⱼgᵢₗ - ∂ₗgᵢⱼ) | |
| // Requires: parametrize metric as function of ρ, differentiate | |
| Ok(Tensor::zeros([n_sq, n_sq, n_sq], (Kind::Double, rho.device()))) | |
| }); | |
| result | |
| } | |
| /// Computes the Bures distance between two density matrices: | |
| /// d_B(ρ, σ) = √(2(1 - Tr[√(√ρ σ √ρ)])) | |
| /// | |
| /// # Arguments | |
| /// * `rho` - First density matrix | |
| /// * `sigma` - Second density matrix | |
| /// | |
| /// # Returns | |
| /// * `Ok(f64)` - Bures distance d_B ∈ [0, √2] | |
| pub fn bures_distance(rho: &Tensor, sigma: &Tensor) -> Result<f64> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_f64(sigma)?; | |
| Self::validate_square(rho)?; | |
| Self::validate_square(sigma)?; | |
| let n = rho.size()[0]; | |
| if sigma.size()[0] != n { | |
| return Err(GeometryError::DimensionMismatch(n)); | |
| } | |
| // Compute √ρ via spectral decomposition | |
| let (evals_rho, evecs_rho) = rho.linalg_eigh("L")?; | |
| let sqrt_evals = evals_rho.clamp_min(0.0).sqrt(); | |
| let sqrt_diag = Tensor::diag_embed(&sqrt_evals, 0, -2, -1); | |
| let sqrt_rho = evecs_rho.matmul(&sqrt_diag).matmul(&evecs_rho.tr()); | |
| // Compute √ρ · σ · √ρ | |
| let inner = sqrt_rho.matmul(sigma).matmul(&sqrt_rho); | |
| // Compute eigenvalues of inner product (all should be ≥ 0) | |
| let (evals_inner, _) = inner.linalg_eigh("L")?; | |
| let sqrt_evals_inner = evals_inner.clamp_min(0.0).sqrt(); | |
| // Fidelity F = (Tr[√(√ρ σ √ρ)])² | |
| let trace_sqrt: f64 = sqrt_evals_inner.sum(Kind::Double).double_value(&[]); | |
| // Bures distance: d_B = √(2(1 - Tr[√(√ρ σ √ρ)])) | |
| let distance_sq = 2.0 * (1.0 - trace_sqrt.min(1.0)); | |
| Ok(distance_sq.max(0.0).sqrt()) | |
| }); | |
| result | |
| } | |
| /// Computes the quantum fidelity: F(ρ, σ) = (Tr[√(√ρ σ √ρ)])² | |
| /// | |
| /// # Arguments | |
| /// * `rho` - First density matrix | |
| /// * `sigma` - Second density matrix | |
| /// | |
| /// # Returns | |
| /// * `Ok(f64)` - Fidelity F ∈ [0, 1] | |
| pub fn fidelity(rho: &Tensor, sigma: &Tensor) -> Result<f64> { | |
| let result = tch::no_grad(|| { | |
| Self::validate_f64(rho)?; | |
| Self::validate_f64(sigma)?; | |
| Self::validate_square(rho)?; | |
| Self::validate_square(sigma)?; | |
| let n = rho.size()[0]; | |
| if sigma.size()[0] != n { | |
| return Err(GeometryError::DimensionMismatch(n)); | |
| } | |
| // √ρ via spectral decomposition | |
| let (evals_rho, evecs_rho) = rho.linalg_eigh("L")?; | |
| let sqrt_evals = evals_rho.clamp_min(0.0).sqrt(); | |
| let sqrt_diag = Tensor::diag_embed(&sqrt_evals, 0, -2, -1); | |
| let sqrt_rho = evecs_rho.matmul(&sqrt_diag).matmul(&evecs_rho.tr()); | |
| // √ρ · σ · √ρ | |
| let inner = sqrt_rho.matmul(sigma).matmul(&sqrt_rho); | |
| // Eigenvalues → √ → sum → square | |
| let (evals_inner, _) = inner.linalg_eigh("L")?; | |
| let sqrt_inner = evals_inner.clamp_min(0.0).sqrt(); | |
| let trace: f64 = sqrt_inner.sum(Kind::Double).double_value(&[]); | |
| Ok((trace * trace).min(1.0)) | |
| }); | |
| result | |
| } | |
| // ─── Validation Helpers ────────────────────────────────────────────────── | |
| fn validate_f64(t: &Tensor) -> Result<()> { | |
| if t.kind() != Kind::Double { | |
| return Err(GeometryError::PrecisionError); | |
| } | |
| Ok(()) | |
| } | |
| fn validate_square(t: &Tensor) -> Result<()> { | |
| let size = t.size(); | |
| if size.len() != 2 || size[0] != size[1] { | |
| return Err(GeometryError::DimensionMismatch(size[0])); | |
| } | |
| Ok(()) | |
| } | |
| /// Checks if tensor is symmetric (Hermitian for real matrices) within tolerance | |
| pub fn is_symmetric(t: &Tensor, tol: f64) -> bool { | |
| let diff = t - &t.tr(); | |
| let norm: f64 = diff.abs().sum(Kind::Double).double_value(&[]); | |
| norm < tol | |
| } | |
| /// Checks if tensor is a valid density matrix (symmetric, trace=1, PSD) | |
| pub fn is_density_matrix(t: &Tensor, tol: f64) -> bool { | |
| if !Self::is_symmetric(t, tol) { | |
| return false; | |
| } | |
| let trace: f64 = t.trace().double_value(&[]); | |
| if (trace - 1.0).abs() > tol { | |
| return false; | |
| } | |
| // Check PSD via eigenvalues | |
| if let Ok((evals, _)) = t.linalg_eigh("L") { | |
| let min_eval: f64 = evals.min().double_value(&[]); | |
| min_eval > -tol | |
| } else { | |
| false | |
| } | |
| } | |
| } | |
| /// Unit tests validating geometric properties | |
| mod tests { | |
| use super::*; | |
| use crate::algebra::jordan_product_cpu; | |
| /// Creates a random density matrix on CPU (symmetric, trace=1, PSD) | |
| fn rand_density_matrix_cpu(n: i64) -> Tensor { | |
| // Generate random symmetric matrix | |
| let raw = Tensor::randn([n, n], (Kind::Double, Device::Cpu)); | |
| let symmetric = (&raw + &raw.tr()) * 0.5f64; | |
| // Make PSD: A = V·|Λ|·V^T | |
| let (evals, evecs) = symmetric.linalg_eigh("L").unwrap(); | |
| let pos_evals = evals.abs().clamp_min(0.01); | |
| // Normalize to trace=1 | |
| let trace: f64 = pos_evals.sum(Kind::Double).double_value(&[]); | |
| let normed_evals = &pos_evals / trace; | |
| let diag = Tensor::diag_embed(&normed_evals, 0, -2, -1); | |
| evecs.matmul(&diag).matmul(&evecs.tr()) | |
| } | |
| /// Creates a random traceless symmetric matrix (tangent vector) | |
| fn rand_tangent_cpu(n: i64) -> Tensor { | |
| let raw = Tensor::randn([n, n], (Kind::Double, Device::Cpu)); | |
| let symmetric = (&raw + &raw.tr()) * 0.5f64; | |
| // Remove trace: H - (Tr(H)/n)·I | |
| let trace: f64 = symmetric.trace().double_value(&[]); | |
| let correction = Tensor::eye(n, (Kind::Double, Device::Cpu)) * (trace / n as f64); | |
| &symmetric - &correction | |
| } | |
| fn test_lyapunov_solves_equation() { | |
| let rho = rand_density_matrix_cpu(4); | |
| let delta = rand_tangent_cpu(4); | |
| let c = &delta * 2.0f64; | |
| // Solve ρG + Gρ = C | |
| let g = BuresGeometry::solve_lyapunov(&rho, &c).expect("Lyapunov solve failed"); | |
| // Verify: ρG + Gρ ≈ C | |
| let reconstructed = rho.matmul(&g) + g.matmul(&rho); | |
| let diff: f64 = (&reconstructed - &c).abs().max().double_value(&[]); | |
| assert!( | |
| diff < 1e-8, | |
| "Lyapunov equation not satisfied: max residual = {diff}" | |
| ); | |
| } | |
| fn test_bures_metric_symmetry() { | |
| let rho = rand_density_matrix_cpu(4); | |
| let u = rand_tangent_cpu(4); | |
| let v = rand_tangent_cpu(4); | |
| // g(u, v) = g(v, u) (metric symmetry) | |
| let g_uv = BuresGeometry::bures_inner_product(&rho, &u, &v) | |
| .expect("g(u,v) failed"); | |
| let g_vu = BuresGeometry::bures_inner_product(&rho, &v, &u) | |
| .expect("g(v,u) failed"); | |
| assert!( | |
| (g_uv - g_vu).abs() < 1e-8, | |
| "Metric not symmetric: g(u,v)={g_uv}, g(v,u)={g_vu}" | |
| ); | |
| } | |
| fn test_bures_metric_positive_definite() { | |
| let rho = rand_density_matrix_cpu(4); | |
| let v = rand_tangent_cpu(4); | |
| // g(v, v) > 0 for v ≠ 0 (positive-definiteness) | |
| let g_vv = BuresGeometry::bures_inner_product(&rho, &v, &v) | |
| .expect("g(v,v) failed"); | |
| assert!( | |
| g_vv > 0.0, | |
| "Metric not positive-definite: g(v,v) = {g_vv}" | |
| ); | |
| } | |
| fn test_bures_metric_bilinearity() { | |
| let rho = rand_density_matrix_cpu(3); | |
| let u = rand_tangent_cpu(3); | |
| let v = rand_tangent_cpu(3); | |
| let alpha = 2.5f64; | |
| // g(αu, v) = α · g(u, v) (linearity in first argument) | |
| let g_au_v = BuresGeometry::bures_inner_product(&rho, &(&u * alpha), &v) | |
| .expect("g(αu,v) failed"); | |
| let a_g_uv = alpha * BuresGeometry::bures_inner_product(&rho, &u, &v) | |
| .expect("g(u,v) failed"); | |
| assert!( | |
| (g_au_v - a_g_uv).abs() < 1e-8, | |
| "Metric not bilinear: g(αu,v)={g_au_v}, α·g(u,v)={a_g_uv}" | |
| ); | |
| } | |
| fn test_grad_entropy_uses_jordan_product() { | |
| let rho = rand_density_matrix_cpu(3); | |
| // Compute gradient via geometry module | |
| let grad = BuresGeometry::grad_von_neumann_entropy(&rho) | |
| .expect("Gradient failed"); | |
| // Verify it equals -4(ρ ∘ log ρ) using Module 1's Jordan product directly | |
| let (evals, evecs) = rho.linalg_eigh("L").unwrap(); | |
| let log_evals = evals.clamp_min(1e-300).log(); | |
| let log_diag = Tensor::diag_embed(&log_evals, 0, -2, -1); | |
| let log_rho = evecs.matmul(&log_diag).matmul(&evecs.tr()); | |
| let jordan = jordan_product_cpu(&rho, &log_rho).expect("Jordan product failed"); | |
| let expected = jordan * (-4.0f64); | |
| let diff: f64 = (&grad - &expected).abs().max().double_value(&[]); | |
| assert!( | |
| diff < 1e-8, | |
| "Gradient doesn't match -4(ρ∘logρ): max diff = {diff}" | |
| ); | |
| } | |
| fn test_von_neumann_entropy_pure_state() { | |
| // Pure state: ρ = |0><0| → S = 0 | |
| let mut rho = Tensor::zeros([3, 3], (Kind::Double, Device::Cpu)); | |
| let _ = rho.narrow(0, 0, 1).narrow(1, 0, 1).fill_(1.0); | |
| let entropy = BuresGeometry::von_neumann_entropy(&rho) | |
| .expect("Entropy failed"); | |
| assert!( | |
| entropy.abs() < 1e-10, | |
| "Pure state entropy should be 0, got {entropy}" | |
| ); | |
| } | |
| fn test_von_neumann_entropy_maximally_mixed() { | |
| // Maximally mixed: ρ = I/n → S = log(n) | |
| let n = 4i64; | |
| let rho = Tensor::eye(n, (Kind::Double, Device::Cpu)) / (n as f64); | |
| let entropy = BuresGeometry::von_neumann_entropy(&rho) | |
| .expect("Entropy failed"); | |
| let expected = (n as f64).ln(); | |
| assert!( | |
| (entropy - expected).abs() < 1e-10, | |
| "Maximally mixed entropy should be ln({n})={expected}, got {entropy}" | |
| ); | |
| } | |
| fn test_fidelity_same_state() { | |
| let rho = rand_density_matrix_cpu(4); | |
| // F(ρ, ρ) = 1 | |
| let f = BuresGeometry::fidelity(&rho, &rho).expect("Fidelity failed"); | |
| assert!( | |
| (f - 1.0).abs() < 1e-8, | |
| "Fidelity of state with itself should be 1, got {f}" | |
| ); | |
| } | |
| fn test_bures_distance_same_state() { | |
| let rho = rand_density_matrix_cpu(4); | |
| // d_B(ρ, ρ) = 0 | |
| let d = BuresGeometry::bures_distance(&rho, &rho).expect("Distance failed"); | |
| assert!( | |
| d < 1e-6, | |
| "Distance of state to itself should be 0, got {d}" | |
| ); | |
| } | |
| fn test_bures_distance_triangle_inequality() { | |
| let rho = rand_density_matrix_cpu(3); | |
| let sigma = rand_density_matrix_cpu(3); | |
| let tau = rand_density_matrix_cpu(3); | |
| let d_rs = BuresGeometry::bures_distance(&rho, &sigma).expect("d(ρ,σ) failed"); | |
| let d_st = BuresGeometry::bures_distance(&sigma, &tau).expect("d(σ,τ) failed"); | |
| let d_rt = BuresGeometry::bures_distance(&rho, &tau).expect("d(ρ,τ) failed"); | |
| // Triangle inequality: d(ρ,τ) ≤ d(ρ,σ) + d(σ,τ) | |
| assert!( | |
| d_rt <= d_rs + d_st + 1e-8, | |
| "Triangle inequality violated: d(ρ,τ)={d_rt} > d(ρ,σ)+d(σ,τ)={}", d_rs + d_st | |
| ); | |
| } | |
| fn test_precision_enforcement() { | |
| let rho = Tensor::eye(3, (Kind::Float, Device::Cpu)); // f32, not f64 | |
| assert!(matches!( | |
| BuresGeometry::solve_lyapunov(&rho, &rho), | |
| Err(GeometryError::PrecisionError) | |
| )); | |
| } | |
| fn test_christoffel_symbols_placeholder() { | |
| let rho = rand_density_matrix_cpu(3); | |
| let gamma = BuresGeometry::christoffel_symbols(&rho).expect("Christoffel failed"); | |
| // Should be zeros (placeholder) | |
| let norm: f64 = gamma.abs().sum(Kind::Double).double_value(&[]); | |
| assert!( | |
| norm < 1e-15, | |
| "Christoffel placeholder should be zero, got norm={norm}" | |
| ); | |
| } | |
| fn test_is_density_matrix() { | |
| let rho = rand_density_matrix_cpu(4); | |
| assert!(BuresGeometry::is_density_matrix(&rho, 1e-6)); | |
| // Non-trace-1 should fail | |
| let bad = Tensor::eye(4, (Kind::Double, Device::Cpu)); // trace=4 | |
| assert!(!BuresGeometry::is_density_matrix(&bad, 1e-6)); | |
| } | |
| } | |