sofic 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sofic/__init__.py +185 -0
- sofic/automata/__init__.py +207 -0
- sofic/automata/_config_simulation.py +40 -0
- sofic/automata/active.py +611 -0
- sofic/automata/alergia.py +222 -0
- sofic/automata/algorithms.py +376 -0
- sofic/automata/atomaton.py +58 -0
- sofic/automata/base.py +161 -0
- sofic/automata/buchi.py +23 -0
- sofic/automata/buchi_simulation.py +67 -0
- sofic/automata/canonical_dual.py +18 -0
- sofic/automata/canonical_extraction.py +122 -0
- sofic/automata/dfa.py +85 -0
- sofic/automata/dfasat.py +195 -0
- sofic/automata/edsm.py +219 -0
- sofic/automata/enumeration.py +44 -0
- sofic/automata/icdfa.py +421 -0
- sofic/automata/idfa.py +363 -0
- sofic/automata/languages/__init__.py +39 -0
- sofic/automata/languages/_quotient_utils.py +64 -0
- sofic/automata/languages/atoms.py +31 -0
- sofic/automata/languages/automaton_ops.py +243 -0
- sofic/automata/languages/base.py +67 -0
- sofic/automata/languages/operations.py +78 -0
- sofic/automata/languages/quotients.py +66 -0
- sofic/automata/languages/residuals.py +25 -0
- sofic/automata/learning.py +79 -0
- sofic/automata/nfa.py +39 -0
- sofic/automata/nwa.py +343 -0
- sofic/automata/nwa_simulation.py +56 -0
- sofic/automata/observation.py +40 -0
- sofic/automata/papni.py +301 -0
- sofic/automata/regex.py +128 -0
- sofic/automata/rfsa.py +35 -0
- sofic/automata/rpni.py +193 -0
- sofic/automata/subsequential.py +201 -0
- sofic/automata/transducer_operations.py +350 -0
- sofic/automata/transducer_simulation.py +150 -0
- sofic/automata/transducers.py +365 -0
- sofic/automata/unifilar.py +107 -0
- sofic/automata/vpa.py +1373 -0
- sofic/automata/vpa_simulation.py +53 -0
- sofic/base.py +153 -0
- sofic/core.py +47 -0
- sofic/examples/__init__.py +86 -0
- sofic/examples/epsilon_machines.py +1089 -0
- sofic/examples/processes.py +1491 -0
- sofic/examples/shifts.py +144 -0
- sofic/exceptions.py +33 -0
- sofic/generators/__init__.py +115 -0
- sofic/generators/_word_measures.py +94 -0
- sofic/generators/alternative_complexity.py +104 -0
- sofic/generators/base.py +327 -0
- sofic/generators/bidirectional_construction.py +717 -0
- sofic/generators/bidirectional_epsilon_machine.py +689 -0
- sofic/generators/block_convergence.py +668 -0
- sofic/generators/block_entropy.py +578 -0
- sofic/generators/channel_measures.py +75 -0
- sofic/generators/conversions.py +182 -0
- sofic/generators/directional_flow.py +245 -0
- sofic/generators/edge_emissions.py +36 -0
- sofic/generators/edge_machine.py +178 -0
- sofic/generators/epsilon_construction.py +193 -0
- sofic/generators/epsilon_inference.py +703 -0
- sofic/generators/epsilon_machine.py +557 -0
- sofic/generators/epsilon_transducer.py +168 -0
- sofic/generators/epsilon_transducer_construction.py +185 -0
- sofic/generators/epsilon_transducer_inference.py +499 -0
- sofic/generators/hmm_inference.py +719 -0
- sofic/generators/information_diagram.py +428 -0
- sofic/generators/lumping.py +447 -0
- sofic/generators/markov.py +100 -0
- sofic/generators/mealy.py +156 -0
- sofic/generators/measures.py +257 -0
- sofic/generators/minimal_generative_model.py +821 -0
- sofic/generators/mixed_state.py +250 -0
- sofic/generators/mixed_state_construction.py +163 -0
- sofic/generators/moore.py +75 -0
- sofic/generators/nmachine.py +78 -0
- sofic/generators/nmachine_construction.py +70 -0
- sofic/generators/pfa.py +100 -0
- sofic/generators/prob.py +291 -0
- sofic/generators/process_equivalence.py +207 -0
- sofic/generators/quasi_inference.py +74 -0
- sofic/generators/quasi_realization.py +97 -0
- sofic/generators/reversal.py +66 -0
- sofic/generators/stack_hmm.py +426 -0
- sofic/generators/stack_inference.py +509 -0
- sofic/generators/stationary.py +134 -0
- sofic/generators/stochastic.py +65 -0
- sofic/generators/synchronization.py +407 -0
- sofic/generators/topological_epsilon_enumeration.py +349 -0
- sofic/generators/words.py +226 -0
- sofic/graph.py +135 -0
- sofic/indexing.py +31 -0
- sofic/inference/__init__.py +45 -0
- sofic/inference/bayesian/__init__.py +68 -0
- sofic/inference/bayesian/comparison.py +199 -0
- sofic/inference/bayesian/counts.py +219 -0
- sofic/inference/bayesian/diversity.py +254 -0
- sofic/inference/bayesian/epsilon.py +270 -0
- sofic/inference/bayesian/hdp_hmm.py +340 -0
- sofic/inference/bayesian/markov.py +294 -0
- sofic/inference/bayesian/pymc_backend.py +71 -0
- sofic/inference/bayesian/stack_hmm.py +215 -0
- sofic/inference/model_selection.py +365 -0
- sofic/inference/spectral.py +564 -0
- sofic/operations.py +16 -0
- sofic/properties.py +339 -0
- sofic/serialization.py +450 -0
- sofic/shifts/__init__.py +48 -0
- sofic/shifts/algorithms.py +84 -0
- sofic/shifts/base.py +49 -0
- sofic/shifts/cover_construction.py +76 -0
- sofic/shifts/covers.py +47 -0
- sofic/shifts/dyck_algorithms.py +100 -0
- sofic/shifts/dyck_enumeration.py +275 -0
- sofic/shifts/markov_dyck.py +172 -0
- sofic/shifts/parry_construction.py +82 -0
- sofic/shifts/sft.py +104 -0
- sofic/shifts/sft_construction.py +52 -0
- sofic/shifts/sliding_block_code.py +156 -0
- sofic/shifts/sofic.py +111 -0
- sofic/shifts/sofic_dyck.py +110 -0
- sofic/shifts/sofic_relation.py +64 -0
- sofic/shifts/textile.py +104 -0
- sofic/shifts/tmc.py +46 -0
- sofic/shifts/tmc_construction.py +58 -0
- sofic/shifts/topological_anatomy.py +150 -0
- sofic/states.py +27 -0
- sofic/testing/__init__.py +8 -0
- sofic/testing/strategies.py +154 -0
- sofic/viz/__init__.py +16 -0
- sofic/viz/_context.py +345 -0
- sofic/viz/_edge.py +216 -0
- sofic/viz/_format.py +89 -0
- sofic/viz/_labels.py +34 -0
- sofic/viz/_names.py +17 -0
- sofic/viz/_rational.py +20 -0
- sofic/viz/_tikz_compile.py +177 -0
- sofic/viz/_tikz_format.py +122 -0
- sofic/viz/_tikz_layout.py +218 -0
- sofic/viz/assets/vaucanson.tikz +71 -0
- sofic/viz/graphviz.py +158 -0
- sofic/viz/idiagram.py +350 -0
- sofic/viz/tikz.py +381 -0
- sofic-0.1.0.dist-info/METADATA +444 -0
- sofic-0.1.0.dist-info/RECORD +150 -0
- sofic-0.1.0.dist-info/WHEEL +4 -0
- sofic-0.1.0.dist-info/licenses/LICENSE.txt +29 -0
sofic/examples/shifts.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Constructors for canonical symbolic-shift examples."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Hashable, Sequence
|
|
6
|
+
|
|
7
|
+
from sofic.shifts.sofic_dyck import SoficDyckShift
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def dyck_shift_order(
|
|
11
|
+
k: int = 2,
|
|
12
|
+
*,
|
|
13
|
+
call_symbols: Sequence[Hashable] | None = None,
|
|
14
|
+
return_symbols: Sequence[Hashable] | None = None,
|
|
15
|
+
) -> SoficDyckShift:
|
|
16
|
+
"""Dyck shift of order ``k`` from Beal, Blockelet & Dima, Example 1.
|
|
17
|
+
|
|
18
|
+
The default symbols follow the paper's notation:
|
|
19
|
+
``A_c = {a1, ..., ak}`` and ``A_r = {b1, ..., bk}``.
|
|
20
|
+
"""
|
|
21
|
+
if k < 1:
|
|
22
|
+
raise ValueError("k must be >= 1")
|
|
23
|
+
calls = tuple(call_symbols) if call_symbols is not None else tuple(f"a{i}" for i in range(1, k + 1))
|
|
24
|
+
returns = tuple(return_symbols) if return_symbols is not None else tuple(f"b{i}" for i in range(1, k + 1))
|
|
25
|
+
if len(calls) != k or len(returns) != k:
|
|
26
|
+
raise ValueError("call_symbols and return_symbols must have length k")
|
|
27
|
+
|
|
28
|
+
return _one_state_dyck_shift(calls, returns, ())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def motzkin_shift(
|
|
32
|
+
call_symbols: Sequence[Hashable] = ("(", "["),
|
|
33
|
+
return_symbols: Sequence[Hashable] = (")", "]"),
|
|
34
|
+
internal_symbols: Sequence[Hashable] = ("i",),
|
|
35
|
+
) -> SoficDyckShift:
|
|
36
|
+
"""Motzkin shift shown on the left of Beal, Blockelet & Dima, Fig. 1."""
|
|
37
|
+
return _one_state_dyck_shift(tuple(call_symbols), tuple(return_symbols), tuple(internal_symbols))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def sofic_dyck_fig1_shift() -> SoficDyckShift:
|
|
41
|
+
"""Two-state sofic-Dyck shift shown on the right of Beal, Blockelet & Dima, Fig. 1."""
|
|
42
|
+
shift = SoficDyckShift(
|
|
43
|
+
call_alphabet=frozenset({"(", "["}),
|
|
44
|
+
return_alphabet=frozenset({")", "]"}),
|
|
45
|
+
internal_alphabet=frozenset({"i"}),
|
|
46
|
+
)
|
|
47
|
+
shift.graph.add_state("1")
|
|
48
|
+
shift.graph.add_state("2")
|
|
49
|
+
|
|
50
|
+
left_paren = shift.add_call_transition("1", "1", "(")
|
|
51
|
+
left_bracket = shift.add_call_transition("1", "1", "[")
|
|
52
|
+
right_paren = shift.add_return_transition("1", "1", ")")
|
|
53
|
+
right_bracket = shift.add_return_transition("1", "1", "]")
|
|
54
|
+
shift.add_internal_transition("1", "2", "i")
|
|
55
|
+
shift.add_internal_transition("2", "1", "i")
|
|
56
|
+
|
|
57
|
+
shift.add_matched_pair(left_paren, right_paren)
|
|
58
|
+
shift.add_matched_pair(left_bracket, right_bracket)
|
|
59
|
+
return shift
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def sofic_dyck_nondeterminizable_shift() -> SoficDyckShift:
|
|
63
|
+
"""Sofic-Dyck shift with no deterministic presentation from Beal et al., Fig. 3."""
|
|
64
|
+
shift = SoficDyckShift(
|
|
65
|
+
call_alphabet=frozenset({"a"}),
|
|
66
|
+
return_alphabet=frozenset({"b"}),
|
|
67
|
+
internal_alphabet=frozenset({"i", "j", "k"}),
|
|
68
|
+
)
|
|
69
|
+
for state in ("1", "2", "3"):
|
|
70
|
+
shift.graph.add_state(state)
|
|
71
|
+
|
|
72
|
+
call = shift.add_call_transition("1", "1", "a")
|
|
73
|
+
return_2 = shift.add_return_transition("2", "2", "b")
|
|
74
|
+
shift.add_return_transition("3", "3", "b")
|
|
75
|
+
shift.add_internal_transition("1", "2", "i")
|
|
76
|
+
shift.add_internal_transition("2", "1", "j")
|
|
77
|
+
shift.add_internal_transition("1", "3", "i")
|
|
78
|
+
shift.add_internal_transition("3", "1", "k")
|
|
79
|
+
|
|
80
|
+
shift.add_matched_pair(call, return_2)
|
|
81
|
+
return shift
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def sofic_dyck_zeta_example_shift() -> SoficDyckShift:
|
|
85
|
+
"""Sofic-Dyck shift used in Beal, Blockelet & Dima's zeta-function example."""
|
|
86
|
+
shift = SoficDyckShift(
|
|
87
|
+
call_alphabet=frozenset({"a", "a'"}),
|
|
88
|
+
return_alphabet=frozenset({"b", "b'"}),
|
|
89
|
+
internal_alphabet=frozenset({"i"}),
|
|
90
|
+
)
|
|
91
|
+
shift.graph.add_state("1")
|
|
92
|
+
shift.graph.add_state("2")
|
|
93
|
+
|
|
94
|
+
return_b = shift.add_return_transition("1", "1", "b")
|
|
95
|
+
call_a = shift.add_call_transition("1", "1", "a")
|
|
96
|
+
call_a_prime = shift.add_call_transition("1", "1", "a'")
|
|
97
|
+
return_b_prime = shift.add_return_transition("1", "1", "b'")
|
|
98
|
+
shift.add_internal_transition("1", "2", "i")
|
|
99
|
+
shift.add_internal_transition("2", "1", "i")
|
|
100
|
+
|
|
101
|
+
shift.add_matched_pair(call_a, return_b)
|
|
102
|
+
shift.add_matched_pair(call_a_prime, return_b_prime)
|
|
103
|
+
return shift
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _one_state_dyck_shift(
|
|
107
|
+
call_symbols: tuple[Hashable, ...],
|
|
108
|
+
return_symbols: tuple[Hashable, ...],
|
|
109
|
+
internal_symbols: tuple[Hashable, ...],
|
|
110
|
+
) -> SoficDyckShift:
|
|
111
|
+
if len(call_symbols) != len(return_symbols):
|
|
112
|
+
raise ValueError("call_symbols and return_symbols must have the same length")
|
|
113
|
+
_require_disjoint(call_symbols, return_symbols, internal_symbols)
|
|
114
|
+
|
|
115
|
+
shift = SoficDyckShift(
|
|
116
|
+
call_alphabet=frozenset(call_symbols),
|
|
117
|
+
return_alphabet=frozenset(return_symbols),
|
|
118
|
+
internal_alphabet=frozenset(internal_symbols),
|
|
119
|
+
)
|
|
120
|
+
state = "1"
|
|
121
|
+
shift.graph.add_state(state)
|
|
122
|
+
|
|
123
|
+
call_refs = {symbol: shift.add_call_transition(state, state, symbol) for symbol in call_symbols}
|
|
124
|
+
return_refs = {symbol: shift.add_return_transition(state, state, symbol) for symbol in return_symbols}
|
|
125
|
+
for symbol in internal_symbols:
|
|
126
|
+
shift.add_internal_transition(state, state, symbol)
|
|
127
|
+
for call_symbol, return_symbol in zip(call_symbols, return_symbols, strict=True):
|
|
128
|
+
shift.add_matched_pair(call_refs[call_symbol], return_refs[return_symbol])
|
|
129
|
+
return shift
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _require_disjoint(*symbol_groups: tuple[Hashable, ...]) -> None:
|
|
133
|
+
symbols = tuple(symbol for group in symbol_groups for symbol in group)
|
|
134
|
+
if len(frozenset(symbols)) != len(symbols):
|
|
135
|
+
raise ValueError("call, return, and internal symbols must be distinct")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = [
|
|
139
|
+
"dyck_shift_order",
|
|
140
|
+
"motzkin_shift",
|
|
141
|
+
"sofic_dyck_fig1_shift",
|
|
142
|
+
"sofic_dyck_nondeterminizable_shift",
|
|
143
|
+
"sofic_dyck_zeta_example_shift",
|
|
144
|
+
]
|
sofic/exceptions.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Exceptions raised by sofic."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SoficError(Exception):
|
|
5
|
+
"""Base class for sofic errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SoficValidationError(SoficError):
|
|
9
|
+
"""Raised when a model fails structural or semantic validation."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NonDeterministicError(SoficValidationError):
|
|
13
|
+
"""Raised when a DFA determinism invariant is violated."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StochasticValidationError(SoficValidationError):
|
|
17
|
+
"""Raised when probability masses are invalid."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UnifilarityError(SoficValidationError):
|
|
21
|
+
"""Raised when a unifilarity invariant is violated."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QuasiStochasticValidationError(SoficValidationError):
|
|
25
|
+
"""Raised when quasi-stochastic invariants are violated."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LumpabilityError(SoficValidationError):
|
|
29
|
+
"""Raised when a partition is not strongly lumpable for a model."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InfiniteTransductionError(SoficError):
|
|
33
|
+
"""Raised when a finite input has infinitely many transducer outputs."""
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Stochastic and quasiprobabilistic generators."""
|
|
2
|
+
|
|
3
|
+
from sofic.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel
|
|
4
|
+
from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine
|
|
5
|
+
from sofic.generators.block_convergence import BlockConvergenceDiagram, BlockConvergenceEstimates
|
|
6
|
+
from sofic.generators.block_entropy import BlockEntropyDiagram, BlockEntropyEstimates
|
|
7
|
+
from sofic.generators.channel_measures import (
|
|
8
|
+
channel_statistical_complexity,
|
|
9
|
+
driven_entropy_rate,
|
|
10
|
+
)
|
|
11
|
+
from sofic.generators.directional_flow import (
|
|
12
|
+
directed_information,
|
|
13
|
+
independent_pair_generator,
|
|
14
|
+
intrinsic_information_flow,
|
|
15
|
+
shared_information_flow,
|
|
16
|
+
synergistic_information_flow,
|
|
17
|
+
transfer_entropy,
|
|
18
|
+
)
|
|
19
|
+
from sofic.generators.epsilon_inference import cssr, spectral, subtree_merge
|
|
20
|
+
from sofic.generators.epsilon_machine import EpsilonMachine
|
|
21
|
+
from sofic.generators.epsilon_transducer import EpsilonTransducer
|
|
22
|
+
from sofic.generators.lumping import LumpabilityError, is_lumpable, lump, normalize_partition
|
|
23
|
+
from sofic.generators.markov import MarkovChain
|
|
24
|
+
from sofic.generators.mealy import MealyHMM
|
|
25
|
+
from sofic.generators.minimal_generative_model import (
|
|
26
|
+
FunctionalGenerativeModel,
|
|
27
|
+
GacsKornerGenerativeModel,
|
|
28
|
+
MinimalGenerativeModel,
|
|
29
|
+
WynerGenerativeModel,
|
|
30
|
+
functional_generative_model,
|
|
31
|
+
gacs_korner_generative_model,
|
|
32
|
+
minimal_generative_model,
|
|
33
|
+
wyner_generative_model,
|
|
34
|
+
)
|
|
35
|
+
from sofic.generators.mixed_state import MixedState, MixedStatePresentation
|
|
36
|
+
from sofic.generators.moore import MooreHMM
|
|
37
|
+
from sofic.generators.nmachine import NMachine
|
|
38
|
+
from sofic.generators.pfa import ProbabilisticFiniteAutomaton
|
|
39
|
+
from sofic.generators.quasi_realization import QuasiRealization
|
|
40
|
+
from sofic.generators.stack_hmm import HiddenMarkovStackModel
|
|
41
|
+
from sofic.generators.stack_inference import (
|
|
42
|
+
fit_stack_hmm_mle,
|
|
43
|
+
learn_stack_hmm_papni,
|
|
44
|
+
stack_cssr,
|
|
45
|
+
stack_subtree_merge,
|
|
46
|
+
)
|
|
47
|
+
from sofic.generators.topological_epsilon_enumeration import (
|
|
48
|
+
count_topological_epsilon_machines,
|
|
49
|
+
epsilon_machine_to_idfa_string,
|
|
50
|
+
idfa_string_to_epsilon_machine,
|
|
51
|
+
is_canonical_topological_epsilon,
|
|
52
|
+
is_minimal_idfa,
|
|
53
|
+
is_strongly_connected_idfa,
|
|
54
|
+
is_topological_epsilon_string,
|
|
55
|
+
iter_topological_epsilon_machines,
|
|
56
|
+
iter_topological_epsilon_strings,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"BidirectionalEpsilonMachine",
|
|
61
|
+
"BlockEntropyDiagram",
|
|
62
|
+
"BlockEntropyEstimates",
|
|
63
|
+
"BlockConvergenceDiagram",
|
|
64
|
+
"BlockConvergenceEstimates",
|
|
65
|
+
"EpsilonMachine",
|
|
66
|
+
"EpsilonTransducer",
|
|
67
|
+
"LumpabilityError",
|
|
68
|
+
"channel_statistical_complexity",
|
|
69
|
+
"driven_entropy_rate",
|
|
70
|
+
"cssr",
|
|
71
|
+
"is_lumpable",
|
|
72
|
+
"lump",
|
|
73
|
+
"normalize_partition",
|
|
74
|
+
"spectral",
|
|
75
|
+
"subtree_merge",
|
|
76
|
+
"fit_stack_hmm_mle",
|
|
77
|
+
"learn_stack_hmm_papni",
|
|
78
|
+
"stack_cssr",
|
|
79
|
+
"stack_subtree_merge",
|
|
80
|
+
"directed_information",
|
|
81
|
+
"independent_pair_generator",
|
|
82
|
+
"intrinsic_information_flow",
|
|
83
|
+
"shared_information_flow",
|
|
84
|
+
"synergistic_information_flow",
|
|
85
|
+
"transfer_entropy",
|
|
86
|
+
"FunctionalGenerativeModel",
|
|
87
|
+
"GacsKornerGenerativeModel",
|
|
88
|
+
"HiddenMarkovModel",
|
|
89
|
+
"HiddenMarkovStackModel",
|
|
90
|
+
"MarkovChain",
|
|
91
|
+
"MealyHMM",
|
|
92
|
+
"MinimalGenerativeModel",
|
|
93
|
+
"MixedState",
|
|
94
|
+
"MixedStatePresentation",
|
|
95
|
+
"MooreHMM",
|
|
96
|
+
"NMachine",
|
|
97
|
+
"ProbabilisticFiniteAutomaton",
|
|
98
|
+
"QuasiRealization",
|
|
99
|
+
"QuasiStochasticModel",
|
|
100
|
+
"StochasticModel",
|
|
101
|
+
"WynerGenerativeModel",
|
|
102
|
+
"count_topological_epsilon_machines",
|
|
103
|
+
"epsilon_machine_to_idfa_string",
|
|
104
|
+
"functional_generative_model",
|
|
105
|
+
"gacs_korner_generative_model",
|
|
106
|
+
"idfa_string_to_epsilon_machine",
|
|
107
|
+
"is_canonical_topological_epsilon",
|
|
108
|
+
"is_minimal_idfa",
|
|
109
|
+
"is_strongly_connected_idfa",
|
|
110
|
+
"is_topological_epsilon_string",
|
|
111
|
+
"iter_topological_epsilon_machines",
|
|
112
|
+
"iter_topological_epsilon_strings",
|
|
113
|
+
"minimal_generative_model",
|
|
114
|
+
"wyner_generative_model",
|
|
115
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Dit-backed word-distribution measures for block curves.
|
|
2
|
+
|
|
3
|
+
Leaf module shared by :mod:`sofic.generators.block_entropy` and
|
|
4
|
+
:mod:`sofic.generators.block_convergence`. It depends only on ``dit`` (via
|
|
5
|
+
:func:`sofic.generators.measures.require_dit`) and must not import either
|
|
6
|
+
block module, so that both can depend on it without a circular import.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from sofic.generators.epsilon_machine import EpsilonMachine
|
|
15
|
+
|
|
16
|
+
_TOL = 1e-15
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _require_dit() -> Any:
|
|
20
|
+
from sofic.generators.measures import require_dit
|
|
21
|
+
|
|
22
|
+
return require_dit("block convergence measures")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _positional_rvs(length: int) -> list[list[int]]:
|
|
26
|
+
return [[index] for index in range(length)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _word_distribution(distribution: dict[tuple[Any, ...], float]) -> Any:
|
|
30
|
+
dit = _require_dit()
|
|
31
|
+
if not distribution:
|
|
32
|
+
return dit.Distribution([()], [1.0])
|
|
33
|
+
total = sum(float(prob) for prob in distribution.values())
|
|
34
|
+
if total <= _TOL:
|
|
35
|
+
return dit.Distribution([()], [1.0])
|
|
36
|
+
outcomes = list(distribution.keys())
|
|
37
|
+
probs = [float(distribution[outcome]) / total for outcome in outcomes]
|
|
38
|
+
dist = dit.Distribution(outcomes, probs)
|
|
39
|
+
dist.set_rv_names([f"X_{index}" for index in range(len(outcomes[0]))])
|
|
40
|
+
return dist
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _block_word_distribution(machine: EpsilonMachine, length: int) -> Any:
|
|
44
|
+
if length == 0:
|
|
45
|
+
dit = _require_dit()
|
|
46
|
+
return dit.Distribution([()], [1.0])
|
|
47
|
+
return _word_distribution(machine.word_probabilities(length))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _block_total_correlation(dist: Any, length: int, block_entropy: float, h1: float) -> float:
|
|
51
|
+
if length <= 1:
|
|
52
|
+
return 0.0
|
|
53
|
+
_require_dit()
|
|
54
|
+
from dit.multivariate import total_correlation
|
|
55
|
+
|
|
56
|
+
return float(total_correlation(dist, rvs=_positional_rvs(length)))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _block_residual_entropy(dist: Any, length: int) -> float:
|
|
60
|
+
if length == 0:
|
|
61
|
+
return 0.0
|
|
62
|
+
_require_dit()
|
|
63
|
+
from dit.multivariate import residual_entropy
|
|
64
|
+
|
|
65
|
+
return float(residual_entropy(dist, rvs=_positional_rvs(length)))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _block_coinformation(dist: Any, length: int, block_entropy: float) -> float:
|
|
69
|
+
if length <= 1:
|
|
70
|
+
return 0.0
|
|
71
|
+
_require_dit()
|
|
72
|
+
from dit.multivariate import coinformation
|
|
73
|
+
|
|
74
|
+
return float(coinformation(dist, rvs=_positional_rvs(length)))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _block_caekl(dist: Any, length: int) -> float:
|
|
78
|
+
if length <= 1:
|
|
79
|
+
return 0.0
|
|
80
|
+
_require_dit()
|
|
81
|
+
from dit.multivariate import caekl_mutual_information
|
|
82
|
+
|
|
83
|
+
return float(caekl_mutual_information(dist, rvs=_positional_rvs(length)))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def residual_entropy_from_distribution(distribution: dict[tuple[Any, ...], float]) -> float:
|
|
87
|
+
"""Dit-backed residual entropy for a word distribution."""
|
|
88
|
+
if not distribution:
|
|
89
|
+
return 0.0
|
|
90
|
+
length = len(next(iter(distribution)))
|
|
91
|
+
if length == 0:
|
|
92
|
+
return 0.0
|
|
93
|
+
dist = _word_distribution(distribution)
|
|
94
|
+
return _block_residual_entropy(dist, length)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Alternative complexity measures for epsilon-machines."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from sofic.generators.stochastic import shannon_entropy
|
|
11
|
+
from sofic.graph import ATTR_PROB
|
|
12
|
+
from sofic.properties import transition_matrix
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from sofic.generators.epsilon_machine import EpsilonMachine
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def structural_information(machine: EpsilonMachine) -> float:
|
|
19
|
+
"""Asymptotic structural information (equals excess entropy for finite-state sources).
|
|
20
|
+
|
|
21
|
+
For a canonical epsilon-machine, :math:`I_{\\mathrm{struct}} = \\lim_{L\\to\\infty}
|
|
22
|
+
H[S_L \\mid X_{0:L}] = E` (Feldman & Crutchfield, 1998; Ellison et al., 2009).
|
|
23
|
+
"""
|
|
24
|
+
return machine.excess_entropy()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def thermodynamic_depth(machine: EpsilonMachine) -> float:
|
|
28
|
+
"""Thermodynamic depth: stationary average mean first-passage time to causal states.
|
|
29
|
+
|
|
30
|
+
Uses the hidden-state transition matrix (summing over emissions) and the
|
|
31
|
+
stationary causal-state distribution (Shalizi & Crutchfield, 1999).
|
|
32
|
+
"""
|
|
33
|
+
pi = machine.stationary_distribution()
|
|
34
|
+
states = list(machine.states())
|
|
35
|
+
if not states:
|
|
36
|
+
return 0.0
|
|
37
|
+
index = {state: i for i, state in enumerate(states)}
|
|
38
|
+
n = len(states)
|
|
39
|
+
transition, _ = transition_matrix(machine, attr=ATTR_PROB, states=states)
|
|
40
|
+
|
|
41
|
+
pi_vec = np.asarray([pi[index[state]] for state in states], dtype=float)
|
|
42
|
+
pi_vec = pi_vec / pi_vec.sum()
|
|
43
|
+
|
|
44
|
+
depths = np.zeros(n, dtype=float)
|
|
45
|
+
for target in range(n):
|
|
46
|
+
depths[target] = _mean_first_passage_time(transition, pi_vec, target)
|
|
47
|
+
|
|
48
|
+
return float(np.dot(pi_vec, depths))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def spectral_complexity(machine: EpsilonMachine) -> float:
|
|
52
|
+
"""Spectral entropy of nontrivial mixed-state transition eigenvalues.
|
|
53
|
+
|
|
54
|
+
Builds the recurrent mixed-state presentation and summarizes the modulus
|
|
55
|
+
spectrum of its transition operator (Riechers & Crutchfield, 2017).
|
|
56
|
+
"""
|
|
57
|
+
from sofic.generators.mixed_state_construction import build_mixed_state_presentation
|
|
58
|
+
|
|
59
|
+
msp = build_mixed_state_presentation(machine)
|
|
60
|
+
states = tuple(msp.recurrent_states)
|
|
61
|
+
if len(states) <= 1:
|
|
62
|
+
return 0.0
|
|
63
|
+
|
|
64
|
+
matrix, _ = transition_matrix(msp, attr=ATTR_PROB, states=states)
|
|
65
|
+
|
|
66
|
+
eigenvalues = np.linalg.eigvals(matrix)
|
|
67
|
+
moduli = np.sort(np.abs(eigenvalues))[::-1]
|
|
68
|
+
nontrivial = moduli[moduli < 1.0 - 1e-9]
|
|
69
|
+
if nontrivial.size == 0:
|
|
70
|
+
return 0.0
|
|
71
|
+
weights = nontrivial / nontrivial.sum()
|
|
72
|
+
return shannon_entropy(weights)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _mean_first_passage_time(transition: np.ndarray, start: np.ndarray, target: int) -> float:
|
|
76
|
+
n = transition.shape[0]
|
|
77
|
+
if n == 0:
|
|
78
|
+
return 0.0
|
|
79
|
+
if n == 1:
|
|
80
|
+
return 0.0
|
|
81
|
+
|
|
82
|
+
# Fundamental-matrix formula for MFPT from i to j.
|
|
83
|
+
pi = _stationary_vector(transition)
|
|
84
|
+
if pi is None:
|
|
85
|
+
pi = np.ones(n) / n
|
|
86
|
+
fundamental = np.linalg.inv(np.eye(n) - transition + np.outer(np.ones(n), pi))
|
|
87
|
+
mfpt = np.zeros(n, dtype=float)
|
|
88
|
+
for i in range(n):
|
|
89
|
+
if i == target:
|
|
90
|
+
mfpt[i] = 0.0
|
|
91
|
+
elif pi[target] <= 0.0:
|
|
92
|
+
mfpt[i] = math.inf
|
|
93
|
+
else:
|
|
94
|
+
mfpt[i] = (fundamental[target, target] - fundamental[i, target]) / pi[target]
|
|
95
|
+
return float(start @ mfpt)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _stationary_vector(transition: np.ndarray) -> np.ndarray | None:
|
|
99
|
+
from sofic.generators.stationary import stationary_distribution_from_transition
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
return stationary_distribution_from_transition(transition)
|
|
103
|
+
except Exception:
|
|
104
|
+
return None
|