mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-25 23:24:03 +00:00
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 <ruv@ruv.net>
This commit is contained in:
parent
2eefef68bb
commit
11c72cfa7f
26 changed files with 4427 additions and 0 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
117
docs/research/climate-consciousness/RESEARCH.md
Normal file
117
docs/research/climate-consciousness/RESEARCH.md
Normal file
|
|
@ -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.
|
||||
94
docs/research/gene-consciousness/RESEARCH.md
Normal file
94
docs/research/gene-consciousness/RESEARCH.md
Normal file
|
|
@ -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.
|
||||
16
examples/climate-consciousness/Cargo.toml
Normal file
16
examples/climate-consciousness/Cargo.toml
Normal file
|
|
@ -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"
|
||||
250
examples/climate-consciousness/src/analysis.rs
Normal file
250
examples/climate-consciousness/src/analysis.rs
Normal file
|
|
@ -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<f64>,
|
||||
/// 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::<f64>() / null_phis.len() as f64
|
||||
};
|
||||
let null_std = if null_phis.len() > 1 {
|
||||
(null_phis
|
||||
.iter()
|
||||
.map(|&p| (p - null_mean).powi(2))
|
||||
.sum::<f64>()
|
||||
/ (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,
|
||||
}
|
||||
}
|
||||
274
examples/climate-consciousness/src/data.rs
Normal file
274
examples/climate-consciousness/src/data.rs
Normal file
|
|
@ -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<f64>,
|
||||
/// Variant label.
|
||||
pub variant: String,
|
||||
}
|
||||
|
||||
/// Transition probability matrix for consciousness analysis.
|
||||
pub struct TransitionMatrix {
|
||||
pub size: usize,
|
||||
pub data: Vec<f64>,
|
||||
}
|
||||
|
||||
/// 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<f64> = 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()
|
||||
}
|
||||
89
examples/climate-consciousness/src/main.rs
Normal file
89
examples/climate-consciousness/src/main.rs
Normal file
|
|
@ -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<String> = 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<T: std::str::FromStr>(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)
|
||||
}
|
||||
485
examples/climate-consciousness/src/report.rs
Normal file
485
examples/climate-consciousness/src/report.rs
Normal file
|
|
@ -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::<f64>() / 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#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1600" font-family="monospace" font-size="12">
|
||||
<style>
|
||||
.title { font-size: 20px; font-weight: bold; fill: #333; }
|
||||
.subtitle { font-size: 14px; fill: #666; }
|
||||
.axis-label { font-size: 11px; fill: #444; }
|
||||
.bar { fill: #4a90d9; }
|
||||
.bar-elnino { fill: #e74c3c; }
|
||||
.bar-null { fill: #ccc; }
|
||||
.bar-month { fill: #2ecc71; }
|
||||
.node-pacific { fill: #3498db; }
|
||||
.node-atlantic { fill: #e67e22; }
|
||||
.node-polar { fill: #9b59b6; }
|
||||
.edge { stroke: #bbb; stroke-width: 0.5; fill: none; }
|
||||
.edge-strong { stroke: #2c3e50; stroke-width: 2; fill: none; }
|
||||
</style>
|
||||
<rect width="1200" height="1600" fill="white"/>
|
||||
<text x="600" y="40" text-anchor="middle" class="title">Climate Teleconnection Consciousness Report</text>
|
||||
<text x="600" y="65" text-anchor="middle" class="subtitle">IIT 4.0 Phi Analysis of Climate Mode Interactions</text>
|
||||
"#,
|
||||
);
|
||||
|
||||
// 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("</svg>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Climate Mode Teleconnections (Neutral)</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\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!(
|
||||
"<line x1=\"{:.0}\" y1=\"{:.0}\" x2=\"{:.0}\" y2=\"{:.0}\" class=\"{}\" stroke-width=\"{:.1}\"/>\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!(
|
||||
"<circle cx=\"{:.0}\" cy=\"{:.0}\" r=\"18\" class=\"{}\" stroke=\"#333\" stroke-width=\"1\"/>\n",
|
||||
px, py, node_classes[i]
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{:.0}\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-size=\"9\" fill=\"white\" font-weight=\"bold\">{}</text>\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!(
|
||||
"<circle cx=\"{}\" cy=\"{}\" r=\"6\" class=\"{}\"/>\n",
|
||||
lx, legend_y, class
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"axis-label\" dominant-baseline=\"middle\">{}</text>\n",
|
||||
lx + 10, legend_y, name
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Regional Phi: Neutral vs El Nino</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
let mut all_phis: Vec<f64> = 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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar\" rx=\"2\"/>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar-elnino\" rx=\"2\"/>\n",
|
||||
gx + bar_w + 2.0, h - 30 - ebh, bar_w, ebh
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\" font-size=\"9\">{}</text>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar\" rx=\"2\"/>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar-elnino\" rx=\"2\"/>\n",
|
||||
gx + bar_w + 2.0, h - 30 - ebh, bar_w, ebh
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\" font-size=\"9\">Full</text>\n",
|
||||
gx + bar_w, h - 15
|
||||
));
|
||||
|
||||
// Legend
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"{}\" y=\"10\" width=\"12\" height=\"12\" class=\"bar\"/>\n", w - 150
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"20\" class=\"axis-label\">Neutral</text>\n", w - 135
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"{}\" y=\"28\" width=\"12\" height=\"12\" class=\"bar-elnino\"/>\n", w - 150
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"38\" class=\"axis-label\">El Nino</text>\n", w - 135
|
||||
));
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Seasonal Phi Cycle (12 months)</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
if monthly.is_empty() {
|
||||
s.push_str("</g>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar-month\" rx=\"2\"/>\n",
|
||||
bx, h - bh - 25, (bar_w - 3.0).max(1.0), bh
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\" font-size=\"9\">{}</text>\n",
|
||||
bx + bar_w / 2.0, h - 10, month
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Null Distribution (Shuffled Correlations) vs Observed Phi</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
if null_phis.is_empty() {
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\">No null samples</text>\n",
|
||||
w / 2, h / 2
|
||||
));
|
||||
s.push_str("</g>\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!(
|
||||
"<rect x=\"{:.1}\" y=\"{}\" width=\"{:.1}\" height=\"{}\" class=\"bar-null\" rx=\"1\"/>\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!(
|
||||
"<line x1=\"{}\" y1=\"0\" x2=\"{}\" y2=\"{}\" stroke=\"#e74c3c\" stroke-width=\"2\"/>\n",
|
||||
obs_x, obs_x, h - 20
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" fill=\"#e74c3c\" font-size=\"10\">Observed</text>\n",
|
||||
obs_x, h - 5
|
||||
));
|
||||
|
||||
s.push_str("</g>\n");
|
||||
s
|
||||
}
|
||||
|
||||
/// Render summary statistics text.
|
||||
fn render_summary_stats(results: &AnalysisResults, x: i32, y: i32) -> String {
|
||||
let mut s = format!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str("<text x=\"0\" y=\"0\" class=\"subtitle\">Summary Statistics</text>\n");
|
||||
|
||||
let null_mean = if results.null_phis.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
results.null_phis.iter().sum::<f64>() / 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!(
|
||||
"<text x=\"0\" y=\"{}\" class=\"axis-label\">{}</text>\n",
|
||||
20 + i * 18, line
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\n");
|
||||
s
|
||||
}
|
||||
16
examples/ecosystem-consciousness/Cargo.toml
Normal file
16
examples/ecosystem-consciousness/Cargo.toml
Normal file
|
|
@ -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"
|
||||
109
examples/ecosystem-consciousness/RESEARCH.md
Normal file
109
examples/ecosystem-consciousness/RESEARCH.md
Normal file
|
|
@ -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.
|
||||
152
examples/ecosystem-consciousness/src/analysis.rs
Normal file
152
examples/ecosystem-consciousness/src/analysis.rs
Normal file
|
|
@ -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<String>,
|
||||
/// Species names
|
||||
pub species_names: Vec<String>,
|
||||
}
|
||||
|
||||
/// 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<EcosystemResult> {
|
||||
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<String> = eco
|
||||
.species
|
||||
.iter()
|
||||
.map(|s| s.trophic_level.color().to_string())
|
||||
.collect();
|
||||
let species_names: Vec<String> = 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
|
||||
}
|
||||
345
examples/ecosystem-consciousness/src/data.rs
Normal file
345
examples/ecosystem-consciousness/src/data.rs
Normal file
|
|
@ -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<Species>,
|
||||
/// Row-major adjacency matrix (energy flow weights, not yet normalized).
|
||||
pub adjacency: Vec<f64>,
|
||||
/// Row-normalized TPM for IIT analysis.
|
||||
pub tpm: Vec<f64>,
|
||||
}
|
||||
|
||||
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<f64> {
|
||||
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<Ecosystem> {
|
||||
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::<f64>() * 0.05);
|
||||
set_symmetric(&mut adj, n, 0, 2, 0.10 + rng.gen::<f64>() * 0.05);
|
||||
set_symmetric(&mut adj, n, 1, 2, 0.12 + rng.gen::<f64>() * 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::<f64>() * 0.2;
|
||||
// Nutrient recycling back
|
||||
adj[prod * n + herb] = 0.02 + rng.gen::<f64>() * 0.02;
|
||||
}
|
||||
}
|
||||
|
||||
// Primary -> Secondary consumers (strong)
|
||||
for herb in 3..6 {
|
||||
for pred in 6..9 {
|
||||
adj[pred * n + herb] = 0.25 + rng.gen::<f64>() * 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-predation among secondary consumers
|
||||
set_edge(&mut adj, n, 6, 7, 0.05 + rng.gen::<f64>() * 0.03);
|
||||
set_edge(&mut adj, n, 7, 8, 0.04 + rng.gen::<f64>() * 0.03);
|
||||
set_edge(&mut adj, n, 8, 6, 0.03 + rng.gen::<f64>() * 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::<f64>() * 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::<f64>() * 0.10;
|
||||
}
|
||||
}
|
||||
|
||||
// Decomposer cross-interactions
|
||||
set_symmetric(&mut adj, n, 9, 10, 0.10 + rng.gen::<f64>() * 0.05);
|
||||
set_symmetric(&mut adj, n, 10, 11, 0.08 + rng.gen::<f64>() * 0.04);
|
||||
set_symmetric(&mut adj, n, 9, 11, 0.06 + rng.gen::<f64>() * 0.03);
|
||||
|
||||
// Secondary consumers occasionally eat decomposers (omnivory)
|
||||
for pred in 6..9 {
|
||||
adj[pred * n + 11] = 0.03 + rng.gen::<f64>() * 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::<f64>() * 0.1; // pest eats crop
|
||||
adj[2 * n + 1] = 0.6 + rng.gen::<f64>() * 0.1; // ladybug eats pest
|
||||
adj[2 * n + 7] = 0.3 + rng.gen::<f64>() * 0.1; // ladybug eats resistant pest
|
||||
|
||||
// Pollinator weakly interacts with crop
|
||||
adj[3 * n + 0] = 0.2 + rng.gen::<f64>() * 0.05; // bee visits crop
|
||||
adj[0 * n + 3] = 0.15 + rng.gen::<f64>() * 0.05; // crop benefits from bee
|
||||
|
||||
// Soil microbes support crop
|
||||
adj[0 * n + 4] = 0.25 + rng.gen::<f64>() * 0.05; // nitrogen fixation
|
||||
adj[0 * n + 5] = 0.20 + rng.gen::<f64>() * 0.05; // mycorrhizal support
|
||||
|
||||
// Crop waste feeds soil microbes (weak)
|
||||
adj[4 * n + 0] = 0.10 + rng.gen::<f64>() * 0.03;
|
||||
adj[5 * n + 0] = 0.08 + rng.gen::<f64>() * 0.03;
|
||||
|
||||
// Weed competes with crop (negative interaction modeled as weak link)
|
||||
adj[6 * n + 0] = 0.05 + rng.gen::<f64>() * 0.02;
|
||||
adj[0 * n + 6] = 0.02 + rng.gen::<f64>() * 0.01;
|
||||
|
||||
// Resistant pest also eats crop
|
||||
adj[7 * n + 0] = 0.5 + rng.gen::<f64>() * 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::<f64>() * 0.1;
|
||||
// Parrotfish grazes algae off coral (mutually beneficial)
|
||||
adj[3 * n + 1] = 0.4 + rng.gen::<f64>() * 0.1;
|
||||
adj[0 * n + 3] = 0.3 + rng.gen::<f64>() * 0.1; // coral benefits from parrotfish
|
||||
// Grouper eats smaller fish
|
||||
adj[4 * n + 2] = 0.3 + rng.gen::<f64>() * 0.1;
|
||||
adj[4 * n + 3] = 0.2 + rng.gen::<f64>() * 0.1;
|
||||
// Sea urchin grazes algae
|
||||
adj[5 * n + 1] = 0.35 + rng.gen::<f64>() * 0.1;
|
||||
// Crown-of-thorns eats coral (destructive)
|
||||
adj[6 * n + 0] = 0.4 + rng.gen::<f64>() * 0.1;
|
||||
// Shark eats grouper and turtle
|
||||
adj[7 * n + 4] = 0.35 + rng.gen::<f64>() * 0.1;
|
||||
adj[7 * n + 8] = 0.15 + rng.gen::<f64>() * 0.05;
|
||||
// Sea turtle eats algae and invertebrates
|
||||
adj[8 * n + 1] = 0.2 + rng.gen::<f64>() * 0.05;
|
||||
adj[8 * n + 5] = 0.15 + rng.gen::<f64>() * 0.05;
|
||||
adj[8 * n + 6] = 0.10 + rng.gen::<f64>() * 0.05;
|
||||
// Plankton feeds coral and clownfish
|
||||
adj[0 * n + 9] = 0.3 + rng.gen::<f64>() * 0.1;
|
||||
adj[2 * n + 9] = 0.2 + rng.gen::<f64>() * 0.05;
|
||||
// Algae and coral compete for space
|
||||
adj[1 * n + 0] = 0.1 + rng.gen::<f64>() * 0.05;
|
||||
adj[0 * n + 1] = 0.08 + rng.gen::<f64>() * 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::<f64>() * 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;
|
||||
}
|
||||
71
examples/ecosystem-consciousness/src/main.rs
Normal file
71
examples/ecosystem-consciousness/src/main.rs
Normal file
|
|
@ -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<String> = 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)
|
||||
}
|
||||
236
examples/ecosystem-consciousness/src/report.rs
Normal file
236
examples/ecosystem-consciousness/src/report.rs
Normal file
|
|
@ -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#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {} {}" font-family="monospace" font-size="12">
|
||||
<style>
|
||||
.title {{ font-size: 20px; font-weight: bold; fill: #333; }}
|
||||
.subtitle {{ font-size: 14px; fill: #666; font-weight: bold; }}
|
||||
.label {{ font-size: 10px; fill: #333; }}
|
||||
.bar {{ fill: #4a90d9; }}
|
||||
.bar-neg {{ fill: #e74c3c; }}
|
||||
.stat {{ font-size: 11px; fill: #444; }}
|
||||
</style>
|
||||
<rect width="{}" height="{}" fill="white"/>
|
||||
<text x="600" y="40" text-anchor="middle" class="title">Ecosystem Consciousness Analysis Report</text>
|
||||
<text x="600" y="65" text-anchor="middle" class="stat">IIT Phi measures integrated information in food web networks</text>
|
||||
"#,
|
||||
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("</svg>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
|
||||
// Panel background
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\" rx=\"5\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
// Title
|
||||
s.push_str(&format!(
|
||||
"<text x=\"15\" y=\"25\" class=\"subtitle\">{} (n={}, Phi={:.6})</text>\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!(
|
||||
"<circle cx=\"{:.0}\" cy=\"{:.0}\" r=\"{:.1}\" fill=\"{}\" stroke=\"#333\" stroke-width=\"1\"/>\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!(
|
||||
"<text x=\"{:.0}\" y=\"{:.0}\" text-anchor=\"middle\" class=\"label\">{}</text>\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!(
|
||||
"<text x=\"{}\" y=\"50\" class=\"subtitle\">Species Phi Contributions</text>\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!(
|
||||
"<text x=\"{}\" y=\"{}\" text-anchor=\"end\" class=\"label\">{}</text>\n",
|
||||
bar_x + 120,
|
||||
ry + (row_h as i32) / 2 + 4,
|
||||
display_name
|
||||
));
|
||||
|
||||
// Bar
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" class=\"{}\" rx=\"2\"/>\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!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"label\">{:+.4}</text>\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!(
|
||||
"<text x=\"15\" y=\"{}\" class=\"stat\">EI_micro={:.3} Emergence={:.3} SVD rank={}/{} Emergence idx={:.3}</text>\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("</g>\n");
|
||||
s
|
||||
}
|
||||
16
examples/gene-consciousness/Cargo.toml
Normal file
16
examples/gene-consciousness/Cargo.toml
Normal file
|
|
@ -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"
|
||||
226
examples/gene-consciousness/src/analysis.rs
Normal file
226
examples/gene-consciousness/src/analysis.rs
Normal file
|
|
@ -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<f64>,
|
||||
/// 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::<f64>()
|
||||
/ 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::<f64>() / null_phis.len() as f64
|
||||
};
|
||||
let null_std = if null_phis.len() > 1 {
|
||||
(null_phis
|
||||
.iter()
|
||||
.map(|&p| (p - null_mean).powi(2))
|
||||
.sum::<f64>()
|
||||
/ (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,
|
||||
}
|
||||
}
|
||||
279
examples/gene-consciousness/src/data.rs
Normal file
279
examples/gene-consciousness/src/data.rs
Normal file
|
|
@ -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<f64>,
|
||||
/// Human-readable gene labels.
|
||||
pub gene_labels: Vec<String>,
|
||||
/// Module index for each gene (0..3).
|
||||
pub module_ids: Vec<usize>,
|
||||
/// 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<f64>,
|
||||
}
|
||||
|
||||
/// 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<usize> = (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<f64> = (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)
|
||||
}
|
||||
87
examples/gene-consciousness/src/main.rs
Normal file
87
examples/gene-consciousness/src/main.rs
Normal file
|
|
@ -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<String> = 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<T: std::str::FromStr>(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)
|
||||
}
|
||||
413
examples/gene-consciousness/src/report.rs
Normal file
413
examples/gene-consciousness/src/report.rs
Normal file
|
|
@ -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::<f64>() / 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#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1600" font-family="monospace" font-size="12">
|
||||
<style>
|
||||
.title { font-size: 20px; font-weight: bold; fill: #333; }
|
||||
.subtitle { font-size: 14px; fill: #666; }
|
||||
.axis-label { font-size: 11px; fill: #444; }
|
||||
.bar { fill: #4a90d9; }
|
||||
.bar-cancer { fill: #e74c3c; }
|
||||
.bar-null { fill: #ccc; }
|
||||
.node-cc { fill: #3498db; }
|
||||
.node-ap { fill: #e74c3c; }
|
||||
.node-gs { fill: #2ecc71; }
|
||||
.node-hk { fill: #95a5a6; }
|
||||
.edge { stroke: #bbb; stroke-width: 0.5; fill: none; }
|
||||
.edge-strong { stroke: #555; stroke-width: 1.5; fill: none; }
|
||||
</style>
|
||||
<rect width="1200" height="1600" fill="white"/>
|
||||
<text x="600" y="40" text-anchor="middle" class="title">Gene Regulatory Network Consciousness Report</text>
|
||||
<text x="600" y="65" text-anchor="middle" class="subtitle">IIT 4.0 Phi Analysis of Regulatory Modules</text>
|
||||
"#,
|
||||
);
|
||||
|
||||
// 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("</svg>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Gene Regulatory Network (Normal)</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\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!(
|
||||
"<line x1=\"{:.0}\" y1=\"{:.0}\" x2=\"{:.0}\" y2=\"{:.0}\" class=\"{}\" opacity=\"{:.2}\"/>\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!(
|
||||
"<circle cx=\"{:.0}\" cy=\"{:.0}\" r=\"12\" class=\"{}\" stroke=\"#333\" stroke-width=\"1\"/>\n",
|
||||
px, py, class
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{:.0}\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-size=\"8\" fill=\"white\">{}</text>\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!(
|
||||
"<circle cx=\"{}\" cy=\"{}\" r=\"6\" class=\"{}\"/>\n",
|
||||
lx, legend_y, class
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"axis-label\" dominant-baseline=\"middle\">{}</text>\n",
|
||||
lx + 10, legend_y, name
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Module Phi: Normal vs Cancer</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
// Collect all phi values to determine scale
|
||||
let mut all_phis: Vec<f64> = 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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar\" rx=\"2\"/>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar-cancer\" rx=\"2\"/>\n",
|
||||
gx + bar_w + 2.0, h - 30 - cbh, bar_w, cbh
|
||||
));
|
||||
}
|
||||
|
||||
// Label
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\" font-size=\"9\">{}</text>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar\" rx=\"2\"/>\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!(
|
||||
"<rect x=\"{:.0}\" y=\"{}\" width=\"{:.0}\" height=\"{}\" class=\"bar-cancer\" rx=\"2\"/>\n",
|
||||
gx + bar_w + 2.0, h - 30 - cbh, bar_w, cbh
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{:.0}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\" font-size=\"9\">Full</text>\n",
|
||||
gx + bar_w, h - 15
|
||||
));
|
||||
|
||||
// Legend
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"{}\" y=\"10\" width=\"12\" height=\"12\" class=\"bar\"/>\n", w - 150
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"20\" class=\"axis-label\">Normal</text>\n", w - 135
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"{}\" y=\"28\" width=\"12\" height=\"12\" class=\"bar-cancer\"/>\n", w - 150
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"38\" class=\"axis-label\">Cancer</text>\n", w - 135
|
||||
));
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-5\" text-anchor=\"middle\" class=\"subtitle\">Null Distribution (Shuffled Networks) vs Observed Phi</text>\n",
|
||||
w / 2
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\"/>\n",
|
||||
w, h
|
||||
));
|
||||
|
||||
if null_phis.is_empty() {
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" class=\"axis-label\">No null samples</text>\n",
|
||||
w / 2, h / 2
|
||||
));
|
||||
s.push_str("</g>\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!(
|
||||
"<rect x=\"{:.1}\" y=\"{}\" width=\"{:.1}\" height=\"{}\" class=\"bar-null\" rx=\"1\"/>\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!(
|
||||
"<line x1=\"{}\" y1=\"0\" x2=\"{}\" y2=\"{}\" stroke=\"#e74c3c\" stroke-width=\"2\"/>\n",
|
||||
obs_x, obs_x, h - 20
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" text-anchor=\"middle\" fill=\"#e74c3c\" font-size=\"10\">Observed</text>\n",
|
||||
obs_x, h - 5
|
||||
));
|
||||
|
||||
s.push_str("</g>\n");
|
||||
s
|
||||
}
|
||||
|
||||
/// Render summary statistics text.
|
||||
fn render_summary_stats(results: &AnalysisResults, x: i32, y: i32) -> String {
|
||||
let mut s = format!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str("<text x=\"0\" y=\"0\" class=\"subtitle\">Summary Statistics</text>\n");
|
||||
|
||||
let null_mean = if results.null_phis.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
results.null_phis.iter().sum::<f64>() / 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!(
|
||||
"<text x=\"0\" y=\"{}\" class=\"axis-label\">{}</text>\n",
|
||||
20 + i * 18, line
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\n");
|
||||
s
|
||||
}
|
||||
16
examples/quantum-consciousness/Cargo.toml
Normal file
16
examples/quantum-consciousness/Cargo.toml
Normal file
|
|
@ -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"
|
||||
119
examples/quantum-consciousness/RESEARCH.md
Normal file
119
examples/quantum-consciousness/RESEARCH.md
Normal file
|
|
@ -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] = |<j|U|i>|^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 |<j|U|i>|^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.
|
||||
169
examples/quantum-consciousness/src/analysis.rs
Normal file
169
examples/quantum-consciousness/src/analysis.rs
Normal file
|
|
@ -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<CircuitResult> {
|
||||
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
|
||||
}
|
||||
415
examples/quantum-consciousness/src/data.rs
Normal file
415
examples/quantum-consciousness/src/data.rs
Normal file
|
|
@ -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 |<j|U|i>|^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<f64>,
|
||||
/// The unitary matrix (row-major, complex: stored as separate re/im vecs).
|
||||
#[allow(dead_code)]
|
||||
pub unitary_re: Vec<f64>,
|
||||
#[allow(dead_code)]
|
||||
pub unitary_im: Vec<f64>,
|
||||
}
|
||||
|
||||
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<QuantumCircuit> {
|
||||
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<f64>, Vec<f64>) {
|
||||
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<f64>, Vec<f64>) {
|
||||
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<f64>, u_im: &mut Vec<f64>,
|
||||
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<f64>, u_im: &mut Vec<f64>,
|
||||
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 = |<j|U|i>|^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<f64> {
|
||||
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::<f64>().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::<f64>() * std::f64::consts::PI;
|
||||
let phi = rng.gen::<f64>() * 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,
|
||||
}
|
||||
}
|
||||
84
examples/quantum-consciousness/src/main.rs
Normal file
84
examples/quantum-consciousness/src/main.rs
Normal file
|
|
@ -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<String> = 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<T: std::str::FromStr>(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)
|
||||
}
|
||||
309
examples/quantum-consciousness/src/report.rs
Normal file
309
examples/quantum-consciousness/src/report.rs
Normal file
|
|
@ -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#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {} {}" font-family="monospace" font-size="12">
|
||||
<style>
|
||||
.title {{ font-size: 20px; font-weight: bold; fill: #333; }}
|
||||
.subtitle {{ font-size: 14px; fill: #666; font-weight: bold; }}
|
||||
.label {{ font-size: 10px; fill: #333; }}
|
||||
.stat {{ font-size: 11px; fill: #444; }}
|
||||
.bar-phi {{ fill: #3498db; }}
|
||||
.bar-emg {{ fill: #e67e22; }}
|
||||
.bar-svd {{ fill: #2ecc71; }}
|
||||
</style>
|
||||
<rect width="{}" height="{}" fill="white"/>
|
||||
<text x="600" y="40" text-anchor="middle" class="title">Quantum Circuit Consciousness Analysis Report</text>
|
||||
<text x="600" y="65" text-anchor="middle" class="stat">IIT Phi applied to quantum circuit measurement statistics</text>
|
||||
"#,
|
||||
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("</svg>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\" rx=\"5\"/>\n",
|
||||
w, h
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-8\" text-anchor=\"middle\" class=\"subtitle\">Phi Comparison</text>\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!(
|
||||
"<text x=\"10\" y=\"{}\" class=\"label\">{}</text>\n",
|
||||
ry + bar_h as i32 / 2 + 4,
|
||||
r.name
|
||||
));
|
||||
|
||||
// Bar
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"150\" y=\"{}\" width=\"{}\" height=\"{}\" class=\"bar-phi\" rx=\"3\"/>\n",
|
||||
ry,
|
||||
bw.max(1),
|
||||
bar_h as i32 - 4
|
||||
));
|
||||
|
||||
// Value
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"label\">{:.6}</text>\n",
|
||||
155 + bw,
|
||||
ry + bar_h as i32 / 2 + 4,
|
||||
r.full_phi
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\" rx=\"5\"/>\n",
|
||||
w, h
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-8\" text-anchor=\"middle\" class=\"subtitle\">Emergence Comparison</text>\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!(
|
||||
"<text x=\"10\" y=\"{}\" class=\"label\">{}</text>\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!(
|
||||
"<rect x=\"150\" y=\"{}\" width=\"{}\" height=\"{}\" class=\"bar-phi\" rx=\"2\" opacity=\"0.7\"/>\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!(
|
||||
"<rect x=\"150\" y=\"{}\" width=\"{}\" height=\"{}\" class=\"bar-emg\" rx=\"2\" opacity=\"0.7\"/>\n",
|
||||
ry + (bar_h as i32 - 4) / 2,
|
||||
emg_w.max(1),
|
||||
(bar_h as i32 - 4) / 2
|
||||
));
|
||||
|
||||
// Values
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"label\">EI={:.3}</text>\n",
|
||||
155 + ei_w,
|
||||
ry + (bar_h as i32) / 4 + 3,
|
||||
r.emergence.ei_micro
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"label\">CE={:.3}</text>\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!(
|
||||
"<rect x=\"150\" y=\"{}\" width=\"12\" height=\"12\" class=\"bar-phi\" opacity=\"0.7\"/>\n\
|
||||
<text x=\"167\" y=\"{}\" class=\"label\">EI (micro)</text>\n\
|
||||
<rect x=\"260\" y=\"{}\" width=\"12\" height=\"12\" class=\"bar-emg\" opacity=\"0.7\"/>\n\
|
||||
<text x=\"277\" y=\"{}\" class=\"label\">Causal Emergence</text>\n",
|
||||
ly, ly + 10, ly, ly + 10
|
||||
));
|
||||
|
||||
s.push_str("</g>\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!("<g transform=\"translate({},{})\">\n", x, y);
|
||||
s.push_str(&format!(
|
||||
"<rect x=\"0\" y=\"0\" width=\"{}\" height=\"{}\" fill=\"#fafafa\" stroke=\"#ddd\" rx=\"5\"/>\n",
|
||||
w, h
|
||||
));
|
||||
s.push_str(&format!(
|
||||
"<text x=\"{}\" y=\"-8\" text-anchor=\"middle\" class=\"subtitle\">Detailed Results</text>\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!(
|
||||
"<text x=\"{}\" y=\"25\" class=\"stat\" font-weight=\"bold\">{}</text>\n",
|
||||
col, hdr
|
||||
));
|
||||
}
|
||||
s.push_str(&format!(
|
||||
"<line x1=\"10\" y1=\"32\" x2=\"{}\" y2=\"32\" stroke=\"#ccc\"/>\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!(
|
||||
"<text x=\"{}\" y=\"{}\" class=\"stat\">{}</text>\n",
|
||||
col, ry, val
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Description section
|
||||
let desc_y = 50 + results.len() as i32 * 22 + 30;
|
||||
s.push_str(&format!(
|
||||
"<text x=\"15\" y=\"{}\" class=\"subtitle\">Circuit Descriptions</text>\n",
|
||||
desc_y
|
||||
));
|
||||
for (i, r) in results.iter().enumerate() {
|
||||
s.push_str(&format!(
|
||||
"<text x=\"15\" y=\"{}\" class=\"stat\">{}: {}</text>\n",
|
||||
desc_y + 20 + i as i32 * 18,
|
||||
r.name,
|
||||
r.description
|
||||
));
|
||||
}
|
||||
|
||||
s.push_str("</g>\n");
|
||||
s
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue