use crate::error::ConsciousnessError;
use crate::simd::marginal_distribution;
use crate::types::{PhiIdResult, TransitionMatrix};
use std::time::Instant;
pub fn compute_phi_id(
tpm: &TransitionMatrix,
partition_mask: u64,
) -> Result<PhiIdResult, ConsciousnessError> {
let n = tpm.n;
if n < 2 {
return Err(crate::error::ValidationError::EmptySystem.into());
}
let start = Instant::now();
let marginal = marginal_distribution(tpm.as_slice(), n);
let mut source_a: Vec<usize> = Vec::new();
let mut source_b: Vec<usize> = Vec::new();
for i in 0..n {
if partition_mask & (1 << i) != 0 {
source_a.push(i);
} else {
source_b.push(i);
}
}
if source_a.is_empty() || source_b.is_empty() {
return Err(crate::error::ValidationError::DimensionMismatch(
"partition must have elements on both sides".into(),
)
.into());
}
let total_mi = mutual_information_past_future(tpm, n, &marginal);
let mi_a = source_mutual_information(tpm, n, &source_a, &marginal);
let mi_b = source_mutual_information(tpm, n, &source_b, &marginal);
let redundancy = mi_a.min(mi_b);
let unique_a = mi_a - redundancy;
let unique_b = mi_b - redundancy;
let synergy = (total_mi - redundancy - unique_a - unique_b).max(0.0);
let te = transfer_entropy(tpm, n, &source_a, &source_b, &marginal);
Ok(PhiIdResult {
redundancy,
unique: vec![unique_a, unique_b],
synergy,
total_mi,
transfer_entropy: te,
elapsed: start.elapsed(),
})
}
fn mutual_information_past_future(tpm: &TransitionMatrix, n: usize, marginal: &[f64]) -> f64 {
let h_future = shannon_entropy(marginal);
let mut h_cond = 0.0f64;
let p_state = 1.0 / n as f64; for i in 0..n {
for j in 0..n {
let tij = tpm.get(i, j);
if tij > 1e-15 {
h_cond -= p_state * tij * tij.ln();
}
}
}
(h_future - h_cond).max(0.0)
}
fn source_mutual_information(
tpm: &TransitionMatrix,
n: usize,
source: &[usize],
marginal: &[f64],
) -> f64 {
let sub_tpm = tpm.marginalize(source);
let sub_n = sub_tpm.n;
let sub_marginal = marginal_distribution(sub_tpm.as_slice(), sub_n);
mutual_information_past_future(&sub_tpm, sub_n, &sub_marginal)
}
fn transfer_entropy(
tpm: &TransitionMatrix,
n: usize,
source: &[usize],
target: &[usize],
marginal: &[f64],
) -> f64 {
let mut all_elements: Vec<usize> = source.to_vec();
all_elements.extend_from_slice(target);
all_elements.sort();
all_elements.dedup();
let mi_ab_to_b = source_mutual_information(tpm, n, &all_elements, marginal);
let mi_b_to_b = source_mutual_information(tpm, n, target, marginal);
(mi_ab_to_b - mi_b_to_b).max(0.0)
}
fn shannon_entropy(p: &[f64]) -> f64 {
let mut h = 0.0f64;
for &pi in p {
if pi > 1e-15 {
h -= pi * pi.ln();
}
}
h
}
#[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 disconnected_tpm() -> TransitionMatrix {
#[rustfmt::skip]
let data = vec![
0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.5,
0.0, 0.0, 0.5, 0.5,
];
TransitionMatrix::new(4, data)
}
#[test]
fn phi_id_decomposes_and_gate() {
let tpm = and_gate_tpm();
let result = compute_phi_id(&tpm, 0b0011).unwrap();
assert!(result.total_mi >= 0.0);
assert!(result.redundancy >= 0.0);
assert!(result.synergy >= 0.0);
assert!(result.unique[0] >= 0.0);
assert!(result.unique[1] >= 0.0);
}
#[test]
fn phi_id_disconnected_components() {
let tpm = disconnected_tpm();
let result = compute_phi_id(&tpm, 0b0011).unwrap();
assert!(result.total_mi >= 0.0);
assert!(result.redundancy >= 0.0);
assert!(result.synergy >= 0.0);
}
#[test]
fn phi_id_transfer_entropy_nonnegative() {
let tpm = and_gate_tpm();
let result = compute_phi_id(&tpm, 0b0011).unwrap();
assert!(result.transfer_entropy >= 0.0);
}
}