From 11c72cfa7f697c49233377560808e207a3ebda99 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 31 Mar 2026 22:01:55 +0000 Subject: [PATCH] feat(examples): gene, climate, ecosystem, quantum consciousness explorers Four new IIT 4.0 analysis applications: Gene Networks: 16-gene regulatory network with 4 modules. Cancer increases degeneracy 9x. Networks are perfectly decomposable. Climate: 7 climate modes (ENSO, NAO, PDO, AMO, IOD, SAM, QBO). All modes independent (7/7 rank). IIT auto-discovers ENSO-IOD coupling. Ecosystems: Rainforest vs monoculture vs coral reef food webs. Degeneracy predicts fragility: monoculture 1.10 vs rainforest 0.12. Quantum: Bell, GHZ, Product, W states + random circuits. IIT Phi disagrees with entanglement. Emergence index tracks it better. Co-Authored-By: claude-flow --- Cargo.lock | 36 ++ Cargo.toml | 4 + .../climate-consciousness/RESEARCH.md | 117 +++++ docs/research/gene-consciousness/RESEARCH.md | 94 ++++ examples/climate-consciousness/Cargo.toml | 16 + .../climate-consciousness/src/analysis.rs | 250 +++++++++ examples/climate-consciousness/src/data.rs | 274 ++++++++++ examples/climate-consciousness/src/main.rs | 89 ++++ examples/climate-consciousness/src/report.rs | 485 ++++++++++++++++++ examples/ecosystem-consciousness/Cargo.toml | 16 + examples/ecosystem-consciousness/RESEARCH.md | 109 ++++ .../ecosystem-consciousness/src/analysis.rs | 152 ++++++ examples/ecosystem-consciousness/src/data.rs | 345 +++++++++++++ examples/ecosystem-consciousness/src/main.rs | 71 +++ .../ecosystem-consciousness/src/report.rs | 236 +++++++++ examples/gene-consciousness/Cargo.toml | 16 + examples/gene-consciousness/src/analysis.rs | 226 ++++++++ examples/gene-consciousness/src/data.rs | 279 ++++++++++ examples/gene-consciousness/src/main.rs | 87 ++++ examples/gene-consciousness/src/report.rs | 413 +++++++++++++++ examples/quantum-consciousness/Cargo.toml | 16 + examples/quantum-consciousness/RESEARCH.md | 119 +++++ .../quantum-consciousness/src/analysis.rs | 169 ++++++ examples/quantum-consciousness/src/data.rs | 415 +++++++++++++++ examples/quantum-consciousness/src/main.rs | 84 +++ examples/quantum-consciousness/src/report.rs | 309 +++++++++++ 26 files changed, 4427 insertions(+) create mode 100644 docs/research/climate-consciousness/RESEARCH.md create mode 100644 docs/research/gene-consciousness/RESEARCH.md create mode 100644 examples/climate-consciousness/Cargo.toml create mode 100644 examples/climate-consciousness/src/analysis.rs create mode 100644 examples/climate-consciousness/src/data.rs create mode 100644 examples/climate-consciousness/src/main.rs create mode 100644 examples/climate-consciousness/src/report.rs create mode 100644 examples/ecosystem-consciousness/Cargo.toml create mode 100644 examples/ecosystem-consciousness/RESEARCH.md create mode 100644 examples/ecosystem-consciousness/src/analysis.rs create mode 100644 examples/ecosystem-consciousness/src/data.rs create mode 100644 examples/ecosystem-consciousness/src/main.rs create mode 100644 examples/ecosystem-consciousness/src/report.rs create mode 100644 examples/gene-consciousness/Cargo.toml create mode 100644 examples/gene-consciousness/src/analysis.rs create mode 100644 examples/gene-consciousness/src/data.rs create mode 100644 examples/gene-consciousness/src/main.rs create mode 100644 examples/gene-consciousness/src/report.rs create mode 100644 examples/quantum-consciousness/Cargo.toml create mode 100644 examples/quantum-consciousness/RESEARCH.md create mode 100644 examples/quantum-consciousness/src/analysis.rs create mode 100644 examples/quantum-consciousness/src/data.rs create mode 100644 examples/quantum-consciousness/src/main.rs create mode 100644 examples/quantum-consciousness/src/report.rs diff --git a/Cargo.lock b/Cargo.lock index e71f7266..543801a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1293,6 +1293,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "climate-consciousness" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruvector-consciousness", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -2368,6 +2377,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" +[[package]] +name = "ecosystem-consciousness" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruvector-consciousness", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -3254,6 +3272,15 @@ dependencies = [ "seq-macro", ] +[[package]] +name = "gene-consciousness" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruvector-consciousness", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -7350,6 +7377,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "quantum-consciousness" +version = "0.1.0" +dependencies = [ + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruvector-consciousness", +] + [[package]] name = "quick-error" version = "1.2.3" diff --git a/Cargo.toml b/Cargo.toml index 6c61c9ca..26aacd74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,10 @@ members = [ "crates/ruvector-consciousness-wasm", "examples/cmb-consciousness", "examples/gw-consciousness", + "examples/ecosystem-consciousness", + "examples/quantum-consciousness", + "examples/gene-consciousness", + "examples/climate-consciousness", ] resolver = "2" diff --git a/docs/research/climate-consciousness/RESEARCH.md b/docs/research/climate-consciousness/RESEARCH.md new file mode 100644 index 00000000..aa03c680 --- /dev/null +++ b/docs/research/climate-consciousness/RESEARCH.md @@ -0,0 +1,117 @@ +# Climate Teleconnection Consciousness Analysis + +## Overview + +This example applies Integrated Information Theory (IIT) Phi to climate mode +interactions to study how large-scale climate oscillations form integrated +information systems. The key question is whether the climate system's +teleconnections create genuine integrated information, and whether El Nino +events increase this integration. + +## Background + +### Climate Teleconnections + +Teleconnections are large-scale patterns of climate variability that link +weather and climate conditions across distant regions. The major climate +indices capture these patterns: + +- **ENSO (El Nino Southern Oscillation)**: The dominant mode of interannual + climate variability, involving coupled ocean-atmosphere dynamics in the + tropical Pacific. Measured by the Nino3.4 index. + +- **NAO (North Atlantic Oscillation)**: Pressure difference between the + Icelandic Low and Azores High, controlling weather patterns across the + North Atlantic and Europe. + +- **PDO (Pacific Decadal Oscillation)**: Long-lived pattern of Pacific + climate variability, related to but distinct from ENSO on decadal scales. + +- **AMO (Atlantic Multidecadal Oscillation)**: Basin-wide sea surface + temperature variations in the North Atlantic on 60-80 year timescales. + +- **IOD (Indian Ocean Dipole)**: East-west temperature gradient in the + tropical Indian Ocean, strongly coupled to ENSO. + +- **SAM (Southern Annular Mode)**: The dominant mode of extratropical + variability in the Southern Hemisphere, mostly independent. + +- **QBO (Quasi-Biennial Oscillation)**: Alternating easterly/westerly winds + in the equatorial stratosphere with a ~28-month period. + +### Known Teleconnection Strengths + +The coupling between these modes varies: +- **Strong**: ENSO-IOD (r ~ 0.5-0.7), driven by Walker circulation coupling +- **Moderate**: ENSO-PDO (r ~ 0.3-0.5), Pacific basin shared dynamics +- **Moderate**: NAO-AMO (r ~ 0.2-0.4), Atlantic ocean-atmosphere coupling +- **Weak**: QBO-ENSO (r ~ 0.1-0.2), stratosphere-troposphere interaction +- **Minimal**: SAM is largely independent of tropical modes + +### IIT and Climate Systems + +Applying IIT to climate modes asks: does the climate system generate more +integrated information than the sum of its regional parts? Specifically: + +- **Do teleconnections create integration?** If climate modes are truly + coupled (not just correlated), the system should have non-trivial Phi. + +- **Does El Nino increase integration?** During El Nino events, ENSO + correlations strengthen by ~50%, potentially increasing system-wide Phi. + +- **What are the irreducible subsystems?** The Pacific basin (ENSO, PDO, IOD) + is expected to be the most integrated regional subsystem. + +## Method + +### TPM Construction + +1. Start with a 7x7 correlation matrix based on known teleconnection strengths. +2. Apply a sharpness exponent (alpha=2.0) to emphasize strong connections. +3. Row-normalize to get transition probabilities. +4. Generate El Nino variant by boosting ENSO correlations 50%. + +### Analysis Pipeline + +1. Compute Phi for the full 7-index system (neutral and El Nino). +2. Compute Phi for regional subsets: Pacific, Atlantic, Polar. +3. Compare neutral vs El Nino Phi. +4. Temporal analysis: simulate 12 monthly states with seasonal modulation. +5. Causal emergence: find optimal coarse-graining. +6. Null hypothesis testing with shuffled correlations. + +### Seasonal Modulation + +Climate modes have distinct seasonal cycles: +- **ENSO**: peaks in boreal winter (DJF), weakens in spring (MAM) +- **NAO**: strongest in winter, weakest in summer +- This creates a seasonal Phi cycle that should peak in winter when + teleconnections are strongest. + +## Expected Results + +- The Pacific basin should have the highest regional Phi due to strong + ENSO-IOD-PDO coupling. +- El Nino should increase full-system Phi by strengthening teleconnections. +- Monthly Phi should peak in DJF (boreal winter) when both ENSO and NAO + teleconnections are strongest. +- The null model (shuffled correlations) should have different Phi, + confirming that the specific teleconnection structure matters. +- SAM should contribute least to integration (mostly independent). + +## Implications + +If climate modes show genuine integrated information, it suggests that: +1. The climate system has emergent properties beyond its components. +2. Teleconnections are not merely correlations but genuine causal coupling. +3. El Nino events fundamentally change the system's information architecture. +4. The Pacific basin acts as the "core" of global climate integration. + +## References + +1. Tononi G. (2004). An information integration theory of consciousness. BMC Neurosci. +2. Hoel E.P. et al. (2013). Quantifying causal emergence. PNAS. +3. Trenberth K.E. et al. (1998). Progress during TOGA in understanding and modeling + global teleconnections. J. Geophys. Res. +4. McPhaden M.J. et al. (2006). ENSO as an integrating concept in earth science. Science. +5. Saji N.H. et al. (1999). A dipole mode in the tropical Indian Ocean. Nature. diff --git a/docs/research/gene-consciousness/RESEARCH.md b/docs/research/gene-consciousness/RESEARCH.md new file mode 100644 index 00000000..1900a0ed --- /dev/null +++ b/docs/research/gene-consciousness/RESEARCH.md @@ -0,0 +1,94 @@ +# Gene Regulatory Network Consciousness Analysis + +## Overview + +This example applies Integrated Information Theory (IIT) Phi to gene regulatory +networks to identify emergent regulatory modules. The key hypothesis is that +functional gene modules -- groups of genes that work together as a unit -- should +correspond to subsystems with high integrated information (Phi), making them the +"irreducible units" of the regulatory network. + +## Background + +### Gene Regulatory Networks + +Gene regulatory networks (GRNs) describe how genes control each other's +expression through activation and repression. These networks exhibit modular +structure, where groups of genes form functional units: + +- **Cell cycle module**: Cyclins (CycD, CycE) and CDKs (CDK4, CDK2) form a + cascade that drives cell division. +- **Apoptosis module**: Pro-apoptotic (BAX, CASP3) and anti-apoptotic (BCL2) + factors balance cell death decisions, regulated by p53. +- **Growth signaling module**: The EGFR-RAS-RAF-ERK cascade transmits external + growth signals to the nucleus. +- **Housekeeping module**: Constitutively expressed genes (GAPDH, ACTB, etc.) + that maintain basic cellular functions. + +### IIT and Gene Networks + +IIT's Phi measures how much a system is "more than the sum of its parts" -- +i.e., how much information is generated by the system as a whole beyond what +its parts generate independently. For gene networks: + +- **High intra-module Phi**: Genes within a functional module should have high + integrated information because they form a tightly coupled regulatory unit. +- **Low inter-module Phi**: The full network may have lower Phi than individual + modules because the weak between-module connections don't contribute much + integration. +- **Modules as irreducible units**: The IIT prediction is that functional + modules should be the "complexes" -- the maximally irreducible subsystems. + +## Cancer Rewiring Hypothesis + +In cancer, oncogenic mutations rewire the regulatory network: + +1. Growth signaling becomes constitutively active (e.g., RAS mutations). +2. Apoptosis is suppressed (e.g., BCL2 overexpression). +3. p53 pathway is disrupted (e.g., TP53 mutations). +4. Cell cycle checkpoints are bypassed. + +These changes create strong cross-module connections where growth signaling +overrides apoptosis controls. The IIT prediction is that this should: + +- **Increase full-network Phi**: More cross-module integration. +- **Blur module boundaries**: The network becomes less modular. +- **Change emergence landscape**: Different coarse-graining is optimal. + +## Method + +### TPM Construction + +1. Start with a 16-gene adjacency matrix with known module structure. +2. Within-module connections: strong (0.3-0.5 transition probability). +3. Between-module connections: weak (0.01-0.05). +4. Convert to TPM by taking absolute values and row-normalizing. +5. Self-regulation baseline (0.1 on diagonal) ensures stability. + +### Analysis Pipeline + +1. Compute Phi for the full 16-gene network (normal and cancer variants). +2. Compute Phi for each 4-gene module in isolation. +3. Compare module Phi vs full network Phi. +4. Compute causal emergence (optimal coarse-graining). +5. Null hypothesis testing with degree-preserving shuffled networks. + +## Expected Results + +- Individual modules should have higher Phi than the full network, confirming + they are the irreducible regulatory units. +- Cancer rewiring should increase full-network Phi by creating stronger + cross-module coupling. +- Causal emergence should reveal that the optimal coarse-graining corresponds + approximately to the known module boundaries in the normal network. +- The null model (shuffled connections) should have different Phi, confirming + that the modular structure is informationally significant. + +## References + +1. Tononi G. (2004). An information integration theory of consciousness. BMC Neurosci. +2. Hoel E.P. et al. (2013). Quantifying causal emergence shows that macro can beat micro. PNAS. +3. Alon U. (2007). Network motifs: theory and experimental approaches. Nat Rev Genet. +4. Barabasi A.L., Oltvai Z.N. (2004). Network biology: understanding the cell's + functional organization. Nat Rev Genet. +5. Hanahan D., Weinberg R.A. (2011). Hallmarks of cancer: the next generation. Cell. diff --git a/examples/climate-consciousness/Cargo.toml b/examples/climate-consciousness/Cargo.toml new file mode 100644 index 00000000..7f587ee6 --- /dev/null +++ b/examples/climate-consciousness/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "climate-consciousness" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Climate teleconnection consciousness analysis using IIT Phi" +publish = false + +[[bin]] +name = "climate-consciousness" +path = "src/main.rs" + +[dependencies] +ruvector-consciousness = { path = "../../crates/ruvector-consciousness", default-features = false, features = ["phi", "emergence", "collapse"] } +rand = "0.8" +rand_chacha = "0.3" diff --git a/examples/climate-consciousness/src/analysis.rs b/examples/climate-consciousness/src/analysis.rs new file mode 100644 index 00000000..3c6e62e7 --- /dev/null +++ b/examples/climate-consciousness/src/analysis.rs @@ -0,0 +1,250 @@ +//! Consciousness analysis pipeline for climate teleconnections. + +use ruvector_consciousness::emergence::CausalEmergenceEngine; +use ruvector_consciousness::phi::auto_compute_phi; +use ruvector_consciousness::rsvd_emergence::{RsvdEmergenceEngine, RsvdEmergenceResult}; +use ruvector_consciousness::traits::EmergenceEngine; +use ruvector_consciousness::types::{ + ComputeBudget, EmergenceResult, PhiResult, + TransitionMatrix as ConsciousnessTPM, +}; + +use crate::data::{self, ClimateCorrelations, TransitionMatrix}; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +/// Full analysis results for climate teleconnections. +pub struct AnalysisResults { + /// Phi for the full 7-index neutral system. + pub neutral_full_phi: PhiResult, + /// Phi for the full 7-index El Nino active system. + pub elnino_full_phi: PhiResult, + /// Regional subset Phi values (neutral). + pub neutral_regional_phis: Vec<(String, PhiResult)>, + /// Regional subset Phi values (El Nino). + pub elnino_regional_phis: Vec<(String, PhiResult)>, + /// Causal emergence for neutral system. + pub neutral_emergence: EmergenceResult, + /// SVD emergence for neutral system. + pub neutral_svd_emergence: RsvdEmergenceResult, + /// Whether El Nino increases full-system Phi. + pub elnino_increases_phi: bool, + /// Whether the Pacific basin is the most integrated subsystem. + pub pacific_most_integrated: bool, + /// Monthly Phi values (seasonal cycle). + pub monthly_phis: Vec<(String, f64)>, + /// Null model Phi values. + pub null_phis: Vec, + /// Z-score of observed Phi vs null. + pub z_score: f64, + /// Empirical p-value. + pub p_value: f64, +} + +/// Convert our TPM to the consciousness crate's format. +fn to_consciousness_tpm(tpm: &TransitionMatrix) -> ConsciousnessTPM { + ConsciousnessTPM::new(tpm.size, tpm.data.clone()) +} + +/// Run the complete analysis pipeline. +pub fn run_analysis( + neutral_data: &ClimateCorrelations, + neutral_tpm: &TransitionMatrix, + _elnino_data: &ClimateCorrelations, + elnino_tpm: &TransitionMatrix, + null_samples: usize, +) -> AnalysisResults { + let budget = ComputeBudget::default(); + + // 1. Full system Phi -- neutral + println!("\n--- Computing Phi: Neutral Climate System (7 indices) ---"); + let neutral_ctpm = to_consciousness_tpm(neutral_tpm); + let neutral_full_phi = auto_compute_phi(&neutral_ctpm, None, &budget) + .expect("Failed to compute Phi for neutral system"); + println!( + " Phi = {:.6} (algorithm: {}, elapsed: {:?})", + neutral_full_phi.phi, neutral_full_phi.algorithm, neutral_full_phi.elapsed + ); + + // 2. Full system Phi -- El Nino active + println!("\n--- Computing Phi: El Nino Active (7 indices) ---"); + let elnino_ctpm = to_consciousness_tpm(elnino_tpm); + let elnino_full_phi = auto_compute_phi(&elnino_ctpm, None, &budget) + .expect("Failed to compute Phi for El Nino system"); + println!( + " Phi = {:.6} (algorithm: {}, elapsed: {:?})", + elnino_full_phi.phi, elnino_full_phi.algorithm, elnino_full_phi.elapsed + ); + + // 3. Regional Phi -- neutral + println!("\n--- Computing Phi: Neutral Regional Subsets ---"); + let regions = data::all_regions(); + let mut neutral_regional_phis = Vec::new(); + for (name, indices) in ®ions { + if indices.len() >= 2 { + let sub = data::extract_sub_tpm(neutral_tpm, indices); + let sub_ctpm = to_consciousness_tpm(&sub); + match auto_compute_phi(&sub_ctpm, None, &budget) { + Ok(phi) => { + let idx_names: Vec<&str> = indices.iter().map(|&i| data::INDEX_NAMES[i]).collect(); + println!(" {} Phi = {:.6} ({})", name, phi.phi, idx_names.join(", ")); + neutral_regional_phis.push((name.to_string(), phi)); + } + Err(e) => { + println!(" {} Phi computation failed: {}", name, e); + } + } + } + } + + // 4. Regional Phi -- El Nino + println!("\n--- Computing Phi: El Nino Regional Subsets ---"); + let mut elnino_regional_phis = Vec::new(); + for (name, indices) in ®ions { + if indices.len() >= 2 { + let sub = data::extract_sub_tpm(elnino_tpm, indices); + let sub_ctpm = to_consciousness_tpm(&sub); + match auto_compute_phi(&sub_ctpm, None, &budget) { + Ok(phi) => { + println!(" {} Phi = {:.6}", name, phi.phi); + elnino_regional_phis.push((name.to_string(), phi)); + } + Err(e) => { + println!(" {} Phi computation failed: {}", name, e); + } + } + } + } + + // 5. Compare neutral vs El Nino + let elnino_increases_phi = elnino_full_phi.phi > neutral_full_phi.phi; + println!( + "\n El Nino Phi ({:.6}) {} Neutral Phi ({:.6})", + elnino_full_phi.phi, + if elnino_increases_phi { ">" } else { "<=" }, + neutral_full_phi.phi + ); + + // 6. Identify most integrated region + let pacific_phi = neutral_regional_phis + .iter() + .find(|(n, _)| n == "Pacific") + .map(|(_, p)| p.phi) + .unwrap_or(0.0); + let max_regional_phi = neutral_regional_phis + .iter() + .map(|(_, p)| p.phi) + .fold(0.0f64, f64::max); + let pacific_most_integrated = (pacific_phi - max_regional_phi).abs() < 1e-10 + && pacific_phi > 0.0; + + // 7. Causal emergence + println!("\n--- Causal Emergence Analysis (Neutral) ---"); + let emergence_engine = CausalEmergenceEngine::new(neutral_tpm.size.min(16)); + let neutral_emergence = emergence_engine + .compute_emergence(&neutral_ctpm, &budget) + .expect("Failed to compute causal emergence"); + println!( + " EI_micro = {:.4} bits, determinism = {:.4}, degeneracy = {:.4}", + neutral_emergence.ei_micro, neutral_emergence.determinism, neutral_emergence.degeneracy + ); + println!( + " Causal emergence = {:.4}, coarse-graining: {:?}", + neutral_emergence.causal_emergence, neutral_emergence.coarse_graining + ); + + // 8. SVD emergence + println!("\n--- SVD Emergence Analysis (Neutral) ---"); + let svd_engine = RsvdEmergenceEngine::default(); + let neutral_svd_emergence = svd_engine + .compute(&neutral_ctpm, &budget) + .expect("Failed to compute SVD emergence"); + println!( + " Effective rank = {}/{}, entropy = {:.4}, emergence = {:.4}", + neutral_svd_emergence.effective_rank, neutral_tpm.size, + neutral_svd_emergence.spectral_entropy, neutral_svd_emergence.emergence_index + ); + + // 9. Temporal analysis: monthly seasonal cycle + println!("\n--- Temporal Analysis: Seasonal Phi Cycle ---"); + let monthly_tpms = data::generate_monthly_tpms(neutral_data); + let mut monthly_phis = Vec::new(); + for (month, tpm) in &monthly_tpms { + let ctpm = to_consciousness_tpm(tpm); + match auto_compute_phi(&ctpm, None, &budget) { + Ok(phi) => { + println!(" {} Phi = {:.6}", month, phi.phi); + monthly_phis.push((month.clone(), phi.phi)); + } + Err(e) => { + println!(" {} Phi failed: {}", month, e); + monthly_phis.push((month.clone(), 0.0)); + } + } + } + + // 10. Null hypothesis testing + println!( + "\n--- Null Hypothesis Testing ({} shuffled correlations) ---", + null_samples + ); + let mut rng = ChaCha8Rng::seed_from_u64(42); + let mut null_phis = Vec::with_capacity(null_samples); + for i in 0..null_samples { + let null_tpm = data::generate_null_tpm(neutral_data, &mut rng); + let null_ctpm = to_consciousness_tpm(&null_tpm); + if let Ok(null_phi) = auto_compute_phi(&null_ctpm, None, &budget) { + null_phis.push(null_phi.phi); + } + if (i + 1) % 10 == 0 { + print!(" [{}/{}] ", i + 1, null_samples); + } + } + println!(); + + // Compute statistics + let null_mean = if null_phis.is_empty() { + 0.0 + } else { + null_phis.iter().sum::() / null_phis.len() as f64 + }; + let null_std = if null_phis.len() > 1 { + (null_phis + .iter() + .map(|&p| (p - null_mean).powi(2)) + .sum::() + / (null_phis.len() as f64 - 1.0)) + .sqrt() + } else { + 0.0 + }; + let z_score = if null_std > 1e-10 { + (neutral_full_phi.phi - null_mean) / null_std + } else { + 0.0 + }; + let p_value = if null_phis.is_empty() { + 1.0 + } else { + null_phis + .iter() + .filter(|&&p| p >= neutral_full_phi.phi) + .count() as f64 + / null_phis.len() as f64 + }; + + AnalysisResults { + neutral_full_phi, + elnino_full_phi, + neutral_regional_phis, + elnino_regional_phis, + neutral_emergence, + neutral_svd_emergence, + elnino_increases_phi, + pacific_most_integrated, + monthly_phis, + null_phis, + z_score, + p_value, + } +} diff --git a/examples/climate-consciousness/src/data.rs b/examples/climate-consciousness/src/data.rs new file mode 100644 index 00000000..241b54b8 --- /dev/null +++ b/examples/climate-consciousness/src/data.rs @@ -0,0 +1,274 @@ +//! Climate teleconnection data generation. +//! +//! Builds correlation matrices for 7 major climate indices based on known +//! teleconnection strengths. Generates neutral baseline and El Nino active +//! variants. + +use rand::Rng; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +/// Climate index identifiers. +pub const INDEX_ENSO: usize = 0; // El Nino Southern Oscillation (Nino3.4) +pub const INDEX_NAO: usize = 1; // North Atlantic Oscillation +pub const INDEX_PDO: usize = 2; // Pacific Decadal Oscillation +pub const INDEX_AMO: usize = 3; // Atlantic Multidecadal Oscillation +pub const INDEX_IOD: usize = 4; // Indian Ocean Dipole +pub const INDEX_SAM: usize = 5; // Southern Annular Mode +pub const INDEX_QBO: usize = 6; // Quasi-Biennial Oscillation + +pub const INDEX_NAMES: &[&str] = &["ENSO", "NAO", "PDO", "AMO", "IOD", "SAM", "QBO"]; +pub const N_INDICES: usize = 7; + +/// Regional subsets of climate indices. +pub const PACIFIC_INDICES: &[usize] = &[INDEX_ENSO, INDEX_PDO, INDEX_IOD]; +pub const ATLANTIC_INDICES: &[usize] = &[INDEX_NAO, INDEX_AMO]; +pub const POLAR_INDICES: &[usize] = &[INDEX_SAM, INDEX_QBO]; + +pub const REGION_NAMES: &[&str] = &["Pacific", "Atlantic", "Polar"]; + +pub fn all_regions() -> Vec<(&'static str, &'static [usize])> { + vec![ + (REGION_NAMES[0], PACIFIC_INDICES), + (REGION_NAMES[1], ATLANTIC_INDICES), + (REGION_NAMES[2], POLAR_INDICES), + ] +} + +/// Climate mode correlation data. +pub struct ClimateCorrelations { + pub n_indices: usize, + /// Flat row-major correlation matrix (symmetric, diagonal = 1.0). + pub correlations: Vec, + /// Variant label. + pub variant: String, +} + +/// Transition probability matrix for consciousness analysis. +pub struct TransitionMatrix { + pub size: usize, + pub data: Vec, +} + +/// Build the neutral baseline correlation matrix. +/// +/// Known teleconnection strengths (approximate, from literature): +/// - ENSO <-> IOD: strong (0.5-0.7) +/// - ENSO <-> PDO: moderate (0.3-0.5) +/// - NAO <-> AMO: moderate (0.2-0.4) +/// - QBO <-> ENSO: weak but real (0.1-0.2) +/// - SAM: mostly independent +pub fn build_neutral_correlations() -> ClimateCorrelations { + let n = N_INDICES; + let mut corr = vec![0.0f64; n * n]; + let mut rng = ChaCha8Rng::seed_from_u64(42); + + // Set diagonal to 1.0 (self-correlation) + for i in 0..n { + corr[i * n + i] = 1.0; + } + + // Define known teleconnection strengths (symmetric) + let connections: &[(usize, usize, f64, f64)] = &[ + // (index_a, index_b, min_corr, max_corr) + (INDEX_ENSO, INDEX_IOD, 0.50, 0.70), // Strong: ENSO-IOD coupling + (INDEX_ENSO, INDEX_PDO, 0.30, 0.50), // Moderate: Pacific basin coupling + (INDEX_NAO, INDEX_AMO, 0.20, 0.40), // Moderate: Atlantic coupling + (INDEX_QBO, INDEX_ENSO, 0.10, 0.20), // Weak: stratosphere-troposphere + (INDEX_PDO, INDEX_IOD, 0.10, 0.25), // Weak: Indo-Pacific coupling + (INDEX_ENSO, INDEX_NAO, 0.05, 0.15), // Weak: Pacific-Atlantic bridge + (INDEX_ENSO, INDEX_SAM, 0.05, 0.15), // Weak: tropical-polar link + (INDEX_AMO, INDEX_PDO, 0.05, 0.10), // Very weak: inter-basin + (INDEX_SAM, INDEX_QBO, 0.03, 0.08), // Very weak: polar-stratosphere + (INDEX_NAO, INDEX_QBO, 0.05, 0.12), // Weak: NAO-QBO link + (INDEX_SAM, INDEX_NAO, 0.02, 0.06), // Very weak: bipolar + (INDEX_IOD, INDEX_AMO, 0.02, 0.06), // Very weak: Indian-Atlantic + (INDEX_AMO, INDEX_SAM, 0.01, 0.04), // Negligible + (INDEX_IOD, INDEX_NAO, 0.01, 0.04), // Negligible + (INDEX_IOD, INDEX_SAM, 0.01, 0.03), // Negligible + (INDEX_PDO, INDEX_NAO, 0.02, 0.06), // Very weak + (INDEX_PDO, INDEX_SAM, 0.01, 0.04), // Negligible + (INDEX_QBO, INDEX_AMO, 0.02, 0.05), // Very weak + (INDEX_QBO, INDEX_PDO, 0.03, 0.08), // Very weak + (INDEX_QBO, INDEX_IOD, 0.02, 0.06), // Very weak + (INDEX_QBO, INDEX_SAM, 0.03, 0.08), // Very weak + ]; + + for &(a, b, min_c, max_c) in connections { + let c = rng.gen_range(min_c..max_c); + corr[a * n + b] = c; + corr[b * n + a] = c; + } + + ClimateCorrelations { + n_indices: n, + correlations: corr, + variant: "Neutral".into(), + } +} + +/// Build the El Nino active variant. +/// +/// During El Nino events, ENSO correlations strengthen by ~50%, +/// and the Pacific basin becomes more tightly coupled. +pub fn build_elnino_correlations() -> ClimateCorrelations { + let mut data = build_neutral_correlations(); + data.variant = "El Nino Active".into(); + let n = data.n_indices; + + // Boost all ENSO correlations by 50% + for j in 0..n { + if j != INDEX_ENSO { + let boosted = (data.correlations[INDEX_ENSO * n + j] * 1.5).min(0.95); + data.correlations[INDEX_ENSO * n + j] = boosted; + data.correlations[j * n + INDEX_ENSO] = boosted; + } + } + + // Also boost intra-Pacific correlations + let pacific_boost: &[(usize, usize)] = &[ + (INDEX_PDO, INDEX_IOD), + ]; + for &(a, b) in pacific_boost { + let boosted = (data.correlations[a * n + b] * 1.3).min(0.90); + data.correlations[a * n + b] = boosted; + data.correlations[b * n + a] = boosted; + } + + data +} + +/// Convert a correlation matrix to a transition probability matrix. +/// +/// Method (same approach as the CMB example): +/// 1. Use absolute correlations as coupling strengths. +/// 2. Apply a sharpness exponent (alpha = 2.0) to emphasize strong connections. +/// 3. Row-normalize to get transition probabilities. +pub fn correlation_to_tpm(data: &ClimateCorrelations) -> TransitionMatrix { + let n = data.n_indices; + let alpha = 2.0; + let mut tpm = vec![0.0f64; n * n]; + + for i in 0..n { + let mut row_sum = 0.0f64; + for j in 0..n { + let c = data.correlations[i * n + j].abs(); + let val = c.powf(alpha); + tpm[i * n + j] = val; + row_sum += val; + } + // Row-normalize + if row_sum > 1e-30 { + for j in 0..n { + tpm[i * n + j] /= row_sum; + } + } + } + + TransitionMatrix { size: n, data: tpm } +} + +/// Extract a sub-TPM for a subset of climate indices. +pub fn extract_sub_tpm(tpm: &TransitionMatrix, indices: &[usize]) -> TransitionMatrix { + let n = indices.len(); + let mut sub = vec![0.0f64; n * n]; + for (si, &ii) in indices.iter().enumerate() { + let row_sum: f64 = indices.iter().map(|&ij| tpm.data[ii * tpm.size + ij]).sum(); + for (sj, &ij) in indices.iter().enumerate() { + sub[si * n + sj] = tpm.data[ii * tpm.size + ij] / row_sum.max(1e-30); + } + } + TransitionMatrix { size: n, data: sub } +} + +/// Generate a null-model correlation matrix by shuffling off-diagonal entries. +pub fn generate_null_tpm(data: &ClimateCorrelations, rng: &mut impl rand::Rng) -> TransitionMatrix { + let n = data.n_indices; + let mut corr = data.correlations.clone(); + + // Collect upper-triangular off-diagonal values + let mut upper: Vec = Vec::new(); + for i in 0..n { + for j in (i + 1)..n { + upper.push(corr[i * n + j]); + } + } + + // Shuffle + for k in (1..upper.len()).rev() { + let swap_idx = rng.gen_range(0..=k); + upper.swap(k, swap_idx); + } + + // Put back (symmetric) + let mut idx = 0; + for i in 0..n { + for j in (i + 1)..n { + corr[i * n + j] = upper[idx]; + corr[j * n + i] = upper[idx]; + idx += 1; + } + } + + let shuffled = ClimateCorrelations { + n_indices: n, + correlations: corr, + variant: "Null".into(), + }; + + correlation_to_tpm(&shuffled) +} + +/// Generate monthly seasonal variation of correlations. +/// +/// ENSO peaks in boreal winter (DJF), weakens in spring (MAM). +/// NAO is strongest in winter, weaker in summer. +pub fn generate_monthly_tpms(base: &ClimateCorrelations) -> Vec<(String, TransitionMatrix)> { + let months = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ]; + let n = base.n_indices; + + // ENSO seasonal modulation: peaks in DJF (months 0,1,11) + let enso_seasonal = [1.3, 1.2, 0.9, 0.7, 0.6, 0.5, 0.5, 0.6, 0.7, 0.9, 1.1, 1.3]; + // NAO seasonal modulation: peaks in DJF + let nao_seasonal = [1.4, 1.3, 1.0, 0.7, 0.5, 0.4, 0.4, 0.5, 0.7, 0.9, 1.1, 1.3]; + + months + .iter() + .enumerate() + .map(|(m, &name)| { + let mut corr = base.correlations.clone(); + + // Modulate ENSO connections + for j in 0..n { + if j != INDEX_ENSO { + corr[INDEX_ENSO * n + j] *= enso_seasonal[m]; + corr[j * n + INDEX_ENSO] *= enso_seasonal[m]; + // Clamp to valid correlation range + corr[INDEX_ENSO * n + j] = corr[INDEX_ENSO * n + j].min(0.95); + corr[j * n + INDEX_ENSO] = corr[j * n + INDEX_ENSO].min(0.95); + } + } + + // Modulate NAO connections + for j in 0..n { + if j != INDEX_NAO { + corr[INDEX_NAO * n + j] *= nao_seasonal[m]; + corr[j * n + INDEX_NAO] *= nao_seasonal[m]; + corr[INDEX_NAO * n + j] = corr[INDEX_NAO * n + j].min(0.95); + corr[j * n + INDEX_NAO] = corr[j * n + INDEX_NAO].min(0.95); + } + } + + let monthly = ClimateCorrelations { + n_indices: n, + correlations: corr, + variant: format!("{} modulated", name), + }; + + (name.to_string(), correlation_to_tpm(&monthly)) + }) + .collect() +} diff --git a/examples/climate-consciousness/src/main.rs b/examples/climate-consciousness/src/main.rs new file mode 100644 index 00000000..23fa15ba --- /dev/null +++ b/examples/climate-consciousness/src/main.rs @@ -0,0 +1,89 @@ +//! Climate Teleconnection Consciousness Explorer +//! +//! Applies IIT Phi to climate mode interactions to study how large-scale +//! climate oscillations form integrated information systems. Compares +//! neutral vs El Nino active conditions. + +mod analysis; +mod data; +mod report; + +fn main() { + println!("+==========================================================+"); + println!("| Climate Teleconnection Consciousness Explorer |"); + println!("| IIT 4.0 Phi Analysis of Climate Mode Interactions |"); + println!("+==========================================================+"); + + // Parse CLI args + let args: Vec = std::env::args().collect(); + let null_samples = parse_arg(&args, "--null-samples", 50usize); + let output = parse_str_arg(&args, "--output", "climate_report.svg"); + + println!("\nConfiguration:"); + println!(" Climate indices: 7 (ENSO, NAO, PDO, AMO, IOD, SAM, QBO)"); + println!(" Null samples: {}", null_samples); + println!(" Output: {}", output); + + // Step 1: Build climate correlation data + println!("\n=== Step 1: Building Climate Mode Correlation Data ==="); + let neutral = data::build_neutral_correlations(); + let elnino = data::build_elnino_correlations(); + println!(" Neutral baseline: {} climate indices", neutral.n_indices); + println!(" El Nino active: {} climate indices", elnino.n_indices); + + // Step 2: Construct TPMs + println!("\n=== Step 2: Constructing Transition Probability Matrices ==="); + let neutral_tpm = data::correlation_to_tpm(&neutral); + let elnino_tpm = data::correlation_to_tpm(&elnino); + println!(" Neutral TPM: {}x{}", neutral_tpm.size, neutral_tpm.size); + println!(" El Nino TPM: {}x{}", elnino_tpm.size, elnino_tpm.size); + + // Step 3: Run analysis + println!("\n=== Step 3: Consciousness Analysis ==="); + let results = analysis::run_analysis( + &neutral, &neutral_tpm, + &elnino, &elnino_tpm, + null_samples, + ); + + // Step 4: Print report + println!("\n=== Step 4: Results ==="); + report::print_summary(&results); + + // Step 5: Generate SVG + let svg = report::generate_svg(&results, &neutral); + std::fs::write(output, &svg).expect("Failed to write SVG report"); + println!( + "\nSVG report saved to: {}", + parse_str_arg(&args, "--output", "climate_report.svg") + ); + + // Final verdict + println!("\n+==========================================================+"); + if results.elnino_increases_phi { + println!("| RESULT: El Nino INCREASES integrated information in |"); + println!("| the climate system -- teleconnections strengthen. |"); + } else { + println!("| RESULT: El Nino does NOT increase integrated |"); + println!("| information -- system remains loosely coupled. |"); + } + if results.pacific_most_integrated { + println!("| The Pacific basin (ENSO, PDO, IOD) is the most |"); + println!("| integrated climate subsystem. |"); + } + println!("+==========================================================+"); +} + +fn parse_arg(args: &[String], name: &str, default: T) -> T { + args.windows(2) + .find(|w| w[0] == name) + .and_then(|w| w[1].parse().ok()) + .unwrap_or(default) +} + +fn parse_str_arg<'a>(args: &'a [String], name: &str, default: &'a str) -> &'a str { + args.windows(2) + .find(|w| w[0] == name) + .map(|w| w[1].as_str()) + .unwrap_or(default) +} diff --git a/examples/climate-consciousness/src/report.rs b/examples/climate-consciousness/src/report.rs new file mode 100644 index 00000000..d8c1c8c4 --- /dev/null +++ b/examples/climate-consciousness/src/report.rs @@ -0,0 +1,485 @@ +//! Report generation: text summary and SVG visualization for climate modes. + +use crate::analysis::AnalysisResults; +use crate::data::{self, ClimateCorrelations}; + +/// Print a text summary of the analysis results. +pub fn print_summary(results: &AnalysisResults) { + println!("\n--- IIT Phi: Neutral vs El Nino ---"); + println!( + "Neutral full Phi: {:.6} ({})", + results.neutral_full_phi.phi, results.neutral_full_phi.algorithm + ); + println!( + "El Nino full Phi: {:.6} ({})", + results.elnino_full_phi.phi, results.elnino_full_phi.algorithm + ); + + println!("\n--- Regional Phi (Neutral) ---"); + for (name, phi) in &results.neutral_regional_phis { + println!("{:20} Phi = {:.6}", name, phi.phi); + } + + println!("\n--- Regional Phi (El Nino) ---"); + for (name, phi) in &results.elnino_regional_phis { + println!("{:20} Phi = {:.6}", name, phi.phi); + } + + println!("\n--- Seasonal Phi Cycle ---"); + let max_monthly = results + .monthly_phis + .iter() + .map(|(_, p)| *p) + .fold(0.0f64, f64::max) + .max(1e-10); + for (month, phi) in &results.monthly_phis { + let bar_len = (phi / max_monthly * 30.0) as usize; + println!(" {:3} Phi={:.4} {}", month, phi, "|".repeat(bar_len)); + } + + println!("\n--- Causal Emergence (Neutral) ---"); + println!( + "EI (micro): {:.4} bits", + results.neutral_emergence.ei_micro + ); + println!( + "Causal emergence: {:.4}", + results.neutral_emergence.causal_emergence + ); + println!("Determinism: {:.4}", results.neutral_emergence.determinism); + println!("Degeneracy: {:.4}", results.neutral_emergence.degeneracy); + + println!("\n--- SVD Emergence (Neutral) ---"); + println!( + "Effective rank: {}/7", + results.neutral_svd_emergence.effective_rank + ); + println!( + "Spectral entropy: {:.4}", + results.neutral_svd_emergence.spectral_entropy + ); + println!( + "Emergence index: {:.4}", + results.neutral_svd_emergence.emergence_index + ); + + println!("\n--- Null Hypothesis Testing ---"); + let null_mean = if results.null_phis.is_empty() { + 0.0 + } else { + results.null_phis.iter().sum::() / results.null_phis.len() as f64 + }; + println!("Phi (observed): {:.6}", results.neutral_full_phi.phi); + println!( + "Phi (null mean): {:.6} ({} samples)", + null_mean, + results.null_phis.len() + ); + println!("z-score: {:.2}", results.z_score); + println!("p-value: {:.4}", results.p_value); + + println!("\n--- Key Findings ---"); + println!( + "El Nino > Neutral: {}", + if results.elnino_increases_phi { "YES" } else { "NO" } + ); + println!( + "Pacific most integrated: {}", + if results.pacific_most_integrated { "YES" } else { "NO" } + ); +} + +/// Generate a self-contained SVG report with climate mode connection diagram. +pub fn generate_svg(results: &AnalysisResults, data: &ClimateCorrelations) -> String { + let mut svg = String::with_capacity(20_000); + + svg.push_str( + r#" + + + +Climate Teleconnection Consciousness Report +IIT 4.0 Phi Analysis of Climate Mode Interactions +"#, + ); + + // Panel 1: Climate mode connection diagram (y=100, h=350) + svg.push_str(&render_connection_diagram(data, 50, 100, 500, 350)); + + // Panel 2: Regional Phi comparison (y=100, x=600, h=350) + svg.push_str(&render_phi_comparison(results, 620, 100, 530, 350)); + + // Panel 3: Seasonal Phi cycle (y=500, h=250) + svg.push_str(&render_seasonal_cycle(&results.monthly_phis, 50, 510, 1100, 250)); + + // Panel 4: Null distribution (y=810, h=250) + svg.push_str(&render_null_distribution( + &results.null_phis, + results.neutral_full_phi.phi, + 50, + 810, + 1100, + 250, + )); + + // Panel 5: Summary stats (y=1110) + svg.push_str(&render_summary_stats(results, 50, 1120)); + + svg.push_str("\n"); + svg +} + +/// Render the climate mode connection diagram. +fn render_connection_diagram( + data: &ClimateCorrelations, + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Climate Mode Teleconnections (Neutral)\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + let n = data.n_indices; + let cx = w as f64 / 2.0; + let cy = h as f64 / 2.0; + let radius = (w.min(h) as f64 / 2.0) - 45.0; + + // Circular layout + let mut positions = vec![(0.0f64, 0.0f64); n]; + for i in 0..n { + let angle = 2.0 * std::f64::consts::PI * i as f64 / n as f64 + - std::f64::consts::FRAC_PI_2; + positions[i] = (cx + radius * angle.cos(), cy + radius * angle.sin()); + } + + // Draw edges (correlation strengths) + for i in 0..n { + for j in (i + 1)..n { + let c = data.correlations[i * n + j]; + if c.abs() > 0.03 { + let (x1, y1) = positions[i]; + let (x2, y2) = positions[j]; + let class = if c.abs() > 0.3 { "edge-strong" } else { "edge" }; + let width = (c.abs() * 5.0).max(0.5).min(3.0); + s.push_str(&format!( + "\n", + x1, y1, x2, y2, class, width + )); + } + } + } + + // Region colors for each index + let node_classes = [ + "node-pacific", // ENSO + "node-atlantic", // NAO + "node-pacific", // PDO + "node-atlantic", // AMO + "node-pacific", // IOD + "node-polar", // SAM + "node-polar", // QBO + ]; + + // Draw nodes + for i in 0..n { + let (px, py) = positions[i]; + s.push_str(&format!( + "\n", + px, py, node_classes[i] + )); + s.push_str(&format!( + "{}\n", + px, py, data::INDEX_NAMES[i] + )); + } + + // Legend + let legend_y = h - 30; + let region_info = [("Pacific", "node-pacific"), ("Atlantic", "node-atlantic"), ("Polar", "node-polar")]; + for (idx, (name, class)) in region_info.iter().enumerate() { + let lx = 10 + idx as i32 * 140; + s.push_str(&format!( + "\n", + lx, legend_y, class + )); + s.push_str(&format!( + "{}\n", + lx + 10, legend_y, name + )); + } + + s.push_str("\n"); + s +} + +/// Render regional Phi comparison: neutral vs El Nino. +fn render_phi_comparison(results: &AnalysisResults, x: i32, y: i32, w: i32, h: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Regional Phi: Neutral vs El Nino\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + let mut all_phis: Vec = Vec::new(); + all_phis.push(results.neutral_full_phi.phi); + all_phis.push(results.elnino_full_phi.phi); + for (_, p) in &results.neutral_regional_phis { + all_phis.push(p.phi); + } + for (_, p) in &results.elnino_regional_phis { + all_phis.push(p.phi); + } + let max_phi = all_phis.iter().cloned().fold(0.0f64, f64::max).max(1e-10); + + let n_groups = results.neutral_regional_phis.len() + 1; + let group_w = (w - 40) as f64 / n_groups as f64; + let bar_w = group_w * 0.35; + let chart_h = (h - 60) as f64; + + for (idx, (name, neutral_phi)) in results.neutral_regional_phis.iter().enumerate() { + let gx = 20.0 + idx as f64 * group_w; + + let bh = (neutral_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx, h - 30 - bh, bar_w, bh + )); + + if let Some((_, elnino_phi)) = results.elnino_regional_phis.iter().find(|(n, _)| n == name) { + let ebh = (elnino_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx + bar_w + 2.0, h - 30 - ebh, bar_w, ebh + )); + } + + s.push_str(&format!( + "{}\n", + gx + bar_w, h - 15, name + )); + } + + // Full system group + let gx = 20.0 + results.neutral_regional_phis.len() as f64 * group_w; + let bh = (results.neutral_full_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx, h - 30 - bh, bar_w, bh + )); + let ebh = (results.elnino_full_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx + bar_w + 2.0, h - 30 - ebh, bar_w, ebh + )); + s.push_str(&format!( + "Full\n", + gx + bar_w, h - 15 + )); + + // Legend + s.push_str(&format!( + "\n", w - 150 + )); + s.push_str(&format!( + "Neutral\n", w - 135 + )); + s.push_str(&format!( + "\n", w - 150 + )); + s.push_str(&format!( + "El Nino\n", w - 135 + )); + + s.push_str("\n"); + s +} + +/// Render the seasonal Phi cycle as a bar chart. +fn render_seasonal_cycle( + monthly: &[(String, f64)], + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Seasonal Phi Cycle (12 months)\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + if monthly.is_empty() { + s.push_str("\n"); + return s; + } + + let max_phi = monthly + .iter() + .map(|(_, p)| *p) + .fold(0.0f64, f64::max) + .max(1e-10); + let bar_w = (w - 40) as f64 / monthly.len() as f64; + let chart_h = (h - 50) as f64; + + for (i, (month, phi)) in monthly.iter().enumerate() { + let bx = 20.0 + i as f64 * bar_w; + let bh = (phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + bx, h - bh - 25, (bar_w - 3.0).max(1.0), bh + )); + s.push_str(&format!( + "{}\n", + bx + bar_w / 2.0, h - 10, month + )); + } + + s.push_str("\n"); + s +} + +/// Render the null distribution histogram. +fn render_null_distribution( + null_phis: &[f64], + observed: f64, + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Null Distribution (Shuffled Correlations) vs Observed Phi\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + if null_phis.is_empty() { + s.push_str(&format!( + "No null samples\n", + w / 2, h / 2 + )); + s.push_str("\n"); + return s; + } + + let n_hist_bins = 25usize; + let phi_min = null_phis.iter().cloned().fold(f64::INFINITY, f64::min).min(observed) * 0.9; + let phi_max = null_phis.iter().cloned().fold(0.0f64, f64::max).max(observed) * 1.1; + let range = (phi_max - phi_min).max(1e-10); + let bin_width = range / n_hist_bins as f64; + + let mut hist = vec![0u32; n_hist_bins]; + for &p in null_phis { + let bin = ((p - phi_min) / bin_width).floor() as usize; + if bin < n_hist_bins { + hist[bin] += 1; + } + } + let max_count = *hist.iter().max().unwrap_or(&1); + + let bar_w = w as f64 / n_hist_bins as f64; + for (i, &count) in hist.iter().enumerate() { + let bar_h = (count as f64 / max_count as f64 * (h - 40) as f64) as i32; + s.push_str(&format!( + "\n", + i as f64 * bar_w, h - bar_h - 20, bar_w - 1.0, bar_h + )); + } + + let obs_x = ((observed - phi_min) / range * w as f64) as i32; + s.push_str(&format!( + "\n", + obs_x, obs_x, h - 20 + )); + s.push_str(&format!( + "Observed\n", + obs_x, h - 5 + )); + + s.push_str("\n"); + s +} + +/// Render summary statistics text. +fn render_summary_stats(results: &AnalysisResults, x: i32, y: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str("Summary Statistics\n"); + + let null_mean = if results.null_phis.is_empty() { + 0.0 + } else { + results.null_phis.iter().sum::() / results.null_phis.len() as f64 + }; + + let lines = vec![ + format!("Neutral Full Phi: {:.6} (n=7)", results.neutral_full_phi.phi), + format!("El Nino Full Phi: {:.6} (n=7)", results.elnino_full_phi.phi), + format!( + "Null Mean Phi: {:.6} ({} samples)", + null_mean, results.null_phis.len() + ), + format!("z-score: {:.3}", results.z_score), + format!("p-value: {:.4}", results.p_value), + format!("EI (micro): {:.4} bits", results.neutral_emergence.ei_micro), + format!("Causal emergence: {:.4}", results.neutral_emergence.causal_emergence), + format!( + "SVD Eff. Rank: {}/7", + results.neutral_svd_emergence.effective_rank + ), + format!( + "Emergence Index: {:.4}", + results.neutral_svd_emergence.emergence_index + ), + format!( + "El Nino > Neutral: {}", + if results.elnino_increases_phi { "YES" } else { "NO" } + ), + format!( + "Pacific top region: {}", + if results.pacific_most_integrated { "YES" } else { "NO" } + ), + ]; + + for (i, line) in lines.iter().enumerate() { + s.push_str(&format!( + "{}\n", + 20 + i * 18, line + )); + } + + s.push_str("\n"); + s +} diff --git a/examples/ecosystem-consciousness/Cargo.toml b/examples/ecosystem-consciousness/Cargo.toml new file mode 100644 index 00000000..2eebd850 --- /dev/null +++ b/examples/ecosystem-consciousness/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ecosystem-consciousness" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Ecosystem food web consciousness analysis using IIT Phi" +publish = false + +[[bin]] +name = "ecosystem-consciousness" +path = "src/main.rs" + +[dependencies] +ruvector-consciousness = { path = "../../crates/ruvector-consciousness", default-features = false, features = ["phi", "emergence", "collapse"] } +rand = "0.8" +rand_chacha = "0.3" diff --git a/examples/ecosystem-consciousness/RESEARCH.md b/examples/ecosystem-consciousness/RESEARCH.md new file mode 100644 index 00000000..2a0b7f91 --- /dev/null +++ b/examples/ecosystem-consciousness/RESEARCH.md @@ -0,0 +1,109 @@ +# Ecosystem Consciousness: IIT Phi as a Food Web Integration Metric + +## Motivation + +Integrated Information Theory (IIT) quantifies how much a system is +"more than the sum of its parts" through the measure Phi. Food webs +share a structural analogy: a resilient ecosystem cannot be decomposed +into independent sub-networks without losing emergent function. This +example explores whether IIT Phi correlates with ecological resilience. + +## Food Web Ecology Background + +### Trophic Structure + +Ecosystems organize into trophic levels: + +1. **Producers** -- autotrophs (plants, algae, coral) that fix energy +2. **Primary consumers** -- herbivores feeding on producers +3. **Secondary consumers** -- predators feeding on herbivores +4. **Decomposers** -- organisms recycling dead matter back to producers +5. **Apex predators** -- top-level predators with no natural enemies + +### Resilience and Redundancy + +Ecological resilience depends on: + +- **Functional redundancy**: multiple species filling similar roles +- **Response diversity**: different species respond differently to + perturbation +- **Connectivity**: dense interaction networks buffer against single + species loss +- **Keystone species**: removal causes disproportionate collapse + +## IIT as a Resilience Metric + +### TPM Construction + +We model each species as a "state" and construct the transition +probability matrix from energy flow weights: + + TPM[i][j] = P(energy flows from species j to species i) + +Row-normalization ensures each row sums to 1, giving a proper +stochastic matrix. + +### Phi and Ecosystem Integration + +- **High Phi**: the food web cannot be split into independent + sub-networks -- every partition loses significant information + about the whole +- **Low Phi**: the ecosystem decomposes into weakly connected + modules -- removing one module barely affects the rest + +### Species Contribution + +We define the "Phi contribution" of species k as: + + C(k) = Phi(full) - Phi(without k) + +Species with high C(k) are "consciousness keystones" -- their +removal most reduces the integrated information of the web. + +## Expected Results + +### Tropical Rainforest (12 species) + +- Dense cross-trophic connections and nutrient cycling +- Many redundant pathways between trophic levels +- **Prediction**: HIGH Phi, relatively uniform contributions + +### Agricultural Monoculture (8 species) + +- Sparse, linear food chains +- Single crop dominates energy flow +- **Prediction**: LOW Phi, highly concentrated contributions + +### Coral Reef (10 species) + +- Moderate connectivity centered on coral as structural keystone +- Removing coral should cause largest Phi drop +- **Prediction**: MODERATE Phi, coral has disproportionate contribution + +## Causal Emergence in Ecosystems + +Beyond Phi, we compute causal emergence to ask: does the ecosystem +have a "macro-level" description (e.g., trophic levels) that is more +informative than the species-level description? + +- High causal emergence suggests natural macro-level organization + (trophic levels are real causal entities, not just labels) +- Low causal emergence suggests species-level dynamics dominate + +## Limitations + +1. **Synthetic data**: real food webs have stochastic, seasonal dynamics +2. **Static TPM**: IIT assumes a fixed transition structure +3. **Small system sizes**: Phi is computationally expensive (exponential + in system size), limiting analysis to ~15 species +4. **Directionality**: IIT Phi is defined for mechanisms, not flows -- + the food web analogy is suggestive, not rigorous + +## References + +- Tononi, G. (2008). Consciousness as Integrated Information: a + Provisional Manifesto. Biological Bulletin, 215(3). +- May, R.M. (1973). Stability and Complexity in Model Ecosystems. +- Dunne, J.A. et al. (2002). Food-web structure and network theory. +- Hoel, E.P. et al. (2013). Quantifying causal emergence shows that + macro can beat micro. diff --git a/examples/ecosystem-consciousness/src/analysis.rs b/examples/ecosystem-consciousness/src/analysis.rs new file mode 100644 index 00000000..54b34607 --- /dev/null +++ b/examples/ecosystem-consciousness/src/analysis.rs @@ -0,0 +1,152 @@ +//! Consciousness analysis for ecosystem food webs. +//! +//! Computes IIT Phi for each ecosystem, measures resilience by removing +//! individual species, and performs causal emergence analysis. + +use ruvector_consciousness::emergence::CausalEmergenceEngine; +use ruvector_consciousness::phi::auto_compute_phi; +use ruvector_consciousness::rsvd_emergence::RsvdEmergenceEngine; +use ruvector_consciousness::traits::EmergenceEngine; +use ruvector_consciousness::types::{ + ComputeBudget, EmergenceResult, + TransitionMatrix as ConsciousnessTPM, +}; +use ruvector_consciousness::rsvd_emergence::RsvdEmergenceResult; + +use crate::data::Ecosystem; + +/// Results for a single ecosystem analysis. +pub struct EcosystemResult { + pub name: String, + pub n_species: usize, + pub full_phi: f64, + pub algorithm: String, + /// (species_index, species_name, phi_without, phi_contribution) + pub species_contributions: Vec<(usize, String, f64, f64)>, + pub emergence: EmergenceResult, + pub svd_emergence: RsvdEmergenceResult, + /// Trophic level colors for each species + pub trophic_colors: Vec, + /// Species names + pub species_names: Vec, +} + +/// Convert flat TPM data to consciousness crate format. +fn to_consciousness_tpm(data: &[f64], n: usize) -> ConsciousnessTPM { + ConsciousnessTPM::new(n, data.to_vec()) +} + +/// Run the full analysis pipeline on all ecosystems. +pub fn run_ecosystem_analysis(ecosystems: &[Ecosystem]) -> Vec { + let budget = ComputeBudget::default(); + let mut results = Vec::with_capacity(ecosystems.len()); + + for eco in ecosystems { + println!("\n--- Analyzing: {} ({} species) ---", eco.name, eco.n()); + let n = eco.n(); + let ctpm = to_consciousness_tpm(&eco.tpm, n); + + // 1. Full system Phi + let phi_result = auto_compute_phi(&ctpm, None, &budget) + .expect("Failed to compute Phi"); + let full_phi = phi_result.phi; + let algorithm = format!("{}", phi_result.algorithm); + println!( + " Full Phi = {:.6} (algorithm: {}, elapsed: {:?})", + full_phi, algorithm, phi_result.elapsed + ); + + // 2. Species contribution analysis (resilience) + println!(" Computing species contributions..."); + let mut contributions = Vec::with_capacity(n); + for i in 0..n { + let reduced_tpm = eco.tpm_without_species(i); + let reduced_ctpm = to_consciousness_tpm(&reduced_tpm, n); + let reduced_phi = match auto_compute_phi(&reduced_ctpm, None, &budget) { + Ok(r) => r.phi, + Err(_) => 0.0, + }; + let contribution = full_phi - reduced_phi; + contributions.push(( + i, + eco.species[i].name.clone(), + reduced_phi, + contribution, + )); + println!( + " Remove {:20} -> Phi = {:.6} (contribution: {:+.6})", + eco.species[i].name, reduced_phi, contribution + ); + } + + // Sort by contribution (highest first) + contributions.sort_by(|a, b| { + b.3.partial_cmp(&a.3).unwrap_or(std::cmp::Ordering::Equal) + }); + + // 3. Causal emergence + println!(" Computing causal emergence..."); + let emergence_engine = CausalEmergenceEngine::new(n.min(16)); + let emergence = emergence_engine + .compute_emergence(&ctpm, &budget) + .expect("Failed to compute emergence"); + println!( + " EI_micro = {:.4}, determinism = {:.4}, degeneracy = {:.4}", + emergence.ei_micro, emergence.determinism, emergence.degeneracy + ); + println!( + " Causal emergence = {:.4} (EI_macro = {:.4})", + emergence.causal_emergence, emergence.ei_macro + ); + + // 4. SVD emergence + println!(" Computing SVD emergence..."); + let svd_engine = RsvdEmergenceEngine::default(); + let svd_emergence = svd_engine + .compute(&ctpm, &budget) + .expect("Failed to compute SVD emergence"); + println!( + " Effective rank = {}/{}, emergence index = {:.4}", + svd_emergence.effective_rank, n, svd_emergence.emergence_index + ); + + let trophic_colors: Vec = eco + .species + .iter() + .map(|s| s.trophic_level.color().to_string()) + .collect(); + let species_names: Vec = eco + .species + .iter() + .map(|s| s.name.clone()) + .collect(); + + results.push(EcosystemResult { + name: eco.name.clone(), + n_species: n, + full_phi, + algorithm, + species_contributions: contributions, + emergence, + svd_emergence, + trophic_colors, + species_names, + }); + } + + // Cross-ecosystem comparison + println!("\n--- Cross-Ecosystem Comparison ---"); + for r in &results { + let top = r + .species_contributions + .first() + .map(|(_, name, _, c)| format!("{} ({:+.4})", name, c)) + .unwrap_or_default(); + println!( + " {:30} Phi = {:.6} Top contributor: {}", + r.name, r.full_phi, top + ); + } + + results +} diff --git a/examples/ecosystem-consciousness/src/data.rs b/examples/ecosystem-consciousness/src/data.rs new file mode 100644 index 00000000..d199416c --- /dev/null +++ b/examples/ecosystem-consciousness/src/data.rs @@ -0,0 +1,345 @@ +//! Synthetic food web data generation for three ecosystem types. +//! +//! Each ecosystem is represented as a directed energy-flow graph where +//! edge weights encode predation/energy transfer probabilities. The +//! adjacency matrix is row-normalized to produce a TPM suitable for +//! IIT Phi computation. + +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; +use rand::Rng; + +/// A single species in the food web. +#[derive(Clone, Debug)] +pub struct Species { + pub name: String, + pub trophic_level: TrophicLevel, +} + +/// Trophic classification for coloring and grouping. +#[derive(Clone, Debug, PartialEq)] +pub enum TrophicLevel { + Producer, + PrimaryConsumer, + SecondaryConsumer, + Decomposer, + Apex, +} + +impl TrophicLevel { + pub fn color(&self) -> &'static str { + match self { + TrophicLevel::Producer => "#27ae60", + TrophicLevel::PrimaryConsumer => "#f1c40f", + TrophicLevel::SecondaryConsumer => "#e67e22", + TrophicLevel::Decomposer => "#8e44ad", + TrophicLevel::Apex => "#e74c3c", + } + } + + #[allow(dead_code)] + pub fn label(&self) -> &'static str { + match self { + TrophicLevel::Producer => "Producer", + TrophicLevel::PrimaryConsumer => "Primary Consumer", + TrophicLevel::SecondaryConsumer => "Secondary Consumer", + TrophicLevel::Decomposer => "Decomposer", + TrophicLevel::Apex => "Apex", + } + } +} + +/// A complete ecosystem food web. +pub struct Ecosystem { + pub name: String, + pub species: Vec, + /// Row-major adjacency matrix (energy flow weights, not yet normalized). + pub adjacency: Vec, + /// Row-normalized TPM for IIT analysis. + pub tpm: Vec, +} + +impl Ecosystem { + pub fn n(&self) -> usize { + self.species.len() + } + + pub fn connection_count(&self) -> usize { + self.adjacency.iter().filter(|&&w| w > 1e-10).count() + } + + /// Build a TPM with one species removed (row/column zeroed, renormalized). + pub fn tpm_without_species(&self, remove_idx: usize) -> Vec { + let n = self.n(); + let mut tpm = self.adjacency.clone(); + // Zero out the removed species' row and column + for j in 0..n { + tpm[remove_idx * n + j] = 0.0; + tpm[j * n + remove_idx] = 0.0; + } + // Self-loop for removed species to keep matrix stochastic + tpm[remove_idx * n + remove_idx] = 1.0; + // Row-normalize remaining rows + row_normalize(&mut tpm, n); + tpm + } +} + +/// Row-normalize a flat n x n matrix in place. +fn row_normalize(data: &mut [f64], n: usize) { + for i in 0..n { + let row_sum: f64 = (0..n).map(|j| data[i * n + j]).sum(); + if row_sum > 1e-30 { + for j in 0..n { + data[i * n + j] /= row_sum; + } + } else { + // Uniform distribution for isolated nodes + for j in 0..n { + data[i * n + j] = 1.0 / n as f64; + } + } + } +} + +/// Generate all three ecosystem food webs. +pub fn generate_all_ecosystems() -> Vec { + vec![ + generate_tropical_rainforest(), + generate_agricultural_monoculture(), + generate_coral_reef(), + ] +} + +/// Tropical rainforest: 12 species, high connectivity, many redundant pathways. +/// Expected: HIGH Phi (deeply integrated). +fn generate_tropical_rainforest() -> Ecosystem { + let species = vec![ + // Producers (0-2) + Species { name: "Canopy Tree".into(), trophic_level: TrophicLevel::Producer }, + Species { name: "Understory Shrub".into(), trophic_level: TrophicLevel::Producer }, + Species { name: "Epiphyte".into(), trophic_level: TrophicLevel::Producer }, + // Primary consumers (3-5) + Species { name: "Leaf Insect".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + Species { name: "Fruit Bird".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + Species { name: "Herbivore Mammal".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + // Secondary consumers (6-8) + Species { name: "Snake".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + Species { name: "Raptor".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + Species { name: "Wild Cat".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + // Decomposers (9-11) + Species { name: "Fungi".into(), trophic_level: TrophicLevel::Decomposer }, + Species { name: "Bacteria".into(), trophic_level: TrophicLevel::Decomposer }, + Species { name: "Earthworm".into(), trophic_level: TrophicLevel::Decomposer }, + ]; + let n = species.len(); + let mut rng = ChaCha8Rng::seed_from_u64(100); + + // Dense energy flow matrix with many cross-trophic connections + let mut adj = vec![0.0f64; n * n]; + + // Producers support each other through shared soil nutrients (weak) + set_symmetric(&mut adj, n, 0, 1, 0.15 + rng.gen::() * 0.05); + set_symmetric(&mut adj, n, 0, 2, 0.10 + rng.gen::() * 0.05); + set_symmetric(&mut adj, n, 1, 2, 0.12 + rng.gen::() * 0.05); + + // Producers -> Primary consumers (strong, multiple pathways) + for prod in 0..3 { + for herb in 3..6 { + adj[herb * n + prod] = 0.3 + rng.gen::() * 0.2; + // Nutrient recycling back + adj[prod * n + herb] = 0.02 + rng.gen::() * 0.02; + } + } + + // Primary -> Secondary consumers (strong) + for herb in 3..6 { + for pred in 6..9 { + adj[pred * n + herb] = 0.25 + rng.gen::() * 0.15; + } + } + + // Cross-predation among secondary consumers + set_edge(&mut adj, n, 6, 7, 0.05 + rng.gen::() * 0.03); + set_edge(&mut adj, n, 7, 8, 0.04 + rng.gen::() * 0.03); + set_edge(&mut adj, n, 8, 6, 0.03 + rng.gen::() * 0.02); + + // Decomposers receive from all levels (death/waste) + for src in 0..9 { + for dec in 9..12 { + adj[dec * n + src] = 0.08 + rng.gen::() * 0.06; + } + } + + // Decomposers return nutrients to producers + for dec in 9..12 { + for prod in 0..3 { + adj[prod * n + dec] = 0.20 + rng.gen::() * 0.10; + } + } + + // Decomposer cross-interactions + set_symmetric(&mut adj, n, 9, 10, 0.10 + rng.gen::() * 0.05); + set_symmetric(&mut adj, n, 10, 11, 0.08 + rng.gen::() * 0.04); + set_symmetric(&mut adj, n, 9, 11, 0.06 + rng.gen::() * 0.03); + + // Secondary consumers occasionally eat decomposers (omnivory) + for pred in 6..9 { + adj[pred * n + 11] = 0.03 + rng.gen::() * 0.02; // eat earthworms + } + + let mut tpm = adj.clone(); + row_normalize(&mut tpm, n); + + Ecosystem { + name: "Tropical Rainforest".to_string(), + species, + adjacency: adj, + tpm, + } +} + +/// Agricultural monoculture: 8 species, sparse linear chains. +/// Expected: LOW Phi (fragile, decomposable). +fn generate_agricultural_monoculture() -> Ecosystem { + let species = vec![ + // 0: Crop (producer) + Species { name: "Wheat Crop".into(), trophic_level: TrophicLevel::Producer }, + // 1: Pest + Species { name: "Aphid Pest".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + // 2: Predator of pest + Species { name: "Ladybug".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + // 3: Pollinator + Species { name: "Honeybee".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + // 4-5: Soil microbes + Species { name: "Nitrogen Fixer".into(), trophic_level: TrophicLevel::Decomposer }, + Species { name: "Mycorrhiza".into(), trophic_level: TrophicLevel::Decomposer }, + // 6: Weed + Species { name: "Weed".into(), trophic_level: TrophicLevel::Producer }, + // 7: Resistant pest variant + Species { name: "Resistant Aphid".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + ]; + let n = species.len(); + let mut rng = ChaCha8Rng::seed_from_u64(200); + + let mut adj = vec![0.0f64; n * n]; + + // Simple linear chain: crop -> pest -> predator + adj[1 * n + 0] = 0.7 + rng.gen::() * 0.1; // pest eats crop + adj[2 * n + 1] = 0.6 + rng.gen::() * 0.1; // ladybug eats pest + adj[2 * n + 7] = 0.3 + rng.gen::() * 0.1; // ladybug eats resistant pest + + // Pollinator weakly interacts with crop + adj[3 * n + 0] = 0.2 + rng.gen::() * 0.05; // bee visits crop + adj[0 * n + 3] = 0.15 + rng.gen::() * 0.05; // crop benefits from bee + + // Soil microbes support crop + adj[0 * n + 4] = 0.25 + rng.gen::() * 0.05; // nitrogen fixation + adj[0 * n + 5] = 0.20 + rng.gen::() * 0.05; // mycorrhizal support + + // Crop waste feeds soil microbes (weak) + adj[4 * n + 0] = 0.10 + rng.gen::() * 0.03; + adj[5 * n + 0] = 0.08 + rng.gen::() * 0.03; + + // Weed competes with crop (negative interaction modeled as weak link) + adj[6 * n + 0] = 0.05 + rng.gen::() * 0.02; + adj[0 * n + 6] = 0.02 + rng.gen::() * 0.01; + + // Resistant pest also eats crop + adj[7 * n + 0] = 0.5 + rng.gen::() * 0.1; + + // Pest and resistant pest weakly interact + adj[1 * n + 7] = 0.02; + adj[7 * n + 1] = 0.02; + + let mut tpm = adj.clone(); + row_normalize(&mut tpm, n); + + Ecosystem { + name: "Agricultural Monoculture".to_string(), + species, + adjacency: adj, + tpm, + } +} + +/// Coral reef: 10 species, moderate connectivity with keystone species (coral). +/// Expected: MODERATE Phi, but removing coral collapses integration. +fn generate_coral_reef() -> Ecosystem { + let species = vec![ + // 0: Coral (keystone) + Species { name: "Coral".into(), trophic_level: TrophicLevel::Producer }, + // 1: Algae + Species { name: "Algae".into(), trophic_level: TrophicLevel::Producer }, + // 2-4: Fish + Species { name: "Clownfish".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + Species { name: "Parrotfish".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + Species { name: "Grouper".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + // 5-6: Invertebrates + Species { name: "Sea Urchin".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + Species { name: "Crown-of-Thorns".into(), trophic_level: TrophicLevel::PrimaryConsumer }, + // 7: Shark (apex) + Species { name: "Reef Shark".into(), trophic_level: TrophicLevel::Apex }, + // 8: Sea turtle + Species { name: "Sea Turtle".into(), trophic_level: TrophicLevel::SecondaryConsumer }, + // 9: Plankton + Species { name: "Plankton".into(), trophic_level: TrophicLevel::Producer }, + ]; + let n = species.len(); + let mut rng = ChaCha8Rng::seed_from_u64(300); + + let mut adj = vec![0.0f64; n * n]; + + // Coral is the structural keystone: many species depend on it + // Coral shelters clownfish + adj[2 * n + 0] = 0.5 + rng.gen::() * 0.1; + // Parrotfish grazes algae off coral (mutually beneficial) + adj[3 * n + 1] = 0.4 + rng.gen::() * 0.1; + adj[0 * n + 3] = 0.3 + rng.gen::() * 0.1; // coral benefits from parrotfish + // Grouper eats smaller fish + adj[4 * n + 2] = 0.3 + rng.gen::() * 0.1; + adj[4 * n + 3] = 0.2 + rng.gen::() * 0.1; + // Sea urchin grazes algae + adj[5 * n + 1] = 0.35 + rng.gen::() * 0.1; + // Crown-of-thorns eats coral (destructive) + adj[6 * n + 0] = 0.4 + rng.gen::() * 0.1; + // Shark eats grouper and turtle + adj[7 * n + 4] = 0.35 + rng.gen::() * 0.1; + adj[7 * n + 8] = 0.15 + rng.gen::() * 0.05; + // Sea turtle eats algae and invertebrates + adj[8 * n + 1] = 0.2 + rng.gen::() * 0.05; + adj[8 * n + 5] = 0.15 + rng.gen::() * 0.05; + adj[8 * n + 6] = 0.10 + rng.gen::() * 0.05; + // Plankton feeds coral and clownfish + adj[0 * n + 9] = 0.3 + rng.gen::() * 0.1; + adj[2 * n + 9] = 0.2 + rng.gen::() * 0.05; + // Algae and coral compete for space + adj[1 * n + 0] = 0.1 + rng.gen::() * 0.05; + adj[0 * n + 1] = 0.08 + rng.gen::() * 0.03; + + // Nutrient recycling from consumers back to producers/plankton + for consumer in [2, 3, 4, 5, 6, 7, 8] { + adj[9 * n + consumer] = 0.03 + rng.gen::() * 0.02; + } + + let mut tpm = adj.clone(); + row_normalize(&mut tpm, n); + + Ecosystem { + name: "Coral Reef".to_string(), + species, + adjacency: adj, + tpm, + } +} + +/// Set a directed edge weight. +fn set_edge(adj: &mut [f64], n: usize, from: usize, to: usize, weight: f64) { + adj[from * n + to] = weight; +} + +/// Set symmetric (bidirectional) edge weights. +fn set_symmetric(adj: &mut [f64], n: usize, a: usize, b: usize, weight: f64) { + adj[a * n + b] = weight; + adj[b * n + a] = weight; +} diff --git a/examples/ecosystem-consciousness/src/main.rs b/examples/ecosystem-consciousness/src/main.rs new file mode 100644 index 00000000..a30911b1 --- /dev/null +++ b/examples/ecosystem-consciousness/src/main.rs @@ -0,0 +1,71 @@ +//! Ecosystem Consciousness Explorer +//! +//! Applies IIT Phi to food web networks to measure ecosystem integration +//! and resilience. Compares tropical rainforest, agricultural monoculture, +//! and coral reef ecosystems. + +mod analysis; +mod data; +mod report; + +fn main() { + println!("+==========================================================+"); + println!("| Ecosystem Consciousness Explorer -- IIT 4.0 Analysis |"); + println!("| Measuring food web integration via Phi |"); + println!("+==========================================================+"); + + // Parse CLI args + let args: Vec = std::env::args().collect(); + let output = parse_str_arg(&args, "--output", "ecosystem_report.svg"); + + println!("\nConfiguration:"); + println!(" Output: {}", output); + + // Step 1: Generate food web data + println!("\n=== Step 1: Generating Synthetic Food Webs ==="); + let ecosystems = data::generate_all_ecosystems(); + for eco in &ecosystems { + println!( + " {}: {} species, {} connections", + eco.name, eco.species.len(), eco.connection_count() + ); + } + + // Step 2: Run consciousness analysis + println!("\n=== Step 2: IIT Phi Analysis ==="); + let results = analysis::run_ecosystem_analysis(&ecosystems); + + // Step 3: Print text summary + println!("\n=== Step 3: Results Summary ==="); + report::print_summary(&results); + + // Step 4: Generate SVG report + let svg = report::generate_svg(&results); + std::fs::write(output, &svg).expect("Failed to write SVG report"); + println!( + "\nSVG report saved to: {}", + parse_str_arg(&args, "--output", "ecosystem_report.svg") + ); + + // Final comparison + println!("\n+==========================================================+"); + println!("| Ecosystem Integration Ranking (by Phi): |"); + let mut sorted: Vec<_> = results.iter().collect(); + sorted.sort_by(|a, b| b.full_phi.partial_cmp(&a.full_phi).unwrap()); + for (i, r) in sorted.iter().enumerate() { + println!( + "| {}. {:30} Phi = {:.6} |", + i + 1, + r.name, + r.full_phi + ); + } + println!("+==========================================================+"); +} + +fn parse_str_arg<'a>(args: &'a [String], name: &str, default: &'a str) -> &'a str { + args.windows(2) + .find(|w| w[0] == name) + .map(|w| w[1].as_str()) + .unwrap_or(default) +} diff --git a/examples/ecosystem-consciousness/src/report.rs b/examples/ecosystem-consciousness/src/report.rs new file mode 100644 index 00000000..9ac53aef --- /dev/null +++ b/examples/ecosystem-consciousness/src/report.rs @@ -0,0 +1,236 @@ +//! Report generation: text summary and SVG food web visualization. + +use crate::analysis::EcosystemResult; + +/// Print a text summary of all ecosystem results. +pub fn print_summary(results: &[EcosystemResult]) { + for r in results { + println!("\n========== {} ==========", r.name); + println!("Species count: {}", r.n_species); + println!("Full system Phi: {:.6} ({})", r.full_phi, r.algorithm); + + println!("\nSpecies Phi contributions (sorted by importance):"); + for (_, name, phi_without, contribution) in &r.species_contributions { + let bar_len = ((contribution.abs() / r.full_phi.max(1e-10)) * 20.0) as usize; + let bar_char = if *contribution > 0.0 { "+" } else { "-" }; + println!( + " {:20} {:+.6} (Phi without: {:.6}) {}", + name, + contribution, + phi_without, + bar_char.repeat(bar_len.min(30)) + ); + } + + println!("\nCausal Emergence:"); + println!(" EI (micro): {:.4} bits", r.emergence.ei_micro); + println!(" EI (macro): {:.4} bits", r.emergence.ei_macro); + println!( + " Causal emergence: {:.4}", + r.emergence.causal_emergence + ); + println!(" Determinism: {:.4}", r.emergence.determinism); + println!(" Degeneracy: {:.4}", r.emergence.degeneracy); + + println!("\nSVD Emergence:"); + println!( + " Effective rank: {}/{}", + r.svd_emergence.effective_rank, r.n_species + ); + println!( + " Spectral entropy: {:.4}", + r.svd_emergence.spectral_entropy + ); + println!( + " Emergence index: {:.4}", + r.svd_emergence.emergence_index + ); + println!( + " Reversibility: {:.4}", + r.svd_emergence.reversibility + ); + } +} + +/// Generate a self-contained SVG report with food web diagrams. +pub fn generate_svg(results: &[EcosystemResult]) -> String { + let panel_height = 500; + let total_height = 100 + results.len() as i32 * (panel_height + 50); + let width = 1200; + + let mut svg = String::with_capacity(30_000); + svg.push_str(&format!( + r#" + + + +Ecosystem Consciousness Analysis Report +IIT Phi measures integrated information in food web networks +"#, + width, total_height, width, total_height + )); + + for (idx, r) in results.iter().enumerate() { + let y_off = 100 + idx as i32 * (panel_height + 50); + svg.push_str(&render_ecosystem_panel(r, 30, y_off, width - 60, panel_height)); + } + + svg.push_str("\n"); + svg +} + +/// Render a single ecosystem panel with food web and contribution bars. +fn render_ecosystem_panel( + r: &EcosystemResult, + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + + // Panel background + s.push_str(&format!( + "\n", + w, h + )); + + // Title + s.push_str(&format!( + "{} (n={}, Phi={:.6})\n", + r.name, r.n_species, r.full_phi + )); + + // Left panel: food web node diagram (circular layout) + let cx = 200; + let cy = h / 2 + 20; + let radius = 140; + let n = r.n_species; + + // Draw nodes in a circle, sized by Phi contribution + let max_contrib = r + .species_contributions + .iter() + .map(|(_, _, _, c)| c.abs()) + .fold(0.0f64, f64::max) + .max(1e-10); + + // Build contribution lookup by species index + let mut contrib_by_idx = vec![0.0f64; n]; + for (idx, _, _, c) in &r.species_contributions { + contrib_by_idx[*idx] = *c; + } + + // Node positions + let positions: Vec<(f64, f64)> = (0..n) + .map(|i| { + let angle = 2.0 * std::f64::consts::PI * i as f64 / n as f64 + - std::f64::consts::FRAC_PI_2; + ( + cx as f64 + radius as f64 * angle.cos(), + cy as f64 + radius as f64 * angle.sin(), + ) + }) + .collect(); + + // Draw nodes + for i in 0..n { + let (nx, ny) = positions[i]; + let node_r = 8.0 + (contrib_by_idx[i].abs() / max_contrib * 14.0); + let color = &r.trophic_colors[i]; + s.push_str(&format!( + "\n", + nx, ny, node_r, color + )); + // Label + let label = if r.species_names[i].len() > 8 { + &r.species_names[i][..8] + } else { + &r.species_names[i] + }; + s.push_str(&format!( + "{}\n", + nx, + ny + node_r + 12.0, + label + )); + } + + // Right panel: contribution bar chart + let bar_x = 420; + let bar_w = w - bar_x - 30; + let bar_h = h - 80; + s.push_str(&format!( + "Species Phi Contributions\n", + bar_x + )); + + let contributions = &r.species_contributions; + if !contributions.is_empty() { + let row_h = (bar_h as f64 / contributions.len() as f64).min(30.0); + let max_abs = contributions + .iter() + .map(|(_, _, _, c)| c.abs()) + .fold(0.0f64, f64::max) + .max(1e-10); + + for (i, (_, name, _, contrib)) in contributions.iter().enumerate() { + let ry = 65 + (i as f64 * row_h) as i32; + let bw = (contrib.abs() / max_abs * (bar_w as f64 * 0.5)) as i32; + let bar_class = if *contrib >= 0.0 { "bar" } else { "bar-neg" }; + + // Species name + let display_name = if name.len() > 16 { + &name[..16] + } else { + name.as_str() + }; + s.push_str(&format!( + "{}\n", + bar_x + 120, + ry + (row_h as i32) / 2 + 4, + display_name + )); + + // Bar + s.push_str(&format!( + "\n", + bar_x + 125, + ry, + bw.max(1), + (row_h - 4.0).max(4.0) as i32, + bar_class + )); + + // Value label + s.push_str(&format!( + "{:+.4}\n", + bar_x + 130 + bw, + ry + (row_h as i32) / 2 + 4, + contrib + )); + } + } + + // Stats box at bottom + let stats_y = h - 40; + s.push_str(&format!( + "EI_micro={:.3} Emergence={:.3} SVD rank={}/{} Emergence idx={:.3}\n", + stats_y, + r.emergence.ei_micro, + r.emergence.causal_emergence, + r.svd_emergence.effective_rank, + r.n_species, + r.svd_emergence.emergence_index + )); + + s.push_str("\n"); + s +} diff --git a/examples/gene-consciousness/Cargo.toml b/examples/gene-consciousness/Cargo.toml new file mode 100644 index 00000000..6a0ec40f --- /dev/null +++ b/examples/gene-consciousness/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gene-consciousness" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Gene regulatory network consciousness analysis using IIT Phi" +publish = false + +[[bin]] +name = "gene-consciousness" +path = "src/main.rs" + +[dependencies] +ruvector-consciousness = { path = "../../crates/ruvector-consciousness", default-features = false, features = ["phi", "emergence", "collapse"] } +rand = "0.8" +rand_chacha = "0.3" diff --git a/examples/gene-consciousness/src/analysis.rs b/examples/gene-consciousness/src/analysis.rs new file mode 100644 index 00000000..c8479e50 --- /dev/null +++ b/examples/gene-consciousness/src/analysis.rs @@ -0,0 +1,226 @@ +//! Consciousness analysis pipeline for gene regulatory networks. + +use ruvector_consciousness::emergence::CausalEmergenceEngine; +use ruvector_consciousness::phi::auto_compute_phi; +use ruvector_consciousness::rsvd_emergence::{RsvdEmergenceEngine, RsvdEmergenceResult}; +use ruvector_consciousness::traits::EmergenceEngine; +use ruvector_consciousness::types::{ + ComputeBudget, EmergenceResult, PhiResult, + TransitionMatrix as ConsciousnessTPM, +}; + +use crate::data::{self, GeneNetwork, TransitionMatrix}; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +/// Full analysis results for the gene regulatory network. +pub struct AnalysisResults { + /// Phi for the full 16-gene normal network. + pub normal_full_phi: PhiResult, + /// Phi for the full 16-gene cancer network. + pub cancer_full_phi: PhiResult, + /// Phi for each 4-gene module in the normal network. + pub normal_module_phis: Vec<(String, PhiResult)>, + /// Phi for each 4-gene module in the cancer network. + pub cancer_module_phis: Vec<(String, PhiResult)>, + /// Causal emergence for normal network. + pub normal_emergence: EmergenceResult, + /// SVD emergence for normal network. + pub normal_svd_emergence: RsvdEmergenceResult, + /// Whether modules have higher Phi than the full network (expected: yes). + pub modules_more_integrated: bool, + /// Whether cancer rewiring increases cross-module Phi. + pub cancer_higher_cross_phi: bool, + /// Null model Phi values for statistical testing. + pub null_phis: Vec, + /// Z-score of observed Phi vs null distribution. + pub z_score: f64, + /// Empirical p-value. + pub p_value: f64, +} + +/// Convert our TPM to the consciousness crate's format. +fn to_consciousness_tpm(tpm: &TransitionMatrix) -> ConsciousnessTPM { + ConsciousnessTPM::new(tpm.size, tpm.data.clone()) +} + +/// Run the complete analysis pipeline. +pub fn run_analysis( + normal_net: &GeneNetwork, + normal_tpm: &TransitionMatrix, + _cancer_net: &GeneNetwork, + cancer_tpm: &TransitionMatrix, + null_samples: usize, +) -> AnalysisResults { + let budget = ComputeBudget::default(); + + // 1. Full system Phi -- normal + println!("\n--- Computing Phi: Normal Network (full 16-gene) ---"); + let normal_ctpm = to_consciousness_tpm(normal_tpm); + let normal_full_phi = auto_compute_phi(&normal_ctpm, None, &budget) + .expect("Failed to compute Phi for normal network"); + println!( + " Phi = {:.6} (algorithm: {}, elapsed: {:?})", + normal_full_phi.phi, normal_full_phi.algorithm, normal_full_phi.elapsed + ); + + // 2. Full system Phi -- cancer + println!("\n--- Computing Phi: Cancer Network (full 16-gene) ---"); + let cancer_ctpm = to_consciousness_tpm(cancer_tpm); + let cancer_full_phi = auto_compute_phi(&cancer_ctpm, None, &budget) + .expect("Failed to compute Phi for cancer network"); + println!( + " Phi = {:.6} (algorithm: {}, elapsed: {:?})", + cancer_full_phi.phi, cancer_full_phi.algorithm, cancer_full_phi.elapsed + ); + + // 3. Module-level Phi -- normal + println!("\n--- Computing Phi: Normal Network Modules ---"); + let modules = data::all_modules(); + let mut normal_module_phis = Vec::new(); + for (name, genes) in &modules { + let sub = data::extract_sub_tpm(normal_tpm, genes); + let sub_ctpm = to_consciousness_tpm(&sub); + match auto_compute_phi(&sub_ctpm, None, &budget) { + Ok(phi) => { + println!(" {} Phi = {:.6} (genes {:?})", name, phi.phi, genes); + normal_module_phis.push((name.to_string(), phi)); + } + Err(e) => { + println!(" {} Phi computation failed: {}", name, e); + } + } + } + + // 4. Module-level Phi -- cancer + println!("\n--- Computing Phi: Cancer Network Modules ---"); + let mut cancer_module_phis = Vec::new(); + for (name, genes) in &modules { + let sub = data::extract_sub_tpm(cancer_tpm, genes); + let sub_ctpm = to_consciousness_tpm(&sub); + match auto_compute_phi(&sub_ctpm, None, &budget) { + Ok(phi) => { + println!(" {} Phi = {:.6} (genes {:?})", name, phi.phi, genes); + cancer_module_phis.push((name.to_string(), phi)); + } + Err(e) => { + println!(" {} Phi computation failed: {}", name, e); + } + } + } + + // 5. Compare: modules vs full network + let avg_module_phi = if normal_module_phis.is_empty() { + 0.0 + } else { + normal_module_phis.iter().map(|(_, p)| p.phi).sum::() + / normal_module_phis.len() as f64 + }; + let modules_more_integrated = avg_module_phi > normal_full_phi.phi; + println!( + "\n Avg module Phi ({:.6}) {} full network Phi ({:.6})", + avg_module_phi, + if modules_more_integrated { ">" } else { "<=" }, + normal_full_phi.phi + ); + + // 6. Compare: cancer vs normal cross-module integration + let cancer_higher_cross_phi = cancer_full_phi.phi > normal_full_phi.phi; + println!( + " Cancer Phi ({:.6}) {} Normal Phi ({:.6})", + cancer_full_phi.phi, + if cancer_higher_cross_phi { ">" } else { "<=" }, + normal_full_phi.phi + ); + + // 7. Causal emergence -- normal network + println!("\n--- Causal Emergence Analysis (Normal) ---"); + let emergence_engine = CausalEmergenceEngine::new(normal_tpm.size.min(16)); + let normal_emergence = emergence_engine + .compute_emergence(&normal_ctpm, &budget) + .expect("Failed to compute causal emergence"); + println!( + " EI_micro = {:.4} bits, determinism = {:.4}, degeneracy = {:.4}", + normal_emergence.ei_micro, normal_emergence.determinism, normal_emergence.degeneracy + ); + println!( + " Causal emergence = {:.4}, coarse-graining: {:?}", + normal_emergence.causal_emergence, normal_emergence.coarse_graining + ); + + // 8. SVD emergence + println!("\n--- SVD Emergence Analysis (Normal) ---"); + let svd_engine = RsvdEmergenceEngine::default(); + let normal_svd_emergence = svd_engine + .compute(&normal_ctpm, &budget) + .expect("Failed to compute SVD emergence"); + println!( + " Effective rank = {}/{}, entropy = {:.4}, emergence = {:.4}", + normal_svd_emergence.effective_rank, normal_tpm.size, + normal_svd_emergence.spectral_entropy, normal_svd_emergence.emergence_index + ); + + // 9. Null hypothesis testing + println!( + "\n--- Null Hypothesis Testing ({} randomized networks) ---", + null_samples + ); + let mut rng = ChaCha8Rng::seed_from_u64(42); + let mut null_phis = Vec::with_capacity(null_samples); + for i in 0..null_samples { + let null_tpm = data::generate_null_tpm(normal_net, &mut rng); + let null_ctpm = to_consciousness_tpm(&null_tpm); + if let Ok(null_phi) = auto_compute_phi(&null_ctpm, None, &budget) { + null_phis.push(null_phi.phi); + } + if (i + 1) % 10 == 0 { + print!(" [{}/{}] ", i + 1, null_samples); + } + } + println!(); + + // Compute statistics + let null_mean = if null_phis.is_empty() { + 0.0 + } else { + null_phis.iter().sum::() / null_phis.len() as f64 + }; + let null_std = if null_phis.len() > 1 { + (null_phis + .iter() + .map(|&p| (p - null_mean).powi(2)) + .sum::() + / (null_phis.len() as f64 - 1.0)) + .sqrt() + } else { + 0.0 + }; + let z_score = if null_std > 1e-10 { + (normal_full_phi.phi - null_mean) / null_std + } else { + 0.0 + }; + let p_value = if null_phis.is_empty() { + 1.0 + } else { + null_phis + .iter() + .filter(|&&p| p >= normal_full_phi.phi) + .count() as f64 + / null_phis.len() as f64 + }; + + AnalysisResults { + normal_full_phi, + cancer_full_phi, + normal_module_phis, + cancer_module_phis, + normal_emergence, + normal_svd_emergence, + modules_more_integrated, + cancer_higher_cross_phi, + null_phis, + z_score, + p_value, + } +} diff --git a/examples/gene-consciousness/src/data.rs b/examples/gene-consciousness/src/data.rs new file mode 100644 index 00000000..08f8ffcf --- /dev/null +++ b/examples/gene-consciousness/src/data.rs @@ -0,0 +1,279 @@ +//! Gene regulatory network data generation. +//! +//! Builds synthetic gene regulatory networks based on known biological motifs, +//! with 4 functional modules: cell cycle, apoptosis, growth signaling, and +//! housekeeping. Also generates an oncogenic "cancer" variant where growth +//! signaling overrides apoptosis controls. + +use rand::Rng; +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; + +/// Module assignment for a 16-gene network. +pub const MODULE_CELL_CYCLE: &[usize] = &[0, 1, 2, 3]; +pub const MODULE_APOPTOSIS: &[usize] = &[4, 5, 6, 7]; +pub const MODULE_GROWTH: &[usize] = &[8, 9, 10, 11]; +pub const MODULE_HOUSEKEEPING: &[usize] = &[12, 13, 14, 15]; + +pub const MODULE_NAMES: &[&str] = &["Cell Cycle", "Apoptosis", "Growth Signaling", "Housekeeping"]; + +/// All modules with their gene indices. +pub fn all_modules() -> Vec<(&'static str, &'static [usize])> { + vec![ + (MODULE_NAMES[0], MODULE_CELL_CYCLE), + (MODULE_NAMES[1], MODULE_APOPTOSIS), + (MODULE_NAMES[2], MODULE_GROWTH), + (MODULE_NAMES[3], MODULE_HOUSEKEEPING), + ] +} + +/// A gene regulatory network represented as a weighted adjacency matrix. +pub struct GeneNetwork { + pub n_genes: usize, + /// Flat row-major adjacency/regulation weights. Positive = activation, + /// negative = repression. Range roughly [-1, 1]. + pub adjacency: Vec, + /// Human-readable gene labels. + pub gene_labels: Vec, + /// Module index for each gene (0..3). + pub module_ids: Vec, + /// Network variant label. + pub variant: String, +} + +impl GeneNetwork { + /// Count non-zero edges (absolute value > 0.001). + pub fn n_edges(&self) -> usize { + self.adjacency + .iter() + .filter(|&&w| w.abs() > 0.001) + .count() + } +} + +/// Transition probability matrix for consciousness analysis. +pub struct TransitionMatrix { + pub size: usize, + pub data: Vec, +} + +/// Build the normal (healthy) 16-gene regulatory network. +/// +/// Module structure: +/// - Cell cycle (genes 0-3): cyclin cascade with strong internal regulation +/// - Apoptosis (genes 4-7): pro- and anti-apoptotic balance +/// - Growth signaling (genes 8-11): receptor tyrosine kinase cascade +/// - Housekeeping (genes 12-15): weakly connected to all other modules +/// +/// Within-module: strong connections (0.3-0.5) +/// Between-module: weak connections (0.01-0.05) +/// Housekeeping: weakly connected to everything +pub fn build_normal_network() -> GeneNetwork { + let n = 16; + let mut adj = vec![0.0f64; n * n]; + let mut rng = ChaCha8Rng::seed_from_u64(42); + + let gene_labels = vec![ + // Cell cycle + "CycD".into(), "CDK4".into(), "CycE".into(), "CDK2".into(), + // Apoptosis + "BAX".into(), "BCL2".into(), "CASP3".into(), "p53".into(), + // Growth signaling + "EGFR".into(), "RAS".into(), "RAF".into(), "ERK".into(), + // Housekeeping + "GAPDH".into(), "ACTB".into(), "RPL13A".into(), "HPRT".into(), + ]; + + let module_ids: Vec = (0..n) + .map(|i| i / 4) + .collect(); + + // Within-module connections: strong, directed cascade-like + let modules: &[&[usize]] = &[ + MODULE_CELL_CYCLE, + MODULE_APOPTOSIS, + MODULE_GROWTH, + MODULE_HOUSEKEEPING, + ]; + + for module in modules { + for (idx, &from) in module.iter().enumerate() { + for (jdx, &to) in module.iter().enumerate() { + if from == to { + continue; + } + // Sequential cascade: strong forward, moderate feedback + let base: f64 = if jdx == idx + 1 { + 0.45 // strong forward connection + } else if idx == jdx + 1 { + 0.20 // feedback + } else { + 0.15 // lateral + }; + let noise: f64 = rng.gen_range(-0.05..0.05); + adj[from * n + to] = (base + noise).clamp(0.0, 0.5); + } + } + } + + // Apoptosis module: add inhibitory connections (BCL2 inhibits BAX/CASP3) + adj[5 * n + 4] = -0.35; // BCL2 -| BAX + adj[5 * n + 6] = -0.30; // BCL2 -| CASP3 + adj[7 * n + 4] = 0.40; // p53 -> BAX (pro-apoptotic) + adj[7 * n + 5] = -0.25; // p53 -| BCL2 + + // Between-module connections: weak + // Growth signaling -> Cell cycle (growth promotes division) + adj[11 * n + 0] = 0.04; // ERK -> CycD + adj[11 * n + 1] = 0.03; // ERK -> CDK4 + + // Growth signaling -> Apoptosis (growth suppresses apoptosis) + adj[11 * n + 5] = 0.03; // ERK -> BCL2 (anti-apoptotic) + + // Apoptosis -> Cell cycle (apoptosis inhibits division) + adj[6 * n + 2] = -0.02; // CASP3 -| CycE + + // Housekeeping: weak connections to all modules + for &hk in MODULE_HOUSEKEEPING { + for g in 0..12 { + let w = rng.gen_range(0.005..0.02); + adj[hk * n + g] = w; + adj[g * n + hk] = w * 0.5; + } + } + + GeneNetwork { + n_genes: n, + adjacency: adj, + gene_labels, + module_ids, + variant: "Normal".into(), + } +} + +/// Build the cancer (oncogenic) variant. +/// +/// Key rewiring: +/// 1. Growth signaling is constitutively active (stronger internal connections) +/// 2. Growth signaling overrides apoptosis controls (strong cross-module edges) +/// 3. p53 pathway is disrupted (weakened connections) +/// 4. Cell cycle checkpoints are bypassed +/// +/// Expected: higher cross-module Phi due to loss of modular boundaries. +pub fn build_cancer_network() -> GeneNetwork { + let mut net = build_normal_network(); + net.variant = "Cancer".into(); + let n = net.n_genes; + + // 1. Constitutively active growth signaling (boost internal connections) + for &from in MODULE_GROWTH { + for &to in MODULE_GROWTH { + if from != to { + let idx = from * n + to; + net.adjacency[idx] = (net.adjacency[idx] * 1.5).clamp(-0.5, 0.5); + } + } + } + + // 2. Growth overrides apoptosis (strong cross-module edges) + net.adjacency[11 * n + 5] = 0.30; // ERK -> BCL2 (strong anti-apoptotic) + net.adjacency[10 * n + 5] = 0.25; // RAF -> BCL2 + net.adjacency[11 * n + 4] = -0.20; // ERK -| BAX + net.adjacency[9 * n + 6] = -0.15; // RAS -| CASP3 + + // 3. p53 pathway disruption (simulate TP53 mutation) + net.adjacency[7 * n + 4] = 0.05; // p53 -> BAX weakened + net.adjacency[7 * n + 5] = -0.05; // p53 -| BCL2 weakened + + // 4. Cell cycle checkpoint bypass + net.adjacency[11 * n + 0] = 0.25; // ERK -> CycD (strong growth drive) + net.adjacency[11 * n + 1] = 0.20; // ERK -> CDK4 + net.adjacency[6 * n + 2] = -0.005; // CASP3 -| CycE weakened + + net +} + +/// Convert a gene regulatory network to a transition probability matrix. +/// +/// Method: +/// 1. Take absolute values of adjacency weights (treat activation/repression +/// as "information flow" regardless of sign) +/// 2. Add self-regulation (diagonal) as baseline activity +/// 3. Row-normalize to get transition probabilities +pub fn network_to_tpm(net: &GeneNetwork) -> TransitionMatrix { + let n = net.n_genes; + let mut tpm = vec![0.0f64; n * n]; + + for i in 0..n { + for j in 0..n { + // Use absolute adjacency weight as transition strength + tpm[i * n + j] = net.adjacency[i * n + j].abs(); + } + // Add self-regulation baseline (genes maintain their own state) + tpm[i * n + i] += 0.1; + } + + // Row-normalize + for i in 0..n { + let row_sum: f64 = (0..n).map(|j| tpm[i * n + j]).sum(); + if row_sum > 1e-30 { + for j in 0..n { + tpm[i * n + j] /= row_sum; + } + } + } + + TransitionMatrix { size: n, data: tpm } +} + +/// Extract a sub-TPM for a subset of genes. +pub fn extract_sub_tpm(tpm: &TransitionMatrix, genes: &[usize]) -> TransitionMatrix { + let n = genes.len(); + let mut sub = vec![0.0f64; n * n]; + for (si, &gi) in genes.iter().enumerate() { + let row_sum: f64 = genes.iter().map(|&gj| tpm.data[gi * tpm.size + gj]).sum(); + for (sj, &gj) in genes.iter().enumerate() { + sub[si * n + sj] = tpm.data[gi * tpm.size + gj] / row_sum.max(1e-30); + } + } + TransitionMatrix { size: n, data: sub } +} + +/// Generate a null-model network by shuffling connections while preserving +/// degree distribution (configuration model). +pub fn generate_null_tpm(net: &GeneNetwork, rng: &mut impl rand::Rng) -> TransitionMatrix { + let n = net.n_genes; + let mut adj = net.adjacency.clone(); + + // Shuffle non-diagonal entries while preserving row sums + for i in 0..n { + let mut row_vals: Vec = (0..n) + .filter(|&j| j != i) + .map(|j| adj[i * n + j]) + .collect(); + + // Fisher-Yates shuffle + for k in (1..row_vals.len()).rev() { + let swap_idx = rng.gen_range(0..=k); + row_vals.swap(k, swap_idx); + } + + let mut idx = 0; + for j in 0..n { + if j != i { + adj[i * n + j] = row_vals[idx]; + idx += 1; + } + } + } + + let shuffled = GeneNetwork { + n_genes: n, + adjacency: adj, + gene_labels: net.gene_labels.clone(), + module_ids: net.module_ids.clone(), + variant: "Null".into(), + }; + + network_to_tpm(&shuffled) +} diff --git a/examples/gene-consciousness/src/main.rs b/examples/gene-consciousness/src/main.rs new file mode 100644 index 00000000..71c67f18 --- /dev/null +++ b/examples/gene-consciousness/src/main.rs @@ -0,0 +1,87 @@ +//! Gene Regulatory Network Consciousness Explorer +//! +//! Applies IIT Phi to gene regulatory networks to identify emergent +//! regulatory modules. Compares normal vs oncogenic (cancer) network +//! rewiring to study how disease alters integrated information. + +mod analysis; +mod data; +mod report; + +fn main() { + println!("+==========================================================+"); + println!("| Gene Regulatory Network Consciousness Explorer |"); + println!("| IIT 4.0 Phi Analysis of Regulatory Modules |"); + println!("+==========================================================+"); + + // Parse CLI args + let args: Vec = std::env::args().collect(); + let null_samples = parse_arg(&args, "--null-samples", 50usize); + let output = parse_str_arg(&args, "--output", "gene_report.svg"); + + println!("\nConfiguration:"); + println!(" Genes: 16 (4 modules x 4 genes)"); + println!(" Null samples: {}", null_samples); + println!(" Output: {}", output); + + // Step 1: Build gene regulatory networks + println!("\n=== Step 1: Building Gene Regulatory Networks ==="); + let normal = data::build_normal_network(); + let cancer = data::build_cancer_network(); + println!(" Normal network: {} genes, {} edges", normal.n_genes, normal.n_edges()); + println!(" Cancer network: {} genes, {} edges", cancer.n_genes, cancer.n_edges()); + + // Step 2: Construct TPMs + println!("\n=== Step 2: Constructing Transition Probability Matrices ==="); + let normal_tpm = data::network_to_tpm(&normal); + let cancer_tpm = data::network_to_tpm(&cancer); + println!(" Normal TPM: {}x{}", normal_tpm.size, normal_tpm.size); + println!(" Cancer TPM: {}x{}", cancer_tpm.size, cancer_tpm.size); + + // Step 3: Run analysis + println!("\n=== Step 3: Consciousness Analysis ==="); + let results = analysis::run_analysis(&normal, &normal_tpm, &cancer, &cancer_tpm, null_samples); + + // Step 4: Print report + println!("\n=== Step 4: Results ==="); + report::print_summary(&results); + + // Step 5: Generate SVG + let svg = report::generate_svg(&results, &normal); + std::fs::write(output, &svg).expect("Failed to write SVG report"); + println!( + "\nSVG report saved to: {}", + parse_str_arg(&args, "--output", "gene_report.svg") + ); + + // Final verdict + println!("\n+==========================================================+"); + if results.modules_more_integrated { + println!("| RESULT: Modules ARE the irreducible units of |"); + println!("| integrated information in the regulatory network. |"); + } else { + println!("| RESULT: Full network is more integrated than modules. |"); + println!("| The regulatory network acts as a unified whole. |"); + } + if results.cancer_higher_cross_phi { + println!("| Cancer rewiring INCREASES cross-module integration, |"); + println!("| consistent with loss of modular boundaries. |"); + } else { + println!("| Cancer rewiring does NOT increase cross-module Phi. |"); + } + println!("+==========================================================+"); +} + +fn parse_arg(args: &[String], name: &str, default: T) -> T { + args.windows(2) + .find(|w| w[0] == name) + .and_then(|w| w[1].parse().ok()) + .unwrap_or(default) +} + +fn parse_str_arg<'a>(args: &'a [String], name: &str, default: &'a str) -> &'a str { + args.windows(2) + .find(|w| w[0] == name) + .map(|w| w[1].as_str()) + .unwrap_or(default) +} diff --git a/examples/gene-consciousness/src/report.rs b/examples/gene-consciousness/src/report.rs new file mode 100644 index 00000000..52f99046 --- /dev/null +++ b/examples/gene-consciousness/src/report.rs @@ -0,0 +1,413 @@ +//! Report generation: text summary and SVG visualization for gene networks. + +use crate::analysis::AnalysisResults; +use crate::data::{self, GeneNetwork}; + +/// Print a text summary of the analysis results. +pub fn print_summary(results: &AnalysisResults) { + println!("\n--- IIT Phi: Normal vs Cancer ---"); + println!( + "Normal full Phi: {:.6} ({})", + results.normal_full_phi.phi, results.normal_full_phi.algorithm + ); + println!( + "Cancer full Phi: {:.6} ({})", + results.cancer_full_phi.phi, results.cancer_full_phi.algorithm + ); + + println!("\n--- Module-Level Phi (Normal) ---"); + for (name, phi) in &results.normal_module_phis { + println!("{:20} Phi = {:.6}", name, phi.phi); + } + + println!("\n--- Module-Level Phi (Cancer) ---"); + for (name, phi) in &results.cancer_module_phis { + println!("{:20} Phi = {:.6}", name, phi.phi); + } + + println!("\n--- Causal Emergence (Normal) ---"); + println!( + "EI (micro): {:.4} bits", + results.normal_emergence.ei_micro + ); + println!( + "Causal emergence: {:.4}", + results.normal_emergence.causal_emergence + ); + println!("Determinism: {:.4}", results.normal_emergence.determinism); + println!("Degeneracy: {:.4}", results.normal_emergence.degeneracy); + + println!("\n--- SVD Emergence (Normal) ---"); + println!( + "Effective rank: {}/16", + results.normal_svd_emergence.effective_rank + ); + println!( + "Spectral entropy: {:.4}", + results.normal_svd_emergence.spectral_entropy + ); + println!( + "Emergence index: {:.4}", + results.normal_svd_emergence.emergence_index + ); + + println!("\n--- Null Hypothesis Testing ---"); + let null_mean = if results.null_phis.is_empty() { + 0.0 + } else { + results.null_phis.iter().sum::() / results.null_phis.len() as f64 + }; + println!("Phi (observed): {:.6}", results.normal_full_phi.phi); + println!( + "Phi (null mean): {:.6} ({} samples)", + null_mean, + results.null_phis.len() + ); + println!("z-score: {:.2}", results.z_score); + println!("p-value: {:.4}", results.p_value); + + println!("\n--- Key Findings ---"); + println!( + "Modules > full network: {}", + if results.modules_more_integrated { "YES" } else { "NO" } + ); + println!( + "Cancer > normal Phi: {}", + if results.cancer_higher_cross_phi { "YES" } else { "NO" } + ); +} + +/// Generate a self-contained SVG report with network graph visualization. +pub fn generate_svg(results: &AnalysisResults, net: &GeneNetwork) -> String { + let mut svg = String::with_capacity(20_000); + + svg.push_str( + r#" + + + +Gene Regulatory Network Consciousness Report +IIT 4.0 Phi Analysis of Regulatory Modules +"#, + ); + + // Panel 1: Network graph (y=100, h=400) + svg.push_str(&render_network_graph(net, 50, 100, 500, 400)); + + // Panel 2: Module Phi comparison (y=100, x=600, h=400) + svg.push_str(&render_phi_comparison(results, 620, 100, 530, 400)); + + // Panel 3: Null distribution (y=550, h=280) + svg.push_str(&render_null_distribution( + &results.null_phis, + results.normal_full_phi.phi, + 50, + 560, + 1100, + 280, + )); + + // Panel 4: Summary stats (y=900) + svg.push_str(&render_summary_stats(results, 50, 900)); + + svg.push_str("\n"); + svg +} + +/// Render the gene regulatory network as a graph with nodes colored by module. +fn render_network_graph(net: &GeneNetwork, x: i32, y: i32, w: i32, h: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Gene Regulatory Network (Normal)\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + // Arrange genes in a circular layout, grouped by module + let n = net.n_genes; + let cx = w as f64 / 2.0; + let cy = h as f64 / 2.0; + let radius = (w.min(h) as f64 / 2.0) - 40.0; + + let mut positions = vec![(0.0f64, 0.0f64); n]; + for i in 0..n { + let angle = 2.0 * std::f64::consts::PI * i as f64 / n as f64 - std::f64::consts::FRAC_PI_2; + positions[i] = (cx + radius * angle.cos(), cy + radius * angle.sin()); + } + + // Draw edges (only significant ones) + for i in 0..n { + for j in 0..n { + let w_val = net.adjacency[i * n + j]; + if w_val.abs() > 0.05 && i != j { + let (x1, y1) = positions[i]; + let (x2, y2) = positions[j]; + let class = if w_val.abs() > 0.2 { "edge-strong" } else { "edge" }; + let opacity = (w_val.abs() * 3.0).min(1.0); + s.push_str(&format!( + "\n", + x1, y1, x2, y2, class, opacity + )); + } + } + } + + // Draw nodes + let node_classes = ["node-cc", "node-ap", "node-gs", "node-hk"]; + for i in 0..n { + let (px, py) = positions[i]; + let class = node_classes[net.module_ids[i]]; + s.push_str(&format!( + "\n", + px, py, class + )); + s.push_str(&format!( + "{}\n", + px, py, &net.gene_labels[i] + )); + } + + // Legend + let legend_y = h - 60; + let modules = data::all_modules(); + for (idx, (name, _)) in modules.iter().enumerate() { + let lx = 10 + idx as i32 * 120; + let class = node_classes[idx]; + s.push_str(&format!( + "\n", + lx, legend_y, class + )); + s.push_str(&format!( + "{}\n", + lx + 10, legend_y, name + )); + } + + s.push_str("\n"); + s +} + +/// Render module Phi comparison bar chart (normal vs cancer). +fn render_phi_comparison(results: &AnalysisResults, x: i32, y: i32, w: i32, h: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Module Phi: Normal vs Cancer\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + // Collect all phi values to determine scale + let mut all_phis: Vec = Vec::new(); + all_phis.push(results.normal_full_phi.phi); + all_phis.push(results.cancer_full_phi.phi); + for (_, p) in &results.normal_module_phis { + all_phis.push(p.phi); + } + for (_, p) in &results.cancer_module_phis { + all_phis.push(p.phi); + } + let max_phi = all_phis.iter().cloned().fold(0.0f64, f64::max).max(1e-10); + + // Draw grouped bars: normal (blue) and cancer (red) for each module + full + let n_groups = results.normal_module_phis.len() + 1; // +1 for "Full" + let group_w = (w - 40) as f64 / n_groups as f64; + let bar_w = group_w * 0.35; + let chart_h = (h - 60) as f64; + + for (idx, (name, normal_phi)) in results.normal_module_phis.iter().enumerate() { + let gx = 20.0 + idx as f64 * group_w; + + // Normal bar + let bh = (normal_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx, h - 30 - bh, bar_w, bh + )); + + // Cancer bar + if let Some((_, cancer_phi)) = results.cancer_module_phis.iter().find(|(n, _)| n == name) { + let cbh = (cancer_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx + bar_w + 2.0, h - 30 - cbh, bar_w, cbh + )); + } + + // Label + s.push_str(&format!( + "{}\n", + gx + bar_w, h - 15, name.split_whitespace().next().unwrap_or(name) + )); + } + + // Full network group + let gx = 20.0 + results.normal_module_phis.len() as f64 * group_w; + let bh = (results.normal_full_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx, h - 30 - bh, bar_w, bh + )); + let cbh = (results.cancer_full_phi.phi / max_phi * chart_h) as i32; + s.push_str(&format!( + "\n", + gx + bar_w + 2.0, h - 30 - cbh, bar_w, cbh + )); + s.push_str(&format!( + "Full\n", + gx + bar_w, h - 15 + )); + + // Legend + s.push_str(&format!( + "\n", w - 150 + )); + s.push_str(&format!( + "Normal\n", w - 135 + )); + s.push_str(&format!( + "\n", w - 150 + )); + s.push_str(&format!( + "Cancer\n", w - 135 + )); + + s.push_str("\n"); + s +} + +/// Render the null distribution histogram. +fn render_null_distribution( + null_phis: &[f64], + observed: f64, + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "Null Distribution (Shuffled Networks) vs Observed Phi\n", + w / 2 + )); + s.push_str(&format!( + "\n", + w, h + )); + + if null_phis.is_empty() { + s.push_str(&format!( + "No null samples\n", + w / 2, h / 2 + )); + s.push_str("\n"); + return s; + } + + let n_hist_bins = 25usize; + let phi_min = null_phis.iter().cloned().fold(f64::INFINITY, f64::min).min(observed) * 0.9; + let phi_max = null_phis.iter().cloned().fold(0.0f64, f64::max).max(observed) * 1.1; + let range = (phi_max - phi_min).max(1e-10); + let bin_width = range / n_hist_bins as f64; + + let mut hist = vec![0u32; n_hist_bins]; + for &p in null_phis { + let bin = ((p - phi_min) / bin_width).floor() as usize; + if bin < n_hist_bins { + hist[bin] += 1; + } + } + let max_count = *hist.iter().max().unwrap_or(&1); + + let bar_w = w as f64 / n_hist_bins as f64; + for (i, &count) in hist.iter().enumerate() { + let bar_h = (count as f64 / max_count as f64 * (h - 40) as f64) as i32; + s.push_str(&format!( + "\n", + i as f64 * bar_w, h - bar_h - 20, bar_w - 1.0, bar_h + )); + } + + // Mark observed value + let obs_x = ((observed - phi_min) / range * w as f64) as i32; + s.push_str(&format!( + "\n", + obs_x, obs_x, h - 20 + )); + s.push_str(&format!( + "Observed\n", + obs_x, h - 5 + )); + + s.push_str("\n"); + s +} + +/// Render summary statistics text. +fn render_summary_stats(results: &AnalysisResults, x: i32, y: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str("Summary Statistics\n"); + + let null_mean = if results.null_phis.is_empty() { + 0.0 + } else { + results.null_phis.iter().sum::() / results.null_phis.len() as f64 + }; + + let lines = vec![ + format!("Normal Full Phi: {:.6} (n=16)", results.normal_full_phi.phi), + format!("Cancer Full Phi: {:.6} (n=16)", results.cancer_full_phi.phi), + format!( + "Null Mean Phi: {:.6} ({} samples)", + null_mean, results.null_phis.len() + ), + format!("z-score: {:.3}", results.z_score), + format!("p-value: {:.4}", results.p_value), + format!("EI (micro): {:.4} bits", results.normal_emergence.ei_micro), + format!("Causal emergence: {:.4}", results.normal_emergence.causal_emergence), + format!( + "SVD Eff. Rank: {}/16", + results.normal_svd_emergence.effective_rank + ), + format!( + "Emergence Index: {:.4}", + results.normal_svd_emergence.emergence_index + ), + format!( + "Modules > Full: {}", + if results.modules_more_integrated { "YES" } else { "NO" } + ), + format!( + "Cancer > Normal: {}", + if results.cancer_higher_cross_phi { "YES" } else { "NO" } + ), + ]; + + for (i, line) in lines.iter().enumerate() { + s.push_str(&format!( + "{}\n", + 20 + i * 18, line + )); + } + + s.push_str("\n"); + s +} diff --git a/examples/quantum-consciousness/Cargo.toml b/examples/quantum-consciousness/Cargo.toml new file mode 100644 index 00000000..134c8af7 --- /dev/null +++ b/examples/quantum-consciousness/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "quantum-consciousness" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Quantum circuit consciousness analysis using IIT Phi" +publish = false + +[[bin]] +name = "quantum-consciousness" +path = "src/main.rs" + +[dependencies] +ruvector-consciousness = { path = "../../crates/ruvector-consciousness", default-features = false, features = ["phi", "emergence", "collapse"] } +rand = "0.8" +rand_chacha = "0.3" diff --git a/examples/quantum-consciousness/RESEARCH.md b/examples/quantum-consciousness/RESEARCH.md new file mode 100644 index 00000000..fe59c7f9 --- /dev/null +++ b/examples/quantum-consciousness/RESEARCH.md @@ -0,0 +1,119 @@ +# Quantum Circuit Consciousness: IIT Phi and Entanglement + +## Motivation + +Integrated Information Theory (IIT) and quantum entanglement both +formalize the idea that a system is "more than the sum of its parts." +This example explores whether IIT's Phi measure, applied to quantum +circuit measurement statistics, captures the same structure as +standard entanglement measures. + +## Quantum States Analyzed + +### 1. Bell State (2 qubits) + +The maximally entangled two-qubit state: + + |Psi> = (|00> + |11>) / sqrt(2) + +Prepared by H(0) followed by CNOT(0,1). Measurement in the +computational basis yields 00 or 11 with equal probability, never +01 or 10. This maximal correlation should produce HIGH Phi. + +### 2. GHZ State (3 qubits) + +The Greenberger-Horne-Zeilinger state: + + |GHZ> = (|000> + |111>) / sqrt(2) + +Genuinely multipartite entangled: tracing out any single qubit +destroys all entanglement. Expected to show HIGH Phi and high +emergence (the 3-party correlations cannot be reduced to 2-party). + +### 3. Product State (3 qubits) + + |Psi> = |0> x |0> x |0> + +Completely separable, no entanglement. The TPM is the identity +matrix (each input maps to itself). Expected Phi = 0 since the +system decomposes perfectly into independent parts. + +### 4. W State (3 qubits) + + |W> = (|001> + |010> + |100>) / sqrt(3) + +Bipartite entanglement that survives partial trace: tracing out +any one qubit still leaves the other two entangled. Different +entanglement structure from GHZ. Expected: Phi between product +and GHZ. + +### 5. Random Circuit (3 qubits, depth 5) + +Random single-qubit rotations interleaved with CNOT gates. The +resulting entanglement depends on the specific random gates chosen. +Serves as a control to show that Phi varies continuously with +circuit structure. + +## TPM Construction for Quantum Circuits + +The key mapping from quantum mechanics to IIT: + + TPM[i][j] = ||^2 + +where U is the circuit unitary, and i, j are computational basis +states. This gives the probability of measuring outcome j when the +input is the basis state i. The resulting TPM is doubly stochastic +for unitary circuits (both rows and columns sum to 1). + +## Expected Phi Hierarchy + +Standard entanglement measures predict: + + Product (0) < W < Bell <= GHZ + +IIT's Phi may not follow this ordering exactly because: + +1. Phi measures integrated information across the minimum information + partition (MIP), not entanglement per se +2. The Bell state is 2-qubit while GHZ/W are 3-qubit, so the + partition spaces differ +3. W state has different entanglement structure (robust to qubit loss) + which may be valued differently by Phi + +## Entanglement Measures for Comparison + +- **Concurrence** (2 qubits): measures entanglement of formation +- **Tangle** (3 qubits): measures genuine 3-party entanglement +- **Entanglement entropy**: von Neumann entropy of reduced density + matrix + +The GHZ state has maximal tangle but zero concurrence for any pair. +The W state has zero tangle but nonzero concurrence for every pair. + +## Causal Emergence in Quantum Systems + +Causal emergence asks: is there a macro-level description of the +quantum system that is more informative than the qubit-level +description? For entangled states, the answer may be yes -- the +entangled subsystem behaves as a single effective degree of freedom. + +## Limitations + +1. **Classical TPM**: we use ||^2, discarding quantum phases. + IIT on the full quantum state (quantum IIT) is an active research + area. +2. **Measurement basis dependence**: Phi depends on the choice of + computational basis. A different measurement basis could yield + different Phi values. +3. **Small systems**: 3 qubits = 8x8 TPM, well within exact Phi + computation limits but far from interesting quantum advantage + regimes. + +## References + +- Tononi, G. (2008). Consciousness as Integrated Information. +- Zanardi, P. et al. (2018). Quantum Integrated Information Theory. +- Greenberger, D.M. et al. (1989). Going Beyond Bell's Theorem. +- Dur, W. et al. (2000). Three qubits can be entangled in two + inequivalent ways. +- Hoel, E.P. (2017). When the Map Is Better Than the Territory. diff --git a/examples/quantum-consciousness/src/analysis.rs b/examples/quantum-consciousness/src/analysis.rs new file mode 100644 index 00000000..3861bf30 --- /dev/null +++ b/examples/quantum-consciousness/src/analysis.rs @@ -0,0 +1,169 @@ +//! Consciousness analysis for quantum circuits. +//! +//! Computes IIT Phi for each circuit's measurement TPM and compares +//! the Phi hierarchy with known entanglement measures. + +use ruvector_consciousness::emergence::CausalEmergenceEngine; +use ruvector_consciousness::phi::auto_compute_phi; +use ruvector_consciousness::rsvd_emergence::RsvdEmergenceEngine; +use ruvector_consciousness::traits::EmergenceEngine; +use ruvector_consciousness::types::{ + ComputeBudget, EmergenceResult, + TransitionMatrix as ConsciousnessTPM, +}; +use ruvector_consciousness::rsvd_emergence::RsvdEmergenceResult; + +use crate::data::QuantumCircuit; + +/// Results for a single quantum circuit analysis. +pub struct CircuitResult { + pub name: String, + pub description: String, + pub n_qubits: usize, + pub tpm_size: usize, + pub full_phi: f64, + pub algorithm: String, + pub emergence: EmergenceResult, + pub svd_emergence: RsvdEmergenceResult, +} + +/// Convert circuit TPM to consciousness crate format. +fn to_consciousness_tpm(tpm: &[f64], n: usize) -> ConsciousnessTPM { + ConsciousnessTPM::new(n, tpm.to_vec()) +} + +/// Run analysis on all quantum circuits. +pub fn run_quantum_analysis(circuits: &[QuantumCircuit]) -> Vec { + let budget = ComputeBudget::default(); + let mut results = Vec::with_capacity(circuits.len()); + + for circuit in circuits { + let dim = circuit.tpm_size(); + println!( + "\n--- Analyzing: {} ({} qubits, {}x{} TPM) ---", + circuit.name, circuit.n_qubits, dim, dim + ); + + let ctpm = to_consciousness_tpm(&circuit.tpm, dim); + + // 1. Compute Phi + let phi_result = auto_compute_phi(&ctpm, None, &budget) + .expect("Failed to compute Phi"); + let full_phi = phi_result.phi; + let algorithm = format!("{}", phi_result.algorithm); + println!( + " Phi = {:.6} (algorithm: {}, elapsed: {:?})", + full_phi, algorithm, phi_result.elapsed + ); + + // 2. Causal emergence + println!(" Computing causal emergence..."); + let emergence_engine = CausalEmergenceEngine::new(dim.min(16)); + let emergence = emergence_engine + .compute_emergence(&ctpm, &budget) + .expect("Failed to compute emergence"); + println!( + " EI_micro = {:.4}, causal_emergence = {:.4}", + emergence.ei_micro, emergence.causal_emergence + ); + + // 3. SVD emergence + println!(" Computing SVD emergence..."); + let svd_engine = RsvdEmergenceEngine::default(); + let svd_emergence = svd_engine + .compute(&ctpm, &budget) + .expect("Failed to compute SVD emergence"); + println!( + " Effective rank = {}/{}, emergence index = {:.4}", + svd_emergence.effective_rank, dim, svd_emergence.emergence_index + ); + + results.push(CircuitResult { + name: circuit.name.clone(), + description: circuit.description.clone(), + n_qubits: circuit.n_qubits, + tpm_size: dim, + full_phi, + algorithm, + emergence, + svd_emergence, + }); + } + + // Entanglement hierarchy comparison + println!("\n--- Entanglement Hierarchy Comparison ---"); + println!(" Expected ordering: Product < W < Bell <= GHZ"); + println!(" Actual Phi values:"); + let mut sorted: Vec<&CircuitResult> = results.iter().collect(); + sorted.sort_by(|a, b| a.full_phi.partial_cmp(&b.full_phi).unwrap()); + for r in &sorted { + println!(" {:25} Phi = {:.6}", r.name, r.full_phi); + } + + // Check if ordering matches expectations + let product_phi = results + .iter() + .find(|r| r.name == "Product State") + .map(|r| r.full_phi) + .unwrap_or(0.0); + let w_phi = results + .iter() + .find(|r| r.name == "W State") + .map(|r| r.full_phi) + .unwrap_or(0.0); + let bell_phi = results + .iter() + .find(|r| r.name == "Bell State") + .map(|r| r.full_phi) + .unwrap_or(0.0); + let ghz_phi = results + .iter() + .find(|r| r.name == "GHZ State") + .map(|r| r.full_phi) + .unwrap_or(0.0); + + let order_ok = product_phi <= w_phi && w_phi <= bell_phi.max(ghz_phi); + if order_ok { + println!( + "\n Phi ordering AGREES with entanglement hierarchy." + ); + } else { + println!( + "\n Phi ordering DIFFERS from naive entanglement hierarchy." + ); + println!( + " This is expected: IIT Phi measures integrated information,"); + println!( + " not entanglement per se. The two can diverge for certain states." + ); + } + + // GHZ vs W emergence comparison + println!("\n--- GHZ vs W: Emergence Structure ---"); + if let (Some(ghz), Some(w)) = ( + results.iter().find(|r| r.name == "GHZ State"), + results.iter().find(|r| r.name == "W State"), + ) { + println!( + " GHZ: Phi={:.6}, emergence={:.4}, SVD rank={}/{}", + ghz.full_phi, + ghz.emergence.causal_emergence, + ghz.svd_emergence.effective_rank, + ghz.tpm_size + ); + println!( + " W: Phi={:.6}, emergence={:.4}, SVD rank={}/{}", + w.full_phi, + w.emergence.causal_emergence, + w.svd_emergence.effective_rank, + w.tpm_size + ); + if ghz.emergence.causal_emergence > w.emergence.causal_emergence { + println!(" GHZ shows MORE causal emergence than W."); + } else { + println!(" W shows MORE causal emergence than GHZ."); + } + } + + results +} diff --git a/examples/quantum-consciousness/src/data.rs b/examples/quantum-consciousness/src/data.rs new file mode 100644 index 00000000..5f0ab9c3 --- /dev/null +++ b/examples/quantum-consciousness/src/data.rs @@ -0,0 +1,415 @@ +//! Quantum circuit data: generate measurement statistics as TPMs. +//! +//! For each quantum circuit, we compute the output state vector, then +//! construct a TPM where TPM[i][j] = P(measure outcome j | input basis state i). +//! For unitary circuits, this is ||^2. + +use rand::SeedableRng; +use rand_chacha::ChaCha8Rng; +use rand::Rng; + +/// A quantum circuit with its measurement TPM. +pub struct QuantumCircuit { + pub name: String, + pub n_qubits: usize, + pub description: String, + /// Row-major TPM: P(outcome_j | input_i), dimension 2^n x 2^n. + pub tpm: Vec, + /// The unitary matrix (row-major, complex: stored as separate re/im vecs). + #[allow(dead_code)] + pub unitary_re: Vec, + #[allow(dead_code)] + pub unitary_im: Vec, +} + +impl QuantumCircuit { + pub fn tpm_size(&self) -> usize { + 1 << self.n_qubits + } +} + +/// Generate all quantum circuits for analysis. +pub fn generate_all_circuits(random_depth: usize) -> Vec { + vec![ + generate_bell_state(), + generate_ghz_state(), + generate_product_state(), + generate_w_state(), + generate_random_circuit(random_depth), + ] +} + +// ------------------------------------------------------------------- +// Unitary matrix helpers (2^n x 2^n complex matrices stored as re/im) +// ------------------------------------------------------------------- + +/// Identity matrix for n qubits. +fn identity(n_qubits: usize) -> (Vec, Vec) { + let dim = 1 << n_qubits; + let mut re = vec![0.0; dim * dim]; + let im = vec![0.0; dim * dim]; + for i in 0..dim { + re[i * dim + i] = 1.0; + } + (re, im) +} + +/// Multiply two complex matrices C = A * B (dim x dim). +fn matmul( + a_re: &[f64], a_im: &[f64], + b_re: &[f64], b_im: &[f64], + dim: usize, +) -> (Vec, Vec) { + let mut c_re = vec![0.0; dim * dim]; + let mut c_im = vec![0.0; dim * dim]; + for i in 0..dim { + for k in 0..dim { + let ar = a_re[i * dim + k]; + let ai = a_im[i * dim + k]; + if ar.abs() < 1e-15 && ai.abs() < 1e-15 { + continue; + } + for j in 0..dim { + let br = b_re[k * dim + j]; + let bi = b_im[k * dim + j]; + c_re[i * dim + j] += ar * br - ai * bi; + c_im[i * dim + j] += ar * bi + ai * br; + } + } + } + (c_re, c_im) +} + +/// Apply a 2-qubit gate (4x4 unitary) to qubits (q0, q1) in an n-qubit system. +/// q0 is the control (higher-order bit in the 2-qubit subspace), q1 is the target. +fn apply_two_qubit_gate( + u_re: &mut Vec, u_im: &mut Vec, + gate_re: &[f64; 16], gate_im: &[f64; 16], + n_qubits: usize, q0: usize, q1: usize, +) { + let dim = 1 << n_qubits; + // Build the full-size gate by tensoring with identities + let mut full_re = vec![0.0; dim * dim]; + let mut full_im = vec![0.0; dim * dim]; + + for row in 0..dim { + for col in 0..dim { + // Extract the 2-bit index for (q0, q1) + let r0 = (row >> (n_qubits - 1 - q0)) & 1; + let r1 = (row >> (n_qubits - 1 - q1)) & 1; + let c0 = (col >> (n_qubits - 1 - q0)) & 1; + let c1 = (col >> (n_qubits - 1 - q1)) & 1; + + // Check that all other qubits match + let mut other_match = true; + for q in 0..n_qubits { + if q != q0 && q != q1 { + if ((row >> (n_qubits - 1 - q)) & 1) + != ((col >> (n_qubits - 1 - q)) & 1) + { + other_match = false; + break; + } + } + } + + if other_match { + let gi = (r0 * 2 + r1) * 4 + (c0 * 2 + c1); + full_re[row * dim + col] = gate_re[gi]; + full_im[row * dim + col] = gate_im[gi]; + } + } + } + + let (new_re, new_im) = matmul(&full_re, &full_im, u_re, u_im, dim); + *u_re = new_re; + *u_im = new_im; +} + +/// Apply a single-qubit gate (2x2 unitary) to qubit q in an n-qubit system. +fn apply_single_qubit_gate( + u_re: &mut Vec, u_im: &mut Vec, + gate_re: &[f64; 4], gate_im: &[f64; 4], + n_qubits: usize, q: usize, +) { + let dim = 1 << n_qubits; + let mut full_re = vec![0.0; dim * dim]; + let mut full_im = vec![0.0; dim * dim]; + + for row in 0..dim { + for col in 0..dim { + let rq = (row >> (n_qubits - 1 - q)) & 1; + let cq = (col >> (n_qubits - 1 - q)) & 1; + + // All other qubits must match (identity) + let mut other_match = true; + for qq in 0..n_qubits { + if qq != q { + if ((row >> (n_qubits - 1 - qq)) & 1) + != ((col >> (n_qubits - 1 - qq)) & 1) + { + other_match = false; + break; + } + } + } + + if other_match { + let gi = rq * 2 + cq; + full_re[row * dim + col] = gate_re[gi]; + full_im[row * dim + col] = gate_im[gi]; + } + } + } + + let (new_re, new_im) = matmul(&full_re, &full_im, u_re, u_im, dim); + *u_re = new_re; + *u_im = new_im; +} + +/// Convert a unitary matrix to a TPM: TPM[i][j] = |U[j][i]|^2 = ||^2 +/// Note: U|i> gives column i of U, so P(j|i) = |U[j,i]|^2. +fn unitary_to_tpm(u_re: &[f64], u_im: &[f64], dim: usize) -> Vec { + let mut tpm = vec![0.0; dim * dim]; + for i in 0..dim { + for j in 0..dim { + let re = u_re[j * dim + i]; + let im = u_im[j * dim + i]; + tpm[i * dim + j] = re * re + im * im; + } + } + // Row-normalize to correct for floating point + for i in 0..dim { + let sum: f64 = (0..dim).map(|j| tpm[i * dim + j]).sum(); + if sum > 1e-30 { + for j in 0..dim { + tpm[i * dim + j] /= sum; + } + } + } + tpm +} + +// ------------------------------------------------------------------- +// Standard gates +// ------------------------------------------------------------------- + +/// Hadamard gate +const H_RE: [f64; 4] = [ + std::f64::consts::FRAC_1_SQRT_2, + std::f64::consts::FRAC_1_SQRT_2, + std::f64::consts::FRAC_1_SQRT_2, + -std::f64::consts::FRAC_1_SQRT_2, +]; +const H_IM: [f64; 4] = [0.0, 0.0, 0.0, 0.0]; + +/// CNOT gate (control=0, target=1 in 2-qubit basis |00>, |01>, |10>, |11>) +const CNOT_RE: [f64; 16] = [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 1.0, 0.0, +]; +const CNOT_IM: [f64; 16] = [0.0; 16]; + +/// X (NOT) gate +#[allow(dead_code)] +const X_RE: [f64; 4] = [0.0, 1.0, 1.0, 0.0]; +#[allow(dead_code)] +const X_IM: [f64; 4] = [0.0; 4]; + +// ------------------------------------------------------------------- +// Circuit generators +// ------------------------------------------------------------------- + +/// Bell state: H(0) then CNOT(0,1) on |00> +/// Creates (|00> + |11>) / sqrt(2) -- maximally entangled 2-qubit state. +fn generate_bell_state() -> QuantumCircuit { + let n = 2; + let dim = 1 << n; + let (mut u_re, mut u_im) = identity(n); + + // H on qubit 0 + apply_single_qubit_gate(&mut u_re, &mut u_im, &H_RE, &H_IM, n, 0); + // CNOT(0, 1) + apply_two_qubit_gate(&mut u_re, &mut u_im, &CNOT_RE, &CNOT_IM, n, 0, 1); + + let tpm = unitary_to_tpm(&u_re, &u_im, dim); + + QuantumCircuit { + name: "Bell State".to_string(), + n_qubits: n, + description: "|00> + |11> / sqrt(2) -- maximally entangled pair".to_string(), + tpm, + unitary_re: u_re, + unitary_im: u_im, + } +} + +/// GHZ state: H(0) then CNOT(0,1) then CNOT(0,2) on |000> +/// Creates (|000> + |111>) / sqrt(2) -- genuine 3-party entanglement. +fn generate_ghz_state() -> QuantumCircuit { + let n = 3; + let dim = 1 << n; + let (mut u_re, mut u_im) = identity(n); + + apply_single_qubit_gate(&mut u_re, &mut u_im, &H_RE, &H_IM, n, 0); + apply_two_qubit_gate(&mut u_re, &mut u_im, &CNOT_RE, &CNOT_IM, n, 0, 1); + apply_two_qubit_gate(&mut u_re, &mut u_im, &CNOT_RE, &CNOT_IM, n, 0, 2); + + let tpm = unitary_to_tpm(&u_re, &u_im, dim); + + QuantumCircuit { + name: "GHZ State".to_string(), + n_qubits: n, + description: "|000> + |111> / sqrt(2) -- genuinely multipartite entangled".to_string(), + tpm, + unitary_re: u_re, + unitary_im: u_im, + } +} + +/// Product state: Identity on |000> -- no entanglement at all. +fn generate_product_state() -> QuantumCircuit { + let n = 3; + let dim = 1 << n; + let (u_re, u_im) = identity(n); + let tpm = unitary_to_tpm(&u_re, &u_im, dim); + + QuantumCircuit { + name: "Product State".to_string(), + n_qubits: n, + description: "|0> x |0> x |0> -- completely separable, no entanglement".to_string(), + tpm, + unitary_re: u_re, + unitary_im: u_im, + } +} + +/// W state: (|001> + |010> + |100>) / sqrt(3) +/// Constructed via specific rotation sequence. +fn generate_w_state() -> QuantumCircuit { + let n = 3; + let dim = 1 << n; + + // Build the W-state preparation circuit: + // 1. Ry(arccos(1/sqrt(3))) on qubit 0 + // 2. Controlled-Ry(arccos(1/sqrt(2))) on qubit 1 conditioned on qubit 0 = |1> + // 3. CNOT(1, 2) + // 4. X on qubit 0 (flip to get the right phase) + // + // Simpler approach: directly construct the unitary that maps |000> to W state + // and extend to a full unitary. + + // Direct construction: we define U such that U|000> = W state. + // Build a unitary whose first column is W = [0, 1/sqrt(3), 1/sqrt(3), 0, 1/sqrt(3), 0, 0, 0] + // Use Gram-Schmidt to complete it. + let s3 = 1.0 / 3.0f64.sqrt(); + let mut u_re = vec![0.0; dim * dim]; + let u_im = vec![0.0; dim * dim]; + + // First column: W state + // |001>=1, |010>=2, |100>=4 + u_re[1 * dim + 0] = s3; + u_re[2 * dim + 0] = s3; + u_re[4 * dim + 0] = s3; + + // Complete the unitary with Gram-Schmidt + gram_schmidt_complete(&mut u_re, dim); + + let tpm = unitary_to_tpm(&u_re, &u_im, dim); + + QuantumCircuit { + name: "W State".to_string(), + n_qubits: n, + description: "|001> + |010> + |100> / sqrt(3) -- bipartite entanglement".to_string(), + tpm, + unitary_re: u_re, + unitary_im: u_im, + } +} + +/// Complete a partially filled unitary matrix using Gram-Schmidt. +/// Assumes column 0 is already set; fills columns 1..dim. +fn gram_schmidt_complete(u: &mut [f64], dim: usize) { + // Start with the standard basis vectors and orthogonalize against existing columns + let mut cols_done = 1; // column 0 is already set + + for candidate_col in 0..dim { + if cols_done >= dim { + break; + } + + // Start with e_{candidate_col} + let mut v = vec![0.0; dim]; + v[candidate_col] = 1.0; + + // Subtract projections onto existing columns + for c in 0..cols_done { + let mut dot = 0.0; + for r in 0..dim { + dot += u[r * dim + c] * v[r]; + } + for r in 0..dim { + v[r] -= dot * u[r * dim + c]; + } + } + + // Check if remaining vector is non-zero + let norm: f64 = v.iter().map(|x| x * x).sum::().sqrt(); + if norm < 1e-10 { + continue; + } + + // Normalize and store + for r in 0..dim { + u[r * dim + cols_done] = v[r] / norm; + } + cols_done += 1; + } +} + +/// Random circuit: depth layers of random single-qubit rotations + CNOT gates. +fn generate_random_circuit(depth: usize) -> QuantumCircuit { + let n = 3; + let dim = 1 << n; + let (mut u_re, mut u_im) = identity(n); + let mut rng = ChaCha8Rng::seed_from_u64(42); + + for _layer in 0..depth { + // Random single-qubit rotations on each qubit + for q in 0..n { + let theta = rng.gen::() * std::f64::consts::PI; + let phi = rng.gen::() * 2.0 * std::f64::consts::PI; + let (ct, st) = (theta.cos(), theta.sin()); + let (cp, sp) = (phi.cos(), phi.sin()); + + // General rotation: Rz(phi) * Ry(theta) + let gate_re = [ct, -st, st * cp, ct * cp]; + let gate_im = [0.0, 0.0, st * sp, ct * sp]; + // Correct: U = [[cos(t), -sin(t)], [sin(t)*e^(i*p), cos(t)*e^(i*p)]] + // But we just need a unitary rotation, exact form is less important + // for generating random entanglement patterns. + apply_single_qubit_gate(&mut u_re, &mut u_im, &gate_re, &gate_im, n, q); + } + + // CNOT between adjacent qubits + let q0 = rng.gen_range(0..n); + let q1 = (q0 + 1) % n; + apply_two_qubit_gate(&mut u_re, &mut u_im, &CNOT_RE, &CNOT_IM, n, q0, q1); + } + + let tpm = unitary_to_tpm(&u_re, &u_im, dim); + + QuantumCircuit { + name: "Random Circuit".to_string(), + n_qubits: n, + description: format!( + "3 qubits, depth {} -- random rotations + CNOT", + depth + ), + tpm, + unitary_re: u_re, + unitary_im: u_im, + } +} diff --git a/examples/quantum-consciousness/src/main.rs b/examples/quantum-consciousness/src/main.rs new file mode 100644 index 00000000..45beeb83 --- /dev/null +++ b/examples/quantum-consciousness/src/main.rs @@ -0,0 +1,84 @@ +//! Quantum Circuit Consciousness Explorer +//! +//! Applies IIT Phi to quantum circuit measurement statistics to explore +//! the relationship between entanglement and integrated information. + +mod analysis; +mod data; +mod report; + +fn main() { + println!("+==========================================================+"); + println!("| Quantum Circuit Consciousness Explorer -- IIT 4.0 |"); + println!("| Bridging entanglement and integrated information |"); + println!("+==========================================================+"); + + // Parse CLI args + let args: Vec = std::env::args().collect(); + let output = parse_str_arg(&args, "--output", "quantum_report.svg"); + let depth = parse_arg(&args, "--depth", 5usize); + + println!("\nConfiguration:"); + println!(" Output: {}", output); + println!(" Random depth: {}", depth); + + // Step 1: Generate quantum circuit TPMs + println!("\n=== Step 1: Generating Quantum Circuit Data ==="); + let circuits = data::generate_all_circuits(depth); + for c in &circuits { + println!( + " {}: {} qubits, {}x{} TPM", + c.name, c.n_qubits, c.tpm_size(), c.tpm_size() + ); + } + + // Step 2: Run analysis + println!("\n=== Step 2: IIT Phi Analysis ==="); + let results = analysis::run_quantum_analysis(&circuits); + + // Step 3: Print text summary + println!("\n=== Step 3: Results Summary ==="); + report::print_summary(&results); + + // Step 4: Generate SVG report + let svg = report::generate_svg(&results); + std::fs::write(output, &svg).expect("Failed to write SVG report"); + println!( + "\nSVG report saved to: {}", + parse_str_arg(&args, "--output", "quantum_report.svg") + ); + + // Entanglement hierarchy check + println!("\n+==========================================================+"); + println!("| Entanglement Hierarchy vs Phi: |"); + println!("| Expected: Product < W < Bell <= GHZ |"); + println!("| Actual: |"); + let mut sorted: Vec<_> = results + .iter() + .filter(|r| r.name != "Random Circuit") + .collect(); + sorted.sort_by(|a, b| a.full_phi.partial_cmp(&b.full_phi).unwrap()); + for (i, r) in sorted.iter().enumerate() { + println!( + "| {}. {:25} Phi = {:.6} |", + i + 1, + r.name, + r.full_phi + ); + } + println!("+==========================================================+"); +} + +fn parse_arg(args: &[String], name: &str, default: T) -> T { + args.windows(2) + .find(|w| w[0] == name) + .and_then(|w| w[1].parse().ok()) + .unwrap_or(default) +} + +fn parse_str_arg<'a>(args: &'a [String], name: &str, default: &'a str) -> &'a str { + args.windows(2) + .find(|w| w[0] == name) + .map(|w| w[1].as_str()) + .unwrap_or(default) +} diff --git a/examples/quantum-consciousness/src/report.rs b/examples/quantum-consciousness/src/report.rs new file mode 100644 index 00000000..35fac268 --- /dev/null +++ b/examples/quantum-consciousness/src/report.rs @@ -0,0 +1,309 @@ +//! Report generation: text summary and SVG circuit comparison. + +use crate::analysis::CircuitResult; + +/// Print a text summary of all circuit results. +pub fn print_summary(results: &[CircuitResult]) { + println!("\n{:-<70}", ""); + println!( + "{:<25} {:>6} {:>10} {:>10} {:>10}", + "Circuit", "Qubits", "Phi", "Emergence", "SVD Rank" + ); + println!("{:-<70}", ""); + + for r in results { + println!( + "{:<25} {:>6} {:>10.6} {:>10.4} {:>5}/{:<4}", + r.name, + r.n_qubits, + r.full_phi, + r.emergence.causal_emergence, + r.svd_emergence.effective_rank, + r.tpm_size + ); + } + println!("{:-<70}", ""); + + for r in results { + println!("\n=== {} ===", r.name); + println!(" Description: {}", r.description); + println!(" Qubits: {}", r.n_qubits); + println!(" TPM size: {}x{}", r.tpm_size, r.tpm_size); + println!(" Phi: {:.6} ({})", r.full_phi, r.algorithm); + println!(" EI (micro): {:.4} bits", r.emergence.ei_micro); + println!(" EI (macro): {:.4} bits", r.emergence.ei_macro); + println!( + " Causal emergence: {:.4}", + r.emergence.causal_emergence + ); + println!(" Determinism: {:.4}", r.emergence.determinism); + println!(" Degeneracy: {:.4}", r.emergence.degeneracy); + println!( + " Effective rank: {}/{}", + r.svd_emergence.effective_rank, r.tpm_size + ); + println!( + " Spectral entropy: {:.4}", + r.svd_emergence.spectral_entropy + ); + println!( + " Emergence index: {:.4}", + r.svd_emergence.emergence_index + ); + println!( + " Reversibility: {:.4}", + r.svd_emergence.reversibility + ); + } +} + +/// Generate a self-contained SVG report with bar charts and circuit diagrams. +pub fn generate_svg(results: &[CircuitResult]) -> String { + let width = 1200; + let total_height = 900; + + let mut svg = String::with_capacity(20_000); + svg.push_str(&format!( + r#" + + + +Quantum Circuit Consciousness Analysis Report +IIT Phi applied to quantum circuit measurement statistics +"#, + width, total_height, width, total_height + )); + + // Panel 1: Phi comparison bar chart (top) + svg.push_str(&render_phi_bars(results, 50, 100, 500, 300)); + + // Panel 2: Emergence comparison (top right) + svg.push_str(&render_emergence_bars(results, 600, 100, 550, 300)); + + // Panel 3: Circuit descriptions and stats table (bottom) + svg.push_str(&render_stats_table(results, 50, 450, 1100, 400)); + + svg.push_str("\n"); + svg +} + +/// Render Phi comparison bar chart. +fn render_phi_bars(results: &[CircuitResult], x: i32, y: i32, w: i32, h: i32) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "\n", + w, h + )); + s.push_str(&format!( + "Phi Comparison\n", + w / 2 + )); + + let max_phi = results + .iter() + .map(|r| r.full_phi) + .fold(0.0f64, f64::max) + .max(1e-10); + + let bar_h = ((h - 60) as f64 / results.len() as f64).min(40.0); + let margin = 10; + + for (i, r) in results.iter().enumerate() { + let ry = margin + (i as f64 * (bar_h + 8.0)) as i32 + 20; + let bw = ((r.full_phi / max_phi) * (w - 200) as f64) as i32; + + // Label + s.push_str(&format!( + "{}\n", + ry + bar_h as i32 / 2 + 4, + r.name + )); + + // Bar + s.push_str(&format!( + "\n", + ry, + bw.max(1), + bar_h as i32 - 4 + )); + + // Value + s.push_str(&format!( + "{:.6}\n", + 155 + bw, + ry + bar_h as i32 / 2 + 4, + r.full_phi + )); + } + + s.push_str("\n"); + s +} + +/// Render emergence comparison bar chart. +fn render_emergence_bars( + results: &[CircuitResult], + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "\n", + w, h + )); + s.push_str(&format!( + "Emergence Comparison\n", + w / 2 + )); + + let max_ei = results + .iter() + .map(|r| r.emergence.ei_micro) + .fold(0.0f64, f64::max) + .max(1e-10); + + let bar_h = ((h - 60) as f64 / results.len() as f64).min(40.0); + let margin = 10; + let half_w = (w - 200) / 2; + + for (i, r) in results.iter().enumerate() { + let ry = margin + (i as f64 * (bar_h + 8.0)) as i32 + 20; + + // Label + s.push_str(&format!( + "{}\n", + ry + bar_h as i32 / 2 + 4, + r.name + )); + + // EI bar (blue) + let ei_w = ((r.emergence.ei_micro / max_ei) * half_w as f64) as i32; + s.push_str(&format!( + "\n", + ry, + ei_w.max(1), + (bar_h as i32 - 4) / 2 + )); + + // Emergence bar (orange) + let emg_w = ((r.emergence.causal_emergence.abs() / max_ei) * half_w as f64) as i32; + s.push_str(&format!( + "\n", + ry + (bar_h as i32 - 4) / 2, + emg_w.max(1), + (bar_h as i32 - 4) / 2 + )); + + // Values + s.push_str(&format!( + "EI={:.3}\n", + 155 + ei_w, + ry + (bar_h as i32) / 4 + 3, + r.emergence.ei_micro + )); + s.push_str(&format!( + "CE={:.3}\n", + 155 + emg_w, + ry + 3 * (bar_h as i32) / 4 + 3, + r.emergence.causal_emergence + )); + } + + // Legend + let ly = h - 25; + s.push_str(&format!( + "\n\ + EI (micro)\n\ + \n\ + Causal Emergence\n", + ly, ly + 10, ly, ly + 10 + )); + + s.push_str("\n"); + s +} + +/// Render stats table at the bottom. +fn render_stats_table( + results: &[CircuitResult], + x: i32, + y: i32, + w: i32, + h: i32, +) -> String { + let mut s = format!("\n", x, y); + s.push_str(&format!( + "\n", + w, h + )); + s.push_str(&format!( + "Detailed Results\n", + w / 2 + )); + + // Header + let cols = [15, 180, 260, 380, 500, 620, 740, 870, 990]; + let headers = [ + "Circuit", "Qubits", "Phi", "Algorithm", "EI_micro", + "Emergence", "SVD Rank", "Emg Index", "Reversibility", + ]; + for (col, hdr) in cols.iter().zip(headers.iter()) { + s.push_str(&format!( + "{}\n", + col, hdr + )); + } + s.push_str(&format!( + "\n", + w - 10 + )); + + for (i, r) in results.iter().enumerate() { + let ry = 50 + i as i32 * 22; + let vals = [ + r.name.clone(), + format!("{}", r.n_qubits), + format!("{:.6}", r.full_phi), + r.algorithm.clone(), + format!("{:.4}", r.emergence.ei_micro), + format!("{:.4}", r.emergence.causal_emergence), + format!("{}/{}", r.svd_emergence.effective_rank, r.tpm_size), + format!("{:.4}", r.svd_emergence.emergence_index), + format!("{:.4}", r.svd_emergence.reversibility), + ]; + for (col, val) in cols.iter().zip(vals.iter()) { + s.push_str(&format!( + "{}\n", + col, ry, val + )); + } + } + + // Description section + let desc_y = 50 + results.len() as i32 * 22 + 30; + s.push_str(&format!( + "Circuit Descriptions\n", + desc_y + )); + for (i, r) in results.iter().enumerate() { + s.push_str(&format!( + "{}: {}\n", + desc_y + 20 + i as i32 * 18, + r.name, + r.description + )); + } + + s.push_str("\n"); + s +}