use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransitionMatrix {
pub data: Vec<f64>,
pub n: usize,
}
impl TransitionMatrix {
#[inline]
pub fn new(n: usize, data: Vec<f64>) -> Self {
assert_eq!(data.len(), n * n, "TPM data length must be n*n");
Self { data, n }
}
pub fn identity(n: usize) -> Self {
let mut data = vec![0.0; n * n];
for i in 0..n {
data[i * n + i] = 1.0;
}
Self { data, n }
}
#[inline(always)]
pub fn get(&self, row: usize, col: usize) -> f64 {
self.data[row * self.n + col]
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> f64 {
*self.data.get_unchecked(row * self.n + col)
}
#[inline(always)]
pub fn set(&mut self, row: usize, col: usize, val: f64) {
self.data[row * self.n + col] = val;
}
#[inline(always)]
pub fn size(&self) -> usize {
self.n
}
#[inline(always)]
pub fn as_slice(&self) -> &[f64] {
&self.data
}
pub fn marginalize(&self, indices: &[usize]) -> TransitionMatrix {
let k = indices.len();
let mut sub = vec![0.0; k * k];
for (si, &i) in indices.iter().enumerate() {
let mut row_sum = 0.0;
for (sj, &j) in indices.iter().enumerate() {
let val = self.get(i, j);
sub[si * k + sj] = val;
row_sum += val;
}
if row_sum > 0.0 {
for sj in 0..k {
sub[si * k + sj] /= row_sum;
}
}
}
TransitionMatrix { data: sub, n: k }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Bipartition {
pub mask: u64,
pub n: usize,
}
impl Bipartition {
pub fn set_a(&self) -> Vec<usize> {
(0..self.n).filter(|&i| self.mask & (1 << i) != 0).collect()
}
pub fn set_b(&self) -> Vec<usize> {
(0..self.n)
.filter(|&i| self.mask & (1 << i) == 0)
.collect()
}
#[inline]
pub fn is_valid(&self) -> bool {
let full = (1u64 << self.n) - 1;
self.mask != 0 && self.mask != full
}
}
pub struct BipartitionIter {
current: u64,
max: u64,
n: usize,
}
impl BipartitionIter {
pub fn new(n: usize) -> Self {
assert!(n <= 63, "bipartition iter supports at most 63 elements");
Self {
current: 1, max: (1u64 << n) - 1,
n,
}
}
}
impl Iterator for BipartitionIter {
type Item = Bipartition;
fn next(&mut self) -> Option<Self::Item> {
while self.current < self.max {
let mask = self.current;
self.current += 1;
return Some(Bipartition { mask, n: self.n });
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.max - self.current) as usize;
(remaining, Some(remaining))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PhiAlgorithm {
Exact,
GreedyBisection,
Spectral,
Stochastic,
Hierarchical,
GeoMIP,
Collapse,
}
impl std::fmt::Display for PhiAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Exact => write!(f, "exact"),
Self::GreedyBisection => write!(f, "greedy-bisection"),
Self::Spectral => write!(f, "spectral"),
Self::Stochastic => write!(f, "stochastic"),
Self::Hierarchical => write!(f, "hierarchical"),
Self::GeoMIP => write!(f, "geomip"),
Self::Collapse => write!(f, "collapse"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhiResult {
pub phi: f64,
pub mip: Bipartition,
pub partitions_evaluated: u64,
pub total_partitions: u64,
pub algorithm: PhiAlgorithm,
pub elapsed: Duration,
pub convergence: Vec<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergenceResult {
pub ei_micro: f64,
pub ei_macro: f64,
pub causal_emergence: f64,
pub coarse_graining: Vec<usize>,
pub determinism: f64,
pub degeneracy: f64,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mechanism {
pub elements: u64,
pub n: usize,
}
impl Mechanism {
pub fn new(elements: u64, n: usize) -> Self {
Self { elements, n }
}
pub fn size(&self) -> usize {
self.elements.count_ones() as usize
}
pub fn indices(&self) -> Vec<usize> {
(0..self.n).filter(|&i| self.elements & (1 << i) != 0).collect()
}
}
pub type Purview = Mechanism;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Distinction {
pub mechanism: Mechanism,
pub cause_repertoire: Vec<f64>,
pub effect_repertoire: Vec<f64>,
pub cause_purview: Purview,
pub effect_purview: Purview,
pub phi_cause: f64,
pub phi_effect: f64,
pub phi: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relation {
pub distinction_indices: Vec<usize>,
pub phi: f64,
pub order: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CauseEffectStructure {
pub n: usize,
pub state: usize,
pub distinctions: Vec<Distinction>,
pub relations: Vec<Relation>,
pub big_phi: f64,
pub sum_phi: f64,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhiIdResult {
pub redundancy: f64,
pub unique: Vec<f64>,
pub synergy: f64,
pub total_mi: f64,
pub transfer_entropy: f64,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PidResult {
pub redundancy: f64,
pub unique: Vec<f64>,
pub synergy: f64,
pub total_mi: f64,
pub num_sources: usize,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StreamingPhiResult {
pub phi: f64,
pub time_steps: usize,
pub phi_ewma: f64,
pub phi_variance: f64,
pub change_detected: bool,
pub history: Vec<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhiBound {
pub lower: f64,
pub upper: f64,
pub confidence: f64,
pub samples: u64,
pub method: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComputeBudget {
pub max_time: Duration,
pub max_partitions: u64,
pub max_memory: usize,
pub approximation_ratio: f64,
}
impl Default for ComputeBudget {
fn default() -> Self {
Self {
max_time: Duration::from_secs(30),
max_partitions: 0,
max_memory: 0,
approximation_ratio: 1.0,
}
}
}
impl ComputeBudget {
pub fn exact() -> Self {
Self::default()
}
pub fn fast() -> Self {
Self {
max_time: Duration::from_millis(100),
max_partitions: 1000,
max_memory: 64 * 1024 * 1024,
approximation_ratio: 0.9,
}
}
}