use crate::error::ConsciousnessError;
use crate::iit4::{intrinsic_difference, mechanism_phi};
use crate::types::{
CauseEffectStructure, ComputeBudget, Distinction, Mechanism, Relation, TransitionMatrix,
};
use std::time::Instant;
pub fn compute_ces(
tpm: &TransitionMatrix,
state: usize,
phi_threshold: f64,
budget: &ComputeBudget,
) -> Result<CauseEffectStructure, ConsciousnessError> {
let n = tpm.n; if n < 2 {
return Err(crate::error::ValidationError::EmptySystem.into());
}
let num_elements = n.trailing_zeros() as usize;
if num_elements > 12 {
return Err(ConsciousnessError::SystemTooLarge { n: num_elements, max: 12 });
}
let start = Instant::now();
let full = (1u64 << num_elements) - 1;
#[cfg(feature = "parallel")]
let distinctions: Vec<Distinction> = if num_elements >= 5 {
use rayon::prelude::*;
(1..=full)
.into_par_iter()
.map(|mech_mask| {
let mechanism = Mechanism::new(mech_mask, num_elements);
mechanism_phi(tpm, &mechanism, state)
})
.filter(|d| d.phi > phi_threshold)
.collect()
} else {
ces_sequential(tpm, state, num_elements, full, phi_threshold, &budget, &start)
};
#[cfg(not(feature = "parallel"))]
let distinctions = ces_sequential(tpm, state, num_elements, full, phi_threshold, budget, &start);
let mut distinctions = distinctions;
distinctions.sort_by(|a, b| b.phi.partial_cmp(&a.phi).unwrap_or(std::cmp::Ordering::Equal));
let relations = compute_relations(&distinctions);
let sum_phi: f64 = distinctions.iter().map(|d| d.phi).sum();
let big_phi = compute_big_phi(tpm, state, &distinctions, budget);
Ok(CauseEffectStructure {
n: num_elements,
state,
distinctions,
relations,
big_phi,
sum_phi,
elapsed: start.elapsed(),
})
}
fn ces_sequential(
tpm: &TransitionMatrix,
state: usize,
num_elements: usize,
full: u64,
phi_threshold: f64,
budget: &ComputeBudget,
start: &Instant,
) -> Vec<Distinction> {
let mut distinctions = Vec::new();
for mech_mask in 1..=full {
if start.elapsed() > budget.max_time {
break;
}
let mechanism = Mechanism::new(mech_mask, num_elements);
let dist = mechanism_phi(tpm, &mechanism, state);
if dist.phi > phi_threshold {
distinctions.push(dist);
}
}
distinctions
}
fn compute_relations(distinctions: &[Distinction]) -> Vec<Relation> {
let mut relations = Vec::new();
let nd = distinctions.len();
for i in 0..nd {
for j in (i + 1)..nd {
let overlap_cause = distinctions[i].cause_purview.elements
& distinctions[j].cause_purview.elements;
let overlap_effect = distinctions[i].effect_purview.elements
& distinctions[j].effect_purview.elements;
if overlap_cause != 0 || overlap_effect != 0 {
let overlap_size = (overlap_cause.count_ones() + overlap_effect.count_ones()) as f64;
let total_size = (distinctions[i].cause_purview.size()
+ distinctions[i].effect_purview.size()
+ distinctions[j].cause_purview.size()
+ distinctions[j].effect_purview.size()) as f64;
let overlap_fraction = if total_size > 0.0 {
overlap_size / total_size
} else {
0.0
};
let phi = (distinctions[i].phi * distinctions[j].phi).sqrt() * overlap_fraction;
if phi > 1e-10 {
relations.push(Relation {
distinction_indices: vec![i, j],
phi,
order: 2,
});
}
}
}
}
relations.sort_by(|a, b| b.phi.partial_cmp(&a.phi).unwrap_or(std::cmp::Ordering::Equal));
relations
}
fn compute_big_phi(
tpm: &TransitionMatrix,
state: usize,
distinctions: &[Distinction],
budget: &ComputeBudget,
) -> f64 {
let num_elements = tpm.n.trailing_zeros() as usize;
if distinctions.is_empty() {
return 0.0;
}
let intact_phi_vec: Vec<f64> = distinctions.iter().map(|d| d.phi).collect();
let full = (1u64 << num_elements) - 1;
let mut min_phi = f64::MAX;
for part_mask in 1..full {
let mut partitioned_phi_vec: Vec<f64> = Vec::with_capacity(distinctions.len());
for dist in distinctions {
let mech_mask = dist.mechanism.elements;
let in_a = mech_mask & part_mask;
let in_b = mech_mask & !part_mask & full;
if in_a != 0 && in_b != 0 {
partitioned_phi_vec.push(0.0);
} else {
partitioned_phi_vec.push(dist.phi);
}
}
let ces_distance = intrinsic_difference(&intact_phi_vec, &partitioned_phi_vec);
min_phi = min_phi.min(ces_distance);
}
if min_phi == f64::MAX { 0.0 } else { min_phi }
}
pub fn ces_complexity(ces: &CauseEffectStructure) -> (usize, usize, f64) {
(ces.distinctions.len(), ces.relations.len(), ces.sum_phi)
}
#[cfg(test)]
mod tests {
use super::*;
fn and_gate_tpm() -> TransitionMatrix {
#[rustfmt::skip]
let data = vec![
0.5, 0.25, 0.25, 0.0,
0.5, 0.25, 0.25, 0.0,
0.5, 0.25, 0.25, 0.0,
0.0, 0.0, 0.0, 1.0,
];
TransitionMatrix::new(4, data)
}
fn identity_tpm() -> TransitionMatrix {
TransitionMatrix::identity(4)
}
#[test]
fn ces_computes_for_small_system() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::exact();
let ces = compute_ces(&tpm, 0, 1e-6, &budget).unwrap();
assert!(ces.distinctions.len() > 0 || ces.big_phi >= 0.0);
assert_eq!(ces.n, 2); assert_eq!(ces.state, 0);
}
#[test]
fn ces_identity_has_distinctions() {
let tpm = identity_tpm();
let budget = ComputeBudget::exact();
let ces = compute_ces(&tpm, 0, 1e-6, &budget).unwrap();
assert!(ces.sum_phi >= 0.0);
}
#[test]
fn ces_rejects_too_large() {
let tpm = TransitionMatrix::identity(8192);
let budget = ComputeBudget::exact();
assert!(compute_ces(&tpm, 0, 1e-6, &budget).is_err());
}
#[test]
fn ces_complexity_reports() {
let tpm = and_gate_tpm();
let budget = ComputeBudget::exact();
let ces = compute_ces(&tpm, 0, 1e-6, &budget).unwrap();
let (nd, nr, sp) = ces_complexity(&ces);
assert!(nd <= (1 << 2)); assert!(sp >= 0.0);
}
}