Skip to content

emic.analysis

Complexity measures and machine analysis.

analysis

Analysis module for epsilon-machine measures.

AnalysisSummary dataclass

AnalysisSummary(
    statistical_complexity: float,
    entropy_rate: float,
    excess_entropy: float,
    crypticity: float,
    num_states: int,
    num_transitions: int,
    alphabet_size: int,
    topological_complexity: float,
)

Complete analysis of an epsilon-machine.

Contains all computed measures for easy access and display.

to_dict

to_dict() -> dict[str, float | int]

Convert to dictionary for serialization.

Source code in src/emic/analysis/summary.py
def to_dict(self) -> dict[str, float | int]:
    """Convert to dictionary for serialization."""
    return {
        "C_mu": self.statistical_complexity,
        "h_mu": self.entropy_rate,
        "E": self.excess_entropy,
        "chi": self.crypticity,
        "num_states": self.num_states,
        "num_transitions": self.num_transitions,
        "alphabet_size": self.alphabet_size,
        "C_top": self.topological_complexity,
    }

__str__

__str__() -> str

Return human-readable summary.

Source code in src/emic/analysis/summary.py
def __str__(self) -> str:
    """Return human-readable summary."""
    return (
        f"ε-Machine Analysis:\n"
        f"  States: {self.num_states}\n"
        f"  Transitions: {self.num_transitions}\n"
        f"  Alphabet: {self.alphabet_size} symbols\n"
        f"  Statistical Complexity Cμ: {self.statistical_complexity:.4f} bits\n"
        f"  Entropy Rate hμ: {self.entropy_rate:.4f} bits/symbol\n"
        f"  Excess Entropy E: {self.excess_entropy:.4f} bits\n"
        f"  Crypticity χ: {self.crypticity:.4f} bits\n"
    )

analyze

analyze(machine: EpsilonMachine[A]) -> AnalysisSummary

Compute all standard measures for an epsilon-machine.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine to analyze

required

Returns:

Type Description
AnalysisSummary

AnalysisSummary with all computed measures

Examples:

>>> from emic.sources.synthetic.golden_mean import GoldenMeanSource
>>> machine = GoldenMeanSource(p=0.5).true_machine
>>> summary = analyze(machine)
>>> summary.num_states
2
Source code in src/emic/analysis/summary.py
def analyze(machine: EpsilonMachine[A]) -> AnalysisSummary:
    """
    Compute all standard measures for an epsilon-machine.

    Args:
        machine: The epsilon-machine to analyze

    Returns:
        AnalysisSummary with all computed measures

    Examples:
        >>> from emic.sources.synthetic.golden_mean import GoldenMeanSource
        >>> machine = GoldenMeanSource(p=0.5).true_machine
        >>> summary = analyze(machine)
        >>> summary.num_states
        2
    """
    c_mu = statistical_complexity(machine)
    h_mu = entropy_rate(machine)
    e = excess_entropy(machine)

    return AnalysisSummary(
        statistical_complexity=c_mu,
        entropy_rate=h_mu,
        excess_entropy=e,
        crypticity=c_mu - e,
        num_states=state_count(machine),
        num_transitions=transition_count(machine),
        alphabet_size=len(machine.alphabet),
        topological_complexity=topological_complexity(machine),
    )

statistical_complexity

statistical_complexity(
    machine: EpsilonMachine[A],
) -> float

Compute the statistical complexity Cμ.

Cμ = H(S) = -Σᵢ πᵢ log₂(πᵢ)

where πᵢ is the stationary probability of state i.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
float

Statistical complexity in bits

Examples:

>>> from emic.sources.synthetic.golden_mean import GoldenMeanSource
>>> machine = GoldenMeanSource(p=0.5).true_machine
>>> 0.9 < statistical_complexity(machine) < 0.95
True
Source code in src/emic/analysis/measures.py
def statistical_complexity(machine: EpsilonMachine[A]) -> float:
    """
    Compute the statistical complexity Cμ.

    Cμ = H(S) = -Σᵢ πᵢ log₂(πᵢ)

    where πᵢ is the stationary probability of state i.

    Args:
        machine: The epsilon-machine

    Returns:
        Statistical complexity in bits

    Examples:
        >>> from emic.sources.synthetic.golden_mean import GoldenMeanSource
        >>> machine = GoldenMeanSource(p=0.5).true_machine
        >>> 0.9 < statistical_complexity(machine) < 0.95
        True
    """
    stationary = machine.stationary_distribution
    return stationary.entropy()

