use crate::{
ebi_traits::ebi_trait_finite_stochastic_language::EbiTraitFiniteStochasticLanguage,
techniques::livelock::IsPartOfLivelock,
};
use ebi_objects::{
AutomatonSemantics, StochasticAutomatonSemantics, StochasticDeterministicFiniteAutomaton,
anyhow::{Context, Result, anyhow},
ebi_arithmetic::{
EbiMatrix, Fraction, FractionMatrix, IdentityMinus, Inversion, Log, One, Signed, Zero, f,
},
};
use ebi_optimisation::ebi_arithmetic::log_polynomial::log_polynomial::LogPolynomial;
use std::ops::Neg;
pub trait Entropy {
fn entropy(&self) -> Result<LogPolynomial>;
}
impl<T: ?Sized> Entropy for T
where
T: EbiTraitFiniteStochasticLanguage,
{
fn entropy(&self) -> Result<LogPolynomial> {
let mut entropy = LogPolynomial::zero();
for (_, probability) in self.iter_traces_probabilities() {
if probability.is_positive() {
entropy -= probability.n_log_n()?
}
}
Ok(entropy)
}
}
impl Entropy for StochasticDeterministicFiniteAutomaton {
fn entropy(&self) -> Result<LogPolynomial> {
let state_visits = number_of_average_visits_per_state(self)?;
let mut sum = LogPolynomial::zero();
for (transition, source, _, _) in self.transitions() {
if let Some(average_visits) = &state_visits[source] {
let probability = self
.transition_2_weight(source, transition)
.ok_or_else(|| anyhow!("Transition weight not found."))?;
if probability.is_positive() {
let mut entropy = probability.n_log_n()?;
entropy *= average_visits;
sum += entropy;
}
}
}
return Ok(-sum);
}
}
pub fn number_of_average_visits_per_state(
sdfa: &StochasticDeterministicFiniteAutomaton,
) -> Result<Vec<Option<Fraction>>> {
let initial_state = if let Some(initial_state) = sdfa.initial_state() {
initial_state
} else {
return Ok(vec![Some(Fraction::zero()); sdfa.number_of_states()]);
};
let mut state_2_transient_state = vec![None; sdfa.number_of_states()];
let mut transient_state_2_state = vec![];
{
let mut livelock_cache = sdfa.get_livelock_cache();
for state in sdfa.states() {
if !livelock_cache.is_state_part_of_livelock(&state)? {
let transient_state = transient_state_2_state.len();
transient_state_2_state.push(state);
state_2_transient_state[state] = Some(transient_state);
}
}
}
if state_2_transient_state[initial_state].is_none() {
return Ok(vec![None; sdfa.number_of_states()]);
}
let mut matrix =
FractionMatrix::new(transient_state_2_state.len(), transient_state_2_state.len());
for (source, (target, probability)) in sdfa
.sources
.iter()
.zip(sdfa.targets.iter().zip(sdfa.probabilities.iter()))
{
if let (Some(source_transient), Some(target_transient)) = (
state_2_transient_state[*source],
state_2_transient_state[*target],
) {
matrix.set(source_transient, target_transient, probability.clone());
}
}
matrix.identity_minus();
matrix = matrix
.invert()
.with_context(|| anyhow!("Cannot compute inversion of (I - PT)."))?;
let mut result = vec![None; sdfa.number_of_states()];
for (transient_state, state) in transient_state_2_state.iter().enumerate() {
result[*state] = matrix.get(initial_state.0, transient_state);
}
Ok(result)
}
pub(crate) fn number_of_average_visits_per_state_approximate(
sdfa: &StochasticDeterministicFiniteAutomaton,
) -> Result<Vec<Fraction>> {
let epsilon = f!(1, 1_000_000_000_000u64);
let max_iterations = 20_000;
c_s_iterative(sdfa, &epsilon, max_iterations)
}
fn c_s_iterative(
sdfa: &StochasticDeterministicFiniteAutomaton,
epsilon: &Fraction,
max_iterations: usize,
) -> Result<Vec<Fraction>> {
let state_count = sdfa.number_of_states();
let Some(initial_state) = sdfa.initial_state() else {
return Ok(vec![Fraction::zero(); state_count]);
};
let mut state_visits = vec![Fraction::zero(); state_count];
let mut next_state_visits = vec![Fraction::zero(); state_count];
for _ in 0..max_iterations {
for value in &mut next_state_visits {
*value = Fraction::zero();
}
for (index, &source) in sdfa.sources.iter().enumerate() {
let target = sdfa.targets[index];
let probability = &sdfa.probabilities[index];
if !probability.is_zero() {
next_state_visits[target] += &state_visits[source] * probability;
}
}
next_state_visits[initial_state] += Fraction::one();
let mut max_diff = Fraction::zero();
for state in 0..state_count {
let mut diff = &next_state_visits[state] - &state_visits[state];
if diff.is_negative() {
diff = diff.neg();
}
if diff > max_diff {
max_diff = diff;
}
}
std::mem::swap(&mut state_visits, &mut next_state_visits);
if &max_diff < epsilon {
break;
}
}
Ok(state_visits)
}
#[cfg(test)]
mod tests {
use crate::techniques::entropy::{Entropy, c_s_iterative, number_of_average_visits_per_state};
use ebi_objects::{
StochasticDeterministicFiniteAutomaton,
anyhow::Result,
ebi_arithmetic::{Fraction, f, fraction::approximate::Approximate},
};
use std::fs;
fn compute_approx(
sdfa: &StochasticDeterministicFiniteAutomaton,
) -> Result<Vec<Option<Fraction>>> {
let epsilon = f!(1, 1_000_000_000_000u64);
let max_iterations = 20_000;
Ok(c_s_iterative(sdfa, &epsilon, max_iterations)?
.into_iter()
.map(|x| Some(x))
.collect())
}
#[test]
fn number_of_average_visits_per_state_test() {
let fin = fs::read_to_string("testfiles/aa-ab-ba.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
let approx = compute_approx(&sdfa).unwrap();
let exact = number_of_average_visits_per_state(&sdfa).unwrap();
assert_eq!(approx, exact);
println!("approx {:?}", approx);
println!("exact {:?}", exact);
}
#[test]
fn number_of_average_visits_per_state_livelock() {
let fin = fs::read_to_string("testfiles/a-livelock.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
let approx = compute_approx(&sdfa).unwrap();
let exact = number_of_average_visits_per_state(&sdfa).unwrap();
assert_eq!(approx[0], exact[0]);
println!("approx {:?}", approx);
println!("exact {:?}", exact);
}
#[test]
fn entropy_livelock() {
let fin = fs::read_to_string("testfiles/a-b-livelock.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
let entropy = sdfa.entropy().unwrap();
assert_eq!(entropy.approximate().unwrap(), 1.5);
}
}