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.
Files changed (150) hide show
  1. sofic/__init__.py +185 -0
  2. sofic/automata/__init__.py +207 -0
  3. sofic/automata/_config_simulation.py +40 -0
  4. sofic/automata/active.py +611 -0
  5. sofic/automata/alergia.py +222 -0
  6. sofic/automata/algorithms.py +376 -0
  7. sofic/automata/atomaton.py +58 -0
  8. sofic/automata/base.py +161 -0
  9. sofic/automata/buchi.py +23 -0
  10. sofic/automata/buchi_simulation.py +67 -0
  11. sofic/automata/canonical_dual.py +18 -0
  12. sofic/automata/canonical_extraction.py +122 -0
  13. sofic/automata/dfa.py +85 -0
  14. sofic/automata/dfasat.py +195 -0
  15. sofic/automata/edsm.py +219 -0
  16. sofic/automata/enumeration.py +44 -0
  17. sofic/automata/icdfa.py +421 -0
  18. sofic/automata/idfa.py +363 -0
  19. sofic/automata/languages/__init__.py +39 -0
  20. sofic/automata/languages/_quotient_utils.py +64 -0
  21. sofic/automata/languages/atoms.py +31 -0
  22. sofic/automata/languages/automaton_ops.py +243 -0
  23. sofic/automata/languages/base.py +67 -0
  24. sofic/automata/languages/operations.py +78 -0
  25. sofic/automata/languages/quotients.py +66 -0
  26. sofic/automata/languages/residuals.py +25 -0
  27. sofic/automata/learning.py +79 -0
  28. sofic/automata/nfa.py +39 -0
  29. sofic/automata/nwa.py +343 -0
  30. sofic/automata/nwa_simulation.py +56 -0
  31. sofic/automata/observation.py +40 -0
  32. sofic/automata/papni.py +301 -0
  33. sofic/automata/regex.py +128 -0
  34. sofic/automata/rfsa.py +35 -0
  35. sofic/automata/rpni.py +193 -0
  36. sofic/automata/subsequential.py +201 -0
  37. sofic/automata/transducer_operations.py +350 -0
  38. sofic/automata/transducer_simulation.py +150 -0
  39. sofic/automata/transducers.py +365 -0
  40. sofic/automata/unifilar.py +107 -0
  41. sofic/automata/vpa.py +1373 -0
  42. sofic/automata/vpa_simulation.py +53 -0
  43. sofic/base.py +153 -0
  44. sofic/core.py +47 -0
  45. sofic/examples/__init__.py +86 -0
  46. sofic/examples/epsilon_machines.py +1089 -0
  47. sofic/examples/processes.py +1491 -0
  48. sofic/examples/shifts.py +144 -0
  49. sofic/exceptions.py +33 -0
  50. sofic/generators/__init__.py +115 -0
  51. sofic/generators/_word_measures.py +94 -0
  52. sofic/generators/alternative_complexity.py +104 -0
  53. sofic/generators/base.py +327 -0
  54. sofic/generators/bidirectional_construction.py +717 -0
  55. sofic/generators/bidirectional_epsilon_machine.py +689 -0
  56. sofic/generators/block_convergence.py +668 -0
  57. sofic/generators/block_entropy.py +578 -0
  58. sofic/generators/channel_measures.py +75 -0
  59. sofic/generators/conversions.py +182 -0
  60. sofic/generators/directional_flow.py +245 -0
  61. sofic/generators/edge_emissions.py +36 -0
  62. sofic/generators/edge_machine.py +178 -0
  63. sofic/generators/epsilon_construction.py +193 -0
  64. sofic/generators/epsilon_inference.py +703 -0
  65. sofic/generators/epsilon_machine.py +557 -0
  66. sofic/generators/epsilon_transducer.py +168 -0
  67. sofic/generators/epsilon_transducer_construction.py +185 -0
  68. sofic/generators/epsilon_transducer_inference.py +499 -0
  69. sofic/generators/hmm_inference.py +719 -0
  70. sofic/generators/information_diagram.py +428 -0
  71. sofic/generators/lumping.py +447 -0
  72. sofic/generators/markov.py +100 -0
  73. sofic/generators/mealy.py +156 -0
  74. sofic/generators/measures.py +257 -0
  75. sofic/generators/minimal_generative_model.py +821 -0
  76. sofic/generators/mixed_state.py +250 -0
  77. sofic/generators/mixed_state_construction.py +163 -0
  78. sofic/generators/moore.py +75 -0
  79. sofic/generators/nmachine.py +78 -0
  80. sofic/generators/nmachine_construction.py +70 -0
  81. sofic/generators/pfa.py +100 -0
  82. sofic/generators/prob.py +291 -0
  83. sofic/generators/process_equivalence.py +207 -0
  84. sofic/generators/quasi_inference.py +74 -0
  85. sofic/generators/quasi_realization.py +97 -0
  86. sofic/generators/reversal.py +66 -0
  87. sofic/generators/stack_hmm.py +426 -0
  88. sofic/generators/stack_inference.py +509 -0
  89. sofic/generators/stationary.py +134 -0
  90. sofic/generators/stochastic.py +65 -0
  91. sofic/generators/synchronization.py +407 -0
  92. sofic/generators/topological_epsilon_enumeration.py +349 -0
  93. sofic/generators/words.py +226 -0
  94. sofic/graph.py +135 -0
  95. sofic/indexing.py +31 -0
  96. sofic/inference/__init__.py +45 -0
  97. sofic/inference/bayesian/__init__.py +68 -0
  98. sofic/inference/bayesian/comparison.py +199 -0
  99. sofic/inference/bayesian/counts.py +219 -0
  100. sofic/inference/bayesian/diversity.py +254 -0
  101. sofic/inference/bayesian/epsilon.py +270 -0
  102. sofic/inference/bayesian/hdp_hmm.py +340 -0
  103. sofic/inference/bayesian/markov.py +294 -0
  104. sofic/inference/bayesian/pymc_backend.py +71 -0
  105. sofic/inference/bayesian/stack_hmm.py +215 -0
  106. sofic/inference/model_selection.py +365 -0
  107. sofic/inference/spectral.py +564 -0
  108. sofic/operations.py +16 -0
  109. sofic/properties.py +339 -0
  110. sofic/serialization.py +450 -0
  111. sofic/shifts/__init__.py +48 -0
  112. sofic/shifts/algorithms.py +84 -0
  113. sofic/shifts/base.py +49 -0
  114. sofic/shifts/cover_construction.py +76 -0
  115. sofic/shifts/covers.py +47 -0
  116. sofic/shifts/dyck_algorithms.py +100 -0
  117. sofic/shifts/dyck_enumeration.py +275 -0
  118. sofic/shifts/markov_dyck.py +172 -0
  119. sofic/shifts/parry_construction.py +82 -0
  120. sofic/shifts/sft.py +104 -0
  121. sofic/shifts/sft_construction.py +52 -0
  122. sofic/shifts/sliding_block_code.py +156 -0
  123. sofic/shifts/sofic.py +111 -0
  124. sofic/shifts/sofic_dyck.py +110 -0
  125. sofic/shifts/sofic_relation.py +64 -0
  126. sofic/shifts/textile.py +104 -0
  127. sofic/shifts/tmc.py +46 -0
  128. sofic/shifts/tmc_construction.py +58 -0
  129. sofic/shifts/topological_anatomy.py +150 -0
  130. sofic/states.py +27 -0
  131. sofic/testing/__init__.py +8 -0
  132. sofic/testing/strategies.py +154 -0
  133. sofic/viz/__init__.py +16 -0
  134. sofic/viz/_context.py +345 -0
  135. sofic/viz/_edge.py +216 -0
  136. sofic/viz/_format.py +89 -0
  137. sofic/viz/_labels.py +34 -0
  138. sofic/viz/_names.py +17 -0
  139. sofic/viz/_rational.py +20 -0
  140. sofic/viz/_tikz_compile.py +177 -0
  141. sofic/viz/_tikz_format.py +122 -0
  142. sofic/viz/_tikz_layout.py +218 -0
  143. sofic/viz/assets/vaucanson.tikz +71 -0
  144. sofic/viz/graphviz.py +158 -0
  145. sofic/viz/idiagram.py +350 -0
  146. sofic/viz/tikz.py +381 -0
  147. sofic-0.1.0.dist-info/METADATA +444 -0
  148. sofic-0.1.0.dist-info/RECORD +150 -0
  149. sofic-0.1.0.dist-info/WHEEL +4 -0
  150. sofic-0.1.0.dist-info/licenses/LICENSE.txt +29 -0