entropy_rate

entropy_rate(machine: EpsilonMachine[A]) -> float

Compute the entropy rate hμ.

hμ = H(X | S) = Σᵢ πᵢ H(X | S = sᵢ)

where H(X | S = sᵢ) is the entropy of the emission distribution from state sᵢ.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
float

Entropy rate in bits per symbol

Examples:

>>> from emic.sources.synthetic.biased_coin import BiasedCoinSource
>>> machine = BiasedCoinSource(p=0.5).true_machine
>>> abs(entropy_rate(machine) - 1.0) < 0.01
True
Source code in src/emic/analysis/measures.py
def entropy_rate(machine: EpsilonMachine[A]) -> float:
    """
    Compute the entropy rate hμ.

    hμ = H(X | S) = Σᵢ πᵢ H(X | S = sᵢ)

    where H(X | S = sᵢ) is the entropy of the emission distribution
    from state sᵢ.

    Args:
        machine: The epsilon-machine

    Returns:
        Entropy rate in bits per symbol

    Examples:
        >>> from emic.sources.synthetic.biased_coin import BiasedCoinSource
        >>> machine = BiasedCoinSource(p=0.5).true_machine
        >>> abs(entropy_rate(machine) - 1.0) < 0.01
        True
    """
    stationary = machine.stationary_distribution
    h = 0.0

    for state in machine.states:
        pi = stationary.probs.get(state.id, 0.0)
        if pi <= 0:
            continue

        # Emission distribution from this state
        emission_probs: dict[A, float] = {}
        for t in state.transitions:
            emission_probs[t.symbol] = emission_probs.get(t.symbol, 0.0) + t.probability

        # Compute entropy of emission distribution
        state_entropy = 0.0
        for prob in emission_probs.values():
            if prob > 0:
                state_entropy -= prob * math.log2(prob)

        h += pi * state_entropy

    return h

excess_entropy

excess_entropy(machine: EpsilonMachine[A]) -> float

Compute the excess entropy E.

E = I(Past; Future) = Cμ + Cμ' - I

For unifilar machines (which epsilon-machines are): E = Cμ (statistical complexity equals excess entropy)

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
float

Excess entropy in bits

Note

For general epsilon-machines, E = Cμ since they are unifilar.

Source code in src/emic/analysis/measures.py
def excess_entropy(machine: EpsilonMachine[A]) -> float:
    """
    Compute the excess entropy E.

    E = I(Past; Future) = Cμ + Cμ' - I

    For unifilar machines (which epsilon-machines are):
    E = Cμ (statistical complexity equals excess entropy)

    Args:
        machine: The epsilon-machine

    Returns:
        Excess entropy in bits

    Note:
        For general epsilon-machines, E = Cμ since they are unifilar.
    """
    # For unifilar machines, excess entropy equals statistical complexity
    return statistical_complexity(machine)

state_count

state_count(machine: EpsilonMachine[A]) -> int

Number of causal states.

A simple but fundamental measure of structural complexity.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
int

Number of states

Source code in src/emic/analysis/measures.py
def state_count(machine: EpsilonMachine[A]) -> int:
    """
    Number of causal states.

    A simple but fundamental measure of structural complexity.

    Args:
        machine: The epsilon-machine

    Returns:
        Number of states
    """
    return len(machine.states)

transition_count

transition_count(machine: EpsilonMachine[A]) -> int

Total number of transitions.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
int

Total number of transitions

Source code in src/emic/analysis/measures.py
def transition_count(machine: EpsilonMachine[A]) -> int:
    """
    Total number of transitions.

    Args:
        machine: The epsilon-machine

    Returns:
        Total number of transitions
    """
    return sum(len(s.transitions) for s in machine.states)

topological_complexity

topological_complexity(
    machine: EpsilonMachine[A],
) -> float

Topological complexity: log₂(number of states).

An upper bound on statistical complexity.

Parameters:

Name Type Description Default
machine EpsilonMachine[A]

The epsilon-machine

required

Returns:

Type Description
float

Topological complexity in bits

Source code in src/emic/analysis/measures.py
def topological_complexity(machine: EpsilonMachine[A]) -> float:
    """
    Topological complexity: log₂(number of states).

    An upper bound on statistical complexity.

    Args:
        machine: The epsilon-machine

    Returns:
        Topological complexity in bits
    """
    n = len(machine.states)
    return math.log2(n) if n > 0 else 0.0