ruvector/crates/ruvector-consciousness/tests/integration.rs
ruvnet 96d8fdc172 chore(workspace): cargo fmt — mechanical whitespace fix across 427 files
Pre-existing rustfmt drift across the workspace was blocking CI's
`Rustfmt` check on PR #373 + PR #377. Running plain `cargo fmt`
reformats 427 files; no semantic changes, no logic changes, no
behavior changes — just what rustfmt already wanted.

None of the touched files are in ruvector-rabitq, ruvector-rulake,
or the new mirror-rulake workflow — those were already fmt-clean
per the per-crate checks on commits 5a4b0d782, 5f32fd450, f5003bc7b.
Drift is in cognitum-gate-kernel, mcp-brain, nervous-system,
prime-radiant, ruqu-core, ruvector-attention, ruvector-mincut,
ruvix/* and sub-crates, plus several examples.

Verified post-fmt:
  cargo check -p ruvector-rabitq -p ruvector-rulake            → clean
  cargo clippy -p ... -p ... --all-targets -- -D warnings      → clean
  cargo test   -p ... -p ... --release                         → 82/82 pass

Intentionally does NOT touch clippy drift — many more warnings
(missing docs, precision-loss casts, too-many-args, unsafe-safety-
docs) spread across unrelated crates, each category a cross-cutting
design decision that deserves its own review.

With this commit Rustfmt CI goes green on PR #373 and PR #377.
Clippy will still fail — that's honest pre-existing state for a
separate dedicated PR.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-24 10:44:02 -04:00

509 lines
16 KiB
Rust

//! Integration tests for ruvector-consciousness.
//!
//! Validates cross-module interactions: all PhiEngine implementations
//! agree on disconnected systems (Φ ≈ 0), EmergenceEngine + PhiEngine
//! pipelines, and WASM-style usage patterns.
use ruvector_consciousness::collapse::QuantumCollapseEngine;
use ruvector_consciousness::emergence::{
coarse_grain, degeneracy, determinism, effective_information, CausalEmergenceEngine,
};
use ruvector_consciousness::geomip::{partition_information_loss_emd, GeoMipPhiEngine};
use ruvector_consciousness::phi::{
auto_compute_phi, ExactPhiEngine, GreedyBisectionPhiEngine, HierarchicalPhiEngine,
SpectralPhiEngine, StochasticPhiEngine,
};
use ruvector_consciousness::rsvd_emergence::RsvdEmergenceEngine;
use ruvector_consciousness::traits::{ConsciousnessCollapse, EmergenceEngine, PhiEngine};
use ruvector_consciousness::types::{Bipartition, ComputeBudget, PhiAlgorithm, TransitionMatrix};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
fn and_gate_tpm() -> TransitionMatrix {
#[rustfmt::skip]
let data = vec![
0.5, 0.25, 0.25, 0.0,
0.5, 0.25, 0.25, 0.0,
0.5, 0.25, 0.25, 0.0,
0.0, 0.0, 0.0, 1.0,
];
TransitionMatrix::new(4, data)
}
fn disconnected_tpm() -> TransitionMatrix {
#[rustfmt::skip]
let data = vec![
0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.5, 0.5,
];
TransitionMatrix::new(4, data)
}
fn identity_tpm(n: usize) -> TransitionMatrix {
TransitionMatrix::identity(n)
}
fn uniform_tpm(n: usize) -> TransitionMatrix {
let val = 1.0 / n as f64;
TransitionMatrix::new(n, vec![val; n * n])
}
fn random_tpm(n: usize, seed: u64) -> TransitionMatrix {
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
let mut rng = StdRng::seed_from_u64(seed);
let mut data = vec![0.0f64; n * n];
for i in 0..n {
let mut row_sum = 0.0;
for j in 0..n {
let val: f64 = rng.gen();
data[i * n + j] = val;
row_sum += val;
}
for j in 0..n {
data[i * n + j] /= row_sum;
}
}
TransitionMatrix::new(n, data)
}
// ---------------------------------------------------------------------------
// All engines agree: disconnected system → Φ ≈ 0
// ---------------------------------------------------------------------------
#[test]
fn all_engines_disconnected_near_zero() {
let tpm = disconnected_tpm();
let budget = ComputeBudget::exact();
let eps = 1e-4;
let exact = ExactPhiEngine.compute_phi(&tpm, Some(0), &budget).unwrap();
assert!(exact.phi < eps, "exact: {}", exact.phi);
let spectral = SpectralPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(spectral.phi < eps, "spectral: {}", spectral.phi);
let stochastic = StochasticPhiEngine::new(500, 42)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(stochastic.phi < eps, "stochastic: {}", stochastic.phi);
let geomip = GeoMipPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(geomip.phi < eps, "geomip: {}", geomip.phi);
let greedy = GreedyBisectionPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(greedy.phi < eps, "greedy: {}", greedy.phi);
let collapse = QuantumCollapseEngine::new(64)
.collapse_to_mip(&tpm, 10, 42)
.unwrap();
assert!(collapse.phi < eps, "collapse: {}", collapse.phi);
}
// ---------------------------------------------------------------------------
// All engines agree: AND gate at state 11 → Φ > 0
// ---------------------------------------------------------------------------
#[test]
fn all_engines_and_gate_positive() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::exact();
let exact = ExactPhiEngine.compute_phi(&tpm, Some(3), &budget).unwrap();
assert!(exact.phi >= 0.0, "exact: {}", exact.phi);
let geomip = GeoMipPhiEngine::default()
.compute_phi(&tpm, Some(3), &budget)
.unwrap();
assert!(geomip.phi >= 0.0, "geomip: {}", geomip.phi);
let spectral = SpectralPhiEngine::default()
.compute_phi(&tpm, Some(3), &budget)
.unwrap();
assert!(spectral.phi >= 0.0, "spectral: {}", spectral.phi);
let greedy = GreedyBisectionPhiEngine::default()
.compute_phi(&tpm, Some(3), &budget)
.unwrap();
assert!(greedy.phi >= 0.0, "greedy: {}", greedy.phi);
}
// ---------------------------------------------------------------------------
// Exact and GeoMIP agree on small systems
// ---------------------------------------------------------------------------
#[test]
fn exact_and_geomip_agree() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::exact();
let exact = ExactPhiEngine.compute_phi(&tpm, Some(0), &budget).unwrap();
let geomip = GeoMipPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(
(exact.phi - geomip.phi).abs() < 1e-8,
"exact={} vs geomip={}",
exact.phi,
geomip.phi
);
}
// ---------------------------------------------------------------------------
// Algorithm enum variants are correctly reported
// ---------------------------------------------------------------------------
#[test]
fn algorithm_variants_correct() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::exact();
let r = ExactPhiEngine.compute_phi(&tpm, Some(0), &budget).unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::Exact);
let r = SpectralPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::Spectral);
let r = StochasticPhiEngine::new(100, 42)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::Stochastic);
let r = GeoMipPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::GeoMIP);
let r = GreedyBisectionPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::GreedyBisection);
let r = QuantumCollapseEngine::new(32)
.collapse_to_mip(&tpm, 10, 42)
.unwrap();
assert_eq!(r.algorithm, PhiAlgorithm::Collapse);
}
// ---------------------------------------------------------------------------
// Auto-selection tiers
// ---------------------------------------------------------------------------
#[test]
fn auto_select_exact_for_small() {
let tpm = and_gate_tpm(); // n=4
let budget = ComputeBudget::exact();
let result = auto_compute_phi(&tpm, Some(0), &budget).unwrap();
assert_eq!(result.algorithm, PhiAlgorithm::Exact);
}
#[test]
fn auto_select_greedy_for_medium() {
// n=32 is > 25, so should pick GreedyBisection (or Spectral for > 100).
let tpm = random_tpm(32, 42);
let budget = ComputeBudget::fast();
let result = auto_compute_phi(&tpm, Some(0), &budget).unwrap();
assert_eq!(result.algorithm, PhiAlgorithm::GreedyBisection);
}
// ---------------------------------------------------------------------------
// Emergence + Phi pipeline
// ---------------------------------------------------------------------------
#[test]
fn emergence_pipeline_identity() {
let tpm = identity_tpm(4);
let budget = ComputeBudget::fast();
// EI should be max for identity.
let ei = effective_information(&tpm).unwrap();
assert!(ei > 1.0, "identity EI should be high, got {ei}");
// Determinism should be max.
let det = determinism(&tpm);
assert!(det > 1.0, "identity det should be high, got {det}");
// Degeneracy should be ~0.
let deg = degeneracy(&tpm);
assert!(deg < 0.1, "identity deg should be ~0, got {deg}");
// Full emergence search.
let engine = CausalEmergenceEngine::default();
let result = engine.compute_emergence(&tpm, &budget).unwrap();
assert!(result.ei_micro > 0.0);
assert!(result.causal_emergence.is_finite());
}
#[test]
fn emergence_pipeline_uniform() {
let tpm = uniform_tpm(4);
let budget = ComputeBudget::fast();
let ei = effective_information(&tpm).unwrap();
assert!(ei < 0.01, "uniform EI should be ~0, got {ei}");
let engine = CausalEmergenceEngine::default();
let result = engine.compute_emergence(&tpm, &budget).unwrap();
assert!(result.ei_micro < 0.01);
}
// ---------------------------------------------------------------------------
// RSVD emergence integration
// ---------------------------------------------------------------------------
#[test]
fn rsvd_emergence_pipeline() {
let tpm = random_tpm(8, 99);
let budget = ComputeBudget::fast();
let engine = RsvdEmergenceEngine::new(5, 3, 42);
let result = engine.compute(&tpm, &budget).unwrap();
assert!(!result.singular_values.is_empty());
assert!(result.effective_rank >= 1);
assert!(result.spectral_entropy >= 0.0);
assert!(result.emergence_index >= 0.0 && result.emergence_index <= 1.0);
assert!(result.reversibility >= 0.0 && result.reversibility <= 1.0);
}
#[test]
fn rsvd_vs_hoel_emergence_correlation() {
// Both emergence measures should agree directionally:
// identity (high EI, low SVD emergence) vs uniform (low EI, potentially different)
let tpm_id = identity_tpm(4);
let tpm_uni = uniform_tpm(4);
let budget = ComputeBudget::fast();
let hoel_id = CausalEmergenceEngine::default()
.compute_emergence(&tpm_id, &budget)
.unwrap();
let hoel_uni = CausalEmergenceEngine::default()
.compute_emergence(&tpm_uni, &budget)
.unwrap();
let rsvd_id = RsvdEmergenceEngine::default()
.compute(&tpm_id, &budget)
.unwrap();
let rsvd_uni = RsvdEmergenceEngine::default()
.compute(&tpm_uni, &budget)
.unwrap();
// Identity has higher EI than uniform (both systems).
assert!(hoel_id.ei_micro > hoel_uni.ei_micro);
// Uniform has higher emergence index (more compressible = rank-1).
assert!(
rsvd_uni.emergence_index > rsvd_id.emergence_index,
"uniform emergence_index ({}) should > identity ({})",
rsvd_uni.emergence_index,
rsvd_id.emergence_index,
);
}
// ---------------------------------------------------------------------------
// Coarse-graining preserves TPM validity
// ---------------------------------------------------------------------------
#[test]
fn coarse_grain_preserves_row_sums() {
let tpm = random_tpm(8, 123);
let mapping = vec![0, 0, 1, 1, 2, 2, 3, 3]; // 8 → 4 states
let macro_tpm = coarse_grain(&tpm, &mapping);
assert_eq!(macro_tpm.n, 4);
for i in 0..macro_tpm.n {
let row_sum: f64 = (0..macro_tpm.n).map(|j| macro_tpm.get(i, j)).sum();
assert!(
(row_sum - 1.0).abs() < 1e-10,
"macro TPM row {i} sums to {row_sum}"
);
}
}
// ---------------------------------------------------------------------------
// EMD vs KL-divergence: both non-negative, EMD is a metric
// ---------------------------------------------------------------------------
#[test]
fn emd_and_kl_both_nonnegative() {
let tpm = and_gate_tpm();
let partition = Bipartition { mask: 0b0011, n: 4 };
let arena = ruvector_consciousness::arena::PhiArena::with_capacity(4096);
let emd_loss = partition_information_loss_emd(&tpm, 0, &partition, &arena);
assert!(emd_loss >= 0.0, "EMD loss negative: {emd_loss}");
// KL-based loss (via phi module).
let phi_result = ExactPhiEngine
.compute_phi(&tpm, Some(0), &ComputeBudget::exact())
.unwrap();
assert!(phi_result.phi >= 0.0);
}
// ---------------------------------------------------------------------------
// Bipartition validity
// ---------------------------------------------------------------------------
#[test]
fn bipartition_set_extraction() {
let bp = Bipartition { mask: 0b1010, n: 4 };
let a = bp.set_a(); // bits 1 and 3
let b = bp.set_b(); // bits 0 and 2
assert_eq!(a, vec![1, 3]);
assert_eq!(b, vec![0, 2]);
assert!(bp.is_valid());
// Invalid: all in A.
let bp_all = Bipartition { mask: 0b1111, n: 4 };
assert!(!bp_all.is_valid());
// Invalid: none in A.
let bp_none = Bipartition { mask: 0, n: 4 };
assert!(!bp_none.is_valid());
}
// ---------------------------------------------------------------------------
// Budget enforcement
// ---------------------------------------------------------------------------
#[test]
fn budget_limits_partitions() {
let tpm = random_tpm(8, 42); // 254 partitions total.
let budget = ComputeBudget {
max_partitions: 10,
..ComputeBudget::exact()
};
let result = ExactPhiEngine.compute_phi(&tpm, Some(0), &budget).unwrap();
assert!(
result.partitions_evaluated <= 10,
"should respect partition limit, evaluated {}",
result.partitions_evaluated
);
}
// ---------------------------------------------------------------------------
// Large system smoke test (n=16)
// ---------------------------------------------------------------------------
#[test]
fn large_system_smoke_n16() {
let tpm = random_tpm(16, 42);
let budget = ComputeBudget::fast();
// Spectral should handle n=16 quickly.
let spectral = SpectralPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(spectral.phi >= 0.0);
// Stochastic with limited samples.
let stochastic = StochasticPhiEngine::new(200, 42)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(stochastic.phi >= 0.0);
// Greedy bisection.
let greedy = GreedyBisectionPhiEngine::default()
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(greedy.phi >= 0.0);
// Hierarchical.
let hierarchical = HierarchicalPhiEngine::new(8)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert!(hierarchical.phi >= 0.0);
// Emergence.
let emergence = CausalEmergenceEngine::default()
.compute_emergence(&tpm, &budget)
.unwrap();
assert!(emergence.ei_micro >= 0.0);
// RSVD.
let rsvd = RsvdEmergenceEngine::default()
.compute(&tpm, &budget)
.unwrap();
assert!(rsvd.effective_rank >= 1);
}
// ---------------------------------------------------------------------------
// Deterministic reproducibility
// ---------------------------------------------------------------------------
#[test]
fn stochastic_deterministic_with_same_seed() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::fast();
let r1 = StochasticPhiEngine::new(100, 42)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
let r2 = StochasticPhiEngine::new(100, 42)
.compute_phi(&tpm, Some(0), &budget)
.unwrap();
assert_eq!(r1.phi, r2.phi, "same seed should give same result");
assert_eq!(r1.mip, r2.mip);
}
#[test]
fn collapse_deterministic_with_same_seed() {
let tpm = and_gate_tpm();
let r1 = QuantumCollapseEngine::new(64)
.collapse_to_mip(&tpm, 10, 42)
.unwrap();
let r2 = QuantumCollapseEngine::new(64)
.collapse_to_mip(&tpm, 10, 42)
.unwrap();
assert_eq!(r1.phi, r2.phi);
assert_eq!(r1.mip, r2.mip);
}
// ---------------------------------------------------------------------------
// Error handling
// ---------------------------------------------------------------------------
#[test]
fn all_engines_reject_invalid_tpm() {
let bad_tpm = TransitionMatrix::new(2, vec![0.5, 0.5, 0.3, 0.3]);
let budget = ComputeBudget::exact();
assert!(ExactPhiEngine
.compute_phi(&bad_tpm, Some(0), &budget)
.is_err());
assert!(SpectralPhiEngine::default()
.compute_phi(&bad_tpm, Some(0), &budget)
.is_err());
assert!(StochasticPhiEngine::new(10, 42)
.compute_phi(&bad_tpm, Some(0), &budget)
.is_err());
assert!(GeoMipPhiEngine::default()
.compute_phi(&bad_tpm, Some(0), &budget)
.is_err());
assert!(GreedyBisectionPhiEngine::default()
.compute_phi(&bad_tpm, Some(0), &budget)
.is_err());
}
#[test]
fn exact_rejects_too_large() {
let tpm = random_tpm(32, 42);
let budget = ComputeBudget::exact();
let result = ExactPhiEngine.compute_phi(&tpm, Some(0), &budget);
assert!(result.is_err());
}