@@ -0,0 +1,53 @@
1
+ """Stack simulation for visibly pushdown automata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Hashable, Iterator, Sequence
6
+ from typing import Any
7
+
8
+ from sofic.automata._config_simulation import simulate_configs
9
+ from sofic.automata.vpa import VisiblyPushdownAutomaton
10
+ from sofic.graph import ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN
11
+
12
+ _BOTTOM = object()
13
+
14
+ Config = tuple[Hashable, tuple[Any, ...]]
15
+
16
+
17
+ def recognizes_vpa(vpa: VisiblyPushdownAutomaton, word: Sequence[Any]) -> bool:
18
+ """Return whether ``vpa`` accepts ``word``."""
19
+ if vpa.initial_state is None:
20
+ return False
21
+
22
+ bottom = _BOTTOM if vpa.bottom_stack_symbol is None else vpa.bottom_stack_symbol
23
+ initial: set[Config] = {(vpa.initial_state, (bottom,))}
24
+
25
+ def step(config: Config, symbol: Any) -> Iterator[Config]:
26
+ state, stack = config
27
+ for transition in vpa.graph.out_transitions(state):
28
+ data = transition.data
29
+ if data.get(ATTR_SYMBOL) != symbol:
30
+ continue
31
+ kind = data.get(ATTR_KIND)
32
+ if kind == KIND_CALL:
33
+ stack_sym = data.get(ATTR_STACK_SYMBOL)
34
+ if stack_sym is None:
35
+ continue
36
+ yield (transition.target, stack + (stack_sym,))
37
+ elif kind == KIND_RETURN:
38
+ stack_sym = data.get(ATTR_STACK_SYMBOL)
39
+ if len(stack) <= 1:
40
+ if vpa.bottom_stack_symbol is None:
41
+ continue
42
+ if stack_sym is not None and stack_sym != stack[-1]:
43
+ continue
44
+ yield (transition.target, stack)
45
+ continue
46
+ if stack_sym is not None and stack_sym != stack[-1]:
47
+ continue
48
+ yield (transition.target, stack[:-1])
49
+ elif kind == KIND_INTERNAL:
50
+ yield (transition.target, stack)
51
+
52
+ current = simulate_configs(initial, word, step)
53
+ return any(state in vpa.accepting_states for state, _ in current)
sofic/base.py ADDED
@@ -0,0 +1,153 @@
1
+ """Abstract base for all sofic state-machine models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Hashable, Iterator
7
+ from copy import deepcopy
8
+ from pathlib import Path
9
+ from typing import Any, Self
10
+
11
+ import networkx as nx
12
+
13
+ from sofic.exceptions import SoficValidationError
14
+ from sofic.graph import Transition, TransitionGraph
15
+ from sofic.indexing import StateIndex
16
+
17
+
18
+ class StateMachine(ABC):
19
+ """Common interface for graph-backed models in sofic."""
20
+
21
+ graph: TransitionGraph
22
+
23
+ def __init__(self, graph: TransitionGraph | None = None) -> None:
24
+ self.graph = graph if graph is not None else TransitionGraph()
25
+
26
+ @abstractmethod
27
+ def validate(self) -> None:
28
+ """Raise :class:`~sofic.exceptions.SoficValidationError` on failure."""
29
+
30
+ def states(self) -> Iterator[Hashable]:
31
+ yield from self.graph.states()
32
+
33
+ def transitions(self) -> Iterator[Transition]:
34
+ yield from self.graph.transitions()
35
+
36
+ def reindex(self) -> StateIndex:
37
+ return StateIndex(self.states())
38
+
39
+ def copy(self) -> Self:
40
+ cloned = object.__new__(self.__class__)
41
+ cloned.graph = self.graph.copy()
42
+ for name, value in self.__dict__.items():
43
+ if name == "graph":
44
+ continue
45
+ cloned.__dict__[name] = deepcopy(value)
46
+ return cloned
47
+
48
+ def to_networkx(self) -> nx.MultiDiGraph:
49
+ return self.graph.nx.copy()
50
+
51
+ @classmethod
52
+ def from_networkx(cls, g: nx.MultiDiGraph, **kwargs: Any) -> Self:
53
+ return cls(graph=TransitionGraph(g.copy()), **kwargs)
54
+
55
+ def to_yaml(self) -> str:
56
+ """Return a YAML representation of this model."""
57
+ from sofic.serialization import model_to_yaml
58
+
59
+ return model_to_yaml(self)
60
+
61
+ def write_yaml(self, path: str | Path) -> None:
62
+ """Write this model as YAML."""
63
+ Path(path).write_text(self.to_yaml(), encoding="utf-8")
64
+
65
+ @classmethod
66
+ def from_yaml(cls, text: str, *, validate: bool = True) -> Self:
67
+ """Reconstruct a model from YAML text."""
68
+ from sofic.serialization import model_from_yaml
69
+
70
+ model = model_from_yaml(text, validate=validate)
71
+ if not isinstance(model, cls):
72
+ raise TypeError(f"YAML contains {type(model).__qualname__}, not a {cls.__qualname__}")
73
+ return model
74
+
75
+ @classmethod
76
+ def read_yaml(cls, path: str | Path, *, validate: bool = True) -> Self:
77
+ """Read a model from a YAML file."""
78
+ return cls.from_yaml(Path(path).read_text(encoding="utf-8"), validate=validate)
79
+
80
+ def reverse(self) -> Self:
81
+ """Return a model with the transition graph transposed."""
82
+ result = self.copy()
83
+ result.graph = self.graph.reverse()
84
+ return result
85
+
86
+ def __repr__(self) -> str:
87
+ states = list(self.states())
88
+ transitions = list(self.transitions())
89
+ return f"{self.__class__.__name__}({len(states)} states, {len(transitions)} transitions)"
90
+
91
+ def _repr_png_(self) -> bytes | None:
92
+ """Notebook image: Graphviz PNG, or ``None`` if rendering is unavailable."""
93
+ try:
94
+ from sofic.viz.graphviz import model_to_png
95
+
96
+ return model_to_png(self)
97
+ except Exception:
98
+ return None
99
+
100
+ def _graphviz_svg(self) -> str | None:
101
+ """Graphviz SVG for notebook vector display."""
102
+ try:
103
+ from sofic.viz.graphviz import model_to_svg
104
+
105
+ return model_to_svg(self)
106
+ except Exception:
107
+ return None
108
+
109
+ def _repr_mimebundle_(
110
+ self,
111
+ include: set[str] | None = None,
112
+ exclude: set[str] | None = None,
113
+ ) -> dict[str, Any] | None:
114
+ bundle: dict[str, Any] = {"text/plain": repr(self)}
115
+
116
+ svg = self._graphviz_svg()
117
+ if svg is not None:
118
+ bundle["image/svg+xml"] = svg
119
+
120
+ if include is not None:
121
+ bundle = {key: value for key, value in bundle.items() if key in include}
122
+ if exclude is not None:
123
+ bundle = {key: value for key, value in bundle.items() if key not in exclude}
124
+
125
+ return bundle if len(bundle) > 1 else None
126
+
127
+ def to_graphviz(self, **kwargs: Any) -> Any:
128
+ """Build a Graphviz diagram for this model."""
129
+ from sofic.viz.graphviz import model_to_graphviz
130
+
131
+ return model_to_graphviz(self, **kwargs)
132
+
133
+ def draw(self, filename: str | None = None, **kwargs: Any) -> str | None:
134
+ """Render this model with Graphviz."""
135
+ from sofic.viz.graphviz import draw
136
+
137
+ return draw(self, filename=filename, **kwargs)
138
+
139
+ def to_tikz(self, **kwargs: Any) -> str:
140
+ """Return a Vaucanson-style TikZ picture for this model."""
141
+ from sofic.viz.tikz import model_to_tikz
142
+
143
+ return model_to_tikz(self, **kwargs)
144
+
145
+ def draw_tikz(self, filename: str | None = None, **kwargs: Any) -> str | None:
146
+ """Write a TikZ fragment for this model."""
147
+ from sofic.viz.tikz import draw_tikz
148
+
149
+ return draw_tikz(self, filename=filename, **kwargs)
150
+
151
+ def _require(self, condition: bool, message: str) -> None:
152
+ if not condition:
153
+ raise SoficValidationError(message)
sofic/core.py ADDED
@@ -0,0 +1,47 @@
1
+ """Core graph-backed model primitives."""
2
+
3
+ from sofic.base import StateMachine
4
+ from sofic.graph import (
5
+ ATTR_EMISSION,
6
+ ATTR_EMISSION_DIST,
7
+ ATTR_EMISSION_EDGE,
8
+ ATTR_FUTURE_SYMBOL,
9
+ ATTR_HIER_STATE,
10
+ ATTR_KIND,
11
+ ATTR_MULTIPLICITY,
12
+ ATTR_OUTPUT,
13
+ ATTR_PROB,
14
+ ATTR_QUASIPROB,
15
+ ATTR_STACK_SYMBOL,
16
+ ATTR_SYMBOL,
17
+ EPSILON,
18
+ KIND_CALL,
19
+ KIND_INTERNAL,
20
+ KIND_RETURN,
21
+ Transition,
22
+ TransitionGraph,
23
+ )
24
+ from sofic.indexing import StateIndex
25
+
26
+ __all__ = [
27
+ "ATTR_EMISSION",
28
+ "ATTR_EMISSION_DIST",
29
+ "ATTR_EMISSION_EDGE",
30
+ "ATTR_FUTURE_SYMBOL",
31
+ "ATTR_HIER_STATE",
32
+ "ATTR_KIND",
33
+ "ATTR_MULTIPLICITY",
34
+ "ATTR_OUTPUT",
35
+ "ATTR_PROB",
36
+ "ATTR_QUASIPROB",
37
+ "ATTR_STACK_SYMBOL",
38
+ "ATTR_SYMBOL",
39
+ "EPSILON",
40
+ "KIND_CALL",
41
+ "KIND_INTERNAL",
42
+ "KIND_RETURN",
43
+ "StateIndex",
44
+ "StateMachine",
45
+ "Transition",
46
+ "TransitionGraph",
47
+ ]
@@ -0,0 +1,86 @@
1
+ """Canonical examples from the computational mechanics literature."""
2
+
3
+ from importlib import import_module as _import_module
4
+
5
+ from sofic.examples.epsilon_machines import (
6
+ TENT_MAP_MISIUREWICZ_PARTITIONS,
7
+ alternating_biased_coins,
8
+ bernoulli,
9
+ butterfly_process,
10
+ ellison_fig9_forward,
11
+ ellison_fig9_reverse,
12
+ ellison_fig15_bidirectional,
13
+ even_process,
14
+ fair_coin,
15
+ golden_mean,
16
+ golden_mean_bidirectional,
17
+ golden_mean_forward,
18
+ golden_mean_markov,
19
+ golden_mean_reverse,
20
+ golden_mean_shift_parry,
21
+ nemo_process,
22
+ noisy_random_phase_slip,
23
+ restricted_golden_mean,
24
+ tent_map_misiurewicz_a,
25
+ tent_map_misiurewicz_bidirectional,
26
+ tent_map_misiurewicz_forward,
27
+ tent_map_misiurewicz_hmm,
28
+ tent_map_misiurewicz_information_expected,
29
+ tent_map_misiurewicz_partition_cuts,
30
+ tent_map_misiurewicz_partition_forward,
31
+ tent_map_misiurewicz_partition_information_expected,
32
+ tent_map_misiurewicz_partition_symbol_matrices,
33
+ tent_map_misiurewicz_reverse,
34
+ )
35
+ from sofic.examples.processes import *
36
+ from sofic.examples.processes import __all__ as _process_all
37
+ from sofic.examples.shifts import (
38
+ dyck_shift_order,
39
+ motzkin_shift,
40
+ sofic_dyck_fig1_shift,
41
+ sofic_dyck_nondeterminizable_shift,
42
+ sofic_dyck_zeta_example_shift,
43
+ )
44
+
45
+ processes = _import_module("sofic.examples.processes")
46
+ shifts = _import_module("sofic.examples.shifts")
47
+
48
+ __all__ = [
49
+ "alternating_biased_coins",
50
+ "bernoulli",
51
+ "butterfly_process",
52
+ "dyck_shift_order",
53
+ "ellison_fig9_forward",
54
+ "ellison_fig9_reverse",
55
+ "ellison_fig15_bidirectional",
56
+ "even_process",
57
+ "fair_coin",
58
+ "golden_mean",
59
+ "golden_mean_forward",
60
+ "golden_mean_markov",
61
+ "golden_mean_reverse",
62
+ "golden_mean_bidirectional",
63
+ "golden_mean_shift_parry",
64
+ "motzkin_shift",
65
+ "nemo_process",
66
+ "noisy_random_phase_slip",
67
+ "restricted_golden_mean",
68
+ "sofic_dyck_fig1_shift",
69
+ "sofic_dyck_nondeterminizable_shift",
70
+ "sofic_dyck_zeta_example_shift",
71
+ "TENT_MAP_MISIUREWICZ_PARTITIONS",
72
+ "tent_map_misiurewicz_a",
73
+ "tent_map_misiurewicz_bidirectional",
74
+ "tent_map_misiurewicz_forward",
75
+ "tent_map_misiurewicz_hmm",
76
+ "tent_map_misiurewicz_information_expected",
77
+ "tent_map_misiurewicz_partition_cuts",
78
+ "tent_map_misiurewicz_partition_forward",
79
+ "tent_map_misiurewicz_partition_information_expected",
80
+ "tent_map_misiurewicz_partition_symbol_matrices",
81
+ "tent_map_misiurewicz_reverse",
82
+ ]
83
+ __all__ += _process_all
84
+ __all__ = [name for name in __all__ if name not in {"processes", "shifts"}]
85
+ __all__.append("processes")
86
+ __all__.append("shifts")