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
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
"""Bidirectional ε-machine — joint forward/reverse causal presentation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Hashable
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
from sofic.exceptions import SoficValidationError
|
|
9
|
+
from sofic.generators.epsilon_machine import EpsilonMachine
|
|
10
|
+
from sofic.generators.mealy import MealyHMM
|
|
11
|
+
|
|
12
|
+
_STEP_S_PLUS_0 = 0
|
|
13
|
+
_STEP_S_MINUS_0 = 1
|
|
14
|
+
_STEP_X_0 = 2
|
|
15
|
+
_STEP_S_PLUS_1 = 3
|
|
16
|
+
_STEP_S_MINUS_1 = 4
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _require_dit():
|
|
20
|
+
from sofic.generators.measures import require_dit
|
|
21
|
+
|
|
22
|
+
return require_dit("entropy measures")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _measure_value(dist: Any, value: Any) -> Any:
|
|
26
|
+
"""Return a dit measure result as Expr when ``dist`` is symbolic, else float."""
|
|
27
|
+
if hasattr(dist, "is_symbolic") and dist.is_symbolic():
|
|
28
|
+
return value
|
|
29
|
+
return float(value)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BidirectionalEpsilonMachine(MealyHMM):
|
|
33
|
+
"""Non-unifilar generator over joint causal states (S⁺, S⁻).
|
|
34
|
+
|
|
35
|
+
Pairs forward and reverse ε-machines into a single presentation whose states
|
|
36
|
+
are ``(forward, reverse)`` tuples. Supports excess entropy, crypticity, and
|
|
37
|
+
information anatomy when ``dit`` is installed.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
41
|
+
>>> from sofic.examples import golden_mean_bidirectional
|
|
42
|
+
>>> bidir = golden_mean_bidirectional(0.5)
|
|
43
|
+
>>> bidir.entropy_rate() > 0
|
|
44
|
+
True
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
forward_machine: EpsilonMachine
|
|
48
|
+
reverse_machine: EpsilonMachine
|
|
49
|
+
_joint_pi: dict[tuple[Hashable, Hashable], Any] | None = None
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
forward_machine: EpsilonMachine | None = None,
|
|
54
|
+
reverse_machine: EpsilonMachine | None = None,
|
|
55
|
+
**kwargs: Any,
|
|
56
|
+
) -> None:
|
|
57
|
+
super().__init__(**kwargs)
|
|
58
|
+
if forward_machine is None or reverse_machine is None:
|
|
59
|
+
raise SoficValidationError("forward_machine and reverse_machine are required")
|
|
60
|
+
self.forward_machine = forward_machine
|
|
61
|
+
self.reverse_machine = reverse_machine
|
|
62
|
+
|
|
63
|
+
def validate(self) -> None:
|
|
64
|
+
super().validate_stochastic()
|
|
65
|
+
for state in self.states():
|
|
66
|
+
if not isinstance(state, tuple) or len(state) != 2:
|
|
67
|
+
raise SoficValidationError(f"bidirectional state must be (forward, reverse) pair, got {state!r}")
|
|
68
|
+
|
|
69
|
+
def is_unifilar(self) -> bool:
|
|
70
|
+
"""Return whether joint emissions are row-unifilar (usually ``False``)."""
|
|
71
|
+
from sofic.properties import is_unifilar_emissions
|
|
72
|
+
|
|
73
|
+
return is_unifilar_emissions(self)
|
|
74
|
+
|
|
75
|
+
def entropy_rate(self) -> Any:
|
|
76
|
+
"""Process entropy rate h_μ (same as the forward ε-machine)."""
|
|
77
|
+
return self.forward_machine.entropy_rate()
|
|
78
|
+
|
|
79
|
+
def copy(self) -> Self:
|
|
80
|
+
cloned = super().copy()
|
|
81
|
+
cloned._joint_pi = None
|
|
82
|
+
return cloned
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_pair(
|
|
86
|
+
cls,
|
|
87
|
+
forward: EpsilonMachine,
|
|
88
|
+
reverse: EpsilonMachine,
|
|
89
|
+
) -> BidirectionalEpsilonMachine:
|
|
90
|
+
from sofic.generators.bidirectional_construction import build_bidirectional_epsilon_machine
|
|
91
|
+
|
|
92
|
+
return build_bidirectional_epsilon_machine(forward, reverse)
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_forward(cls, forward: EpsilonMachine) -> BidirectionalEpsilonMachine:
|
|
96
|
+
from sofic.generators.bidirectional_construction import infer_reverse_epsilon_machine
|
|
97
|
+
|
|
98
|
+
reverse = infer_reverse_epsilon_machine(forward)
|
|
99
|
+
return cls.from_pair(forward, reverse)
|
|
100
|
+
|
|
101
|
+
def joint_distribution(self) -> dict[tuple[Hashable, Hashable], Any]:
|
|
102
|
+
if self._joint_pi is not None:
|
|
103
|
+
return dict(self._joint_pi)
|
|
104
|
+
from sofic.generators.bidirectional_construction import joint_distribution
|
|
105
|
+
|
|
106
|
+
return joint_distribution(self)
|
|
107
|
+
|
|
108
|
+
def forward_epsilon_machine(self) -> EpsilonMachine:
|
|
109
|
+
from sofic.generators.bidirectional_construction import forward_epsilon_machine
|
|
110
|
+
|
|
111
|
+
return forward_epsilon_machine(self)
|
|
112
|
+
|
|
113
|
+
def reverse_epsilon_machine(self) -> EpsilonMachine:
|
|
114
|
+
from sofic.generators.bidirectional_construction import reverse_epsilon_machine
|
|
115
|
+
|
|
116
|
+
return reverse_epsilon_machine(self)
|
|
117
|
+
|
|
118
|
+
def step_distribution(self) -> Any:
|
|
119
|
+
from sofic.generators.bidirectional_construction import bidirectional_step_distribution
|
|
120
|
+
|
|
121
|
+
return bidirectional_step_distribution(self)
|
|
122
|
+
|
|
123
|
+
def predicted_information(self) -> Any:
|
|
124
|
+
"""ρ_μ = I[X₀ : S⁺₀] — predicted information rate (James et al., 2013)."""
|
|
125
|
+
dit = _require_dit()
|
|
126
|
+
dist = self.step_distribution()
|
|
127
|
+
return _measure_value(dist, dit.shannon.mutual_information(dist, [_STEP_X_0], [_STEP_S_PLUS_0]))
|
|
128
|
+
|
|
129
|
+
def bound_information(self) -> Any:
|
|
130
|
+
"""b_μ = I[X₀ : S⁻₁ | S⁺₀] — bound information rate (James et al., 2013)."""
|
|
131
|
+
dit = _require_dit()
|
|
132
|
+
dist = self.step_distribution()
|
|
133
|
+
return _measure_value(
|
|
134
|
+
dist,
|
|
135
|
+
dit.shannon.conditional_entropy(dist, [_STEP_X_0], [_STEP_S_PLUS_0])
|
|
136
|
+
- dit.shannon.conditional_entropy(
|
|
137
|
+
dist,
|
|
138
|
+
[_STEP_X_0],
|
|
139
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1],
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def ephemeral_information(self) -> Any:
|
|
144
|
+
"""r_μ = H[X₀ | S⁺₀, S⁻₁] — ephemeral information rate (James et al., 2013)."""
|
|
145
|
+
dit = _require_dit()
|
|
146
|
+
dist = self.step_distribution()
|
|
147
|
+
return _measure_value(
|
|
148
|
+
dist,
|
|
149
|
+
dit.shannon.conditional_entropy(
|
|
150
|
+
dist,
|
|
151
|
+
[_STEP_X_0],
|
|
152
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1],
|
|
153
|
+
),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def structural_ephemeral_information(self) -> Any:
|
|
157
|
+
"""r_μ^struct = H[S⁺₁ | S⁺₀, S⁻₁] — structural (branching) part of r_μ.
|
|
158
|
+
|
|
159
|
+
The next forward causal state S⁺₁ is a deterministic function of S⁺₀ and
|
|
160
|
+
X₀, so this equals I[X₀ : S⁺₁ | S⁺₀, S⁻₁]: the ephemeral randomness that
|
|
161
|
+
selects among transitions to *different* next states (edges with
|
|
162
|
+
structural consequence) and is not resolved by the future S⁻₁. Together
|
|
163
|
+
with :meth:`parallel_edge_information` it partitions
|
|
164
|
+
:meth:`ephemeral_information` (r_μ = r_μ^struct + r_μ^par).
|
|
165
|
+
|
|
166
|
+
This refinement of the information anatomy (James et al., 2013) has no
|
|
167
|
+
separate canonical source; it follows from the determinism of the
|
|
168
|
+
forward transition function.
|
|
169
|
+
"""
|
|
170
|
+
dit = _require_dit()
|
|
171
|
+
dist = self.step_distribution()
|
|
172
|
+
return _measure_value(
|
|
173
|
+
dist,
|
|
174
|
+
dit.shannon.conditional_entropy(
|
|
175
|
+
dist,
|
|
176
|
+
[_STEP_S_PLUS_1],
|
|
177
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1],
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def parallel_edge_information(self) -> Any:
|
|
182
|
+
"""r_μ^par = H[X₀ | S⁺₀, S⁺₁, S⁻₁] — parallel-edge (gauge) part of r_μ.
|
|
183
|
+
|
|
184
|
+
Once the source S⁺₀ and destination S⁺₁ forward causal states are both
|
|
185
|
+
fixed, the residual symbol uncertainty is pure output relabeling on edges
|
|
186
|
+
"from the same state to the same state" — no structural consequence. It
|
|
187
|
+
is the gauge component of the ephemeral information, complementary to
|
|
188
|
+
:meth:`structural_ephemeral_information` (r_μ = r_μ^struct + r_μ^par).
|
|
189
|
+
|
|
190
|
+
Refinement of the information anatomy (James et al., 2013); no separate
|
|
191
|
+
canonical source.
|
|
192
|
+
"""
|
|
193
|
+
dit = _require_dit()
|
|
194
|
+
dist = self.step_distribution()
|
|
195
|
+
return _measure_value(
|
|
196
|
+
dist,
|
|
197
|
+
dit.shannon.conditional_entropy(
|
|
198
|
+
dist,
|
|
199
|
+
[_STEP_X_0],
|
|
200
|
+
[_STEP_S_PLUS_0, _STEP_S_PLUS_1, _STEP_S_MINUS_1],
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def reverse_structural_ephemeral_information(self) -> Any:
|
|
205
|
+
"""r̄_μ^struct = H[S⁻₀ | S⁺₀, S⁻₁] — reverse structural (branching) part of r_μ.
|
|
206
|
+
|
|
207
|
+
Time-reversed mirror of :meth:`structural_ephemeral_information`. The
|
|
208
|
+
previous reverse causal state S⁻₀ is a deterministic function of S⁻₁ and
|
|
209
|
+
X₀, so this equals I[X₀ : S⁻₀ | S⁺₀, S⁻₁]: the ephemeral randomness that
|
|
210
|
+
the *past* cannot foresee yet which selects among *different* retrodictive
|
|
211
|
+
(reverse) states — the branch the reverse ε-machine must resolve. It
|
|
212
|
+
partitions as r̄_μ^struct = r_μ^rev + r_μ^joint (see
|
|
213
|
+
:meth:`reverse_only_structural_ephemeral` and
|
|
214
|
+
:meth:`joint_structural_ephemeral`).
|
|
215
|
+
|
|
216
|
+
Refinement of the bidirectional information taxonomy
|
|
217
|
+
(:cite:`jurgens2026taxonomy`; James et al., 2013); follows from the
|
|
218
|
+
determinism of the reverse transition function.
|
|
219
|
+
"""
|
|
220
|
+
dit = _require_dit()
|
|
221
|
+
dist = self.step_distribution()
|
|
222
|
+
return _measure_value(
|
|
223
|
+
dist,
|
|
224
|
+
dit.shannon.conditional_entropy(
|
|
225
|
+
dist,
|
|
226
|
+
[_STEP_S_MINUS_0],
|
|
227
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1],
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def forward_only_structural_ephemeral(self) -> Any:
|
|
232
|
+
"""r_μ^fwd = H[S⁺₁ | S⁺₀, S⁻₁, S⁻₀] — forward-only ephemeral branch.
|
|
233
|
+
|
|
234
|
+
One of the four atoms of the five-variable ephemeral partition
|
|
235
|
+
r_μ = r_μ^fwd + r_μ^rev + r_μ^joint + r_μ^gauge. It is the present
|
|
236
|
+
randomness that changes the *next forward* causal state S⁺₁ while leaving
|
|
237
|
+
the *previous reverse* causal state S⁻₀ (and the future S⁻₁) unresolved:
|
|
238
|
+
a branch the future forgets but that the reverse presentation never even
|
|
239
|
+
sees. In the taxonomy of :cite:`jurgens2026taxonomy` this is the
|
|
240
|
+
persistent forward ephemeral rate ``p.r⁺_μ``.
|
|
241
|
+
"""
|
|
242
|
+
dit = _require_dit()
|
|
243
|
+
dist = self.step_distribution()
|
|
244
|
+
return _measure_value(
|
|
245
|
+
dist,
|
|
246
|
+
dit.shannon.conditional_entropy(
|
|
247
|
+
dist,
|
|
248
|
+
[_STEP_S_PLUS_1],
|
|
249
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1, _STEP_S_MINUS_0],
|
|
250
|
+
),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def reverse_only_structural_ephemeral(self) -> Any:
|
|
254
|
+
"""r_μ^rev = H[S⁻₀ | S⁺₀, S⁻₁, S⁺₁] — reverse-only ephemeral branch.
|
|
255
|
+
|
|
256
|
+
Time-reversed mirror of :meth:`forward_only_structural_ephemeral` and one
|
|
257
|
+
of the four atoms of r_μ = r_μ^fwd + r_μ^rev + r_μ^joint + r_μ^gauge. It
|
|
258
|
+
is the present randomness that changes the *previous reverse* causal state
|
|
259
|
+
S⁻₀ while leaving the *next forward* state S⁺₁ (and the future S⁻₁)
|
|
260
|
+
unresolved — the branch only the retrodictor must resolve. In the taxonomy
|
|
261
|
+
of :cite:`jurgens2026taxonomy` this is the persistent reverse ephemeral
|
|
262
|
+
rate ``p.r⁻_μ`` and is a clean arrow-of-time diagnostic: it can be nonzero
|
|
263
|
+
while r_μ^fwd vanishes (e.g. the noisy random phase-slip process).
|
|
264
|
+
"""
|
|
265
|
+
dit = _require_dit()
|
|
266
|
+
dist = self.step_distribution()
|
|
267
|
+
return _measure_value(
|
|
268
|
+
dist,
|
|
269
|
+
dit.shannon.conditional_entropy(
|
|
270
|
+
dist,
|
|
271
|
+
[_STEP_S_MINUS_0],
|
|
272
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1, _STEP_S_PLUS_1],
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def joint_structural_ephemeral(self) -> Any:
|
|
277
|
+
"""r_μ^joint = I[S⁺₁ : S⁻₀ | S⁺₀, S⁻₁] — joint ephemeral branch.
|
|
278
|
+
|
|
279
|
+
One of the four atoms of r_μ = r_μ^fwd + r_μ^rev + r_μ^joint + r_μ^gauge:
|
|
280
|
+
the present randomness that *simultaneously* selects the next forward
|
|
281
|
+
state S⁺₁ and the previous reverse state S⁻₀ — a single coin flip both
|
|
282
|
+
presentations must branch on but which the future S⁻₁ still forgets. It is
|
|
283
|
+
shared by the forward and reverse structural ephemeral rates
|
|
284
|
+
(r_μ^struct = r_μ^fwd + r_μ^joint and r̄_μ^struct = r_μ^rev + r_μ^joint),
|
|
285
|
+
and equals the persistent bidirectional ephemeral rate ``p.r±_μ`` of
|
|
286
|
+
:cite:`jurgens2026taxonomy`. Nonzero for the golden mean.
|
|
287
|
+
"""
|
|
288
|
+
dit = _require_dit()
|
|
289
|
+
dist = self.step_distribution()
|
|
290
|
+
return _measure_value(
|
|
291
|
+
dist,
|
|
292
|
+
dit.shannon.conditional_entropy(dist, [_STEP_S_PLUS_1], [_STEP_S_PLUS_0, _STEP_S_MINUS_1])
|
|
293
|
+
- dit.shannon.conditional_entropy(
|
|
294
|
+
dist,
|
|
295
|
+
[_STEP_S_PLUS_1],
|
|
296
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1, _STEP_S_MINUS_0],
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def pure_gauge_information(self) -> Any:
|
|
301
|
+
"""r_μ^gauge = H[X₀ | S⁺₀, S⁻₁, S⁺₁, S⁻₀] — pure-gauge ephemeral branch.
|
|
302
|
+
|
|
303
|
+
The fourth atom of r_μ = r_μ^fwd + r_μ^rev + r_μ^joint + r_μ^gauge: once
|
|
304
|
+
the source S⁺₀, both next states S⁺₁ and S⁻₀, and the future S⁻₁ are all
|
|
305
|
+
fixed, the residual symbol uncertainty is pure output relabeling on edges
|
|
306
|
+
with *no* structural consequence in either time direction — parallel edges
|
|
307
|
+
from the same state to the same state. It is the transient ephemeral rate
|
|
308
|
+
``t.r_μ`` of :cite:`jurgens2026taxonomy` and is the whole of r_μ for an
|
|
309
|
+
i.i.d. fair coin. Sharper than :meth:`parallel_edge_information`, which
|
|
310
|
+
still lumps in the reverse-only branch (r_μ^par = r_μ^rev + r_μ^gauge).
|
|
311
|
+
"""
|
|
312
|
+
dit = _require_dit()
|
|
313
|
+
dist = self.step_distribution()
|
|
314
|
+
return _measure_value(
|
|
315
|
+
dist,
|
|
316
|
+
dit.shannon.conditional_entropy(
|
|
317
|
+
dist,
|
|
318
|
+
[_STEP_X_0],
|
|
319
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1, _STEP_S_PLUS_1, _STEP_S_MINUS_0],
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def bound_structural_information(self) -> Any:
|
|
324
|
+
"""b_μ^struct = I[S⁺₁ : S⁻₁ | S⁺₀] — structural (branching) part of b_μ.
|
|
325
|
+
|
|
326
|
+
Companion to :meth:`structural_ephemeral_information`: the transition
|
|
327
|
+
randomness that changes the next forward causal state *and* is shared
|
|
328
|
+
with the future S⁻₁. Together with :meth:`bound_parallel_edge_information`
|
|
329
|
+
it partitions :meth:`bound_information` (b_μ = b_μ^struct + b_μ^par).
|
|
330
|
+
|
|
331
|
+
Refinement of the information anatomy (James et al., 2013); no separate
|
|
332
|
+
canonical source.
|
|
333
|
+
"""
|
|
334
|
+
dit = _require_dit()
|
|
335
|
+
dist = self.step_distribution()
|
|
336
|
+
return _measure_value(
|
|
337
|
+
dist,
|
|
338
|
+
dit.shannon.conditional_entropy(dist, [_STEP_S_PLUS_1], [_STEP_S_PLUS_0])
|
|
339
|
+
- dit.shannon.conditional_entropy(
|
|
340
|
+
dist,
|
|
341
|
+
[_STEP_S_PLUS_1],
|
|
342
|
+
[_STEP_S_PLUS_0, _STEP_S_MINUS_1],
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def bound_parallel_edge_information(self) -> Any:
|
|
347
|
+
"""b_μ^par = I[X₀ : S⁻₁ | S⁺₀, S⁺₁] — parallel-edge (gauge) part of b_μ.
|
|
348
|
+
|
|
349
|
+
Companion to :meth:`parallel_edge_information`: the output-relabeling
|
|
350
|
+
randomness on a fixed transition (source and destination forward states
|
|
351
|
+
held constant) that is nonetheless shared with the future S⁻₁. Together
|
|
352
|
+
with :meth:`bound_structural_information` it partitions
|
|
353
|
+
:meth:`bound_information` (b_μ = b_μ^struct + b_μ^par).
|
|
354
|
+
|
|
355
|
+
Refinement of the information anatomy (James et al., 2013); no separate
|
|
356
|
+
canonical source.
|
|
357
|
+
"""
|
|
358
|
+
dit = _require_dit()
|
|
359
|
+
dist = self.step_distribution()
|
|
360
|
+
return _measure_value(
|
|
361
|
+
dist,
|
|
362
|
+
dit.shannon.conditional_entropy(dist, [_STEP_X_0], [_STEP_S_PLUS_0, _STEP_S_PLUS_1])
|
|
363
|
+
- dit.shannon.conditional_entropy(
|
|
364
|
+
dist,
|
|
365
|
+
[_STEP_X_0],
|
|
366
|
+
[_STEP_S_PLUS_0, _STEP_S_PLUS_1, _STEP_S_MINUS_1],
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def reverse_bound_information(self) -> Any:
|
|
371
|
+
"""b̄_μ = I[X₀ : S⁺₀ | S⁻₁] — reverse (retrodictive) bound information.
|
|
372
|
+
|
|
373
|
+
Time-reversed mirror of :meth:`bound_information`: the present information
|
|
374
|
+
shared with the *past* causal state S⁺₀ but not already carried by the
|
|
375
|
+
future S⁻₁. Bound information is time-reversal invariant, so b̄_μ = b_μ
|
|
376
|
+
(:cite:`James2011`); this method computes it from the reverse triple and
|
|
377
|
+
the equality is asserted in the test-suite as a symmetry check.
|
|
378
|
+
"""
|
|
379
|
+
dit = _require_dit()
|
|
380
|
+
dist = self.step_distribution()
|
|
381
|
+
return _measure_value(
|
|
382
|
+
dist,
|
|
383
|
+
dit.shannon.conditional_entropy(dist, [_STEP_X_0], [_STEP_S_MINUS_1])
|
|
384
|
+
- dit.shannon.conditional_entropy(
|
|
385
|
+
dist,
|
|
386
|
+
[_STEP_X_0],
|
|
387
|
+
[_STEP_S_MINUS_1, _STEP_S_PLUS_0],
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def reverse_bound_structural_information(self) -> Any:
|
|
392
|
+
"""b̄_μ^struct = I[S⁻₀ : S⁺₀ | S⁻₁] — structural part of the reverse bound.
|
|
393
|
+
|
|
394
|
+
Time-reversed mirror of :meth:`bound_structural_information`: the branch
|
|
395
|
+
that changes the previous reverse causal state S⁻₀ *and* is shared with
|
|
396
|
+
the past S⁺₀. By Theorem A′ the reverse bound has no gauge part
|
|
397
|
+
(:meth:`reverse_bound_gauge_information` ≈ 0), so b̄_μ^struct = b̄_μ = b_μ.
|
|
398
|
+
|
|
399
|
+
Refinement of the bidirectional information taxonomy
|
|
400
|
+
(:cite:`jurgens2026taxonomy`; James et al., 2013).
|
|
401
|
+
"""
|
|
402
|
+
dit = _require_dit()
|
|
403
|
+
dist = self.step_distribution()
|
|
404
|
+
return _measure_value(
|
|
405
|
+
dist,
|
|
406
|
+
dit.shannon.conditional_entropy(dist, [_STEP_S_MINUS_0], [_STEP_S_MINUS_1])
|
|
407
|
+
- dit.shannon.conditional_entropy(
|
|
408
|
+
dist,
|
|
409
|
+
[_STEP_S_MINUS_0],
|
|
410
|
+
[_STEP_S_MINUS_1, _STEP_S_PLUS_0],
|
|
411
|
+
),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def reverse_bound_gauge_information(self) -> Any:
|
|
415
|
+
"""b̄_μ^gauge = I[X₀ : S⁺₀ | S⁻₁, S⁻₀] — reverse bound-gauge (Theorem A′).
|
|
416
|
+
|
|
417
|
+
Time-reversed mirror of :meth:`bound_parallel_edge_information`. Theorem A′
|
|
418
|
+
(the reverse of James et al.'s bound-gauge vanishing, Theorem A) asserts
|
|
419
|
+
this is identically zero: once the previous reverse state S⁻₀ is fixed,
|
|
420
|
+
the present symbol X₀ carries nothing further about the past S⁺₀ that the
|
|
421
|
+
future S⁻₁ did not already supply. Computed here to verify the theorem
|
|
422
|
+
numerically; the test-suite pins it to ≈ 0.
|
|
423
|
+
|
|
424
|
+
Refinement of the bidirectional information taxonomy
|
|
425
|
+
(:cite:`jurgens2026taxonomy`; James et al., 2013).
|
|
426
|
+
"""
|
|
427
|
+
dit = _require_dit()
|
|
428
|
+
dist = self.step_distribution()
|
|
429
|
+
return _measure_value(
|
|
430
|
+
dist,
|
|
431
|
+
dit.shannon.conditional_entropy(dist, [_STEP_X_0], [_STEP_S_MINUS_1, _STEP_S_MINUS_0])
|
|
432
|
+
- dit.shannon.conditional_entropy(
|
|
433
|
+
dist,
|
|
434
|
+
[_STEP_X_0],
|
|
435
|
+
[_STEP_S_MINUS_1, _STEP_S_MINUS_0, _STEP_S_PLUS_0],
|
|
436
|
+
),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def internal_markov_entropy_rate(self) -> Any:
|
|
440
|
+
"""h_μ^imc = H[S⁺₁ | S⁺₀] — entropy rate of the internal (causal-state) chain.
|
|
441
|
+
|
|
442
|
+
The ε-machine's causal states form a stationary Markov chain; this is its
|
|
443
|
+
entropy rate. By forward unifilarity (S⁺₁ = φ⁺(S⁺₀, X₀)) it equals
|
|
444
|
+
I[X₀ : S⁺₁ | S⁺₀] — the present randomness that *changes the next forward
|
|
445
|
+
state* — and decomposes through the anatomy as
|
|
446
|
+
|
|
447
|
+
h_μ^imc = b_μ + r_μ^fwd + r_μ^joint = h_μ − r_μ^rev − r_μ^gauge,
|
|
448
|
+
|
|
449
|
+
i.e. the process entropy rate with the parallel-edge (pure output
|
|
450
|
+
relabeling) randomness removed.
|
|
451
|
+
|
|
452
|
+
This is **not** time-reversal symmetric in general. The reverse
|
|
453
|
+
causal-state chain rate :meth:`reverse_internal_markov_entropy_rate`
|
|
454
|
+
differs by r_μ^fwd − r_μ^rev — the arrow-of-time asymmetry — and the two
|
|
455
|
+
coincide exactly when r_μ^fwd = r_μ^rev (e.g. the golden mean, even
|
|
456
|
+
process and Nemo, but not the noisy random phase slip or the butterfly).
|
|
457
|
+
|
|
458
|
+
Refinement of the information anatomy (:cite:`James2011`); no separate
|
|
459
|
+
canonical source.
|
|
460
|
+
"""
|
|
461
|
+
dit = _require_dit()
|
|
462
|
+
dist = self.step_distribution()
|
|
463
|
+
return _measure_value(
|
|
464
|
+
dist,
|
|
465
|
+
dit.shannon.conditional_entropy(dist, [_STEP_S_PLUS_1], [_STEP_S_PLUS_0]),
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def reverse_internal_markov_entropy_rate(self) -> Any:
|
|
469
|
+
"""h̄_μ^imc = H[S⁻₀ | S⁻₁] — entropy rate of the reverse causal-state chain.
|
|
470
|
+
|
|
471
|
+
Time-reversed mirror of :meth:`internal_markov_entropy_rate`. By reverse
|
|
472
|
+
unifilarity (S⁻₀ = φ⁻(S⁻₁, X₀)) it equals I[X₀ : S⁻₀ | S⁻₁] and
|
|
473
|
+
|
|
474
|
+
h̄_μ^imc = b_μ + r_μ^rev + r_μ^joint = h_μ − r_μ^fwd − r_μ^gauge.
|
|
475
|
+
|
|
476
|
+
Equals :meth:`internal_markov_entropy_rate` iff r_μ^fwd = r_μ^rev; the
|
|
477
|
+
difference h_μ^imc − h̄_μ^imc = r_μ^fwd − r_μ^rev is an arrow-of-time
|
|
478
|
+
diagnostic (:cite:`jurgens2026taxonomy`; James et al., 2013).
|
|
479
|
+
"""
|
|
480
|
+
dit = _require_dit()
|
|
481
|
+
dist = self.step_distribution()
|
|
482
|
+
return _measure_value(
|
|
483
|
+
dist,
|
|
484
|
+
dit.shannon.conditional_entropy(dist, [_STEP_S_MINUS_0], [_STEP_S_MINUS_1]),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
def caekl_causal_information(self) -> Any:
|
|
488
|
+
"""J[S⁺₀ : X₀ : S⁻₁] — CAEKL mutual information among past, present, and future.
|
|
489
|
+
|
|
490
|
+
Chan-AlBashabsheh-Ebrahimi-Kaced-Liu multivariate mutual information
|
|
491
|
+
(:cite:`chan2015multivariate`) over the information-anatomy triple
|
|
492
|
+
(:cite:`James2013`): the forward causal state S⁺₀ (past), the present
|
|
493
|
+
symbol X₀, and the reverse causal state S⁻₁ (future). Finite and
|
|
494
|
+
closed-form since the causal states are finite sufficient statistics of
|
|
495
|
+
the semi-infinite past and future.
|
|
496
|
+
"""
|
|
497
|
+
_require_dit()
|
|
498
|
+
from dit.multivariate import caekl_mutual_information
|
|
499
|
+
|
|
500
|
+
dist = self.step_distribution()
|
|
501
|
+
return _measure_value(
|
|
502
|
+
dist,
|
|
503
|
+
caekl_mutual_information(dist, rvs=[[_STEP_S_PLUS_0], [_STEP_X_0], [_STEP_S_MINUS_1]]),
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
def excess_entropy(self) -> Any:
|
|
507
|
+
"""Exact excess entropy E = I[S⁺; S⁻] from the bidirectional joint distribution."""
|
|
508
|
+
dit = _require_dit()
|
|
509
|
+
joint = self.joint_distribution()
|
|
510
|
+
if not joint:
|
|
511
|
+
return 0.0
|
|
512
|
+
|
|
513
|
+
from sofic.generators.prob import as_prob, has_symbolic, sum_probs
|
|
514
|
+
|
|
515
|
+
pi_plus: dict[Any, Any] = {}
|
|
516
|
+
pi_minus: dict[Any, Any] = {}
|
|
517
|
+
for (alpha, gamma), mass in joint.items():
|
|
518
|
+
pi_plus[alpha] = sum_probs([pi_plus.get(alpha, 0), mass])
|
|
519
|
+
pi_minus[gamma] = sum_probs([pi_minus.get(gamma, 0), mass])
|
|
520
|
+
|
|
521
|
+
plus_outcomes = list(pi_plus.keys())
|
|
522
|
+
minus_outcomes = list(pi_minus.keys())
|
|
523
|
+
joint_outcomes = list(joint.keys())
|
|
524
|
+
plus_pmf = [as_prob(pi_plus[s]) for s in plus_outcomes]
|
|
525
|
+
minus_pmf = [as_prob(pi_minus[s]) for s in minus_outcomes]
|
|
526
|
+
joint_pmf = [as_prob(joint[outcome]) for outcome in joint_outcomes]
|
|
527
|
+
if has_symbolic(joint_pmf):
|
|
528
|
+
from dit.symbolic import symbolic_distribution
|
|
529
|
+
|
|
530
|
+
plus_dist = symbolic_distribution(plus_outcomes, plus_pmf)
|
|
531
|
+
minus_dist = symbolic_distribution(minus_outcomes, minus_pmf)
|
|
532
|
+
joint_dist = symbolic_distribution(joint_outcomes, joint_pmf)
|
|
533
|
+
return dit.shannon.entropy(plus_dist) + dit.shannon.entropy(minus_dist) - dit.shannon.entropy(joint_dist)
|
|
534
|
+
plus_dist = dit.Distribution(plus_outcomes, plus_pmf)
|
|
535
|
+
minus_dist = dit.Distribution(minus_outcomes, minus_pmf)
|
|
536
|
+
joint_dist = dit.Distribution(joint_outcomes, joint_pmf)
|
|
537
|
+
return float(dit.shannon.entropy(plus_dist) + dit.shannon.entropy(minus_dist) - dit.shannon.entropy(joint_dist))
|
|
538
|
+
|
|
539
|
+
def statistical_complexity(self) -> Any:
|
|
540
|
+
"""C± = H[S⁺, S⁻] under the bidirectional stationary distribution."""
|
|
541
|
+
dit = _require_dit()
|
|
542
|
+
joint = self.joint_distribution()
|
|
543
|
+
if not joint:
|
|
544
|
+
return 0.0
|
|
545
|
+
outcomes = list(joint.keys())
|
|
546
|
+
probs = [joint[outcome] for outcome in outcomes]
|
|
547
|
+
from sofic.generators.prob import has_symbolic
|
|
548
|
+
|
|
549
|
+
if has_symbolic(probs):
|
|
550
|
+
from dit.symbolic import symbolic_distribution
|
|
551
|
+
|
|
552
|
+
return dit.shannon.entropy(symbolic_distribution(outcomes, probs))
|
|
553
|
+
return float(dit.shannon.entropy(dit.Distribution(outcomes, probs)))
|
|
554
|
+
|
|
555
|
+
def crypticity(self) -> Any:
|
|
556
|
+
"""χ = C± − E for a bidirectional presentation."""
|
|
557
|
+
return self.statistical_complexity() - self.excess_entropy()
|
|
558
|
+
|
|
559
|
+
def minimal_generative_model(self, **kwargs: Any) -> Any:
|
|
560
|
+
"""Construct the minimal-state-entropy generative presentation."""
|
|
561
|
+
from sofic.generators.minimal_generative_model import minimal_generative_model
|
|
562
|
+
|
|
563
|
+
return minimal_generative_model(self, **kwargs)
|
|
564
|
+
|
|
565
|
+
def wyner_generative_model(self, **kwargs: Any) -> Any:
|
|
566
|
+
"""Construct the Wyner-common-information generative presentation."""
|
|
567
|
+
from sofic.generators.minimal_generative_model import wyner_generative_model
|
|
568
|
+
|
|
569
|
+
return wyner_generative_model(self, **kwargs)
|
|
570
|
+
|
|
571
|
+
def functional_generative_model(self, **kwargs: Any) -> Any:
|
|
572
|
+
"""Construct the functional-common-information generative presentation."""
|
|
573
|
+
from sofic.generators.minimal_generative_model import functional_generative_model
|
|
574
|
+
|
|
575
|
+
return functional_generative_model(self, **kwargs)
|
|
576
|
+
|
|
577
|
+
def gacs_korner_generative_model(self, **kwargs: Any) -> Any:
|
|
578
|
+
"""Construct the Gács-Körner (deterministic meet) generative presentation."""
|
|
579
|
+
from sofic.generators.minimal_generative_model import gacs_korner_generative_model
|
|
580
|
+
|
|
581
|
+
return gacs_korner_generative_model(self, **kwargs)
|
|
582
|
+
|
|
583
|
+
def generative_complexity(self, **kwargs: Any) -> float:
|
|
584
|
+
"""C_g = H[G] for the minimal generative model."""
|
|
585
|
+
return self.minimal_generative_model(**kwargs).generative_complexity()
|
|
586
|
+
|
|
587
|
+
def information_anatomy(self) -> dict[str, Any]:
|
|
588
|
+
"""Return ρ_μ, b_μ, r_μ, h_μ, E, χ, and the structural/gauge refinement.
|
|
589
|
+
|
|
590
|
+
The ``*_structural`` / ``*_gauge`` keys split the ephemeral (r_μ) and
|
|
591
|
+
bound (b_μ) rates along whether the randomness changes the next forward
|
|
592
|
+
causal state (structural) or merely relabels the output on a fixed
|
|
593
|
+
transition (gauge); each pair sums to its parent.
|
|
594
|
+
|
|
595
|
+
See :meth:`five_variable_anatomy` for the finer four-atom ephemeral
|
|
596
|
+
partition and the reverse-time bound mirror over the full joint
|
|
597
|
+
``Pr(S⁺₀, S⁻₀, X₀, S⁺₁, S⁻₁)``.
|
|
598
|
+
"""
|
|
599
|
+
h_mu = self.entropy_rate()
|
|
600
|
+
return {
|
|
601
|
+
"rho_mu": self.predicted_information(),
|
|
602
|
+
"bound_mu": self.bound_information(),
|
|
603
|
+
"ephemeral_mu": self.ephemeral_information(),
|
|
604
|
+
"ephemeral_structural": self.structural_ephemeral_information(),
|
|
605
|
+
"ephemeral_gauge": self.parallel_edge_information(),
|
|
606
|
+
"bound_structural": self.bound_structural_information(),
|
|
607
|
+
"bound_gauge": self.bound_parallel_edge_information(),
|
|
608
|
+
"entropy_rate": h_mu,
|
|
609
|
+
"excess_entropy": self.excess_entropy(),
|
|
610
|
+
"crypticity": self.crypticity(),
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
def five_variable_anatomy(self) -> dict[str, Any]:
|
|
614
|
+
"""Full five-variable anatomy over ``Pr(S⁺₀, S⁻₀, X₀, S⁺₁, S⁻₁)``.
|
|
615
|
+
|
|
616
|
+
Extends :meth:`information_anatomy` with the two unifilarity relations
|
|
617
|
+
(forward ``S⁺₁ = φ⁺(S⁺₀, X₀)`` and reverse ``S⁻₀ = φ⁻(S⁻₁, X₀)``) that let
|
|
618
|
+
both next-states be read off the present. This yields:
|
|
619
|
+
|
|
620
|
+
* the four-atom ephemeral partition
|
|
621
|
+
``r_μ = r_μ^fwd + r_μ^rev + r_μ^joint + r_μ^gauge`` (keys
|
|
622
|
+
``ephemeral_forward``, ``ephemeral_reverse``, ``ephemeral_joint``,
|
|
623
|
+
``ephemeral_pure_gauge``), all ≥ 0, refining the coarse
|
|
624
|
+
structural/gauge split (``r_μ^struct = r_μ^fwd + r_μ^joint``,
|
|
625
|
+
``r_μ^par = r_μ^rev + r_μ^gauge``);
|
|
626
|
+
* the reverse structural ephemeral rate
|
|
627
|
+
``r̄_μ^struct = r_μ^rev + r_μ^joint`` (key
|
|
628
|
+
``ephemeral_structural_reverse``);
|
|
629
|
+
* the reverse-time bound mirror ``b̄_μ``, ``b̄_μ^struct`` and the
|
|
630
|
+
Theorem-A′ gauge residual ``b̄_μ^gauge`` (≈ 0);
|
|
631
|
+
* the internal (causal-state) Markov-chain entropy rates
|
|
632
|
+
``h_μ^imc = H[S⁺₁ | S⁺₀]`` (key ``internal_markov_rate``) and its
|
|
633
|
+
reverse ``H[S⁻₀ | S⁻₁]`` (key ``internal_markov_rate_reverse``), whose
|
|
634
|
+
difference ``r_μ^fwd − r_μ^rev`` is an arrow-of-time diagnostic.
|
|
635
|
+
|
|
636
|
+
These atoms map onto the fourteen measures of the prediction taxonomy of
|
|
637
|
+
:cite:`jurgens2026taxonomy`; the structural/gauge (unifilarity) reading is
|
|
638
|
+
the refinement layered on top.
|
|
639
|
+
"""
|
|
640
|
+
anatomy = self.information_anatomy()
|
|
641
|
+
anatomy.update(
|
|
642
|
+
{
|
|
643
|
+
"ephemeral_forward": self.forward_only_structural_ephemeral(),
|
|
644
|
+
"ephemeral_reverse": self.reverse_only_structural_ephemeral(),
|
|
645
|
+
"ephemeral_joint": self.joint_structural_ephemeral(),
|
|
646
|
+
"ephemeral_pure_gauge": self.pure_gauge_information(),
|
|
647
|
+
"ephemeral_structural_reverse": self.reverse_structural_ephemeral_information(),
|
|
648
|
+
"bound_reverse": self.reverse_bound_information(),
|
|
649
|
+
"bound_structural_reverse": self.reverse_bound_structural_information(),
|
|
650
|
+
"bound_gauge_reverse": self.reverse_bound_gauge_information(),
|
|
651
|
+
"internal_markov_rate": self.internal_markov_entropy_rate(),
|
|
652
|
+
"internal_markov_rate_reverse": self.reverse_internal_markov_entropy_rate(),
|
|
653
|
+
}
|
|
654
|
+
)
|
|
655
|
+
return anatomy
|
|
656
|
+
|
|
657
|
+
def information_diagram(
|
|
658
|
+
self,
|
|
659
|
+
*,
|
|
660
|
+
show_zero: bool = False,
|
|
661
|
+
atoms: str | None = None,
|
|
662
|
+
tol: float = 1e-9,
|
|
663
|
+
) -> Any:
|
|
664
|
+
"""The five-variable information-anatomy I-diagram over the step joint.
|
|
665
|
+
|
|
666
|
+
Returns an :class:`~sofic.generators.information_diagram.InformationDiagram`:
|
|
667
|
+
the ``2⁵ − 1 = 31`` signed I-measure atoms of ``Pr(S⁺₀, S⁻₀, X₀, S⁺₁, S⁻₁)``
|
|
668
|
+
(:cite:`yeung1991new`), each classified into an anatomy role and laid out
|
|
669
|
+
in a fixed order, with the named totals ``r_μ``, ``b⁺_μ``, ``b⁻_μ``,
|
|
670
|
+
``q_μ``, ``σ_μ``, ``χ⁺``, ``χ⁻``. Pass ``atoms="generic"`` to retain the
|
|
671
|
+
21 generically nonzero membership sets (Table II's 14 plus 7 cancelling
|
|
672
|
+
extras), or ``atoms="all"`` for every Yeung atom. See
|
|
673
|
+
:func:`sofic.viz.plot_information_diagram` to draw it.
|
|
674
|
+
"""
|
|
675
|
+
from sofic.generators.information_diagram import information_diagram
|
|
676
|
+
|
|
677
|
+
return information_diagram(self, show_zero=show_zero, atoms=atoms, tol=tol)
|
|
678
|
+
|
|
679
|
+
def plot_information_diagram(self, **kwargs: Any) -> Any:
|
|
680
|
+
"""Draw :meth:`information_diagram` as a colour-coded UpSet plot.
|
|
681
|
+
|
|
682
|
+
Thin wrapper over :func:`sofic.viz.plot_information_diagram`; keyword
|
|
683
|
+
arguments (``atoms``, ``show_zero``, ``role_colors``, ``annotate``,
|
|
684
|
+
``title``, ``figsize``) are forwarded. Requires the optional
|
|
685
|
+
``sofic[viz]`` extra.
|
|
686
|
+
"""
|
|
687
|
+
from sofic.viz.idiagram import plot_information_diagram
|
|
688
|
+
|
|
689
|
+
return plot_information_diagram(self, **kwargs)
|