diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8cebd7b..8e65de3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,7 +26,7 @@ jobs: run: | set -euo pipefail tag="${GITHUB_REF_NAME#v}" - built=$(ls dist/pensive-*.tar.gz | sed -E 's|.*/pensive-(.+)\.tar\.gz|\1|') + built=$(ls dist/sofic-*.tar.gz | sed -E 's|.*/sofic-(.+)\.tar\.gz|\1|') echo "tag=$tag built=$built" [ "$tag" = "$built" ] || { echo "::error::Built version $built != tag $tag"; exit 1; } @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/pensive + url: https://pypi.org/p/sofic permissions: id-token: write contents: write diff --git a/.gitignore b/.gitignore index c96a63b..eff506f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ MANIFEST .venv/ # Packaging -pensive.egg-info/ +sofic.egg-info/ # Test and coverage .coverage* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e34c9a0..c33e6d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,11 @@ Contributing ------------ -If you'd like a feature added to ``pensive`` or notice any problems, please file an issue, or, even better, open a pull request. We'll work with you to ensure that the code is tested and documented. +If you'd like a feature added to ``sofic`` or notice any problems, please file an issue, or, even better, open a pull request. We'll work with you to ensure that the code is tested and documented. -It's appropriate to file an issue when you encounter a problem with `pensive`. Be sure to include +It's appropriate to file an issue when you encounter a problem with `sofic`. Be sure to include -* the `pensive` version you're using +* the `sofic` version you're using * the operating system and Python version * full tracebacks diff --git a/LICENSE.txt b/LICENSE.txt index de4c4e4..c6ef328 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2026, pensive contributors. +Copyright (c) 2026, sofic contributors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index c184abc..683b209 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ ======= -pensive +sofic ======= -``pensive`` is a Python package for hidden Markov models, symbolic dynamics, +``sofic`` is a Python package for hidden Markov models, symbolic dynamics, finite state machines, and other stochastic symbol generators. Basic Information @@ -11,12 +11,12 @@ Basic Information Documentation ~~~~~~~~~~~~~~ -https://pensive.readthedocs.io +https://sofic.readthedocs.io Repository ~~~~~~~~~~ -https://github.com/dit/pensive +https://github.com/dit/sofic Dependencies ~~~~~~~~~~~~ @@ -35,8 +35,8 @@ Clone the repository and install development dependencies with ``uv``: .. code-block:: bash - git clone https://github.com/dit/pensive.git - cd pensive + git clone https://github.com/dit/sofic.git + cd sofic uv sync --extra dev Run tests with ``uv run pytest``. See the ``generalinfo`` page in the Sphinx @@ -47,7 +47,7 @@ Introduction Many natural and engineered processes produce sequences of symbols whose statistics are governed by latent structure: hidden states, transition rules, -or algebraic constraints on allowed paths. ``pensive`` collects algorithms and +or algebraic constraints on allowed paths. ``sofic`` collects algorithms and data structures for representing, simulating, and analyzing such generators behind a single, composable Python API. @@ -56,15 +56,15 @@ construction, validation, simulation, visualization, (de)serialization, and a large library of structural and information-theoretic measures. The three main families are: -* **Stochastic generators** (``pensive.generators``) — Markov chains, hidden +* **Stochastic generators** (``sofic.generators``) — Markov chains, hidden Markov models (Moore and Mealy presentations), ε-machines, probabilistic finite automata, mixed-state presentations, and quasiprobabilistic generators. These assign probabilities to sequences. -* **Finite automata** (``pensive.automata``) — DFAs, NFAs, transducers +* **Finite automata** (``sofic.automata``) — DFAs, NFAs, transducers (Mealy/Moore machines), regular languages, Büchi automata, visibly pushdown and nested-word automata, and residual finite-state automata. These recognize or transform languages. -* **Symbolic shifts** (``pensive.shifts``) — shifts of finite type, sofic +* **Symbolic shifts** (``sofic.shifts``) — shifts of finite type, sofic shifts, topological Markov chains, and Dyck/sofic-Dyck shifts. These describe the *support* (set of allowed sequences) of a process. @@ -77,15 +77,15 @@ Installation .. code-block:: bash - pip install pensive + pip install sofic Optional extras: -* ``pensive[viz]`` — Graphviz diagrams in terminals and Jupyter -* ``pensive[bayes]`` — PyMC/ArviZ backends for Bayesian inference -* ``pensive[test]`` — pytest, hypothesis, and graphviz for the test suite -* ``pensive[docs]`` — Sphinx, IPython, and matplotlib for the docs -* ``pensive[dev]`` — linting, type checking, docs, and all of the above +* ``sofic[viz]`` — Graphviz diagrams in terminals and Jupyter +* ``sofic[bayes]`` — PyMC/ArviZ backends for Bayesian inference +* ``sofic[test]`` — pytest, hypothesis, and graphviz for the test suite +* ``sofic[docs]`` — Sphinx, IPython, and matplotlib for the docs +* ``sofic[dev]`` — linting, type checking, docs, and all of the above Quickstart ---------- @@ -102,7 +102,7 @@ Stochastic generators .. code-block:: python - from pensive import MarkovChain + from sofic import MarkovChain mc = MarkovChain(initial_distribution={"sunny": 0.5, "rainy": 0.5}) mc.add_transition("sunny", "sunny", 0.9) @@ -120,7 +120,7 @@ states and ``P(target | source)`` on edges: .. code-block:: python - from pensive import MooreHMM + from sofic import MooreHMM moore = MooreHMM( observation_alphabet=frozenset({"H", "T"}), @@ -144,7 +144,7 @@ states and ``P(target | source)`` on edges: .. code-block:: python - from pensive import MealyHMM + from sofic import MealyHMM gm = MealyHMM( observation_alphabet=frozenset({0, 1}), @@ -159,8 +159,8 @@ states and ``P(target | source)`` on edges: gm.is_unifilar() # True gm.word_probability([1, 0, 1]) # 0.1667 -Many canonical models ship in ``pensive.examples``, so the golden mean is -also just ``from pensive.examples import golden_mean; gm = golden_mean(0.5)``. +Many canonical models ship in ``sofic.examples``, so the golden mean is +also just ``from sofic.examples import golden_mean; gm = golden_mean(0.5)``. **ε-machine** — the minimal unifilar (causal-state) presentation of a stationary process. Build one from any HMM, from an observed sequence, or directly, and @@ -168,7 +168,7 @@ read off computational-mechanics quantities: .. code-block:: python - from pensive import EpsilonMachine + from sofic import EpsilonMachine eps = EpsilonMachine.from_hmm(gm) # minimize an HMM presentation # eps = EpsilonMachine.from_sequence(data, method="cssr", Lmax=4) # infer @@ -187,7 +187,7 @@ be checked as you go): .. code-block:: python - from pensive import DFA + from sofic import DFA dfa = DFA( input_alphabet=frozenset({"a", "b"}), @@ -212,7 +212,7 @@ when you need one: .. code-block:: python - from pensive import NFA + from sofic import NFA nfa = NFA( input_alphabet=frozenset({"a", "b"}), @@ -233,7 +233,7 @@ inverts bits: .. code-block:: python - from pensive import MealyMachine + from sofic import MealyMachine inv = MealyMachine( input_alphabet=frozenset({"0", "1"}), @@ -258,7 +258,7 @@ forbidden words. The golden-mean shift forbids ``11``: .. code-block:: python - from pensive import ShiftOfFiniteType + from sofic import ShiftOfFiniteType sft = ShiftOfFiniteType.from_forbidden_words( {("1", "1")}, @@ -276,7 +276,7 @@ topological entropy or extract the measure of maximal entropy: .. code-block:: python import numpy as np - from pensive import TopologicalMarkovChain + from sofic import TopologicalMarkovChain tmc = TopologicalMarkovChain.from_adjacency( np.array([[1, 1], [1, 0]], dtype=float), # golden-mean adjacency @@ -292,7 +292,7 @@ NFA). This is the even shift (even-length runs of ``0`` between ``1``\ s): .. code-block:: python - from pensive import SoficShift + from sofic import SoficShift even = SoficShift(symbol_alphabet=frozenset({0, 1})) even.add_transition("even", "even", 0) @@ -342,7 +342,7 @@ Every model can also be round-tripped through YAML: text = eps.to_yaml() restored = EpsilonMachine.from_yaml(text) - # or: pensive.model_to_yaml(eps) / pensive.model_from_yaml(text) + # or: sofic.model_to_yaml(eps) / sofic.model_from_yaml(text) Information anatomy (advanced) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -352,7 +352,7 @@ transient-information anatomy of a process (James, Burke & Crutchfield, 2013): .. code-block:: python - from pensive.examples import golden_mean_bidirectional, tent_map_misiurewicz_bidirectional + from sofic.examples import golden_mean_bidirectional, tent_map_misiurewicz_bidirectional bidir = golden_mean_bidirectional(0.5) bidir.statistical_complexity() # 1.5850 bits (C±) @@ -366,4 +366,4 @@ transient-information anatomy of a process (James, Burke & Crutchfield, 2013): License ------- -``pensive`` is distributed under the BSD 3-Clause License; see ``LICENSE.txt``. +``sofic`` is distributed under the BSD 3-Clause License; see ``LICENSE.txt``. diff --git a/docs/automata/algorithms.rst b/docs/automata/algorithms.rst index 524852f..4dd0a9e 100644 --- a/docs/automata/algorithms.rst +++ b/docs/automata/algorithms.rst @@ -1,11 +1,11 @@ .. algorithms.rst -.. py:module:: pensive.automata.algorithms +.. py:module:: sofic.automata.algorithms ********** Algorithms ********** -Standard automata operations on :class:`~pensive.automata.base.LabeledAutomaton` +Standard automata operations on :class:`~sofic.automata.base.LabeledAutomaton` instances. The implementations cover subset construction, DFA equivalence and minimization, Brzozowski double reversal, Moore refinement, Hopcroft refinement, and state-elimination conversion to regular expressions @@ -13,7 +13,7 @@ and state-elimination conversion to regular expressions .. ipython:: - In [1]: from pensive.automata import DFA, trim, minimize, equivalent + In [1]: from sofic.automata import DFA, trim, minimize, equivalent In [2]: dfa = DFA( ...: input_alphabet=frozenset({0}), @@ -43,6 +43,6 @@ API .. autofunction:: minimize_moore .. autofunction:: minimize_brzozowski .. autofunction:: equivalent -.. autofunction:: pensive.automata.regex.automaton_to_regex +.. autofunction:: sofic.automata.regex.automaton_to_regex .. autodata:: MinimizationAlgorithm diff --git a/docs/automata/atomaton.rst b/docs/automata/atomaton.rst index 8a35c0b..b9c7cd5 100644 --- a/docs/automata/atomaton.rst +++ b/docs/automata/atomaton.rst @@ -1,5 +1,5 @@ .. atomaton.rst -.. py:module:: pensive.automata.atomaton +.. py:module:: sofic.automata.atomaton ******** Átomaton diff --git a/docs/automata/automata.rst b/docs/automata/automata.rst index 4605521..e9829a7 100644 --- a/docs/automata/automata.rst +++ b/docs/automata/automata.rst @@ -4,7 +4,7 @@ Automata ******** -The :mod:`pensive.automata` package provides finite automata, transducers, and +The :mod:`sofic.automata` package provides finite automata, transducers, and regular-language algebra. The finite-automata foundations follow the classical DFA/NFA and regular-language literature :cite:`RabinScott1959,HopcroftUllman1979`. diff --git a/docs/automata/buchi.rst b/docs/automata/buchi.rst index b4552b0..fb85a39 100644 --- a/docs/automata/buchi.rst +++ b/docs/automata/buchi.rst @@ -1,5 +1,5 @@ .. buchi.rst -.. py:module:: pensive.automata.buchi +.. py:module:: sofic.automata.buchi ***************** Büchi Automata @@ -14,5 +14,5 @@ API .. autoclass:: BuchiAutomaton :members: accepts_lasso, accepts_omega -.. autofunction:: pensive.automata.buchi_simulation.accepts_lasso_buchi -.. autofunction:: pensive.automata.buchi_simulation.accepts_omega_buchi +.. autofunction:: sofic.automata.buchi_simulation.accepts_lasso_buchi +.. autofunction:: sofic.automata.buchi_simulation.accepts_omega_buchi diff --git a/docs/automata/dfa.rst b/docs/automata/dfa.rst index 4d0b3bb..23a0a49 100644 --- a/docs/automata/dfa.rst +++ b/docs/automata/dfa.rst @@ -1,5 +1,5 @@ .. dfa.rst -.. py:module:: pensive.automata.dfa +.. py:module:: sofic.automata.dfa *** DFA @@ -11,7 +11,7 @@ and no ε-transitions, following the standard finite-automata model .. ipython:: - In [1]: from pensive.automata import DFA + In [1]: from sofic.automata import DFA In [2]: dfa = DFA( ...: input_alphabet=frozenset({0, 1}), diff --git a/docs/automata/icdfa.rst b/docs/automata/icdfa.rst index 45fccb4..d4c0f95 100644 --- a/docs/automata/icdfa.rst +++ b/docs/automata/icdfa.rst @@ -1,5 +1,5 @@ .. icdfa.rst -.. py:module:: pensive.automata.icdfa +.. py:module:: sofic.automata.icdfa ***** ICDFA @@ -23,7 +23,7 @@ API .. autofunction:: count_icdfa .. autofunction:: count_icdfa_empty -.. autofunction:: pensive.automata.idfa.iter_idfa_strings -.. autofunction:: pensive.automata.idfa.rank_idfa_string -.. autofunction:: pensive.automata.idfa.unrank_idfa_string -.. autofunction:: pensive.automata.idfa.count_accessible_idfa +.. autofunction:: sofic.automata.idfa.iter_idfa_strings +.. autofunction:: sofic.automata.idfa.rank_idfa_string +.. autofunction:: sofic.automata.idfa.unrank_idfa_string +.. autofunction:: sofic.automata.idfa.count_accessible_idfa diff --git a/docs/automata/languages.rst b/docs/automata/languages.rst index 1283f1e..d5f6698 100644 --- a/docs/automata/languages.rst +++ b/docs/automata/languages.rst @@ -1,19 +1,19 @@ .. languages.rst -.. py:module:: pensive.automata.languages.base +.. py:module:: sofic.automata.languages.base ***************** Regular Languages ***************** -The :mod:`pensive.automata.languages` subpackage provides regular-language +The :mod:`sofic.automata.languages` subpackage provides regular-language algebra via the :class:`RegularLanguage` protocol. Quotients, residuals, and atoms use the standard regular-language viewpoint :cite:`Kleene1956,Nerode1958,BrzozowskiTamm2011`. .. ipython:: - In [1]: from pensive.automata import DFA, AutomatonLanguage - In [2]: from pensive.automata.languages import union + In [1]: from sofic.automata import DFA, AutomatonLanguage + In [2]: from sofic.automata.languages import union API === @@ -24,17 +24,17 @@ API .. autoclass:: ExplicitLanguage :members: alphabet -.. autofunction:: pensive.automata.languages.operations.union -.. autofunction:: pensive.automata.languages.operations.intersection -.. autofunction:: pensive.automata.languages.operations.complement -.. autofunction:: pensive.automata.languages.operations.difference -.. autofunction:: pensive.automata.languages.operations.concat -.. autofunction:: pensive.automata.languages.operations.kleene_star -.. autofunction:: pensive.automata.languages.operations.reverse -.. autofunction:: pensive.automata.languages.quotients.left_quotient -.. autofunction:: pensive.automata.languages.quotients.right_quotient -.. autofunction:: pensive.automata.languages.quotients.left_quotients -.. autofunction:: pensive.automata.languages.quotients.residuals -.. autofunction:: pensive.automata.languages.residuals.prime_residuals -.. autofunction:: pensive.automata.languages.atoms.atoms -.. autofunction:: pensive.automata.languages.atoms.prime_atoms +.. autofunction:: sofic.automata.languages.operations.union +.. autofunction:: sofic.automata.languages.operations.intersection +.. autofunction:: sofic.automata.languages.operations.complement +.. autofunction:: sofic.automata.languages.operations.difference +.. autofunction:: sofic.automata.languages.operations.concat +.. autofunction:: sofic.automata.languages.operations.kleene_star +.. autofunction:: sofic.automata.languages.operations.reverse +.. autofunction:: sofic.automata.languages.quotients.left_quotient +.. autofunction:: sofic.automata.languages.quotients.right_quotient +.. autofunction:: sofic.automata.languages.quotients.left_quotients +.. autofunction:: sofic.automata.languages.quotients.residuals +.. autofunction:: sofic.automata.languages.residuals.prime_residuals +.. autofunction:: sofic.automata.languages.atoms.atoms +.. autofunction:: sofic.automata.languages.atoms.prime_atoms diff --git a/docs/automata/learning.rst b/docs/automata/learning.rst index 24fa73d..34c9de2 100644 --- a/docs/automata/learning.rst +++ b/docs/automata/learning.rst @@ -1,11 +1,11 @@ .. learning.rst -.. py:module:: pensive.automata.learning +.. py:module:: sofic.automata.learning ******** Learning ******** -``pensive`` provides both **active** and **passive** automata learning. +``sofic`` provides both **active** and **passive** automata learning. Active learning (NL\*) ====================== @@ -14,7 +14,7 @@ Active learning of maximized prime átomatons via NL\* with a membership teacher, following Angluin-style learning and its nondeterministic extension :cite:`Angluin1987,Bollig2009`: -.. autofunction:: pensive.automata.learning.learn_maximized_prime_atomaton +.. autofunction:: sofic.automata.learning.learn_maximized_prime_atomaton Passive learning (RPNI) ======================= @@ -25,24 +25,24 @@ consistent with the sample :cite:`Lang1998`: .. code-block:: python - from pensive.automata import learn_dfa_rpni + from sofic.automata import learn_dfa_rpni dfa = learn_dfa_rpni(positive=["ab", "abab"], negative=["a", "b"]) dfa.validate() -.. autofunction:: pensive.automata.rpni.learn_dfa_rpni +.. autofunction:: sofic.automata.rpni.learn_dfa_rpni Passive learning (PAPNI) ======================== PAPNI extends passive inference to visibly pushdown languages. Words over a -:class:`~pensive.automata.papni.DyckAlphabet` are stack-encoded, a DFA is +:class:`~sofic.automata.papni.DyckAlphabet` are stack-encoded, a DFA is induced over the encoding, and the result is decoded to a -:class:`~pensive.shifts.sofic_dyck.SoficDyckShift` :cite:`Muskardin2025`: +:class:`~sofic.shifts.sofic_dyck.SoficDyckShift` :cite:`Muskardin2025`: .. code-block:: python - from pensive.automata import DyckAlphabet, learn_sofic_dyck_shift_papni + from sofic.automata import DyckAlphabet, learn_sofic_dyck_shift_papni alphabet = DyckAlphabet( call_alphabet=frozenset({"("}), @@ -54,11 +54,11 @@ induced over the encoding, and the result is decoded to a For fitting probabilities on the learned topology, see :doc:`../generators/stack_inference`. -.. autoclass:: pensive.automata.papni.DyckAlphabet +.. autoclass:: sofic.automata.papni.DyckAlphabet :members: classify, symbol_alphabet -.. autofunction:: pensive.automata.papni.learn_sofic_dyck_shift_papni -.. autofunction:: pensive.automata.papni.is_well_matched -.. autofunction:: pensive.automata.papni.papni_encode -.. autofunction:: pensive.automata.papni.papni_encode_samples -.. autofunction:: pensive.automata.papni.sofic_dyck_shift_from_papni_dfa +.. autofunction:: sofic.automata.papni.learn_sofic_dyck_shift_papni +.. autofunction:: sofic.automata.papni.is_well_matched +.. autofunction:: sofic.automata.papni.papni_encode +.. autofunction:: sofic.automata.papni.papni_encode_samples +.. autofunction:: sofic.automata.papni.sofic_dyck_shift_from_papni_dfa diff --git a/docs/automata/nfa.rst b/docs/automata/nfa.rst index 8f0d0e3..67f6c30 100644 --- a/docs/automata/nfa.rst +++ b/docs/automata/nfa.rst @@ -1,5 +1,5 @@ .. nfa.rst -.. py:module:: pensive.automata.nfa +.. py:module:: sofic.automata.nfa *** NFA @@ -11,7 +11,7 @@ languages :cite:`RabinScott1959,HopcroftUllman1979`. .. ipython:: - In [1]: from pensive.automata import NFA, determinize + In [1]: from sofic.automata import NFA, determinize In [2]: nfa = NFA( ...: input_alphabet=frozenset({0, 1}), diff --git a/docs/automata/nwa.rst b/docs/automata/nwa.rst index 26c203d..2fabf4e 100644 --- a/docs/automata/nwa.rst +++ b/docs/automata/nwa.rst @@ -1,5 +1,5 @@ .. nwa.rst -.. py:module:: pensive.automata.nwa +.. py:module:: sofic.automata.nwa ********************* Nested Word Automata @@ -8,7 +8,7 @@ Nested Word Automata :class:`NestedWordAutomaton` recognizes nested words: finite words whose input also carries a call, return, or internal role for each position and an explicit call-return matching relation. This differs from -:class:`~pensive.automata.vpa.VisiblyPushdownAutomaton`, where the alphabet +:class:`~sofic.automata.vpa.VisiblyPushdownAutomaton`, where the alphabet partition itself determines which positions are calls and returns. Nested words, nested-word automata, and visibly pushdown languages are introduced by Alur and Madhusudan :cite:`AlurMadhusudan2009`. @@ -22,7 +22,7 @@ Constructor sketch .. code-block:: python - from pensive.automata import NestedWord, NestedWordAutomaton + from sofic.automata import NestedWord, NestedWordAutomaton nwa = NestedWordAutomaton( call_alphabet=frozenset({"("}), @@ -62,4 +62,4 @@ API .. autoclass:: NestedWordAutomaton :members: add_call_transition, add_return_transition, add_internal_transition, recognizes, recognizes_visible, from_vpa, to_vpa -.. autofunction:: pensive.automata.nwa_simulation.recognizes_nwa +.. autofunction:: sofic.automata.nwa_simulation.recognizes_nwa diff --git a/docs/automata/observation_table.rst b/docs/automata/observation_table.rst index aef3cf4..a384074 100644 --- a/docs/automata/observation_table.rst +++ b/docs/automata/observation_table.rst @@ -1,5 +1,5 @@ .. observation_table.rst -.. py:module:: pensive.automata.observation +.. py:module:: sofic.automata.observation ***************** Observation Table @@ -15,6 +15,6 @@ API .. autoclass:: ObservationTable -.. autofunction:: pensive.automata.canonical_extraction.observation_to_canonical_rfsa -.. autofunction:: pensive.automata.canonical_extraction.observation_to_atomaton -.. autofunction:: pensive.automata.canonical_extraction.observation_to_minimal_dfa +.. autofunction:: sofic.automata.canonical_extraction.observation_to_canonical_rfsa +.. autofunction:: sofic.automata.canonical_extraction.observation_to_atomaton +.. autofunction:: sofic.automata.canonical_extraction.observation_to_minimal_dfa diff --git a/docs/automata/rfsa.rst b/docs/automata/rfsa.rst index 0364b73..ea30abd 100644 --- a/docs/automata/rfsa.rst +++ b/docs/automata/rfsa.rst @@ -1,5 +1,5 @@ .. rfsa.rst -.. py:module:: pensive.automata.rfsa +.. py:module:: sofic.automata.rfsa *** RFSA diff --git a/docs/automata/transducers.rst b/docs/automata/transducers.rst index 0495939..c301b78 100644 --- a/docs/automata/transducers.rst +++ b/docs/automata/transducers.rst @@ -1,5 +1,5 @@ .. transducers.rst -.. py:module:: pensive.automata.transducers +.. py:module:: sofic.automata.transducers *********** Transducers @@ -12,7 +12,7 @@ Mealy transition-output and Moore state-output conventions * :class:`MealyMachine` — output on transitions. * :class:`MooreMachine` — output on states. -Epsilon moves use :data:`pensive.graph.EPSILON` explicitly. An epsilon input +Epsilon moves use :data:`sofic.graph.EPSILON` explicitly. An epsilon input transition advances the transducer without consuming an input symbol; an epsilon output transition emits no output symbol. ``transduce`` expands epsilon closures before and after each consumed input symbol, and raises @@ -29,15 +29,15 @@ Composition The composition helpers mirror the common ``cmpy`` transducer operations: -* :func:`pensive.automata.transducer_operations.compose_tt` serially composes +* :func:`sofic.automata.transducer_operations.compose_tt` serially composes transducers. -* :func:`pensive.automata.transducer_operations.compose_tg` composes a +* :func:`sofic.automata.transducer_operations.compose_tg` composes a transducer with a stochastic generator and returns a joint input/output generator. -* :func:`pensive.automata.transducer_operations.transduce_generator` returns +* :func:`sofic.automata.transducer_operations.transduce_generator` returns the output-only generator induced by driving a transducer with a generator. -* :func:`pensive.automata.transducer_operations.cartesian_product_tt` and - :func:`pensive.automata.transducer_operations.cartesian_product_gg` build +* :func:`sofic.automata.transducer_operations.cartesian_product_tt` and + :func:`sofic.automata.transducer_operations.cartesian_product_gg` build tuple-symbol Cartesian products. For convenience, :class:`MealyMachine` also exposes ``compose``, @@ -53,10 +53,10 @@ API .. autoclass:: MooreMachine :members: add_transition, set_output, transduce -.. autofunction:: pensive.automata.transducer_simulation.transduce_mealy -.. autofunction:: pensive.automata.transducer_simulation.transduce_moore -.. autofunction:: pensive.automata.transducer_operations.compose_tt -.. autofunction:: pensive.automata.transducer_operations.compose_tg -.. autofunction:: pensive.automata.transducer_operations.transduce_generator -.. autofunction:: pensive.automata.transducer_operations.cartesian_product_tt -.. autofunction:: pensive.automata.transducer_operations.cartesian_product_gg +.. autofunction:: sofic.automata.transducer_simulation.transduce_mealy +.. autofunction:: sofic.automata.transducer_simulation.transduce_moore +.. autofunction:: sofic.automata.transducer_operations.compose_tt +.. autofunction:: sofic.automata.transducer_operations.compose_tg +.. autofunction:: sofic.automata.transducer_operations.transduce_generator +.. autofunction:: sofic.automata.transducer_operations.cartesian_product_tt +.. autofunction:: sofic.automata.transducer_operations.cartesian_product_gg diff --git a/docs/automata/unifilar_automaton.rst b/docs/automata/unifilar_automaton.rst index 7a207b8..03172b5 100644 --- a/docs/automata/unifilar_automaton.rst +++ b/docs/automata/unifilar_automaton.rst @@ -1,5 +1,5 @@ .. unifilar_automaton.rst -.. py:module:: pensive.automata.unifilar +.. py:module:: sofic.automata.unifilar ****************** Unifilar Automaton diff --git a/docs/automata/vpa.rst b/docs/automata/vpa.rst index ea13fc5..35b54ce 100644 --- a/docs/automata/vpa.rst +++ b/docs/automata/vpa.rst @@ -1,5 +1,5 @@ .. vpa.rst -.. py:module:: pensive.automata.vpa +.. py:module:: sofic.automata.vpa ************************* Visibly Pushdown Automata @@ -109,4 +109,4 @@ API .. autoclass:: CanonicalVisiblyPushdownAutomaton -.. autofunction:: pensive.automata.vpa_simulation.recognizes_vpa +.. autofunction:: sofic.automata.vpa_simulation.recognizes_vpa diff --git a/docs/basicinfo.rst.txt b/docs/basicinfo.rst.txt index 958b878..a106f4f 100644 --- a/docs/basicinfo.rst.txt +++ b/docs/basicinfo.rst.txt @@ -1,8 +1,8 @@ Documentation: - https://pensive.readthedocs.io + https://sofic.readthedocs.io Repository: - https://github.com/dit/pensive + https://github.com/dit/sofic Dependencies: * Python 3.11+ @@ -15,11 +15,11 @@ Dependencies: Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~~~ -* ``graphviz`` (``pensive[viz]``): Graphviz diagram rendering in terminals and Jupyter -* ``pymc``, ``arviz`` (``pensive[bayes]``): PyMC/ArviZ backends for Bayesian inference -* ``pytest``, ``hypothesis``, ``graphviz`` (``pensive[test]``): test-suite dependencies -* ``sphinx``, ``ipython``, ``matplotlib`` (``pensive[docs]``): documentation build -* ``pensive[dev]``: linting, type checking, docs, and all of the above +* ``graphviz`` (``sofic[viz]``): Graphviz diagram rendering in terminals and Jupyter +* ``pymc``, ``arviz`` (``sofic[bayes]``): PyMC/ArviZ backends for Bayesian inference +* ``pytest``, ``hypothesis``, ``graphviz`` (``sofic[test]``): test-suite dependencies +* ``sphinx``, ``ipython``, ``matplotlib`` (``sofic[docs]``): documentation build +* ``sofic[dev]``: linting, type checking, docs, and all of the above License: BSD 3-Clause, see LICENSE.txt for details. diff --git a/docs/conf.py b/docs/conf.py index 45205b8..7cf0fd6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ import os -import pensive +import sofic on_rtd = os.environ.get("READTHEDOCS", None) == "True" @@ -28,7 +28,7 @@ "import numpy as np", "np.set_printoptions(legacy='1.25')", "np.random.seed(0)", - "import pensive", + "import sofic", ] ipython_savefig_dir = "images/" ipython_warning_is_error = False @@ -37,15 +37,15 @@ source_suffix = ".rst" master_doc = "index" -project = "pensive" -copyright = "2026, pensive contributors" # noqa: A001 -version = pensive.__version__ -release = pensive.__version__ +project = "sofic" +copyright = "2026, sofic contributors" # noqa: A001 +version = sofic.__version__ +release = sofic.__version__ exclude_patterns = ["_build"] add_module_names = False pygments_style = "sphinx" -modindex_common_prefix = ["pensive."] +modindex_common_prefix = ["sofic."] todo_include_todos = not on_rtd # -- Math macros (single source of truth for HTML and PDF) --------------------- @@ -70,7 +70,7 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] -htmlhelp_basename = "pensivedoc" +htmlhelp_basename = "soficdoc" _LATEX_RENEW = {"H"} @@ -99,18 +99,18 @@ def _macros_to_latex(macros, renew): } latex_documents = [ - ("index", "pensive.tex", "pensive Documentation", "pensive Contributors", "manual"), + ("index", "sofic.tex", "sofic Documentation", "sofic Contributors", "manual"), ] -man_pages = [("index", "pensive", "pensive Documentation", ["pensive Contributors"], 1)] +man_pages = [("index", "sofic", "sofic Documentation", ["sofic Contributors"], 1)] texinfo_documents = [ ( "index", - "pensive", - "pensive Documentation", - "pensive Contributors", - "pensive", + "sofic", + "sofic Documentation", + "sofic Contributors", + "sofic", "Stochastic symbol generators in Python.", "Science", ), diff --git a/docs/core/core.rst b/docs/core/core.rst index 0232737..1241939 100644 --- a/docs/core/core.rst +++ b/docs/core/core.rst @@ -4,9 +4,9 @@ Core **** -The :mod:`pensive.core` module collects the graph-backed -:class:`~pensive.core.StateMachine` abstraction, the -:class:`~pensive.core.TransitionGraph` wrapper, and graph attribute constants +The :mod:`sofic.core` module collects the graph-backed +:class:`~sofic.core.StateMachine` abstraction, the +:class:`~sofic.core.TransitionGraph` wrapper, and graph attribute constants shared by automata, generators, and symbolic models. .. toctree:: diff --git a/docs/core/exceptions.rst b/docs/core/exceptions.rst index ecb4a44..ee77057 100644 --- a/docs/core/exceptions.rst +++ b/docs/core/exceptions.rst @@ -1,13 +1,13 @@ .. exceptions.rst -.. py:module:: pensive.exceptions +.. py:module:: sofic.exceptions ********** Exceptions ********** -``pensive`` raises typed exceptions when models fail validation: +``sofic`` raises typed exceptions when models fail validation: -* :class:`PensiveValidationError` — general structural or semantic failure +* :class:`SoficValidationError` — general structural or semantic failure * :class:`NonDeterministicError` — DFA determinism violated * :class:`StochasticValidationError` — invalid probability masses * :class:`UnifilarityError` — unifilarity invariant violated @@ -16,8 +16,8 @@ Exceptions API === -.. autoclass:: PensiveError -.. autoclass:: PensiveValidationError +.. autoclass:: SoficError +.. autoclass:: SoficValidationError .. autoclass:: NonDeterministicError .. autoclass:: StochasticValidationError .. autoclass:: UnifilarityError diff --git a/docs/core/properties.rst b/docs/core/properties.rst index 9b19aa7..0df97d9 100644 --- a/docs/core/properties.rst +++ b/docs/core/properties.rst @@ -1,5 +1,5 @@ .. properties.rst -.. py:module:: pensive.properties +.. py:module:: sofic.properties ********** Properties @@ -12,7 +12,7 @@ finite automata, symbolic dynamics, and computational mechanics .. ipython:: - In [1]: from pensive.examples import golden_mean; from pensive.properties import is_unifilar_emissions + In [1]: from sofic.examples import golden_mean; from sofic.properties import is_unifilar_emissions In [2]: eps = golden_mean(0.5) diff --git a/docs/core/serialization.rst b/docs/core/serialization.rst index 1732bc6..bcfc090 100644 --- a/docs/core/serialization.rst +++ b/docs/core/serialization.rst @@ -1,18 +1,18 @@ .. serialization.rst -.. py:module:: pensive.serialization +.. py:module:: sofic.serialization ************* Serialization ************* -Every :class:`~pensive.core.StateMachine` round-trips through a self-describing +Every :class:`~sofic.core.StateMachine` round-trips through a self-describing YAML document. The schema records the concrete model class, the transition graph (states, edges, and typed attributes), and any data not determined by the graph alone — for example an HMM's initial distribution or a shift's alphabet. .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -22,10 +22,10 @@ graph alone — for example an HMM's initial distribution or a shift's alphabet. In [5]: restored.validate() -The instance methods :meth:`~pensive.core.StateMachine.to_yaml`, -:meth:`~pensive.core.StateMachine.write_yaml`, -:meth:`~pensive.core.StateMachine.from_yaml`, and -:meth:`~pensive.core.StateMachine.read_yaml` delegate to the module-level +The instance methods :meth:`~sofic.core.StateMachine.to_yaml`, +:meth:`~sofic.core.StateMachine.write_yaml`, +:meth:`~sofic.core.StateMachine.from_yaml`, and +:meth:`~sofic.core.StateMachine.read_yaml` delegate to the module-level functions below. The polymorphic :func:`model_from_yaml` / :func:`from_yaml` readers reconstruct the correct subclass from the serialized class tag, so they are convenient when the concrete type is not known in advance. diff --git a/docs/core/state_machine.rst b/docs/core/state_machine.rst index b7c44a8..b4a6dd7 100644 --- a/docs/core/state_machine.rst +++ b/docs/core/state_machine.rst @@ -1,12 +1,12 @@ .. state_machine.rst -.. py:module:: pensive.core +.. py:module:: sofic.core ************* StateMachine ************* :class:`StateMachine` is the abstract base class for every graph-backed model in -``pensive``. Subclasses implement :meth:`~StateMachine.validate` to enforce +``sofic``. Subclasses implement :meth:`~StateMachine.validate` to enforce structural and semantic invariants. Common operations @@ -16,7 +16,7 @@ Build a model, validate it, and inspect its graph: .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -34,7 +34,7 @@ Reverse the transition graph: .. ipython:: - In [7]: from pensive.operations import reverse + In [7]: from sofic.operations import reverse In [8]: rev = reverse(eps) @@ -52,7 +52,7 @@ For HMM-style generators, emitted alphabets are inferred from graph emissions when loading YAML. Initial distributions remain serialized because they are not determined by the transition graph in general. -Jupyter notebooks display models as Graphviz SVG when ``pensive[viz]`` is +Jupyter notebooks display models as Graphviz SVG when ``sofic[viz]`` is installed (see :doc:`../viz`). API @@ -63,4 +63,4 @@ API .. autoclass:: StateIndex -.. autofunction:: pensive.operations.reverse +.. autofunction:: sofic.operations.reverse diff --git a/docs/core/transition_graph.rst b/docs/core/transition_graph.rst index eebb8fb..d227f84 100644 --- a/docs/core/transition_graph.rst +++ b/docs/core/transition_graph.rst @@ -1,5 +1,5 @@ .. transition_graph.rst -.. py:module:: pensive.core +.. py:module:: sofic.core :no-index: **************** @@ -7,12 +7,12 @@ TransitionGraph **************** :class:`TransitionGraph` wraps a :class:`networkx.MultiDiGraph` with a thin API -for adding states, adding transitions, and iterating edges. All ``pensive`` +for adding states, adding transitions, and iterating edges. All ``sofic`` models store their structure in a ``TransitionGraph``. .. ipython:: - In [1]: from pensive.core import TransitionGraph, ATTR_EMISSION, ATTR_PROB + In [1]: from sofic.core import TransitionGraph, ATTR_EMISSION, ATTR_PROB In [2]: g = TransitionGraph() diff --git a/docs/examples.rst b/docs/examples.rst index 9acd3c6..05d2da2 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,11 +1,11 @@ .. examples.rst -.. py:module:: pensive.examples +.. py:module:: sofic.examples ******** Examples ******** -The :mod:`pensive.examples` module is a catalog of canonical ε-machines, +The :mod:`sofic.examples` module is a catalog of canonical ε-machines, symbolic shifts, and related generators from the computational mechanics and symbolic dynamics literature. @@ -67,16 +67,16 @@ figures: Process library --------------- -In addition to the curated ε-machines above, :mod:`pensive.examples.processes` +In addition to the curated ε-machines above, :mod:`sofic.examples.processes` ports a large library of parametrized process factories (``GoldenMean``, ``Even``, ``Nemo``, ``IID``, ``Ising``, ``Ehrenfest``, the periodic and ``Misiurewicz`` families, and many more). Each is a function that returns a -generator, defaulting to an :class:`~pensive.generators.epsilon_machine.EpsilonMachine` +generator, defaulting to an :class:`~sofic.generators.epsilon_machine.EpsilonMachine` but accepting a ``machine_type`` argument: .. ipython:: - In [1]: from pensive.examples import GoldenMean, Even, Nemo + In [1]: from sofic.examples import GoldenMean, Even, Nemo In [2]: gm = GoldenMean(bias=0.5) @@ -90,25 +90,25 @@ parametrized tests and sweeps: .. ipython:: - In [4]: from pensive.examples import processes + In [4]: from sofic.examples import processes In [5]: len(processes.process_list) > 0 Out[5]: True A parallel set of transducer factories (``BitFlip``, ``Parity``, ``Delay``, ``BinaryChannel``, …) lives alongside the processes and produces -:class:`~pensive.automata.transducers.MealyMachine` instances. +:class:`~sofic.automata.transducers.MealyMachine` instances. -Symbolic-shift examples (:mod:`pensive.examples.shifts`) provide the +Symbolic-shift examples (:mod:`sofic.examples.shifts`) provide the sofic-Dyck factories listed above; access them via -``from pensive.examples import dyck_shift_order`` or the ``shifts`` module. +``from sofic.examples import dyck_shift_order`` or the ``shifts`` module. Example ------- .. ipython:: - In [1]: from pensive.examples import golden_mean, even_process, bernoulli + In [1]: from sofic.examples import golden_mean, even_process, bernoulli In [2]: for factory in (golden_mean, even_process, bernoulli): ...: model = factory(0.5) diff --git a/docs/generators/alternative_complexity.rst b/docs/generators/alternative_complexity.rst index afb1a01..a4ae14d 100644 --- a/docs/generators/alternative_complexity.rst +++ b/docs/generators/alternative_complexity.rst @@ -1,5 +1,5 @@ .. alternative_complexity.rst -.. py:module:: pensive.generators.alternative_complexity +.. py:module:: sofic.generators.alternative_complexity ************************* Alternative complexities @@ -12,7 +12,7 @@ finite-state epsilon-machines. .. autofunction:: thermodynamic_depth .. autofunction:: spectral_complexity -:class:`~pensive.generators.epsilon_machine.EpsilonMachine` also exposes -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.structural_information`, -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.thermodynamic_depth`, and -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.spectral_complexity`. +:class:`~sofic.generators.epsilon_machine.EpsilonMachine` also exposes +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.structural_information`, +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.thermodynamic_depth`, and +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.spectral_complexity`. diff --git a/docs/generators/bidirectional_epsilon_machine.rst b/docs/generators/bidirectional_epsilon_machine.rst index deeaac1..55ef51b 100644 --- a/docs/generators/bidirectional_epsilon_machine.rst +++ b/docs/generators/bidirectional_epsilon_machine.rst @@ -1,5 +1,5 @@ .. bidirectional_epsilon_machine.rst -.. py:module:: pensive.generators.bidirectional_epsilon_machine +.. py:module:: sofic.generators.bidirectional_epsilon_machine *************************** Bidirectional ε-Machine @@ -14,7 +14,7 @@ causal states :math:`(S^+, S^-)` :cite:`Ellison2011`. .. ipython:: - In [1]: from pensive.examples import golden_mean_bidirectional + In [1]: from sofic.examples import golden_mean_bidirectional In [2]: bidir = golden_mean_bidirectional(0.5) @@ -119,7 +119,7 @@ follows from the determinism of the forward transition function. .. ipython:: - In [13]: from pensive.examples import butterfly_process + In [13]: from sofic.examples import butterfly_process In [14]: bidir = butterfly_process().to_bidirectional() diff --git a/docs/generators/block_convergence.rst b/docs/generators/block_convergence.rst index 78d4873..3f4677c 100644 --- a/docs/generators/block_convergence.rst +++ b/docs/generators/block_convergence.rst @@ -55,7 +55,7 @@ Exact vs estimated rates ------------------------ James (2011) curves ``H``, ``T``, ``R``, ``B``, ``Q``, and ``W`` satisfy block -identities checked by :meth:`~pensive.generators.block_convergence.BlockConvergenceDiagram.validate_identities`. +identities checked by :meth:`~sofic.generators.block_convergence.BlockConvergenceDiagram.validate_identities`. When a bidirectional ε-machine is available, the promoted rates ``h_μ``, ``ρ_μ``, ``b_μ``, and ``r_μ`` are **exact** (from the step distribution); see :doc:`information_anatomy`. @@ -64,7 +64,7 @@ The CAEKL curve ``J(ℓ)`` is separate: for each ``ℓ`` it is **exact** given t ε-machine word distribution and ``dit``'s partition-minimization definition of CAEKL. There is no bidirectional closed form analogous to ``ρ_μ = I[X₀:S⁺₀]``. The extensive rate ``j_μ`` is promoted from finite differences ``J(ℓ)-J(ℓ-1)``; -when :attr:`~pensive.generators.block_convergence.BlockConvergenceEstimates.caekl_rate_converged` +when :attr:`~sofic.generators.block_convergence.BlockConvergenceEstimates.caekl_rate_converged` is ``True``, that rate is certified from a stable affine tail. Multivariate ordering :cite:`chan2015multivariate` gives ``J(ℓ) ≤ B(ℓ) ≤ T(ℓ)`` @@ -74,7 +74,7 @@ has ``h_μ = 1`` and ``j_μ = 0``). CAEKL partition minimization costs grow quickly with ``ℓ`` (Bell-number partitions); pass ``max_caekl_length`` to -:func:`~pensive.generators.block_convergence.block_convergence_estimates` +:func:`~sofic.generators.block_convergence.block_convergence_estimates` to cap how far ``J(ℓ)`` is computed when ``max_length`` is large. Example @@ -82,7 +82,7 @@ Example .. ipython:: - In [1]: from pensive.examples import golden_mean, even_process, noisy_random_phase_slip + In [1]: from sofic.examples import golden_mean, even_process, noisy_random_phase_slip In [2]: eps = golden_mean(0.5) @@ -107,16 +107,16 @@ Example API --- -.. autofunction:: pensive.generators.block_convergence.block_caekl +.. autofunction:: sofic.generators.block_convergence.block_caekl -.. autofunction:: pensive.generators.block_convergence.block_convergence_diagram +.. autofunction:: sofic.generators.block_convergence.block_convergence_diagram -.. autofunction:: pensive.generators.block_convergence.block_convergence_estimates +.. autofunction:: sofic.generators.block_convergence.block_convergence_estimates -.. autofunction:: pensive.generators.block_convergence.plot_block_convergence_diagram +.. autofunction:: sofic.generators.block_convergence.plot_block_convergence_diagram -.. autoclass:: pensive.generators.block_convergence.BlockConvergenceDiagram +.. autoclass:: sofic.generators.block_convergence.BlockConvergenceDiagram :members: plot, validate_identities, j_mu, J_inf, caekl_rate_converged -.. autoclass:: pensive.generators.block_convergence.BlockConvergenceEstimates +.. autoclass:: sofic.generators.block_convergence.BlockConvergenceEstimates :members: information_anatomy diff --git a/docs/generators/constructions.rst b/docs/generators/constructions.rst index ae10a14..5118db3 100644 --- a/docs/generators/constructions.rst +++ b/docs/generators/constructions.rst @@ -12,25 +12,25 @@ and the bidirectional builder follows the forward/reverse construction ε-Machine construction ====================== -.. autofunction:: pensive.generators.epsilon_construction.build_epsilon_machine +.. autofunction:: sofic.generators.epsilon_construction.build_epsilon_machine Mixed-state presentation ======================== -.. autofunction:: pensive.generators.mixed_state_construction.build_mixed_state_presentation +.. autofunction:: sofic.generators.mixed_state_construction.build_mixed_state_presentation :no-index: Bidirectional ε-machine ========================= -.. autofunction:: pensive.generators.bidirectional_construction.build_bidirectional_epsilon_machine -.. autofunction:: pensive.generators.bidirectional_construction.infer_reverse_epsilon_machine -.. autofunction:: pensive.generators.bidirectional_construction.joint_distribution -.. autofunction:: pensive.generators.bidirectional_construction.forward_epsilon_machine -.. autofunction:: pensive.generators.bidirectional_construction.reverse_epsilon_machine +.. autofunction:: sofic.generators.bidirectional_construction.build_bidirectional_epsilon_machine +.. autofunction:: sofic.generators.bidirectional_construction.infer_reverse_epsilon_machine +.. autofunction:: sofic.generators.bidirectional_construction.joint_distribution +.. autofunction:: sofic.generators.bidirectional_construction.forward_epsilon_machine +.. autofunction:: sofic.generators.bidirectional_construction.reverse_epsilon_machine n-Machine construction ====================== -.. autofunction:: pensive.generators.nmachine_construction.build_nmachine +.. autofunction:: sofic.generators.nmachine_construction.build_nmachine :no-index: diff --git a/docs/generators/conversions.rst b/docs/generators/conversions.rst index 28da59d..60009b6 100644 --- a/docs/generators/conversions.rst +++ b/docs/generators/conversions.rst @@ -1,5 +1,5 @@ .. conversions.rst -.. py:module:: pensive.generators.conversions +.. py:module:: sofic.generators.conversions *********** Conversions diff --git a/docs/generators/directional_flow.rst b/docs/generators/directional_flow.rst index f4d79a3..359fc9c 100644 --- a/docs/generators/directional_flow.rst +++ b/docs/generators/directional_flow.rst @@ -1,5 +1,5 @@ .. directional_flow.rst -.. py:module:: pensive.generators.directional_flow +.. py:module:: sofic.generators.directional_flow ******************* Directional flow diff --git a/docs/generators/edge_machine.rst b/docs/generators/edge_machine.rst index e9009e2..cc165a7 100644 --- a/docs/generators/edge_machine.rst +++ b/docs/generators/edge_machine.rst @@ -1,5 +1,5 @@ .. edge_machine.rst -.. py:module:: pensive.generators.edge_machine +.. py:module:: sofic.generators.edge_machine ************* Edge Machine @@ -12,7 +12,7 @@ computational mechanics :cite:`Rabiner1989,Crutchfield1994`. .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_hmm + In [1]: from sofic.examples import tent_map_misiurewicz_hmm In [2]: edge = tent_map_misiurewicz_hmm().to_edge_machine() diff --git a/docs/generators/epsilon_inference.rst b/docs/generators/epsilon_inference.rst index 78e17c1..6af90eb 100644 --- a/docs/generators/epsilon_inference.rst +++ b/docs/generators/epsilon_inference.rst @@ -1,12 +1,12 @@ .. epsilon_inference.rst -.. py:module:: pensive.generators.epsilon_inference +.. py:module:: sofic.generators.epsilon_inference ************************** ε-Machine inference ************************** Sample-based reconstruction of ε-machines from observed symbol sequences. -This complements the **oracle** path :func:`~pensive.generators.epsilon_construction.build_epsilon_machine`, +This complements the **oracle** path :func:`~sofic.generators.epsilon_construction.build_epsilon_machine`, which merges probabilistically equivalent states in a *given* generator. Two algorithms are implemented: @@ -20,8 +20,8 @@ Quick start .. code-block:: python import numpy as np - from pensive.examples import even_process - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.examples import even_process + from sofic.generators.epsilon_machine import EpsilonMachine rng = np.random.default_rng(0) oracle = even_process(0.5) @@ -58,7 +58,7 @@ numerical tolerance for finite-sample estimates. Unified entry point =================== -Use :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_sequence` to +Use :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_sequence` to dispatch to CSSR or subtree merging (see :doc:`epsilon_machine`). Related inference methods (not yet implemented) diff --git a/docs/generators/epsilon_machine.rst b/docs/generators/epsilon_machine.rst index 98572b2..ec9debb 100644 --- a/docs/generators/epsilon_machine.rst +++ b/docs/generators/epsilon_machine.rst @@ -1,5 +1,5 @@ .. epsilon_machine.rst -.. py:module:: pensive.generators.epsilon_machine +.. py:module:: sofic.generators.epsilon_machine *********** ε-Machine @@ -17,7 +17,7 @@ Build from an HMM via partition refinement: .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -69,14 +69,14 @@ API .. autoclass:: EpsilonMachine :members: from_hmm, from_sequence, from_time_reversed, to_bidirectional, block_entropy_diagram, block_entropy_estimates, plot_block_entropy_diagram, block_convergence_diagram, block_convergence_estimates, plot_block_convergence_diagram, caekl_block_information, caekl_rate, caekl_intercept, caekl_rate_converged, approximate_entropy_rate, approximate_excess_entropy, approximate_information_anatomy, statistical_complexity, bidirectional_statistical_complexity, excess_entropy, predicted_information, bound_information, ephemeral_information, information_anatomy, caekl_causal_information, crypticity, bidirectional_crypticity, causal_irreversibility, stored_information_decomposition, transient_information, oracular_information, gauge_information, predictability_gain, structural_information, thermodynamic_depth, spectral_complexity, markov_order, is_markov, cryptic_order, is_exactly_synchronizable -.. autoclass:: pensive.generators.block_entropy.BlockEntropyDiagram +.. autoclass:: sofic.generators.block_entropy.BlockEntropyDiagram :members: plot, transient_information -.. autoclass:: pensive.generators.block_entropy.BlockEntropyEstimates +.. autoclass:: sofic.generators.block_entropy.BlockEntropyEstimates :members: information_anatomy -.. autoclass:: pensive.generators.block_convergence.BlockConvergenceDiagram +.. autoclass:: sofic.generators.block_convergence.BlockConvergenceDiagram :members: plot, validate_identities :no-index: -.. autoclass:: pensive.generators.block_convergence.BlockConvergenceEstimates +.. autoclass:: sofic.generators.block_convergence.BlockConvergenceEstimates :members: information_anatomy :no-index: diff --git a/docs/generators/generative_models.rst b/docs/generators/generative_models.rst index a39c270..eb0993a 100644 --- a/docs/generators/generative_models.rst +++ b/docs/generators/generative_models.rst @@ -1,5 +1,5 @@ .. generative_models.rst -.. py:module:: pensive.generators.minimal_generative_model +.. py:module:: sofic.generators.minimal_generative_model ***************** Generative Models @@ -7,9 +7,9 @@ Generative Models The statistical complexity :math:`C_\mu = \H{S^+}` is the cost of *prediction*: the memory a unifilar (ε-machine) presentation must carry. A process can often -be *generated* with less memory by a non-unifilar machine. ``pensive`` builds +be *generated* with less memory by a non-unifilar machine. ``sofic`` builds these minimal generators from a -:class:`~pensive.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine`, +:class:`~sofic.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine`, each realizing a different **common information** between the forward causal state :math:`S^+` and the reverse causal state :math:`S^-`. @@ -38,13 +38,13 @@ sit in a fixed order: This captures only the conserved "core" (phase / ergodic structure) and is often trivial for mixing processes. -Each returns a (generally non-unifilar) :class:`~pensive.generators.mealy.MealyHMM` +Each returns a (generally non-unifilar) :class:`~sofic.generators.mealy.MealyHMM` subclass whose ``generative_complexity()`` is the corresponding common information, and which reproduces the source process. .. code-block:: python - from pensive.examples import golden_mean_bidirectional + from sofic.examples import golden_mean_bidirectional bidir = golden_mean_bidirectional(0.5) @@ -56,8 +56,8 @@ information, and which reproduces the source process. The optimizers mirror the corresponding ``dit`` common-information routines and require ``dit`` (a core dependency). The convenience methods -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.minimal_generative_model` -and friends on an :class:`~pensive.generators.epsilon_machine.EpsilonMachine` +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.minimal_generative_model` +and friends on an :class:`~sofic.generators.epsilon_machine.EpsilonMachine` build the bidirectional presentation first. API diff --git a/docs/generators/generators.rst b/docs/generators/generators.rst index dcb4422..5153898 100644 --- a/docs/generators/generators.rst +++ b/docs/generators/generators.rst @@ -4,7 +4,7 @@ Generators ********** -The :mod:`pensive.generators` package provides stochastic and quasiprobabilistic +The :mod:`sofic.generators` package provides stochastic and quasiprobabilistic symbol generators: hidden Markov models, ε-machines, mixed-state presentations, and related constructions. The stochastic-generator foundations are hidden Markov models, computational mechanics, and symbolic process presentations diff --git a/docs/generators/hidden_markov_model.rst b/docs/generators/hidden_markov_model.rst index 95829e6..af3dc35 100644 --- a/docs/generators/hidden_markov_model.rst +++ b/docs/generators/hidden_markov_model.rst @@ -1,12 +1,12 @@ .. hidden_markov_model.rst -.. py:module:: pensive.generators.base +.. py:module:: sofic.generators.base ******************* Hidden Markov Model ******************* -:class:`~pensive.generators.base.StochasticModel` is the base for row-stochastic -generators. :class:`~pensive.generators.base.HiddenMarkovModel` adds an +:class:`~sofic.generators.base.StochasticModel` is the base for row-stochastic +generators. :class:`~sofic.generators.base.HiddenMarkovModel` adds an observation alphabet and emission semantics. The HMM conventions and standard inference problems follow Baum and Petrie and Rabiner's tutorial :cite:`BaumPetrie1966,Rabiner1989`. @@ -14,15 +14,15 @@ inference problems follow Baum and Petrie and Rabiner's tutorial Mealy vs Moore ============== -* :class:`~pensive.generators.mealy.MealyHMM` — joint transition +* :class:`~sofic.generators.mealy.MealyHMM` — joint transition :math:`P(q', o \mid q)` on edges. -* :class:`~pensive.generators.moore.MooreHMM` — emission distribution - :math:`P(o \mid q)` on states; convert with :func:`~pensive.generators.conversions.moore_to_mealy`. +* :class:`~sofic.generators.moore.MooreHMM` — emission distribution + :math:`P(o \mid q)` on states; convert with :func:`~sofic.generators.conversions.moore_to_mealy`. The naming follows Mealy and Moore machine conventions :cite:`Mealy1955,Moore1956`. .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -55,7 +55,7 @@ API .. autoclass:: StochasticModel .. autoclass:: HiddenMarkovModel :members: word_probability, log_word_probability, word_probabilities, conditional_word_probability, is_equal_process, joint_block_distribution, to_sofic_shift, to_support_nfa, to_support_dfa -.. autoclass:: pensive.generators.mealy.MealyHMM +.. autoclass:: sofic.generators.mealy.MealyHMM :members: add_transition, is_counifilar, is_irreducible, is_ergodic, is_stationary, is_detailed_balance, is_periodic, is_strictly_sofic, to_edge_machine -.. autoclass:: pensive.generators.moore.MooreHMM +.. autoclass:: sofic.generators.moore.MooreHMM :members: add_transition, set_emission_distribution, to_mealy diff --git a/docs/generators/hidden_markov_stack_model.rst b/docs/generators/hidden_markov_stack_model.rst index 86fd9d2..d51f0f3 100644 --- a/docs/generators/hidden_markov_stack_model.rst +++ b/docs/generators/hidden_markov_stack_model.rst @@ -1,5 +1,5 @@ .. hidden_markov_stack_model.rst -.. py:module:: pensive.generators.stack_hmm +.. py:module:: sofic.generators.stack_hmm ************************* Hidden Markov Stack Model @@ -9,7 +9,7 @@ A :class:`HiddenMarkovStackModel` is a stochastic visibly-pushdown generator. Its hidden configuration consists of a finite control state and a stack of pending call edges. Edges are partitioned into call, return, and internal roles, and return edges are enabled by the same matched-edge relation used by -:class:`~pensive.shifts.sofic_dyck.SoficDyckShift`. +:class:`~sofic.shifts.sofic_dyck.SoficDyckShift`. Edge probabilities are interpreted as weights over the transitions enabled by the current stack configuration. Those enabled weights are normalized at each diff --git a/docs/generators/hmm_inference.rst b/docs/generators/hmm_inference.rst index a46d0c0..e8c6535 100644 --- a/docs/generators/hmm_inference.rst +++ b/docs/generators/hmm_inference.rst @@ -1,5 +1,5 @@ .. hmm_inference.rst -.. py:module:: pensive.generators.hmm_inference +.. py:module:: sofic.generators.hmm_inference ************* HMM Inference @@ -10,13 +10,13 @@ Forward-backward, Viterbi decoding, and sampling for hidden Markov models .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) In [3]: obs = [0, 1, 0, 0, 1] - In [4]: from pensive.generators.hmm_inference import forward, viterbi, sample + In [4]: from sofic.generators.hmm_inference import forward, viterbi, sample In [5]: alpha = forward(eps, obs) diff --git a/docs/generators/information_anatomy.rst b/docs/generators/information_anatomy.rst index dc791d5..20d6581 100644 --- a/docs/generators/information_anatomy.rst +++ b/docs/generators/information_anatomy.rst @@ -16,7 +16,7 @@ predicted, bound, and ephemeral components :cite:`James2013`: .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_bidirectional + In [1]: from sofic.examples import tent_map_misiurewicz_bidirectional In [2]: bidir = tent_map_misiurewicz_bidirectional() @@ -44,13 +44,13 @@ appendix walkthrough. API === -Use :meth:`~pensive.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.information_anatomy` -on bidirectional models. :class:`~pensive.generators.epsilon_machine.EpsilonMachine` +Use :meth:`~sofic.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.information_anatomy` +on bidirectional models. :class:`~sofic.generators.epsilon_machine.EpsilonMachine` also exposes these quantities by building its bidirectional presentation. When that construction is unavailable, use -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.approximate_information_anatomy` -or :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.block_convergence_estimates`. +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.approximate_information_anatomy` +or :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.block_convergence_estimates`. These finite-block estimates do not replace the exact bidirectional quantities; they report the current block-length approximation to ``h_mu``, ``E``, ``rho_mu``, ``b_mu``, ``r_mu``, ``q_mu``, ``w_mu``, block coinformation, CAEKL, @@ -71,7 +71,7 @@ semi-infinite past and future, keeping the quantity finite: .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_bidirectional + In [1]: from sofic.examples import tent_map_misiurewicz_bidirectional In [2]: bidir = tent_map_misiurewicz_bidirectional() @@ -88,11 +88,11 @@ CAEKL rate ``j_μ`` The block CAEKL curve ``J(ℓ)`` and its asymptotic rate ``j_μ`` are documented in :doc:`block_convergence`. Per-block values ``J(ℓ)`` are exact from -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.caekl_block_information`; +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.caekl_block_information`; ``j_μ`` is **not** a single-step bidirectional quantity like ``ρ_μ``. When the affine tail of ``J(ℓ)`` stabilizes, -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.caekl_rate_converged` -returns ``True`` and :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.caekl_rate` +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.caekl_rate_converged` +returns ``True`` and :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.caekl_rate` is exact. Multivariate ordering yields ``j_μ ≤ b_μ ≤ ρ_μ``; ``j_μ`` is not determined by ``h_μ`` alone. @@ -100,15 +100,15 @@ Causal irreversibility and stored information ============================================= Time-asymmetric stored information is available via -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.causal_irreversibility` -and :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.stored_information_decomposition` +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.causal_irreversibility` +and :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.stored_information_decomposition` :cite:`Crutchfield2009,Ellison2009`. Finite-block convergence scalars — transient, oracular, gauge, and predictability-gain -information — are exposed as :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.transient_information`, -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.oracular_information`, -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.gauge_information`, and -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.predictability_gain`. +information — are exposed as :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.transient_information`, +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.oracular_information`, +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.gauge_information`, and +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.predictability_gain`. Topological anatomy =================== diff --git a/docs/generators/lumping.rst b/docs/generators/lumping.rst index ce9b684..b8a07fb 100644 --- a/docs/generators/lumping.rst +++ b/docs/generators/lumping.rst @@ -1,5 +1,5 @@ .. lumping.rst -.. py:module:: pensive.generators.lumping +.. py:module:: sofic.generators.lumping ******* Lumping @@ -25,7 +25,7 @@ The common value is the lumped transition probability Hidden Markov models impose the condition per emitted symbol so that the lumped model generates the same observed process. A -:class:`~pensive.generators.mealy.MealyHMM` with joint edge law +:class:`~sofic.generators.mealy.MealyHMM` with joint edge law :math:`P(t, o \mid s)` is lumpable when .. math:: @@ -33,18 +33,18 @@ model generates the same observed process. A \sum_{t \in B_j} P(t, o \mid s) = \sum_{t \in B_j} P(t, o \mid s') \qquad \text{for all } s, s' \in B_i,\; \text{all symbols } o, -while a :class:`~pensive.generators.moore.MooreHMM` additionally requires the +while a :class:`~sofic.generators.moore.MooreHMM` additionally requires the state emission law :math:`P(o \mid s)` to be identical across each block. :func:`is_lumpable` tests the condition and :func:`lump` builds the coarse model, -raising :class:`~pensive.exceptions.LumpabilityError` for a non-lumpable +raising :class:`~sofic.exceptions.LumpabilityError` for a non-lumpable partition unless ``check=False``. Because lumping can destroy unifilarity, an -:class:`~pensive.generators.epsilon_machine.EpsilonMachine` lumps to a plain +:class:`~sofic.generators.epsilon_machine.EpsilonMachine` lumps to a plain ``MealyHMM``. .. ipython:: - In [1]: from pensive import MarkovChain + In [1]: from sofic import MarkovChain In [2]: chain = MarkovChain(initial_distribution={"A": 1.0}) diff --git a/docs/generators/markov_chain.rst b/docs/generators/markov_chain.rst index 2261f85..af69164 100644 --- a/docs/generators/markov_chain.rst +++ b/docs/generators/markov_chain.rst @@ -1,5 +1,5 @@ .. markov_chain.rst -.. py:module:: pensive.generators.markov +.. py:module:: sofic.generators.markov *********** MarkovChain @@ -11,7 +11,7 @@ with states. Finite-state Markov-chain terminology follows standard treatments .. ipython:: - In [1]: from pensive.examples import golden_mean_markov + In [1]: from sofic.examples import golden_mean_markov In [2]: chain = golden_mean_markov(0.5) diff --git a/docs/generators/mixed_state_presentation.rst b/docs/generators/mixed_state_presentation.rst index 4f10bf8..79ba4c1 100644 --- a/docs/generators/mixed_state_presentation.rst +++ b/docs/generators/mixed_state_presentation.rst @@ -1,5 +1,5 @@ .. mixed_state_presentation.rst -.. py:module:: pensive.generators.mixed_state +.. py:module:: sofic.generators.mixed_state ************************* Mixed-State Presentation @@ -12,12 +12,12 @@ time-symmetric computational mechanics :cite:`Ellison2009`. Use :meth:`MixedStatePresentation.to_recurrent` to drop transient belief states and work only with the recurrent component. Pure recurrent components are -returned as :class:`~pensive.generators.epsilon_machine.EpsilonMachine`; -otherwise the result is a unifilar :class:`~pensive.generators.mealy.MealyHMM`. +returned as :class:`~sofic.generators.epsilon_machine.EpsilonMachine`; +otherwise the result is a unifilar :class:`~sofic.generators.mealy.MealyHMM`. .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_hmm + In [1]: from sofic.examples import tent_map_misiurewicz_hmm In [2]: hmm = tent_map_misiurewicz_hmm() @@ -31,4 +31,4 @@ API .. autoclass:: MixedState .. autoclass:: MixedStatePresentation -.. autofunction:: pensive.generators.mixed_state_construction.build_mixed_state_presentation +.. autofunction:: sofic.generators.mixed_state_construction.build_mixed_state_presentation diff --git a/docs/generators/nmachine.rst b/docs/generators/nmachine.rst index 9def507..5514d6b 100644 --- a/docs/generators/nmachine.rst +++ b/docs/generators/nmachine.rst @@ -1,5 +1,5 @@ .. nmachine.rst -.. py:module:: pensive.generators.nmachine +.. py:module:: sofic.generators.nmachine ********* n-Machine @@ -16,5 +16,5 @@ API .. autoclass:: NMachine :members: from_epsilon_machine, collision_entropy, process_negativity -.. autofunction:: pensive.generators.nmachine_construction.build_nmachine -.. autofunction:: pensive.generators.nmachine_construction.coarse_grained_distribution +.. autofunction:: sofic.generators.nmachine_construction.build_nmachine +.. autofunction:: sofic.generators.nmachine_construction.coarse_grained_distribution diff --git a/docs/generators/probabilistic_finite_automaton.rst b/docs/generators/probabilistic_finite_automaton.rst index 393e2c1..7957cfc 100644 --- a/docs/generators/probabilistic_finite_automaton.rst +++ b/docs/generators/probabilistic_finite_automaton.rst @@ -1,5 +1,5 @@ .. probabilistic_finite_automaton.rst -.. py:module:: pensive.generators.pfa +.. py:module:: sofic.generators.pfa ******************************* Probabilistic Finite Automaton diff --git a/docs/generators/quasi_realization.rst b/docs/generators/quasi_realization.rst index e7f1645..376c9ec 100644 --- a/docs/generators/quasi_realization.rst +++ b/docs/generators/quasi_realization.rst @@ -1,5 +1,5 @@ .. quasi_realization.rst -.. py:module:: pensive.generators.quasi_realization +.. py:module:: sofic.generators.quasi_realization ***************** Quasi-Realization @@ -16,6 +16,6 @@ API .. autoclass:: QuasiRealization .. autoclass:: QuasiStochasticModel -.. autofunction:: pensive.generators.quasi_inference.transition_matrices -.. autofunction:: pensive.generators.quasi_inference.stationary_quasidistribution -.. autofunction:: pensive.generators.quasi_inference.word_probability +.. autofunction:: sofic.generators.quasi_inference.transition_matrices +.. autofunction:: sofic.generators.quasi_inference.stationary_quasidistribution +.. autofunction:: sofic.generators.quasi_inference.word_probability diff --git a/docs/generators/stack_inference.rst b/docs/generators/stack_inference.rst index 9356be9..05a286b 100644 --- a/docs/generators/stack_inference.rst +++ b/docs/generators/stack_inference.rst @@ -1,20 +1,20 @@ .. stack_inference.rst -.. py:module:: pensive.generators.stack_inference +.. py:module:: sofic.generators.stack_inference ********************* Stack-HMM Inference ********************* Inference routines that reconstruct a -:class:`~pensive.generators.stack_hmm.HiddenMarkovStackModel` from sequences -over a :class:`~pensive.automata.papni.DyckAlphabet`. These extend the +:class:`~sofic.generators.stack_hmm.HiddenMarkovStackModel` from sequences +over a :class:`~sofic.automata.papni.DyckAlphabet`. These extend the finite-state inference of :doc:`epsilon_inference` with visibly pushdown stack semantics :cite:`BealBlockeletDima2015`. Two families are provided: * **Topology known.** Given a - :class:`~pensive.shifts.sofic_dyck.SoficDyckShift` presentation, + :class:`~sofic.shifts.sofic_dyck.SoficDyckShift` presentation, :func:`fit_stack_hmm_mle` estimates smoothed maximum-likelihood transition weights from a sample. * **Topology unknown.** :func:`stack_cssr` and :func:`stack_subtree_merge` @@ -27,8 +27,8 @@ Two families are provided: .. code-block:: python - from pensive.automata import DyckAlphabet - from pensive.generators import stack_cssr + from sofic.automata import DyckAlphabet + from sofic.generators import stack_cssr alphabet = DyckAlphabet( call_alphabet=frozenset({"("}), diff --git a/docs/generators/symbolic_hmm.rst b/docs/generators/symbolic_hmm.rst index b9695b7..34526f7 100644 --- a/docs/generators/symbolic_hmm.rst +++ b/docs/generators/symbolic_hmm.rst @@ -1,10 +1,10 @@ .. symbolic_hmm.rst -************************* +************************** Symbolic HMM probabilities -************************* +************************** -In addition to floating-point transition probabilities, :mod:`pensive` can carry +In addition to floating-point transition probabilities, :mod:`sofic` can carry exact `sympy `_ expressions on HMM edges. Stationary distributions, mixed-state presentations, bidirectional constructions, and information anatomy then return sympy expressions that can be substituted and @@ -12,19 +12,19 @@ simplified exactly. Install the optional extra:: - pip install pensive[symbolic] + pip install sofic[symbolic] Building a parametric machine ============================== Pass a sympy symbol as the control parameter to the tent-map examples (or to -:meth:`~pensive.generators.mealy.MealyHMM.add_transition` directly): +:meth:`~sofic.generators.mealy.MealyHMM.add_transition` directly): .. ipython:: In [1]: import sympy as sp - In [2]: from pensive.examples import tent_map_misiurewicz_forward + In [2]: from sofic.examples import tent_map_misiurewicz_forward In [3]: a = sp.symbols("a", positive=True) @@ -41,65 +41,64 @@ for the tent map at a Misiurewicz parameter via the bidirectional ε-machine :cite:`James2013`. With symbolic probabilities the same pipeline returns a sympy expression that evaluates to the supplement's numerical value: +The pipeline is: the published Fig. 6 (right) non-unifilar HMM is minimized to +its ε-machine (Fig. 7) via :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm`, +and the bidirectional machine (Fig. 8) is built with a symbolic control +parameter ``a``. + .. ipython:: In [1]: import sympy as sp - In [2]: from pensive.examples import ( - ...: tent_map_misiurewicz_a, - ...: tent_map_misiurewicz_bidirectional, - ...: tent_map_misiurewicz_hmm, - ...: tent_map_misiurewicz_information_expected, - ...: ) - ...: from pensive.generators.epsilon_machine import EpsilonMachine + In [2]: from sofic.examples import tent_map_misiurewicz_a, tent_map_misiurewicz_bidirectional, tent_map_misiurewicz_hmm, tent_map_misiurewicz_information_expected - In [3]: a = sp.symbols("a", positive=True) + In [3]: from sofic.generators.epsilon_machine import EpsilonMachine - # Published Fig. 6 (right) non-unifilar HMM → ε-machine (Fig. 7) - In [4]: eps = EpsilonMachine.from_hmm(tent_map_misiurewicz_hmm()) + In [4]: a = sp.symbols("a", positive=True) - # Bidirectional machine (Fig. 8) with symbolic ``a`` - In [5]: bidir = tent_map_misiurewicz_bidirectional(a) + In [5]: eps = EpsilonMachine.from_hmm(tent_map_misiurewicz_hmm()) - In [6]: r = bidir.ephemeral_information() + In [6]: bidir = tent_map_misiurewicz_bidirectional(a) - In [7]: a_num = tent_map_misiurewicz_a() + In [7]: r = bidir.ephemeral_information() - @doctest float - In [8]: float(r.subs(a, a_num)) - Out[8]: 0.648257836793515 + In [8]: a_num = tent_map_misiurewicz_a() @doctest float - In [9]: tent_map_misiurewicz_information_expected(a_num)["ephemeral_mu"] + In [9]: float(r.subs(a, a_num)) Out[9]: 0.648257836793515 + @doctest float + In [10]: tent_map_misiurewicz_information_expected(a_num)["ephemeral_mu"] + Out[10]: 0.648257836793515 + The supplement's rational-in-``a`` formula ``r_μ = (1/4)(3 - 2/(a+1) - 4/(a+2) + 9/(2a+3))`` is recovered by -:func:`~pensive.examples.epsilon_machines.tent_map_misiurewicz_information_expected` +:func:`~sofic.examples.epsilon_machines.tent_map_misiurewicz_information_expected` and agrees with the machine-derived rate at the Misiurewicz root of ``a``. -:func:`~pensive.examples.epsilon_machines.tent_map_misiurewicz_hmm` is the +:func:`~sofic.examples.epsilon_machines.tent_map_misiurewicz_hmm` is the published 4-state Fig.~6 (right) topology (non-unifilar at ``A`` and ``D``). Symbolic machines also draw: Graphviz / TikZ labels use -:func:`~pensive.viz._format.format_prob_label` so expressions like ``a/(a+1)`` +:func:`~sofic.viz._format.format_prob_label` so expressions like ``a/(a+1)`` appear on edges instead of raising on ``float(...)``. The machine carries the Misiurewicz minimal polynomial ``a**3 - 2*a - 2`` as -:class:`~pensive.generators.prob.SymbolConstraints`; belief de-duplication and +:class:`~sofic.generators.prob.SymbolConstraints`; belief de-duplication and causal-state merging then compare symbolic probabilities *modulo* that relation (in the residue field ``Q[a]/(a**3 - 2*a - 2)``), so -:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_hmm` recovers the +:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm` recovers the 4-state Fig.~7 machine. A model without constraints keeps the two states that coincide only at the Misiurewicz root distinct. Probability helpers =================== -Low-level coercion and comparison live in :mod:`pensive.generators.prob`: +Low-level coercion and comparison live in :mod:`sofic.generators.prob`: -* :func:`~pensive.generators.prob.as_prob` — store floats or exact sympy Expr -* :func:`~pensive.generators.prob.is_symbolic` / :func:`~pensive.generators.prob.has_symbolic` -* :func:`~pensive.generators.prob.simplify_prob`, :func:`~pensive.generators.prob.probs_equal` -* :class:`~pensive.generators.prob.SymbolConstraints` — compare probabilities modulo algebraic side-relations +* :func:`~sofic.generators.prob.as_prob` — store floats or exact sympy Expr +* :func:`~sofic.generators.prob.is_symbolic` / :func:`~sofic.generators.prob.has_symbolic` +* :func:`~sofic.generators.prob.simplify_prob`, :func:`~sofic.generators.prob.probs_equal` +* :class:`~sofic.generators.prob.SymbolConstraints` — compare probabilities modulo algebraic side-relations See also :doc:`information_anatomy` and :doc:`mixed_state_presentation`. diff --git a/docs/generators/synchronization.rst b/docs/generators/synchronization.rst index 9862fd5..95cd534 100644 --- a/docs/generators/synchronization.rst +++ b/docs/generators/synchronization.rst @@ -1,5 +1,5 @@ .. synchronization.rst -.. py:module:: pensive.generators.synchronization +.. py:module:: sofic.generators.synchronization *************** Synchronization @@ -10,7 +10,7 @@ cryptic order :math:`k_\chi` from ε-machine graph structure alone. .. ipython:: - In [1]: from pensive.examples import golden_mean + In [1]: from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -25,7 +25,7 @@ cryptic order :math:`k_\chi` from ε-machine graph structure alone. API === -.. autofunction:: pensive.generators.synchronization.markov_order_from_graph -.. autofunction:: pensive.generators.synchronization.cryptic_order_from_graph -.. autofunction:: pensive.generators.synchronization.is_exactly_synchronizable -.. autofunction:: pensive.generators.synchronization.graph_from_epsilon_machine +.. autofunction:: sofic.generators.synchronization.markov_order_from_graph +.. autofunction:: sofic.generators.synchronization.cryptic_order_from_graph +.. autofunction:: sofic.generators.synchronization.is_exactly_synchronizable +.. autofunction:: sofic.generators.synchronization.graph_from_epsilon_machine diff --git a/docs/generators/topological_epsilon_enumeration.rst b/docs/generators/topological_epsilon_enumeration.rst index 5a5a15b..97c102c 100644 --- a/docs/generators/topological_epsilon_enumeration.rst +++ b/docs/generators/topological_epsilon_enumeration.rst @@ -1,5 +1,5 @@ .. topological_epsilon_enumeration.rst -.. py:module:: pensive.generators.topological_epsilon_enumeration +.. py:module:: sofic.generators.topological_epsilon_enumeration **************************************** Topological ε-Machine Enumeration diff --git a/docs/index.rst b/docs/index.rst index b492a4e..1fbd238 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,11 @@ -.. pensive documentation master file -.. py:module:: pensive +.. sofic documentation master file +.. py:module:: sofic ****************************************************************** -:mod:`pensive`: stochastic symbol generators +:mod:`sofic`: stochastic symbol generators ****************************************************************** -:mod:`pensive` is a Python package for hidden Markov models, symbolic dynamics, +:mod:`sofic` is a Python package for hidden Markov models, symbolic dynamics, finite state machines, and other stochastic symbol generators. Introduction @@ -13,7 +13,7 @@ Introduction Many natural and engineered processes produce sequences of symbols whose statistics are governed by latent structure: hidden states, transition rules, -or algebraic constraints on allowed paths. ``pensive`` collects algorithms and +or algebraic constraints on allowed paths. ``sofic`` collects algorithms and data structures for representing, simulating, and analyzing such generators in a consistent, composable Python API built on NumPy, SciPy, and NetworkX. @@ -23,19 +23,19 @@ large library of structural and information-theoretic measures. The model families group into three presentations of a process and a set of tools for inferring them from data: -* **Stochastic generators** (:mod:`pensive.generators`) — Markov chains, hidden +* **Stochastic generators** (:mod:`sofic.generators`) — Markov chains, hidden Markov models (Mealy and Moore presentations), ε-machines and their bidirectional/mixed-state relatives, probabilistic finite automata, stack HMMs, and signed quasiprobabilistic generators. These assign probabilities to sequences and expose the full computational-mechanics toolkit. -* **Finite automata** (:mod:`pensive.automata`) — DFAs, NFAs, transducers, +* **Finite automata** (:mod:`sofic.automata`) — DFAs, NFAs, transducers, regular-language algebra, residual/átomaton canonical forms, Büchi automata, and visibly pushdown / nested-word automata. These recognize or transform languages. -* **Symbolic shifts** (:mod:`pensive.shifts`) — shifts of finite type, sofic +* **Symbolic shifts** (:mod:`sofic.shifts`) — shifts of finite type, sofic shifts, topological Markov chains, Dyck and sofic-Dyck shifts, and their covers. These describe the *support* (set of allowed sequences) of a process. -* **Inference** (:mod:`pensive.inference`) — Bayesian structural inference for +* **Inference** (:mod:`sofic.inference`) — Bayesian structural inference for Markov chains, ε-machines, and stack HMMs via exact conjugate Dirichlet evidences, with optional PyMC backends. diff --git a/docs/inference/epsilon.rst b/docs/inference/epsilon.rst index d6a57d9..6570a97 100644 --- a/docs/inference/epsilon.rst +++ b/docs/inference/epsilon.rst @@ -1,5 +1,5 @@ .. epsilon.rst -.. py:module:: pensive.inference.bayesian.epsilon +.. py:module:: sofic.inference.bayesian.epsilon ******************** ε-Machine Inference @@ -8,15 +8,15 @@ Bayesian inference over a *fixed unifilar topology* and its unknown start state :cite:`Strelioff2014`. A :class:`DirichletDistributionEM` holds a product-of-Dirichlets prior/posterior over the edge probabilities of a candidate -:class:`~pensive.generators.mealy.MealyHMM`; an +:class:`~sofic.generators.mealy.MealyHMM`; an :class:`EpsilonMachinePosterior` (aliased ``InferEM``) marginalizes over the unknown start state as well, giving the model evidence and posterior-mean machine. .. code-block:: python - from pensive.examples import golden_mean - from pensive.inference.bayesian import EpsilonMachinePosterior + from sofic.examples import golden_mean + from sofic.inference.bayesian import EpsilonMachinePosterior topology = golden_mean(0.5) # a unifilar candidate post = EpsilonMachinePosterior(topology, data=data) @@ -30,11 +30,11 @@ Model comparison and diversity posterior probability and can sample the posterior predictive process. The posterior **process diversity** quantifies how spread out the posterior is over distinct processes (as opposed to over parameterizations); see -:class:`~pensive.inference.bayesian.diversity.PosteriorDiversityResult`. +:class:`~sofic.inference.bayesian.diversity.PosteriorDiversityResult`. .. code-block:: python - from pensive.inference.bayesian import ModelComparisonEM + from sofic.inference.bayesian import ModelComparisonEM cmp = ModelComparisonEM(machines=[m0, m1, m2], data=data) cmp.model_probabilities() @@ -52,22 +52,22 @@ Posterior over a fixed topology .. autoclass:: DirichletDistributionEM :members: set_edge_alpha, get_edge_alpha, get_node_alpha, log_evidence_start_node, mean_edge_probability, posterior_mean_machine, generate_sample -.. autoclass:: pensive.inference.bayesian.counts.PathCountEM +.. autoclass:: sofic.inference.bayesian.counts.PathCountEM :members: get_edges, get_nodes, get_edge_count, get_node_count, get_possible_start_nodes Model comparison ---------------- -.. autoclass:: pensive.inference.bayesian.comparison.ModelComparisonEM +.. autoclass:: sofic.inference.bayesian.comparison.ModelComparisonEM :members: log_evidence, model_probabilities, generate_sample, machine_diversity, process_diversity Process diversity ----------------- -.. autoclass:: pensive.inference.bayesian.diversity.PosteriorDiversityResult +.. autoclass:: sofic.inference.bayesian.diversity.PosteriorDiversityResult -.. autofunction:: pensive.inference.bayesian.diversity.posterior_process_diversity -.. autofunction:: pensive.inference.bayesian.diversity.machine_diversity -.. autofunction:: pensive.inference.bayesian.diversity.process_identification_word_length -.. autofunction:: pensive.inference.bayesian.diversity.posterior_mean_word_distribution -.. autofunction:: pensive.inference.bayesian.diversity.word_distribution_to_pmf +.. autofunction:: sofic.inference.bayesian.diversity.posterior_process_diversity +.. autofunction:: sofic.inference.bayesian.diversity.machine_diversity +.. autofunction:: sofic.inference.bayesian.diversity.process_identification_word_length +.. autofunction:: sofic.inference.bayesian.diversity.posterior_mean_word_distribution +.. autofunction:: sofic.inference.bayesian.diversity.word_distribution_to_pmf diff --git a/docs/inference/inference.rst b/docs/inference/inference.rst index cccced1..4109d58 100644 --- a/docs/inference/inference.rst +++ b/docs/inference/inference.rst @@ -4,8 +4,8 @@ Inference ********* -The :mod:`pensive.inference` package infers stochastic generators from data. -Its Bayesian core (:mod:`pensive.inference.bayesian`) implements exact +The :mod:`sofic.inference` package infers stochastic generators from data. +Its Bayesian core (:mod:`sofic.inference.bayesian`) implements exact conjugate **Dirichlet–multinomial** structural inference for Markov chains, ε-machines, and stack HMMs, following the Bayesian structural inference programme of Strelioff & Crutchfield :cite:`Strelioff2014`. @@ -15,12 +15,12 @@ of Dirichlet distributions, one per transition row, so the marginal likelihood (model evidence) is available in closed form. Model comparison then ranks a set of candidate topologies — Markov orders, unifilar ε-machines, or stack topologies — by their posterior probabilities, with no sampling required. -Optional `PyMC `_ backends (``pip install pensive[bayes]``) +Optional `PyMC `_ backends (``pip install sofic[bayes]``) expose the same models for full posterior sampling. The historical names ``InferMC`` and ``InferEM`` are retained as aliases for -:class:`~pensive.inference.bayesian.markov.MarkovChainPosterior` and -:class:`~pensive.inference.bayesian.epsilon.EpsilonMachinePosterior`. +:class:`~sofic.inference.bayesian.markov.MarkovChainPosterior` and +:class:`~sofic.inference.bayesian.epsilon.EpsilonMachinePosterior`. .. note:: diff --git a/docs/inference/markov.rst b/docs/inference/markov.rst index 0ce8418..a897cf4 100644 --- a/docs/inference/markov.rst +++ b/docs/inference/markov.rst @@ -1,5 +1,5 @@ .. markov.rst -.. py:module:: pensive.inference.bayesian.markov +.. py:module:: sofic.inference.bayesian.markov ********************** Markov-Chain Inference @@ -11,11 +11,11 @@ places a :class:`DirichletPriorMC` over the transition rows of an order-``k`` chain, accumulates counts from the data, and exposes the conjugate posterior in closed form. It yields point estimates (posterior-mean or maximum-likelihood transition probabilities), the model evidence, and a -:class:`~pensive.generators.mealy.MealyHMM` realization of the fitted chain. +:class:`~sofic.generators.mealy.MealyHMM` realization of the fitted chain. .. code-block:: python - from pensive.inference.bayesian import MarkovChainPosterior + from sofic.inference.bayesian import MarkovChainPosterior post = MarkovChainPosterior(alphabet=("0", "1"), data=data, order=2) post.log_evidence() # marginal likelihood of the order-2 model @@ -30,7 +30,7 @@ orders. .. code-block:: python - from pensive.inference.bayesian import ModelComparisonMC + from sofic.inference.bayesian import ModelComparisonMC cmp = ModelComparisonMC(alphabet=("0", "1"), data=data, min_order=0, max_order=4) cmp.model_probabilities() # {order: P(order | data)} @@ -45,10 +45,10 @@ API .. autoclass:: DirichletPriorMC :members: create_random_prior, set_alpha, get_alpha -.. autoclass:: pensive.inference.bayesian.comparison.ModelComparisonMC +.. autoclass:: sofic.inference.bayesian.comparison.ModelComparisonMC :members: log_evidence, model_probabilities, most_probable_model -.. autoclass:: pensive.inference.bayesian.comparison.ModelComparisonMC2 +.. autoclass:: sofic.inference.bayesian.comparison.ModelComparisonMC2 -.. autoclass:: pensive.inference.bayesian.counts.WordCountsMC +.. autoclass:: sofic.inference.bayesian.counts.WordCountsMC :members: add_counts_from, get_word_count, set_word_count, clear_word_counts diff --git a/docs/inference/pymc.rst b/docs/inference/pymc.rst index 4b1a60d..00053ba 100644 --- a/docs/inference/pymc.rst +++ b/docs/inference/pymc.rst @@ -1,5 +1,5 @@ .. pymc.rst -.. py:module:: pensive.inference.bayesian.pymc_backend +.. py:module:: sofic.inference.bayesian.pymc_backend ************* PyMC Backends @@ -10,11 +10,11 @@ evidences. When full posterior samples are needed — for credible intervals, posterior-predictive checks, or hierarchical extensions — the posteriors expose `PyMC `_ models through ``as_pymc_model()`` and the builders below. These require the optional backend -(``pip install pensive[bayes]``, which installs PyMC and ArviZ). +(``pip install sofic[bayes]``, which installs PyMC and ArviZ). .. code-block:: python - from pensive.inference.bayesian import MarkovChainPosterior + from sofic.inference.bayesian import MarkovChainPosterior post = MarkovChainPosterior(alphabet=("0", "1"), data=data, order=1) model = post.as_pymc_model() # a pymc.Model diff --git a/docs/inference/stack_hmm.rst b/docs/inference/stack_hmm.rst index 7d9d55a..a7691f8 100644 --- a/docs/inference/stack_hmm.rst +++ b/docs/inference/stack_hmm.rst @@ -1,11 +1,11 @@ .. stack_hmm.rst -.. py:module:: pensive.inference.bayesian.stack_hmm +.. py:module:: sofic.inference.bayesian.stack_hmm ******************** Stack-HMM Inference ******************** -Bayesian inference for :class:`~pensive.generators.stack_hmm.HiddenMarkovStackModel` +Bayesian inference for :class:`~sofic.generators.stack_hmm.HiddenMarkovStackModel` topologies. As with the finite-state case, the posterior over the transition probabilities *enabled by each stack configuration* factorizes into Dirichlet rows, so evidence and posterior-mean models are closed-form @@ -14,7 +14,7 @@ generally infinite, counting uses a bounded ``max_stack_depth``. .. code-block:: python - from pensive.inference.bayesian import StackHMMPosterior + from sofic.inference.bayesian import StackHMMPosterior post = StackHMMPosterior(topology, data=data, max_stack_depth=8) post.log_evidence() @@ -24,7 +24,7 @@ Model comparison ================ :class:`ModelComparisonStackHMM` ranks enumerated stack topologies (for example -those produced by :func:`~pensive.shifts.dyck_enumeration.iter_sofic_dyck_topologies`) +those produced by :func:`~sofic.shifts.dyck_enumeration.iter_sofic_dyck_topologies`) by conjugate marginal likelihood. API diff --git a/docs/install.rst.txt b/docs/install.rst.txt index 9460803..c0ee622 100644 --- a/docs/install.rst.txt +++ b/docs/install.rst.txt @@ -5,7 +5,7 @@ The easiest way to install is: .. code-block:: bash - pip install pensive + pip install sofic This pulls in ``dit`` (and thus the information-theoretic measures) as a core dependency. Optional extras add visualization, Bayesian backends, and the @@ -13,19 +13,19 @@ development toolchain: .. code-block:: bash - pip install "pensive[viz]" # Graphviz diagrams - pip install "pensive[bayes]" # PyMC/ArviZ backends for Bayesian inference - pip install "pensive[dev]" # tests, linting, docs, and all extras + pip install "sofic[viz]" # Graphviz diagrams + pip install "sofic[bayes]" # PyMC/ArviZ backends for Bayesian inference + pip install "sofic[dev]" # tests, linting, docs, and all extras For development, we recommend `uv `_: .. code-block:: bash - git clone https://github.com/dit/pensive.git - cd pensive + git clone https://github.com/dit/sofic.git + cd sofic uv sync --extra dev -This installs ``pensive`` in editable mode with all development dependencies. +This installs ``sofic`` in editable mode with all development dependencies. **Testing** diff --git a/docs/notation.rst b/docs/notation.rst index 3582b97..8b2b058 100644 --- a/docs/notation.rst +++ b/docs/notation.rst @@ -4,20 +4,20 @@ Notation ******** -``pensive`` is a scientific tool, and much of this documentation uses +``sofic`` is a scientific tool, and much of this documentation uses mathematical expressions for computational mechanics and information theory. Graph-backed models =================== -Every model in ``pensive`` is a :class:`~pensive.core.StateMachine`: a labeled +Every model in ``sofic`` is a :class:`~sofic.core.StateMachine`: a labeled directed multigraph with typed node and edge attributes. * **States** are hashable labels (strings, integers, tuples, etc.). * **Transitions** are directed edges with attribute dictionaries. * **Alphabets** depend on model type: input symbols, emissions, or outputs. -Edge attribute keys (from :mod:`pensive.core`) include: +Edge attribute keys (from :mod:`sofic.core`) include: * ``ATTR_SYMBOL`` — input symbol on automaton edges * ``ATTR_EMISSION`` — emitted symbol on generator edges @@ -49,7 +49,7 @@ Information anatomy satisfies :math:`h_\mu = b_\mu + r_\mu` Generative complexity and common information ============================================ -Alongside the *predictive* complexity :math:`C_\mu = \H{S^+}`, ``pensive`` +Alongside the *predictive* complexity :math:`C_\mu = \H{S^+}`, ``sofic`` computes *generative* complexities: the minimal state entropy of a (possibly non-unifilar) generator of the process. Each corresponds to a common information between the forward and reverse causal states :math:`S^+` and @@ -67,22 +67,22 @@ See :doc:`generators/generative_models`. Directional information flow ============================ -For bivariate generators emitting paired symbols :math:`(X, Y)`, ``pensive`` +For bivariate generators emitting paired symbols :math:`(X, Y)`, ``sofic`` computes transfer entropy :cite:`Schreiber2000`, directed information, and the intrinsic/shared/synergistic decomposition of information flow. See :doc:`generators/directional_flow`. Information-theoretic quantities require ``dit`` :cite:`James2018`, which is a -core dependency of ``pensive``. +core dependency of ``sofic``. Unifilarity =========== The word **unifilar** appears in two contexts: -* :class:`~pensive.automata.unifilar.UnifilarAutomaton` — right-resolving on +* :class:`~sofic.automata.unifilar.UnifilarAutomaton` — right-resolving on **input symbols** (formal language theory). -* :class:`~pensive.generators.epsilon_machine.EpsilonMachine` — row-unifilar on +* :class:`~sofic.generators.epsilon_machine.EpsilonMachine` — row-unifilar on **emissions** (computational mechanics / causal states). These are distinct predicates; an ε-machine is unifilar in the generator sense. diff --git a/docs/quickstart.rst.txt b/docs/quickstart.rst.txt index 0f84c8c..9b3cf8f 100644 --- a/docs/quickstart.rst.txt +++ b/docs/quickstart.rst.txt @@ -17,7 +17,7 @@ read off computational-mechanics quantities :cite:`Crutchfield1994,Ellison2009`: .. ipython:: - In [1]: import pensive; from pensive.examples import golden_mean + In [1]: import sofic; from sofic.examples import golden_mean In [2]: eps = golden_mean(0.5) @@ -45,7 +45,7 @@ minimize and extract a regular expression: .. ipython:: - In [1]: from pensive import DFA + In [1]: from sofic import DFA In [2]: dfa = DFA(input_alphabet=frozenset({"a", "b"}), initial_states=frozenset({"even"}), accepting_states=frozenset({"even"})) @@ -69,7 +69,7 @@ topological entropy (the log of the golden ratio) :cite:`LindMarcus1995`: .. ipython:: - In [1]: import numpy as np; from pensive import TopologicalMarkovChain + In [1]: import numpy as np; from sofic import TopologicalMarkovChain In [2]: tmc = TopologicalMarkovChain.from_adjacency(np.array([[1, 1], [1, 0]], dtype=float), symbol_alphabet=frozenset({0, 1})) @@ -90,7 +90,7 @@ the excess entropy and crypticity: .. ipython:: - In [1]: from pensive.examples import golden_mean_bidirectional + In [1]: from sofic.examples import golden_mean_bidirectional In [2]: bidir = golden_mean_bidirectional(0.5) @@ -114,7 +114,7 @@ Bound and ephemeral information rates from a bidirectional ε-machine .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_bidirectional; bidir = tent_map_misiurewicz_bidirectional() + In [1]: from sofic.examples import tent_map_misiurewicz_bidirectional; bidir = tent_map_misiurewicz_bidirectional() @doctest float In [2]: bidir.entropy_rate() @@ -136,7 +136,7 @@ transitions: .. ipython:: - In [1]: from pensive.examples import tent_map_misiurewicz_hmm; edge = tent_map_misiurewicz_hmm().to_edge_machine() + In [1]: from sofic.examples import tent_map_misiurewicz_hmm; edge = tent_map_misiurewicz_hmm().to_edge_machine() @doctest In [2]: len(list(edge.states())) @@ -153,11 +153,11 @@ Converting between presentations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The presentations are linked by a web of conversions: HMMs minimize to -ε-machines (:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_hmm`), +ε-machines (:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm`), probabilistic models drop their weights to become automata or shifts -(:meth:`~pensive.generators.base.HiddenMarkovModel.to_sofic_shift`, -:meth:`~pensive.generators.base.HiddenMarkovModel.to_support_dfa`), NFAs -determinize to DFAs (:meth:`~pensive.automata.nfa.NFA.determinize`), and shifts +(:meth:`~sofic.generators.base.HiddenMarkovModel.to_sofic_shift`, +:meth:`~sofic.generators.base.HiddenMarkovModel.to_support_dfa`), NFAs +determinize to DFAs (:meth:`~sofic.automata.nfa.NFA.determinize`), and shifts yield their maximum-entropy measure -(:meth:`~pensive.shifts.tmc.TopologicalMarkovChain.parry_measure`). Every model +(:meth:`~sofic.shifts.tmc.TopologicalMarkovChain.parry_measure`). Every model round-trips through YAML via ``to_yaml`` / ``from_yaml``. diff --git a/docs/shifts/covers.rst b/docs/shifts/covers.rst index cd5b078..5ce62bd 100644 --- a/docs/shifts/covers.rst +++ b/docs/shifts/covers.rst @@ -1,5 +1,5 @@ .. covers.rst -.. py:module:: pensive.shifts.covers +.. py:module:: sofic.shifts.covers ****** Covers @@ -14,7 +14,7 @@ Fischer and Krieger covers convert a Sofic shift into unifilar presentations .. ipython:: - In [1]: from pensive.examples import golden_mean_shift_parry; parry = golden_mean_shift_parry() + In [1]: from sofic.examples import golden_mean_shift_parry; parry = golden_mean_shift_parry() In [2]: parry.validate() @@ -28,5 +28,5 @@ API .. autoclass:: LeftKriegerCover .. autoclass:: RightKriegerCover -.. autofunction:: pensive.shifts.cover_construction.left_fischer_from_sofic -.. autofunction:: pensive.shifts.cover_construction.right_fischer_from_sofic +.. autofunction:: sofic.shifts.cover_construction.left_fischer_from_sofic +.. autofunction:: sofic.shifts.cover_construction.right_fischer_from_sofic diff --git a/docs/shifts/dyck_enumeration.rst b/docs/shifts/dyck_enumeration.rst index 6a1ac07..9e50554 100644 --- a/docs/shifts/dyck_enumeration.rst +++ b/docs/shifts/dyck_enumeration.rst @@ -1,11 +1,11 @@ .. dyck_enumeration.rst -.. py:module:: pensive.shifts.dyck_enumeration +.. py:module:: sofic.shifts.dyck_enumeration **************** Dyck Enumeration **************** -Small :class:`~pensive.shifts.sofic_dyck.SoficDyckShift` topologies can be +Small :class:`~sofic.shifts.sofic_dyck.SoficDyckShift` topologies can be enumerated exhaustively via a canonical string encoding, analogous to the topological ε-machine enumeration of :doc:`../generators/topological_epsilon_enumeration` and finitary-process enumeration :cite:`Johnson2010,BealBlockeletDima2015`. @@ -17,7 +17,7 @@ unique canonical string, so the iterators emit each shift exactly once. .. code-block:: python - from pensive.shifts import iter_sofic_dyck_topologies, count_dyck_graph_strings + from sofic.shifts import iter_sofic_dyck_topologies, count_dyck_graph_strings n = count_dyck_graph_strings(n=1, call_symbols=("a",), return_symbols=("A",)) diff --git a/docs/shifts/markov_dyck_shift.rst b/docs/shifts/markov_dyck_shift.rst index 8bdfba0..f2e5b7f 100644 --- a/docs/shifts/markov_dyck_shift.rst +++ b/docs/shifts/markov_dyck_shift.rst @@ -1,12 +1,12 @@ .. markov_dyck_shift.rst -.. py:module:: pensive.shifts.markov_dyck +.. py:module:: sofic.shifts.markov_dyck ***************** Markov-Dyck Shift ***************** A :class:`MarkovDyckShift` is the Markov-Dyck specialization of -:class:`~pensive.shifts.sofic_dyck.SoficDyckShift`. It can be built from a +:class:`~sofic.shifts.sofic_dyck.SoficDyckShift`. It can be built from a matrix or from a directed graph, with graph constructors for both edge-type and vertex-type Markov-Dyck shifts :cite:`Matsumoto2014`. diff --git a/docs/shifts/shift_of_finite_type.rst b/docs/shifts/shift_of_finite_type.rst index 8a0eec0..571679e 100644 --- a/docs/shifts/shift_of_finite_type.rst +++ b/docs/shifts/shift_of_finite_type.rst @@ -1,5 +1,5 @@ .. shift_of_finite_type.rst -.. py:module:: pensive.shifts.sft +.. py:module:: sofic.shifts.sft ********************* Shift of Finite Type @@ -10,7 +10,7 @@ alphabet, following standard symbolic-dynamics terminology :cite:`LindMarcus1995 .. ipython:: - In [1]: from pensive.shifts import ShiftOfFiniteType + In [1]: from sofic.shifts import ShiftOfFiniteType In [2]: sft = ShiftOfFiniteType.from_forbidden_words( ...: forbidden={(1, 1)}, @@ -25,4 +25,4 @@ API .. autoclass:: ShiftOfFiniteType :members: from_forbidden_words, from_presentation -.. autofunction:: pensive.shifts.sft_construction.from_forbidden_words +.. autofunction:: sofic.shifts.sft_construction.from_forbidden_words diff --git a/docs/shifts/shifts.rst b/docs/shifts/shifts.rst index 6f204a0..32cc99b 100644 --- a/docs/shifts/shifts.rst +++ b/docs/shifts/shifts.rst @@ -4,7 +4,7 @@ Shifts ****** -The :mod:`pensive.shifts` package provides symbolic dynamics: shifts of finite +The :mod:`sofic.shifts` package provides symbolic dynamics: shifts of finite type, Sofic shifts, Dyck shifts, topological Markov chains, and covers :cite:`LindMarcus1995`. diff --git a/docs/shifts/sofic_dyck_shift.rst b/docs/shifts/sofic_dyck_shift.rst index a90eaa0..a6bd8d5 100644 --- a/docs/shifts/sofic_dyck_shift.rst +++ b/docs/shifts/sofic_dyck_shift.rst @@ -1,5 +1,5 @@ .. sofic_dyck_shift.rst -.. py:module:: pensive.shifts.sofic_dyck +.. py:module:: sofic.shifts.sofic_dyck **************** Sofic-Dyck Shift @@ -23,6 +23,6 @@ API .. autofunction:: transition_ref -.. autofunction:: pensive.shifts.dyck_algorithms.is_admissible_word +.. autofunction:: sofic.shifts.dyck_algorithms.is_admissible_word -.. autofunction:: pensive.shifts.dyck_algorithms.admissible_words +.. autofunction:: sofic.shifts.dyck_algorithms.admissible_words diff --git a/docs/shifts/sofic_shift.rst b/docs/shifts/sofic_shift.rst index 16a33df..34964c1 100644 --- a/docs/shifts/sofic_shift.rst +++ b/docs/shifts/sofic_shift.rst @@ -1,5 +1,5 @@ .. sofic_shift.rst -.. py:module:: pensive.shifts.sofic +.. py:module:: sofic.shifts.sofic *********** Sofic Shift @@ -17,4 +17,4 @@ API .. autoclass:: SoficShift :members: topological_entropy, parry_measure, topological_anatomy -.. autofunction:: pensive.shifts.tmc_construction.topological_entropy +.. autofunction:: sofic.shifts.tmc_construction.topological_entropy diff --git a/docs/shifts/topological_anatomy.rst b/docs/shifts/topological_anatomy.rst index db872b3..4d6359e 100644 --- a/docs/shifts/topological_anatomy.rst +++ b/docs/shifts/topological_anatomy.rst @@ -1,5 +1,5 @@ .. topological_anatomy.rst -.. py:module:: pensive.shifts.topological_anatomy +.. py:module:: sofic.shifts.topological_anatomy ******************************* Topological Information Anatomy @@ -30,7 +30,7 @@ anatomy is closed-form (requires ``dit``). Pipeline: put the presentation in right-resolving (unifilar) form (Fischer cover :cite:`Fischer1975` when needed), build the Parry chain from the Perron data, -minimize to the causal :class:`~pensive.generators.epsilon_machine.EpsilonMachine`, +minimize to the causal :class:`~sofic.generators.epsilon_machine.EpsilonMachine`, and read its bidirectional anatomy. Two shifts with the *same* :math:`h_\mathrm{top} = \log_2\varphi` can have @@ -40,7 +40,7 @@ entropy across both parts, while the sofic even shift is purely bound .. ipython:: - In [1]: from pensive.shifts import SoficShift + In [1]: from sofic.shifts import SoficShift In [2]: gm = SoficShift(symbol_alphabet=frozenset({0, 1})) @@ -67,16 +67,16 @@ entropy across both parts, while the sofic even shift is purely bound Out[10]: 0.5527864045001022 The parts add up to :math:`h_\mathrm{top}`, which equals -:meth:`~pensive.shifts.sofic.SoficShift.topological_entropy` divided by +:meth:`~sofic.shifts.sofic.SoficShift.topological_entropy` divided by :math:`\ln 2` on a right-resolving presentation. API === -.. autoclass:: pensive.shifts.sofic.SoficShift +.. autoclass:: sofic.shifts.sofic.SoficShift :members: parry_measure, topological_anatomy :noindex: -.. autofunction:: pensive.shifts.topological_anatomy.topological_anatomy +.. autofunction:: sofic.shifts.topological_anatomy.topological_anatomy -.. autofunction:: pensive.shifts.topological_anatomy.parry_measure_sofic +.. autofunction:: sofic.shifts.topological_anatomy.parry_measure_sofic diff --git a/docs/shifts/topological_markov_chain.rst b/docs/shifts/topological_markov_chain.rst index b19d035..bba680d 100644 --- a/docs/shifts/topological_markov_chain.rst +++ b/docs/shifts/topological_markov_chain.rst @@ -1,5 +1,5 @@ .. topological_markov_chain.rst -.. py:module:: pensive.shifts.tmc +.. py:module:: sofic.shifts.tmc ************************ Topological Markov Chain @@ -11,7 +11,7 @@ adjacency matrix. Its Parry measure is the maximum-entropy stochastic generator .. ipython:: - In [1]: import numpy as np; from pensive.shifts import TopologicalMarkovChain; adj = np.array([[1, 1], [1, 0]], dtype=int); tmc = TopologicalMarkovChain.from_adjacency(adj, symbol_alphabet=frozenset({0, 1})) + In [1]: import numpy as np; from sofic.shifts import TopologicalMarkovChain; adj = np.array([[1, 1], [1, 0]], dtype=int); tmc = TopologicalMarkovChain.from_adjacency(adj, symbol_alphabet=frozenset({0, 1})) @doctest float In [2]: tmc.topological_entropy() @@ -23,4 +23,4 @@ API .. autoclass:: TopologicalMarkovChain :members: from_adjacency, parry_measure, to_sofic_shift, topological_entropy -.. autofunction:: pensive.shifts.parry_construction.parry_measure +.. autofunction:: sofic.shifts.parry_construction.parry_measure diff --git a/docs/viz.rst b/docs/viz.rst index dbb3138..cade977 100644 --- a/docs/viz.rst +++ b/docs/viz.rst @@ -1,5 +1,5 @@ .. viz.rst -.. py:module:: pensive.viz +.. py:module:: sofic.viz ************* Visualization @@ -12,18 +12,18 @@ Every model can be rendered as a diagram, either through `Graphviz Graphviz ======== -Call :meth:`~pensive.core.StateMachine.draw` or -:meth:`~pensive.core.StateMachine.to_graphviz` on any model when Graphviz is -installed (``pip install pensive[viz]`` plus the system ``graphviz`` binary): +Call :meth:`~sofic.core.StateMachine.draw` or +:meth:`~sofic.core.StateMachine.to_graphviz` on any model when Graphviz is +installed (``pip install sofic[viz]`` plus the system ``graphviz`` binary): .. code-block:: python - from pensive.examples import golden_mean + from sofic.examples import golden_mean eps = golden_mean(0.5) eps.draw() # write / view a rendered diagram dot = eps.to_graphviz() # a graphviz.Digraph for further styling - svg = pensive.viz.model_to_svg(eps) + svg = sofic.viz.model_to_svg(eps) Models implement Jupyter display via ``_repr_mimebundle_``, so simply evaluating a model in a notebook shows its diagram when Graphviz is available. @@ -35,7 +35,7 @@ For publication-quality figures, emit TikZ source or a compiled image: .. code-block:: python - from pensive.examples import golden_mean + from sofic.examples import golden_mean eps = golden_mean(0.5) tikz = eps.to_tikz() # a LaTeX/TikZ fragment diff --git a/pensive/testing/__init__.py b/pensive/testing/__init__.py deleted file mode 100644 index 0c10b56..0000000 --- a/pensive/testing/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Optional Hypothesis strategies for pensive models.""" - -from pensive.testing.strategies import dfas, epsilon_machines - -__all__ = [ - "dfas", - "epsilon_machines", -] diff --git a/pensive/viz/__init__.py b/pensive/viz/__init__.py deleted file mode 100644 index 274300b..0000000 --- a/pensive/viz/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Visualization for pensive models (Graphviz and TikZ).""" - -from pensive.viz.graphviz import draw, model_to_graphviz, model_to_svg -from pensive.viz.tikz import compile_tikz, draw_tikz, model_to_tikz, model_to_tikz_image - -__all__ = [ - "compile_tikz", - "draw", - "draw_tikz", - "model_to_graphviz", - "model_to_svg", - "model_to_tikz", - "model_to_tikz_image", -] diff --git a/pyproject.toml b/pyproject.toml index 4991a24..48c7610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" # ── Project metadata (PEP 621) ────────────────────────────────────────── [project] -name = "pensive" +name = "sofic" dynamic = ["version"] description = "Python package for hidden Markov models, symbolic dynamics, finite state machines, and stochastic symbol generators." readme = "README.rst" @@ -35,9 +35,9 @@ dependencies = [ ] [project.urls] -Homepage = "https://github.com/dit/pensive" -Repository = "https://github.com/dit/pensive" -Documentation = "https://pensive.readthedocs.io" +Homepage = "https://github.com/dit/sofic" +Repository = "https://github.com/dit/sofic" +Documentation = "https://sofic.readthedocs.io" [project.optional-dependencies] test = [ @@ -56,10 +56,11 @@ docs = [ "sphinxcontrib-bibtex", "ipython", "matplotlib", + "sofic[symbolic]", ] dev = [ - "pensive[test,viz]", - "pensive[docs]", + "sofic[test,viz]", + "sofic[docs]", "ruff", "ty", ] @@ -87,20 +88,20 @@ source = "vcs" [tool.hatch.build.targets.sdist] include = [ - "pensive/", + "sofic/", "tests/", "README.rst", "LICENSE.txt", ] [tool.hatch.build.targets.wheel] -packages = ["pensive"] +packages = ["sofic"] # ── Pytest ─────────────────────────────────────────────────────────────── [tool.pytest.ini_options] addopts = [ - "--cov=pensive", + "--cov=sofic", "--cov-report=term-missing", "--numprocesses=auto", "--durations=25", @@ -120,7 +121,7 @@ filterwarnings = [ [tool.coverage.run] branch = true -source = ["pensive"] +source = ["sofic"] omit = [ "*/tests/*", ] @@ -180,10 +181,10 @@ ignore = [ "test_*.py" = ["N817", "S101"] "__init__.py" = ["F401", "F403"] # PEP 695 type-parameter syntax requires Python 3.12+; keep TypeVar for 3.11. -"pensive/automata/_config_simulation.py" = ["UP047"] +"sofic/automata/_config_simulation.py" = ["UP047"] [tool.ruff.lint.isort] -known-first-party = ["pensive"] +known-first-party = ["sofic"] # ── ty (type checking) ────────────────────────────────────────────────── @@ -195,6 +196,8 @@ python-version = "3.13" allowed-unresolved-imports = [ "pymc", "pymc.**", + "sympy", + "sympy.**", ] [tool.ty.rules] @@ -215,7 +218,7 @@ no-matching-overload = "warn" possibly-unresolved-reference = "warn" [tool.ty.src] -include = ["pensive/", "tests/"] +include = ["sofic/", "tests/"] [[tool.ty.overrides]] include = ["tests/**"] diff --git a/pensive/__init__.py b/sofic/__init__.py similarity index 89% rename from pensive/__init__.py rename to sofic/__init__.py index e7b001b..71e48bb 100644 --- a/pensive/__init__.py +++ b/sofic/__init__.py @@ -1,16 +1,16 @@ """ -pensive is a Python package for hidden Markov models, symbolic dynamics, +sofic is a Python package for hidden Markov models, symbolic dynamics, finite state machines, and other stochastic symbol generators. """ try: from importlib.metadata import version - __version__ = version("pensive") + __version__ = version("sofic") except Exception: __version__ = "0.0.0" -from pensive.automata import ( +from sofic.automata import ( DFA, NFA, Atomaton, @@ -43,8 +43,8 @@ transduce_generator, trim, ) -from pensive.core import EPSILON, StateIndex, StateMachine, Transition, TransitionGraph -from pensive.generators import ( +from sofic.core import EPSILON, StateIndex, StateMachine, Transition, TransitionGraph +from sofic.generators import ( BidirectionalEpsilonMachine, BlockConvergenceDiagram, BlockConvergenceEstimates, @@ -74,9 +74,9 @@ minimal_generative_model, wyner_generative_model, ) -from pensive.operations import reverse -from pensive.serialization import from_yaml, model_from_yaml, model_to_yaml, read_yaml -from pensive.shifts import ( +from sofic.operations import reverse +from sofic.serialization import from_yaml, model_from_yaml, model_to_yaml, read_yaml +from sofic.shifts import ( LeftFischerCover, LeftKriegerCover, MarkovDyckShift, diff --git a/pensive/automata/__init__.py b/sofic/automata/__init__.py similarity index 76% rename from pensive/automata/__init__.py rename to sofic/automata/__init__.py index 035cc02..176b11f 100644 --- a/pensive/automata/__init__.py +++ b/sofic/automata/__init__.py @@ -1,7 +1,7 @@ """Finite automata and transducers.""" # ``atomaton`` is an intentional pun on atomic automaton. -from pensive.automata.algorithms import ( +from sofic.automata.algorithms import ( MinimizationAlgorithm, complete, determinize, @@ -9,11 +9,11 @@ minimize, trim, ) -from pensive.automata.atomaton import Atomaton, AtomicAutomaton, MaximizedPrimeAtomaton -from pensive.automata.base import LabeledAutomaton -from pensive.automata.buchi import BuchiAutomaton -from pensive.automata.dfa import DFA -from pensive.automata.icdfa import ( +from sofic.automata.atomaton import Atomaton, AtomicAutomaton, MaximizedPrimeAtomaton +from sofic.automata.base import LabeledAutomaton +from sofic.automata.buchi import BuchiAutomaton +from sofic.automata.dfa import DFA +from sofic.automata.icdfa import ( ICDFAString, count_flag_sequences, count_icdfa, @@ -30,7 +30,7 @@ string_from_flags, validate_icdfa_empty_string, ) -from pensive.automata.idfa import ( +from sofic.automata.idfa import ( MISSING_TRANSITION, count_accessible_idfa, first_idfa_string, @@ -40,11 +40,11 @@ unrank_idfa_string, validate_idfa_string, ) -from pensive.automata.languages import AutomatonLanguage, RegularLanguage -from pensive.automata.nfa import NFA -from pensive.automata.nwa import NestedWord, NestedWordAutomaton -from pensive.automata.observation import ObservationTable -from pensive.automata.papni import ( +from sofic.automata.languages import AutomatonLanguage, RegularLanguage +from sofic.automata.nfa import NFA +from sofic.automata.nwa import NestedWord, NestedWordAutomaton +from sofic.automata.observation import ObservationTable +from sofic.automata.papni import ( DyckAlphabet, is_well_matched, learn_sofic_dyck_shift_papni, @@ -52,10 +52,10 @@ papni_encode_samples, sofic_dyck_shift_from_papni_dfa, ) -from pensive.automata.regex import automaton_to_regex -from pensive.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton -from pensive.automata.rpni import learn_dfa_rpni -from pensive.automata.transducer_operations import ( +from sofic.automata.regex import automaton_to_regex +from sofic.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton +from sofic.automata.rpni import learn_dfa_rpni +from sofic.automata.transducer_operations import ( ERROR_STATE, ERROR_SYMBOL, cartesian_product_gg, @@ -64,9 +64,9 @@ compose_tt, transduce_generator, ) -from pensive.automata.transducers import MealyMachine, MooreMachine, Transducer -from pensive.automata.unifilar import UnifilarAutomaton -from pensive.automata.vpa import ( +from sofic.automata.transducers import MealyMachine, MooreMachine, Transducer +from sofic.automata.unifilar import UnifilarAutomaton +from sofic.automata.vpa import ( CallDrivenAutomaton, CanonicalVisiblyPushdownAutomaton, CompositeVisiblyPushdownAutomaton, diff --git a/pensive/automata/_config_simulation.py b/sofic/automata/_config_simulation.py similarity index 100% rename from pensive/automata/_config_simulation.py rename to sofic/automata/_config_simulation.py diff --git a/pensive/automata/algorithms.py b/sofic/automata/algorithms.py similarity index 97% rename from pensive/automata/algorithms.py rename to sofic/automata/algorithms.py index 800df11..7c00c3a 100644 --- a/pensive/automata/algorithms.py +++ b/sofic/automata/algorithms.py @@ -6,10 +6,10 @@ from collections.abc import Hashable, Sequence from typing import Any, Literal, TypeVar -from pensive.automata.base import LabeledAutomaton -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.automata.base import LabeledAutomaton +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.graph import ATTR_SYMBOL, EPSILON MinimizationAlgorithm = Literal["hopcroft", "moore", "brzozowski"] @@ -60,8 +60,8 @@ def complete(dfa: DFA, alphabet: frozenset[Any] | None = None) -> DFA: def reverse(aut: NFA | DFA) -> NFA: """Return an NFA recognizing the reversed language. - For a generic :class:`~pensive.base.StateMachine`, use - :func:`~pensive.operations.reverse` instead. + For a generic :class:`~sofic.base.StateMachine`, use + :func:`~sofic.operations.reverse` instead. """ return aut.reverse() diff --git a/pensive/automata/atomaton.py b/sofic/automata/atomaton.py similarity index 67% rename from pensive/automata/atomaton.py rename to sofic/automata/atomaton.py index cb3fcaa..6b5a0ab 100644 --- a/pensive/automata/atomaton.py +++ b/sofic/automata/atomaton.py @@ -4,13 +4,13 @@ from typing import TYPE_CHECKING, Any -from pensive.automata.dfa import DFA -from pensive.automata.languages.base import RegularLanguage -from pensive.automata.nfa import NFA +from sofic.automata.dfa import DFA +from sofic.automata.languages.base import RegularLanguage +from sofic.automata.nfa import NFA if TYPE_CHECKING: - from pensive.automata.observation import ObservationTable - from pensive.automata.rfsa import CanonicalRFSA + from sofic.automata.observation import ObservationTable + from sofic.automata.rfsa import CanonicalRFSA class AtomicAutomaton(NFA): @@ -26,12 +26,12 @@ class Atomaton(AtomicAutomaton): @classmethod def from_language(cls, language: RegularLanguage | NFA, **kwargs: Any) -> Atomaton: - from pensive.automata.canonical_extraction import atomaton_from_language + from sofic.automata.canonical_extraction import atomaton_from_language return atomaton_from_language(language) def to_minimal_dfa_via_double_reversal(self) -> DFA: - from pensive.automata.algorithms import minimize + from sofic.automata.algorithms import minimize return minimize(self, algorithm="brzozowski") @@ -41,18 +41,18 @@ class MaximizedPrimeAtomaton(AtomicAutomaton): @classmethod def from_language(cls, language: RegularLanguage | NFA, **kwargs: Any) -> MaximizedPrimeAtomaton: - from pensive.automata.canonical_extraction import maximized_prime_atomaton_from_language + from sofic.automata.canonical_extraction import maximized_prime_atomaton_from_language return maximized_prime_atomaton_from_language(language) @classmethod def from_observation_table(cls, table: ObservationTable, **kwargs: Any) -> MaximizedPrimeAtomaton: - from pensive.automata.canonical_extraction import observation_to_maximized_prime_atomaton + from sofic.automata.canonical_extraction import observation_to_maximized_prime_atomaton return observation_to_maximized_prime_atomaton(table) @classmethod def from_canonical_rfsa(cls, rfsa: CanonicalRFSA, **kwargs: Any) -> MaximizedPrimeAtomaton: - from pensive.automata.canonical_dual import dual_atomaton_from_rfsa + from sofic.automata.canonical_dual import dual_atomaton_from_rfsa return dual_atomaton_from_rfsa(rfsa) diff --git a/pensive/automata/base.py b/sofic/automata/base.py similarity index 85% rename from pensive/automata/base.py rename to sofic/automata/base.py index c2c7823..f107546 100644 --- a/pensive/automata/base.py +++ b/sofic/automata/base.py @@ -6,12 +6,12 @@ from collections.abc import Hashable, Iterator, Sequence from typing import TYPE_CHECKING, Any -from pensive.base import StateMachine -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.base import StateMachine +from sofic.graph import ATTR_SYMBOL, EPSILON if TYPE_CHECKING: - from pensive.automata.dfa import DFA - from pensive.automata.nfa import NFA + from sofic.automata.dfa import DFA + from sofic.automata.nfa import NFA class LabeledAutomaton(StateMachine): @@ -47,7 +47,7 @@ def recognizes(self, word: Sequence[Any]) -> bool: def words_of_length(self, length: int) -> Iterator[tuple[Any, ...]]: """Yield accepted words of exactly ``length`` symbols.""" - from pensive.automata.enumeration import words_of_length + from sofic.automata.enumeration import words_of_length yield from words_of_length(self, length) @@ -57,25 +57,25 @@ def iter_language(self, max_length: int | None = None) -> Iterator[tuple[Any, .. If ``max_length`` is omitted, the iterator is unbounded and may not terminate for finite languages after yielding their last word. """ - from pensive.automata.enumeration import iter_language + from sofic.automata.enumeration import iter_language yield from iter_language(self, max_length=max_length) def to_regex(self) -> str: """Return a regular expression for the accepted language.""" - from pensive.automata.regex import automaton_to_regex + from sofic.automata.regex import automaton_to_regex return automaton_to_regex(self) def union(self, other: LabeledAutomaton) -> NFA: """Return an NFA recognizing the union of this language and ``other``.""" - from pensive.automata.languages.automaton_ops import union_nfa + from sofic.automata.languages.automaton_ops import union_nfa return union_nfa(self, other) def intersection(self, other: LabeledAutomaton) -> DFA: """Return a DFA recognizing the intersection with ``other``.""" - from pensive.automata.languages.automaton_ops import intersection_dfa + from sofic.automata.languages.automaton_ops import intersection_dfa return intersection_dfa(self, other) @@ -85,19 +85,19 @@ def intersect(self, other: LabeledAutomaton) -> DFA: def complement(self, alphabet: frozenset[Any] | None = None) -> DFA: """Return a complete DFA recognizing the complement over ``alphabet``.""" - from pensive.automata.languages.automaton_ops import complement_dfa + from sofic.automata.languages.automaton_ops import complement_dfa return complement_dfa(self, self.input_alphabet if alphabet is None else alphabet) def difference(self, other: LabeledAutomaton, alphabet: frozenset[Any] | None = None) -> DFA: """Return a DFA recognizing this language minus ``other``.""" - from pensive.automata.languages.automaton_ops import difference_dfa + from sofic.automata.languages.automaton_ops import difference_dfa return difference_dfa(self, other, alphabet=alphabet) def concat(self, other: LabeledAutomaton) -> NFA: """Return an NFA recognizing concatenation with ``other``.""" - from pensive.automata.languages.automaton_ops import concat_nfa + from sofic.automata.languages.automaton_ops import concat_nfa return concat_nfa(self, other) @@ -107,7 +107,7 @@ def concatenate(self, other: LabeledAutomaton) -> NFA: def kleene_star(self) -> NFA: """Return an NFA recognizing the Kleene star of this language.""" - from pensive.automata.languages.automaton_ops import kleene_star_nfa + from sofic.automata.languages.automaton_ops import kleene_star_nfa return kleene_star_nfa(self) @@ -117,7 +117,7 @@ def star(self) -> NFA: def is_deterministic(self) -> bool: """Return whether this automaton is DFA-deterministic.""" - from pensive.properties import is_deterministic_automaton + from sofic.properties import is_deterministic_automaton return is_deterministic_automaton(self) @@ -151,7 +151,7 @@ def _run_nfa(self, word: Sequence[Any], start: set[Hashable] | None = None) -> s def reverse(self) -> NFA: """Return an NFA recognizing the reversed language.""" - from pensive.automata.nfa import NFA + from sofic.automata.nfa import NFA return NFA( input_alphabet=self.input_alphabet, diff --git a/pensive/automata/buchi.py b/sofic/automata/buchi.py similarity index 76% rename from pensive/automata/buchi.py rename to sofic/automata/buchi.py index 5d4ac13..7cae49b 100644 --- a/pensive/automata/buchi.py +++ b/sofic/automata/buchi.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from typing import Any -from pensive.automata.nfa import NFA +from sofic.automata.nfa import NFA class BuchiAutomaton(NFA): @@ -13,11 +13,11 @@ class BuchiAutomaton(NFA): def accepts_lasso(self, prefix: Sequence[Any], loop: Sequence[Any]) -> bool: """Accept if infinitely repeating ``loop`` after ``prefix`` visits accepting states infinitely often.""" - from pensive.automata.buchi_simulation import accepts_lasso_buchi + from sofic.automata.buchi_simulation import accepts_lasso_buchi return accepts_lasso_buchi(self, prefix, loop) def accepts_omega(self, word: Sequence[Any]) -> bool: - from pensive.automata.buchi_simulation import accepts_omega_buchi + from sofic.automata.buchi_simulation import accepts_omega_buchi return accepts_omega_buchi(self, word) diff --git a/pensive/automata/buchi_simulation.py b/sofic/automata/buchi_simulation.py similarity index 97% rename from pensive/automata/buchi_simulation.py rename to sofic/automata/buchi_simulation.py index e1419ed..26da207 100644 --- a/pensive/automata/buchi_simulation.py +++ b/sofic/automata/buchi_simulation.py @@ -5,7 +5,7 @@ from collections.abc import Hashable, Sequence from typing import Any -from pensive.automata.buchi import BuchiAutomaton +from sofic.automata.buchi import BuchiAutomaton def accepts_lasso_buchi(ba: BuchiAutomaton, prefix: Sequence[Any], loop: Sequence[Any]) -> bool: diff --git a/pensive/automata/canonical_dual.py b/sofic/automata/canonical_dual.py similarity index 57% rename from pensive/automata/canonical_dual.py rename to sofic/automata/canonical_dual.py index a9acd18..b8b7d43 100644 --- a/pensive/automata/canonical_dual.py +++ b/sofic/automata/canonical_dual.py @@ -2,17 +2,17 @@ from __future__ import annotations -from pensive.automata.atomaton import MaximizedPrimeAtomaton -from pensive.automata.rfsa import CanonicalRFSA +from sofic.automata.atomaton import MaximizedPrimeAtomaton +from sofic.automata.rfsa import CanonicalRFSA def dual_atomaton_from_rfsa(rfsa: CanonicalRFSA) -> MaximizedPrimeAtomaton: - from pensive.automata.canonical_extraction import maximized_prime_atomaton_from_language + from sofic.automata.canonical_extraction import maximized_prime_atomaton_from_language return maximized_prime_atomaton_from_language(rfsa) def dual_rfsa_from_atomaton(atomaton: MaximizedPrimeAtomaton) -> CanonicalRFSA: - from pensive.automata.canonical_extraction import canonical_rfsa_from_language + from sofic.automata.canonical_extraction import canonical_rfsa_from_language return canonical_rfsa_from_language(atomaton) diff --git a/pensive/automata/canonical_extraction.py b/sofic/automata/canonical_extraction.py similarity index 91% rename from pensive/automata/canonical_extraction.py rename to sofic/automata/canonical_extraction.py index d9ffcdf..f8e5d5a 100644 --- a/pensive/automata/canonical_extraction.py +++ b/sofic/automata/canonical_extraction.py @@ -4,15 +4,15 @@ from typing import Any -from pensive.automata.atomaton import Atomaton, MaximizedPrimeAtomaton -from pensive.automata.dfa import DFA -from pensive.automata.languages.automaton_ops import ( +from sofic.automata.atomaton import Atomaton, MaximizedPrimeAtomaton +from sofic.automata.dfa import DFA +from sofic.automata.languages.automaton_ops import ( minimal_dfa_from_language, ) -from pensive.automata.languages.base import AutomatonLanguage, RegularLanguage, as_language -from pensive.automata.nfa import NFA -from pensive.automata.observation import ObservationTable -from pensive.automata.rfsa import CanonicalRFSA +from sofic.automata.languages.base import AutomatonLanguage, RegularLanguage, as_language +from sofic.automata.nfa import NFA +from sofic.automata.observation import ObservationTable +from sofic.automata.rfsa import CanonicalRFSA def _language_automaton(language: RegularLanguage | NFA | DFA) -> NFA | DFA: diff --git a/pensive/automata/dfa.py b/sofic/automata/dfa.py similarity index 85% rename from pensive/automata/dfa.py rename to sofic/automata/dfa.py index 84f06e6..382b2c1 100644 --- a/pensive/automata/dfa.py +++ b/sofic/automata/dfa.py @@ -5,13 +5,13 @@ from collections.abc import Hashable, Sequence from typing import TYPE_CHECKING, Any -from pensive.automata.base import LabeledAutomaton -from pensive.exceptions import NonDeterministicError -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.automata.base import LabeledAutomaton +from sofic.exceptions import NonDeterministicError +from sofic.graph import ATTR_SYMBOL, EPSILON if TYPE_CHECKING: - from pensive.automata.algorithms import MinimizationAlgorithm - from pensive.automata.nfa import NFA + from sofic.automata.algorithms import MinimizationAlgorithm + from sofic.automata.nfa import NFA class DFA(LabeledAutomaton): @@ -61,14 +61,14 @@ def recognizes(self, word: Sequence[Any]) -> bool: return state in self.accepting_states def determinize(self) -> DFA: - from pensive.automata.algorithms import trim + from sofic.automata.algorithms import trim return trim(self) @classmethod def from_nfa(cls, nfa: NFA, **kwargs: Any) -> DFA: - from pensive.automata.algorithms import determinize - from pensive.automata.nfa import NFA as _NFA + from sofic.automata.algorithms import determinize + from sofic.automata.nfa import NFA as _NFA if not isinstance(nfa, _NFA): raise TypeError("from_nfa requires an NFA") @@ -80,6 +80,6 @@ def minimize( *, alphabet: frozenset[Any] | None = None, ) -> DFA: - from pensive.automata.algorithms import minimize + from sofic.automata.algorithms import minimize return minimize(self, algorithm=algorithm, alphabet=alphabet) diff --git a/pensive/automata/enumeration.py b/sofic/automata/enumeration.py similarity index 90% rename from pensive/automata/enumeration.py rename to sofic/automata/enumeration.py index 7827f34..3da18f2 100644 --- a/pensive/automata/enumeration.py +++ b/sofic/automata/enumeration.py @@ -6,7 +6,7 @@ from itertools import product from typing import Any -from pensive.automata.base import LabeledAutomaton +from sofic.automata.base import LabeledAutomaton def words_of_length(automaton: LabeledAutomaton, length: int) -> Iterator[tuple[Any, ...]]: @@ -39,6 +39,6 @@ def iter_language( def _effective_alphabet(automaton: LabeledAutomaton) -> frozenset[Any]: - from pensive.automata.algorithms import _effective_alphabet as _shared + from sofic.automata.algorithms import _effective_alphabet as _shared return _shared(automaton) diff --git a/pensive/automata/icdfa.py b/sofic/automata/icdfa.py similarity index 98% rename from pensive/automata/icdfa.py rename to sofic/automata/icdfa.py index fca053a..8723930 100644 --- a/pensive/automata/icdfa.py +++ b/sofic/automata/icdfa.py @@ -14,8 +14,8 @@ from math import comb from typing import Any -from pensive.automata.dfa import DFA -from pensive.exceptions import PensiveValidationError +from sofic.automata.dfa import DFA +from sofic.exceptions import SoficValidationError __all__ = [ "ICDFAString", @@ -36,7 +36,7 @@ ] -class ICDFAEnumerationError(PensiveValidationError): +class ICDFAEnumerationError(SoficValidationError): """Raised when ICDFA enumeration or string conversion fails.""" @@ -370,7 +370,7 @@ def dfa_to_icdfa_string( symbol_order: Sequence[Any] | None = None, ) -> ICDFAString: """Encode a complete initially-connected DFA as a canonical ICDFA string.""" - from pensive.automata.algorithms import _forward_reachable + from sofic.automata.algorithms import _forward_reachable reachable = _forward_reachable(dfa) if reachable != set(dfa.states()): diff --git a/pensive/automata/idfa.py b/sofic/automata/idfa.py similarity index 97% rename from pensive/automata/idfa.py rename to sofic/automata/idfa.py index 09c997b..ad7db6d 100644 --- a/pensive/automata/idfa.py +++ b/sofic/automata/idfa.py @@ -11,12 +11,12 @@ from collections.abc import Iterator, Sequence from functools import cache -from pensive.automata.icdfa import ( +from sofic.automata.icdfa import ( _upper_bound_at, _validate_flags, next_flags, ) -from pensive.exceptions import PensiveValidationError +from sofic.exceptions import SoficValidationError __all__ = [ "MISSING_TRANSITION", @@ -37,7 +37,7 @@ MISSING_TRANSITION = -1 -class IDFAEnumerationError(PensiveValidationError): +class IDFAEnumerationError(SoficValidationError): """Raised when incomplete accessible DFA enumeration fails.""" @@ -286,8 +286,8 @@ def idfa_string_to_topological_graph( k: int, alphabet: Sequence[object] | None = None, ): - """Decode an IDFA string into a :class:`~pensive.generators.synchronization.TopologicalUnifilarGraph`.""" - from pensive.generators.synchronization import TopologicalUnifilarGraph + """Decode an IDFA string into a :class:`~sofic.generators.synchronization.TopologicalUnifilarGraph`.""" + from sofic.generators.synchronization import TopologicalUnifilarGraph validate_idfa_string(transitions, n=n, k=k) if alphabet is None: diff --git a/pensive/automata/languages/__init__.py b/sofic/automata/languages/__init__.py similarity index 58% rename from pensive/automata/languages/__init__.py rename to sofic/automata/languages/__init__.py index 76a513e..5f3224b 100644 --- a/pensive/automata/languages/__init__.py +++ b/sofic/automata/languages/__init__.py @@ -1,8 +1,8 @@ """Regular-language algebra for automata constructions.""" -from pensive.automata.languages.atoms import atoms, is_prime_atom, prime_atoms -from pensive.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage -from pensive.automata.languages.operations import ( +from sofic.automata.languages.atoms import atoms, is_prime_atom, prime_atoms +from sofic.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage +from sofic.automata.languages.operations import ( complement, concat, difference, @@ -12,8 +12,8 @@ reverse, union, ) -from pensive.automata.languages.quotients import left_quotient, left_quotients, residuals, right_quotient -from pensive.automata.languages.residuals import is_composed_residual, prime_residuals +from sofic.automata.languages.quotients import left_quotient, left_quotients, residuals, right_quotient +from sofic.automata.languages.residuals import is_composed_residual, prime_residuals __all__ = [ "AutomatonLanguage", diff --git a/pensive/automata/languages/_quotient_utils.py b/sofic/automata/languages/_quotient_utils.py similarity index 91% rename from pensive/automata/languages/_quotient_utils.py rename to sofic/automata/languages/_quotient_utils.py index 78440ee..8adceb7 100644 --- a/pensive/automata/languages/_quotient_utils.py +++ b/sofic/automata/languages/_quotient_utils.py @@ -5,8 +5,8 @@ from collections.abc import Sequence from typing import Any -from pensive.automata.languages.automaton_ops import minimal_dfa_from_language -from pensive.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language +from sofic.automata.languages.automaton_ops import minimal_dfa_from_language +from sofic.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language def _words_up_to(length: int, alphabet: frozenset[Any]) -> list[tuple[Any, ...]]: diff --git a/pensive/automata/languages/atoms.py b/sofic/automata/languages/atoms.py similarity index 75% rename from pensive/automata/languages/atoms.py rename to sofic/automata/languages/atoms.py index 6f0b0f3..dcdfa25 100644 --- a/pensive/automata/languages/atoms.py +++ b/sofic/automata/languages/atoms.py @@ -2,10 +2,10 @@ from __future__ import annotations -from pensive.automata.languages._quotient_utils import _alphabet_of, _languages_equal, _residual_from_state -from pensive.automata.languages.automaton_ops import minimal_dfa_from_language -from pensive.automata.languages.base import AutomatonLanguage, RegularLanguage, as_language -from pensive.automata.languages.quotients import left_quotients +from sofic.automata.languages._quotient_utils import _alphabet_of, _languages_equal, _residual_from_state +from sofic.automata.languages.automaton_ops import minimal_dfa_from_language +from sofic.automata.languages.base import AutomatonLanguage, RegularLanguage, as_language +from sofic.automata.languages.quotients import left_quotients def atoms(language: RegularLanguage) -> frozenset[RegularLanguage]: diff --git a/pensive/automata/languages/automaton_ops.py b/sofic/automata/languages/automaton_ops.py similarity index 96% rename from pensive/automata/languages/automaton_ops.py rename to sofic/automata/languages/automaton_ops.py index 164831c..593e472 100644 --- a/pensive/automata/languages/automaton_ops.py +++ b/sofic/automata/languages/automaton_ops.py @@ -6,11 +6,11 @@ from collections.abc import Callable, Hashable, Sequence from typing import Any -from pensive.automata.algorithms import complete, determinize, minimize, trim -from pensive.automata.base import LabeledAutomaton -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.graph import ATTR_SYMBOL, EPSILON, TransitionGraph +from sofic.automata.algorithms import complete, determinize, minimize, trim +from sofic.automata.base import LabeledAutomaton +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.graph import ATTR_SYMBOL, EPSILON, TransitionGraph _LEFT = object() _RIGHT = object() @@ -238,6 +238,6 @@ def state_residual_languages(dfa: DFA) -> dict[Hashable, DFA]: def _effective_alphabet(aut: LabeledAutomaton) -> frozenset[Any]: - from pensive.automata.algorithms import _effective_alphabet as _shared + from sofic.automata.algorithms import _effective_alphabet as _shared return _shared(aut) diff --git a/pensive/automata/languages/base.py b/sofic/automata/languages/base.py similarity index 97% rename from pensive/automata/languages/base.py rename to sofic/automata/languages/base.py index 91a30aa..ffce817 100644 --- a/pensive/automata/languages/base.py +++ b/sofic/automata/languages/base.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from typing import Any, Protocol, runtime_checkable -from pensive.automata.base import LabeledAutomaton +from sofic.automata.base import LabeledAutomaton @runtime_checkable diff --git a/pensive/automata/languages/operations.py b/sofic/automata/languages/operations.py similarity index 95% rename from pensive/automata/languages/operations.py rename to sofic/automata/languages/operations.py index 8e40ee7..c9b662a 100644 --- a/pensive/automata/languages/operations.py +++ b/sofic/automata/languages/operations.py @@ -4,7 +4,7 @@ from typing import Any -from pensive.automata.languages.automaton_ops import ( +from sofic.automata.languages.automaton_ops import ( complement_dfa, concat_nfa, difference_dfa, @@ -12,7 +12,7 @@ kleene_star_nfa, union_nfa, ) -from pensive.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language +from sofic.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language def union(left: RegularLanguage, right: RegularLanguage) -> RegularLanguage: diff --git a/pensive/automata/languages/quotients.py b/sofic/automata/languages/quotients.py similarity index 92% rename from pensive/automata/languages/quotients.py rename to sofic/automata/languages/quotients.py index 47a5f62..070cb16 100644 --- a/pensive/automata/languages/quotients.py +++ b/sofic/automata/languages/quotients.py @@ -5,18 +5,18 @@ from collections.abc import Sequence from typing import Any -from pensive.automata.languages._quotient_utils import ( +from sofic.automata.languages._quotient_utils import ( _prefixes_if_suffix, _residual_from_state, _suffixes_if_prefix, _words_up_to, ) -from pensive.automata.languages.automaton_ops import ( +from sofic.automata.languages.automaton_ops import ( left_quotient_automaton, minimal_dfa_from_language, right_quotient_automaton, ) -from pensive.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language +from sofic.automata.languages.base import AutomatonLanguage, ExplicitLanguage, RegularLanguage, as_language def left_quotient(u: Sequence[Any], language: RegularLanguage) -> RegularLanguage: diff --git a/pensive/automata/languages/residuals.py b/sofic/automata/languages/residuals.py similarity index 81% rename from pensive/automata/languages/residuals.py rename to sofic/automata/languages/residuals.py index 1335aa9..ddaee4f 100644 --- a/pensive/automata/languages/residuals.py +++ b/sofic/automata/languages/residuals.py @@ -2,13 +2,13 @@ from __future__ import annotations -from pensive.automata.languages._quotient_utils import ( +from sofic.automata.languages._quotient_utils import ( _alphabet_of, _is_union_of_others, _languages_equal, ) -from pensive.automata.languages.base import ExplicitLanguage, RegularLanguage -from pensive.automata.languages.quotients import left_quotients +from sofic.automata.languages.base import ExplicitLanguage, RegularLanguage +from sofic.automata.languages.quotients import left_quotients def prime_residuals(language: RegularLanguage) -> frozenset[RegularLanguage]: diff --git a/pensive/automata/learning.py b/sofic/automata/learning.py similarity index 90% rename from pensive/automata/learning.py rename to sofic/automata/learning.py index 21e7ccf..021cc07 100644 --- a/pensive/automata/learning.py +++ b/sofic/automata/learning.py @@ -5,9 +5,9 @@ from collections.abc import Sequence from typing import Any -from pensive.automata.atomaton import MaximizedPrimeAtomaton -from pensive.automata.languages.base import RegularLanguage -from pensive.automata.observation import ObservationTable +from sofic.automata.atomaton import MaximizedPrimeAtomaton +from sofic.automata.languages.base import RegularLanguage +from sofic.automata.observation import ObservationTable def learn_maximized_prime_atomaton( @@ -70,7 +70,7 @@ def _find_counterexample( alphabet: frozenset[Any], max_len: int = 8, ) -> tuple[Any, ...] | None: - from pensive.automata.languages._quotient_utils import _words_up_to + from sofic.automata.languages._quotient_utils import _words_up_to for length in range(max_len + 1): for word in _words_up_to(length, alphabet): diff --git a/pensive/automata/nfa.py b/sofic/automata/nfa.py similarity index 75% rename from pensive/automata/nfa.py rename to sofic/automata/nfa.py index 8033b49..020e102 100644 --- a/pensive/automata/nfa.py +++ b/sofic/automata/nfa.py @@ -5,12 +5,12 @@ from collections.abc import Hashable, Sequence from typing import TYPE_CHECKING, Any -from pensive.automata.base import LabeledAutomaton -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.automata.base import LabeledAutomaton +from sofic.graph import ATTR_SYMBOL, EPSILON if TYPE_CHECKING: - from pensive.automata.algorithms import MinimizationAlgorithm - from pensive.automata.dfa import DFA + from sofic.automata.algorithms import MinimizationAlgorithm + from sofic.automata.dfa import DFA class NFA(LabeledAutomaton): @@ -24,7 +24,7 @@ def recognizes(self, word: Sequence[Any]) -> bool: return bool(final & self.accepting_states) def determinize(self, *, alphabet: frozenset[Any] | None = None) -> DFA: - from pensive.automata.algorithms import determinize + from sofic.automata.algorithms import determinize return determinize(self, alphabet=alphabet) @@ -34,6 +34,6 @@ def minimize( *, alphabet: frozenset[Any] | None = None, ) -> DFA: - from pensive.automata.algorithms import minimize + from sofic.automata.algorithms import minimize return minimize(self, algorithm=algorithm, alphabet=alphabet) diff --git a/pensive/automata/nwa.py b/sofic/automata/nwa.py similarity index 92% rename from pensive/automata/nwa.py rename to sofic/automata/nwa.py index fb2e94a..70f1ac0 100644 --- a/pensive/automata/nwa.py +++ b/sofic/automata/nwa.py @@ -6,9 +6,9 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any -from pensive.base import StateMachine -from pensive.exceptions import PensiveValidationError -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.exceptions import SoficValidationError +from sofic.graph import ( ATTR_HIER_STATE, ATTR_KIND, ATTR_STACK_SYMBOL, @@ -19,7 +19,7 @@ ) if TYPE_CHECKING: - from pensive.automata.vpa import VisiblyPushdownAutomaton + from sofic.automata.vpa import VisiblyPushdownAutomaton _KINDS = frozenset({KIND_CALL, KIND_RETURN, KIND_INTERNAL}) @@ -77,26 +77,26 @@ def from_visible_word( def validate(self) -> None: """Raise if the matching relation is not a valid nested-word relation.""" if len(self.symbols) != len(self.kinds) or len(self.symbols) != len(self.matching): - raise PensiveValidationError("symbols, kinds, and matching must have the same length") + raise SoficValidationError("symbols, kinds, and matching must have the same length") for index, kind in enumerate(self.kinds): if kind not in _KINDS: - raise PensiveValidationError(f"invalid nested-word kind {kind!r} at position {index}") + raise SoficValidationError(f"invalid nested-word kind {kind!r} at position {index}") for index, (kind, partner) in enumerate(zip(self.kinds, self.matching, strict=True)): if partner is None: continue if not isinstance(partner, int) or partner < 0 or partner >= len(self.symbols): - raise PensiveValidationError(f"matching partner {partner!r} out of range at position {index}") + raise SoficValidationError(f"matching partner {partner!r} out of range at position {index}") if self.matching[partner] != index: - raise PensiveValidationError("matching relation must be symmetric") + raise SoficValidationError("matching relation must be symmetric") partner_kind = self.kinds[partner] if kind == KIND_INTERNAL: - raise PensiveValidationError("internal positions cannot be matched") + raise SoficValidationError("internal positions cannot be matched") if kind == KIND_CALL and not (partner_kind == KIND_RETURN and index < partner): - raise PensiveValidationError("call positions must match later return positions") + raise SoficValidationError("call positions must match later return positions") if kind == KIND_RETURN and not (partner_kind == KIND_CALL and partner < index): - raise PensiveValidationError("return positions must match earlier call positions") + raise SoficValidationError("return positions must match earlier call positions") stack: list[int] = [] for index, (kind, partner) in enumerate(zip(self.kinds, self.matching, strict=True)): @@ -104,7 +104,7 @@ def validate(self) -> None: stack.append(index) elif kind == KIND_RETURN and partner is not None: if not stack or stack[-1] != partner: - raise PensiveValidationError("matching relation must be properly nested") + raise SoficValidationError("matching relation must be properly nested") stack.pop() @@ -207,7 +207,7 @@ def add_internal_transition(self, source: Hashable, target: Hashable, symbol: An return self.graph.add_transition(source, target, **data) def recognizes(self, word: NestedWord) -> bool: - from pensive.automata.nwa_simulation import recognizes_nwa + from sofic.automata.nwa_simulation import recognizes_nwa return recognizes_nwa(self, word) @@ -265,7 +265,7 @@ def to_vpa(self, *, tag_symbols: bool = True) -> VisiblyPushdownAutomaton: ``("call", symbol)``, ``("return", symbol)``, and ``("internal", symbol)``. If false, the role alphabets must be disjoint. """ - from pensive.automata.vpa import VisiblyPushdownAutomaton + from sofic.automata.vpa import VisiblyPushdownAutomaton if not tag_symbols: _require_disjoint_visible_alphabets(self.call_alphabet, self.return_alphabet, self.internal_alphabet) diff --git a/pensive/automata/nwa_simulation.py b/sofic/automata/nwa_simulation.py similarity index 88% rename from pensive/automata/nwa_simulation.py rename to sofic/automata/nwa_simulation.py index d639185..4371e97 100644 --- a/pensive/automata/nwa_simulation.py +++ b/sofic/automata/nwa_simulation.py @@ -5,9 +5,9 @@ from collections.abc import Hashable, Iterator from typing import Any -from pensive.automata._config_simulation import simulate_configs -from pensive.automata.nwa import NestedWord, NestedWordAutomaton -from pensive.graph import ATTR_HIER_STATE, ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.automata._config_simulation import simulate_configs +from sofic.automata.nwa import NestedWord, NestedWordAutomaton +from sofic.graph import ATTR_HIER_STATE, ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN Config = tuple[Hashable, tuple[Any, ...]] diff --git a/pensive/automata/observation.py b/sofic/automata/observation.py similarity index 65% rename from pensive/automata/observation.py rename to sofic/automata/observation.py index 170ef4b..3bfd361 100644 --- a/pensive/automata/observation.py +++ b/sofic/automata/observation.py @@ -6,9 +6,9 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from pensive.automata.atomaton import Atomaton, MaximizedPrimeAtomaton - from pensive.automata.dfa import DFA - from pensive.automata.rfsa import CanonicalRFSA + from sofic.automata.atomaton import Atomaton, MaximizedPrimeAtomaton + from sofic.automata.dfa import DFA + from sofic.automata.rfsa import CanonicalRFSA @dataclass @@ -20,21 +20,21 @@ class ObservationTable: membership: dict[tuple[Any, ...], bool] = field(default_factory=dict) def to_minimal_dfa(self) -> DFA: - from pensive.automata.canonical_extraction import observation_to_minimal_dfa + from sofic.automata.canonical_extraction import observation_to_minimal_dfa return observation_to_minimal_dfa(self) def to_canonical_rfsa(self) -> CanonicalRFSA: - from pensive.automata.canonical_extraction import observation_to_canonical_rfsa + from sofic.automata.canonical_extraction import observation_to_canonical_rfsa return observation_to_canonical_rfsa(self) def to_atomaton(self) -> Atomaton: - from pensive.automata.canonical_extraction import observation_to_atomaton + from sofic.automata.canonical_extraction import observation_to_atomaton return observation_to_atomaton(self) def to_maximized_prime_atomaton(self) -> MaximizedPrimeAtomaton: - from pensive.automata.canonical_extraction import observation_to_maximized_prime_atomaton + from sofic.automata.canonical_extraction import observation_to_maximized_prime_atomaton return observation_to_maximized_prime_atomaton(self) diff --git a/pensive/automata/papni.py b/sofic/automata/papni.py similarity index 97% rename from pensive/automata/papni.py rename to sofic/automata/papni.py index 0dda6e3..d1c367f 100644 --- a/pensive/automata/papni.py +++ b/sofic/automata/papni.py @@ -6,10 +6,10 @@ from dataclasses import dataclass from typing import Any -from pensive.automata.dfa import DFA -from pensive.automata.rpni import learn_dfa_rpni -from pensive.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN -from pensive.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref +from sofic.automata.dfa import DFA +from sofic.automata.rpni import learn_dfa_rpni +from sofic.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref __all__ = [ "DyckAlphabet", diff --git a/pensive/automata/regex.py b/sofic/automata/regex.py similarity index 97% rename from pensive/automata/regex.py rename to sofic/automata/regex.py index 06fbcc3..34311bc 100644 --- a/pensive/automata/regex.py +++ b/sofic/automata/regex.py @@ -6,8 +6,8 @@ from dataclasses import dataclass from typing import Any -from pensive.automata.base import LabeledAutomaton -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.automata.base import LabeledAutomaton +from sofic.graph import ATTR_SYMBOL, EPSILON @dataclass(frozen=True, slots=True) diff --git a/pensive/automata/rfsa.py b/sofic/automata/rfsa.py similarity index 71% rename from pensive/automata/rfsa.py rename to sofic/automata/rfsa.py index faf7519..61933c5 100644 --- a/pensive/automata/rfsa.py +++ b/sofic/automata/rfsa.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING, Any -from pensive.automata.languages.base import RegularLanguage -from pensive.automata.nfa import NFA +from sofic.automata.languages.base import RegularLanguage +from sofic.automata.nfa import NFA if TYPE_CHECKING: - from pensive.automata.observation import ObservationTable + from sofic.automata.observation import ObservationTable class ResidualFiniteStateAutomaton(NFA): @@ -24,12 +24,12 @@ class CanonicalRFSA(ResidualFiniteStateAutomaton): @classmethod def from_language(cls, language: RegularLanguage | NFA, **kwargs: Any) -> CanonicalRFSA: - from pensive.automata.canonical_extraction import canonical_rfsa_from_language + from sofic.automata.canonical_extraction import canonical_rfsa_from_language return canonical_rfsa_from_language(language) @classmethod def from_observation_table(cls, table: ObservationTable, **kwargs: Any) -> CanonicalRFSA: - from pensive.automata.canonical_extraction import observation_to_canonical_rfsa + from sofic.automata.canonical_extraction import observation_to_canonical_rfsa return observation_to_canonical_rfsa(table) diff --git a/pensive/automata/rpni.py b/sofic/automata/rpni.py similarity index 99% rename from pensive/automata/rpni.py rename to sofic/automata/rpni.py index 801ba91..d63b544 100644 --- a/pensive/automata/rpni.py +++ b/sofic/automata/rpni.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, field from typing import Any -from pensive.automata.dfa import DFA +from sofic.automata.dfa import DFA __all__ = ["learn_dfa_rpni"] diff --git a/pensive/automata/transducer_operations.py b/sofic/automata/transducer_operations.py similarity index 98% rename from pensive/automata/transducer_operations.py rename to sofic/automata/transducer_operations.py index 649b6d6..0bb528c 100644 --- a/pensive/automata/transducer_operations.py +++ b/sofic/automata/transducer_operations.py @@ -9,10 +9,10 @@ import numpy as np -from pensive.automata.transducers import ERROR_STATE, ERROR_SYMBOL, MealyMachine -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_SYMBOL, EPSILON +from sofic.automata.transducers import ERROR_STATE, ERROR_SYMBOL, MealyMachine +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_SYMBOL, EPSILON def cartesian_product_gg( diff --git a/pensive/automata/transducer_simulation.py b/sofic/automata/transducer_simulation.py similarity index 95% rename from pensive/automata/transducer_simulation.py rename to sofic/automata/transducer_simulation.py index 260cb1b..cd700d5 100644 --- a/pensive/automata/transducer_simulation.py +++ b/sofic/automata/transducer_simulation.py @@ -7,10 +7,10 @@ import networkx as nx -from pensive.automata._config_simulation import simulate_configs -from pensive.automata.transducers import MealyMachine, MooreMachine, Transducer -from pensive.exceptions import InfiniteTransductionError -from pensive.graph import ATTR_OUTPUT, ATTR_SYMBOL, EPSILON +from sofic.automata._config_simulation import simulate_configs +from sofic.automata.transducers import MealyMachine, MooreMachine, Transducer +from sofic.exceptions import InfiniteTransductionError +from sofic.graph import ATTR_OUTPUT, ATTR_SYMBOL, EPSILON def transduce_mealy(mealy: MealyMachine, word: Sequence[Any]) -> set[tuple[Any, ...]]: diff --git a/pensive/automata/transducers.py b/sofic/automata/transducers.py similarity index 94% rename from pensive/automata/transducers.py rename to sofic/automata/transducers.py index a4a0440..2acb1ce 100644 --- a/pensive/automata/transducers.py +++ b/sofic/automata/transducers.py @@ -9,9 +9,9 @@ import numpy as np -from pensive.base import StateMachine -from pensive.exceptions import StochasticValidationError -from pensive.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_SYMBOL, EPSILON +from sofic.base import StateMachine +from sofic.exceptions import StochasticValidationError +from sofic.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_SYMBOL, EPSILON ERROR_SYMBOL = "?" ERROR_STATE = "?" @@ -42,7 +42,7 @@ def validate(self) -> None: def is_deterministic(self) -> bool: """Return whether each state has at most one transition per input symbol.""" - from pensive.properties import is_deterministic_transducer + from sofic.properties import is_deterministic_transducer return is_deterministic_transducer(self) @@ -102,7 +102,7 @@ def add_transition( ) -> int: """Add a transition with input ``symbol`` and optional output. - Use :data:`pensive.graph.EPSILON` for empty input or empty output. + Use :data:`sofic.graph.EPSILON` for empty input or empty output. Omitting ``prob`` keeps the transition topological; probability-aware helpers treat missing probabilities as weight ``1``. """ @@ -114,7 +114,7 @@ def add_transition( return self.graph.add_transition(source, target, **data) def transduce(self, word: Sequence[Any]) -> set[tuple[Any, ...]]: - from pensive.automata.transducer_simulation import transduce_mealy + from sofic.automata.transducer_simulation import transduce_mealy return transduce_mealy(self, word) @@ -162,19 +162,19 @@ def output_machine(self, *, build: bool = False) -> Any: def compose(self, other: MealyMachine, **kwargs: Any) -> MealyMachine: """Return the serial composition ``other`` after this transducer.""" - from pensive.automata.transducer_operations import compose_tt + from sofic.automata.transducer_operations import compose_tt return compose_tt((self, other), **kwargs) def joint_machine(self, generator: Any, **kwargs: Any) -> Any: """Return the joint input/output generator induced by ``generator``.""" - from pensive.automata.transducer_operations import compose_tg + from sofic.automata.transducer_operations import compose_tg return compose_tg(self, generator, joint=True, **kwargs) def transduce_generator(self, generator: Any, **kwargs: Any) -> Any: """Return the output generator induced by driving this transducer.""" - from pensive.automata.transducer_operations import transduce_generator + from sofic.automata.transducer_operations import transduce_generator return transduce_generator(self, generator, **kwargs) @@ -269,7 +269,7 @@ def set_output(self, state: Hashable, output: Any) -> None: self.graph.nx.nodes[state][ATTR_OUTPUT] = output def transduce(self, word: Sequence[Any]) -> set[tuple[Any, ...]]: - from pensive.automata.transducer_simulation import transduce_moore + from sofic.automata.transducer_simulation import transduce_moore return transduce_moore(self, word) @@ -291,8 +291,8 @@ def _transition_probability(data: Mapping[str, Any]) -> float: def _partial_machine(machine: MealyMachine, label_attr: str, *, build: bool) -> Any: - from pensive.generators.epsilon_machine import EpsilonMachine - from pensive.generators.mealy import MealyHMM + from sofic.generators.epsilon_machine import EpsilonMachine + from sofic.generators.mealy import MealyHMM states = tuple(machine.states()) initial_states = tuple(machine.initial_states) or states diff --git a/pensive/automata/unifilar.py b/sofic/automata/unifilar.py similarity index 75% rename from pensive/automata/unifilar.py rename to sofic/automata/unifilar.py index b66d7c8..67c113b 100644 --- a/pensive/automata/unifilar.py +++ b/sofic/automata/unifilar.py @@ -5,9 +5,9 @@ from collections.abc import Hashable, Sequence from typing import Any -from pensive.automata.base import LabeledAutomaton -from pensive.exceptions import UnifilarityError -from pensive.graph import ATTR_SYMBOL, EPSILON +from sofic.automata.base import LabeledAutomaton +from sofic.exceptions import UnifilarityError +from sofic.graph import ATTR_SYMBOL, EPSILON class UnifilarAutomaton(LabeledAutomaton): @@ -15,7 +15,7 @@ class UnifilarAutomaton(LabeledAutomaton): def is_unifilar(self) -> bool: """Return whether this presentation is right-resolving (unifilar).""" - from pensive.properties import is_unifilar_symbols + from sofic.properties import is_unifilar_symbols return is_unifilar_symbols(self) @@ -38,18 +38,18 @@ def recognizes(self, word: Sequence[Any]) -> bool: def markov_order(self) -> int | float: """Markov order ``R`` for this right-resolving (unifilar) presentation.""" - from pensive.generators.synchronization import graph_from_unifilar_automaton, markov_order_from_graph + from sofic.generators.synchronization import graph_from_unifilar_automaton, markov_order_from_graph return markov_order_from_graph(graph_from_unifilar_automaton(self)) def cryptic_order(self) -> int | float: """Cryptic order ``k_chi`` for this right-resolving presentation.""" - from pensive.generators.synchronization import cryptic_order_from_graph, graph_from_unifilar_automaton + from sofic.generators.synchronization import cryptic_order_from_graph, graph_from_unifilar_automaton return cryptic_order_from_graph(graph_from_unifilar_automaton(self)) def is_exactly_synchronizable(self) -> bool: """Return whether this presentation has finite Markov order.""" - from pensive.generators.synchronization import graph_from_unifilar_automaton, is_exactly_synchronizable + from sofic.generators.synchronization import graph_from_unifilar_automaton, is_exactly_synchronizable return is_exactly_synchronizable(graph_from_unifilar_automaton(self)) diff --git a/pensive/automata/vpa.py b/sofic/automata/vpa.py similarity index 99% rename from pensive/automata/vpa.py rename to sofic/automata/vpa.py index 71fcbc6..088cdcd 100644 --- a/pensive/automata/vpa.py +++ b/sofic/automata/vpa.py @@ -6,9 +6,9 @@ from collections.abc import Hashable, Iterable, Mapping, Sequence from typing import Any -from pensive.base import StateMachine -from pensive.exceptions import NonDeterministicError -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.exceptions import NonDeterministicError +from sofic.graph import ( ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, @@ -179,7 +179,7 @@ def internal_transition_map(self) -> dict[tuple[Hashable, Any], Hashable]: return result def recognizes(self, word: Sequence[Any]) -> bool: - from pensive.automata.vpa_simulation import recognizes_vpa + from sofic.automata.vpa_simulation import recognizes_vpa return recognizes_vpa(self, word) diff --git a/pensive/automata/vpa_simulation.py b/sofic/automata/vpa_simulation.py similarity index 89% rename from pensive/automata/vpa_simulation.py rename to sofic/automata/vpa_simulation.py index 7ae1f13..55efe6c 100644 --- a/pensive/automata/vpa_simulation.py +++ b/sofic/automata/vpa_simulation.py @@ -5,9 +5,9 @@ from collections.abc import Hashable, Iterator, Sequence from typing import Any -from pensive.automata._config_simulation import simulate_configs -from pensive.automata.vpa import VisiblyPushdownAutomaton -from pensive.graph import ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.automata._config_simulation import simulate_configs +from sofic.automata.vpa import VisiblyPushdownAutomaton +from sofic.graph import ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN _BOTTOM = object() diff --git a/pensive/base.py b/sofic/base.py similarity index 83% rename from pensive/base.py rename to sofic/base.py index e613b5a..bfa14da 100644 --- a/pensive/base.py +++ b/sofic/base.py @@ -1,4 +1,4 @@ -"""Abstract base for all pensive state-machine models.""" +"""Abstract base for all sofic state-machine models.""" from __future__ import annotations @@ -10,13 +10,13 @@ import networkx as nx -from pensive.exceptions import PensiveValidationError -from pensive.graph import Transition, TransitionGraph -from pensive.indexing import StateIndex +from sofic.exceptions import SoficValidationError +from sofic.graph import Transition, TransitionGraph +from sofic.indexing import StateIndex class StateMachine(ABC): - """Common interface for graph-backed models in pensive.""" + """Common interface for graph-backed models in sofic.""" graph: TransitionGraph @@ -25,7 +25,7 @@ def __init__(self, graph: TransitionGraph | None = None) -> None: @abstractmethod def validate(self) -> None: - """Raise :class:`~pensive.exceptions.PensiveValidationError` on failure.""" + """Raise :class:`~sofic.exceptions.SoficValidationError` on failure.""" def states(self) -> Iterator[Hashable]: yield from self.graph.states() @@ -54,7 +54,7 @@ def from_networkx(cls, g: nx.MultiDiGraph, **kwargs: Any) -> Self: def to_yaml(self) -> str: """Return a YAML representation of this model.""" - from pensive.serialization import model_to_yaml + from sofic.serialization import model_to_yaml return model_to_yaml(self) @@ -65,7 +65,7 @@ def write_yaml(self, path: str | Path) -> None: @classmethod def from_yaml(cls, text: str, *, validate: bool = True) -> Self: """Reconstruct a model from YAML text.""" - from pensive.serialization import model_from_yaml + from sofic.serialization import model_from_yaml model = model_from_yaml(text, validate=validate) if not isinstance(model, cls): @@ -91,7 +91,7 @@ def __repr__(self) -> str: def _repr_png_(self) -> bytes | None: """Notebook image: Graphviz PNG, or ``None`` if rendering is unavailable.""" try: - from pensive.viz.graphviz import model_to_png + from sofic.viz.graphviz import model_to_png return model_to_png(self) except Exception: @@ -100,7 +100,7 @@ def _repr_png_(self) -> bytes | None: def _graphviz_svg(self) -> str | None: """Graphviz SVG for notebook vector display.""" try: - from pensive.viz.graphviz import model_to_svg + from sofic.viz.graphviz import model_to_svg return model_to_svg(self) except Exception: @@ -126,28 +126,28 @@ def _repr_mimebundle_( def to_graphviz(self, **kwargs: Any) -> Any: """Build a Graphviz diagram for this model.""" - from pensive.viz.graphviz import model_to_graphviz + from sofic.viz.graphviz import model_to_graphviz return model_to_graphviz(self, **kwargs) def draw(self, filename: str | None = None, **kwargs: Any) -> str | None: """Render this model with Graphviz.""" - from pensive.viz.graphviz import draw + from sofic.viz.graphviz import draw return draw(self, filename=filename, **kwargs) def to_tikz(self, **kwargs: Any) -> str: """Return a Vaucanson-style TikZ picture for this model.""" - from pensive.viz.tikz import model_to_tikz + from sofic.viz.tikz import model_to_tikz return model_to_tikz(self, **kwargs) def draw_tikz(self, filename: str | None = None, **kwargs: Any) -> str | None: """Write a TikZ fragment for this model.""" - from pensive.viz.tikz import draw_tikz + from sofic.viz.tikz import draw_tikz return draw_tikz(self, filename=filename, **kwargs) def _require(self, condition: bool, message: str) -> None: if not condition: - raise PensiveValidationError(message) + raise SoficValidationError(message) diff --git a/pensive/core.py b/sofic/core.py similarity index 88% rename from pensive/core.py rename to sofic/core.py index 2a1489c..2737559 100644 --- a/pensive/core.py +++ b/sofic/core.py @@ -1,7 +1,7 @@ """Core graph-backed model primitives.""" -from pensive.base import StateMachine -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.graph import ( ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_EMISSION_EDGE, @@ -21,7 +21,7 @@ Transition, TransitionGraph, ) -from pensive.indexing import StateIndex +from sofic.indexing import StateIndex __all__ = [ "ATTR_EMISSION", diff --git a/pensive/examples/__init__.py b/sofic/examples/__init__.py similarity index 86% rename from pensive/examples/__init__.py rename to sofic/examples/__init__.py index 6164ada..c74872b 100644 --- a/pensive/examples/__init__.py +++ b/sofic/examples/__init__.py @@ -2,7 +2,7 @@ from importlib import import_module as _import_module -from pensive.examples.epsilon_machines import ( +from sofic.examples.epsilon_machines import ( alternating_biased_coins, bernoulli, butterfly_process, @@ -27,9 +27,9 @@ tent_map_misiurewicz_information_expected, tent_map_misiurewicz_reverse, ) -from pensive.examples.processes import * -from pensive.examples.processes import __all__ as _process_all -from pensive.examples.shifts import ( +from sofic.examples.processes import * +from sofic.examples.processes import __all__ as _process_all +from sofic.examples.shifts import ( dyck_shift_order, motzkin_shift, sofic_dyck_fig1_shift, @@ -37,8 +37,8 @@ sofic_dyck_zeta_example_shift, ) -processes = _import_module("pensive.examples.processes") -shifts = _import_module("pensive.examples.shifts") +processes = _import_module("sofic.examples.processes") +shifts = _import_module("sofic.examples.shifts") __all__ = [ "alternating_biased_coins", diff --git a/pensive/examples/epsilon_machines.py b/sofic/examples/epsilon_machines.py similarity index 93% rename from pensive/examples/epsilon_machines.py rename to sofic/examples/epsilon_machines.py index 4896661..73b8bf7 100644 --- a/pensive/examples/epsilon_machines.py +++ b/sofic/examples/epsilon_machines.py @@ -23,19 +23,19 @@ import numpy as np -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_FUTURE_SYMBOL, ATTR_PROB, TransitionGraph -from pensive.shifts.tmc import TopologicalMarkovChain -from pensive.states import sequential_labels +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_FUTURE_SYMBOL, ATTR_PROB, TransitionGraph +from sofic.shifts.tmc import TopologicalMarkovChain +from sofic.states import sequential_labels def _stationary_distribution( states: Sequence[Hashable], symbol_matrices: Mapping[Any, np.ndarray], ) -> dict[Hashable, Any]: - from pensive.generators.prob import as_prob, has_symbolic - from pensive.generators.stationary import stationary_distribution_from_transition + from sofic.generators.prob import as_prob, has_symbolic + from sofic.generators.stationary import stationary_distribution_from_transition transition = sum(symbol_matrices.values()) pi = stationary_distribution_from_transition(transition) @@ -56,7 +56,7 @@ def from_symbol_matrices( ``matrices[x][i, j]`` is ``Pr(S' = states[j], X = x | S = states[i])``. Entries may be floats or exact sympy expressions. """ - from pensive.generators.prob import as_prob, has_symbolic, is_positive_mass + from sofic.generators.prob import as_prob, has_symbolic, is_positive_mass state_list = tuple(states) symbol_list = tuple(symbols) @@ -101,7 +101,7 @@ def bernoulli(p: float = 0.5, *, symbols: tuple[Any, Any] = ("0", "1")) -> Epsil """Memoryless (Bernoulli) source with ``P(symbols[0]) = 1 - p``.""" if not 0.0 < p < 1.0: raise ValueError("p must be in (0, 1)") - from pensive.examples.processes import _edge_machine + from sofic.examples.processes import _edge_machine zero, one = symbols state = sequential_labels(1)[0] @@ -145,7 +145,7 @@ def noisy_random_phase_slip() -> EpsilonMachine: emission noise at state ``D``. Prototype for block-convergence figures in *Anatomy of a Bit* :cite:`James2011`. """ - from pensive.examples.processes import _edge_machine + from sofic.examples.processes import _edge_machine states = sequential_labels(5) a, b, c, d, e = states @@ -223,7 +223,7 @@ def golden_mean_bidirectional(p: float = 0.5): Joint states ``(A, C)``, ``(A, D)``, ``(B, C)`` as in Ellison et al., arXiv:0905.3587, Fig.~4(c). """ - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine return BidirectionalEpsilonMachine.from_pair( golden_mean_forward(p), @@ -383,7 +383,7 @@ def butterfly_process() -> EpsilonMachine: 6: "C", 7: "E", } - from pensive.examples.processes import _edge_machine + from sofic.examples.processes import _edge_machine edges = [] for source in states: @@ -400,7 +400,7 @@ def butterfly_process() -> EpsilonMachine: def ellison_fig9_forward() -> EpsilonMachine: """Forward ε-machine from Ellison et al., arXiv:1107.2168, Fig.~9.""" - from pensive.examples.processes import _edge_machine + from sofic.examples.processes import _edge_machine return _edge_machine( [ @@ -439,7 +439,7 @@ def tent_map_misiurewicz_fig7_symbol_matrices( ``C`` and a ``1`` self-loop. When ``a`` is a sympy expression the matrices use object dtype with exact entries. """ - from pensive.generators.prob import is_symbolic, zeros + from sofic.generators.prob import is_symbolic, zeros if a is None: a = tent_map_misiurewicz_a() @@ -471,10 +471,10 @@ def tent_map_misiurewicz_hmm(a: Any | None = None) -> MealyHMM: James, Burke & Crutchfield, *Chaos Forgets and Remembers* (2013), supplement Fig.~6 (right): generating partition overlaid on the Markov-partition chain. Non-unifilar at ``A`` (two ``0`` outs) and ``D`` (three ``1`` outs). - :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_hmm` recovers + :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm` recovers the Fig.~7 ε-machine. """ - from pensive.generators.prob import as_prob, is_symbolic + from sofic.generators.prob import as_prob, is_symbolic if a is None: a = tent_map_misiurewicz_a() @@ -514,7 +514,7 @@ def tent_map_misiurewicz_hmm(a: Any | None = None) -> MealyHMM: # minimal polynomial lets ``EpsilonMachine.from_hmm`` recognize the two # mixed states that coincide only under the constraint and recover the # 4-state Fig.~7 machine. - from pensive.generators.prob import SymbolConstraints + from sofic.generators.prob import SymbolConstraints hmm.symbol_constraints = SymbolConstraints([a**3 - 2 * a - 2]) @@ -570,7 +570,7 @@ def _tent_map_misiurewicz_fig8_edges( Joint labels use ``S⁺:S⁻`` from James et al. (2013), supplement Fig.~8. Edge probabilities are the figure's ``1/2`` and ``a/(a+1)`` templates. """ - from pensive.generators.prob import is_symbolic + from sofic.generators.prob import is_symbolic relabel = _tent_map_misiurewicz_fig8_reverse_relabel() name = { @@ -622,8 +622,8 @@ def _tent_map_misiurewicz_fig8_edges( def _stationary_distribution_from_joint_graph( graph: TransitionGraph, ) -> dict[tuple[str, str], Any]: - from pensive.generators.prob import as_prob, has_symbolic, zeros - from pensive.generators.stationary import stationary_distribution_from_transition + from sofic.generators.prob import as_prob, has_symbolic, zeros + from sofic.generators.stationary import stationary_distribution_from_transition states = list(graph.states()) if not states: @@ -655,7 +655,7 @@ def _project_bidirectional_side( future_symbols: Mapping[str, Any] | None = None, ) -> EpsilonMachine: """Marginalize a hand-built bidirectional graph to an ε-machine presentation.""" - from pensive.generators.prob import ( + from sofic.generators.prob import ( as_prob, is_positive_mass, simplify_prob, @@ -718,8 +718,8 @@ def _project_bidirectional_side( def tent_map_misiurewicz_bidirectional_fig8(a: Any | None = None): """Hand-built supplement Fig.~8 bidirectional ε-machine.""" - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.prob import as_prob, is_symbolic + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.prob import as_prob, is_symbolic if a is None: a = tent_map_misiurewicz_a() @@ -743,14 +743,14 @@ def tent_map_misiurewicz_bidirectional_fig8(a: Any | None = None): future_symbols={"E": 0, "F": 1, "G": 1, "H": -1}, ) if is_symbolic(a): - from pensive.generators.epsilon_machine import _row_normalized_presentation + from sofic.generators.epsilon_machine import _row_normalized_presentation reverse = _row_normalized_presentation(reverse_raw) else: try: reverse = EpsilonMachine.from_hmm(reverse_raw) except Exception: - from pensive.generators.epsilon_machine import _row_normalized_presentation + from sofic.generators.epsilon_machine import _row_normalized_presentation reverse = _row_normalized_presentation(reverse_raw) _annotate_tent_map_misiurewicz_reverse_future_symbols(reverse) @@ -778,7 +778,7 @@ def tent_map_misiurewicz_information_expected(a: Any | None = None) -> dict[str, symbolic. The ephemeral rate is ``r_μ = (1/4)*(3 - 2/(a+1) - 4/(a+2) + 9/(2a+3))``. """ - from pensive.generators.prob import is_symbolic + from sofic.generators.prob import is_symbolic if a is None: a = tent_map_misiurewicz_a() @@ -808,7 +808,7 @@ def ellison_fig9_reverse() -> EpsilonMachine: MSP-derived presentation used for Fig.~15 bidirectional pairing via Eq.~(15). """ - from pensive.generators.reversal import time_reverse_stochastic + from sofic.generators.reversal import time_reverse_stochastic forward = ellison_fig9_forward() reverse = EpsilonMachine.from_hmm(time_reverse_stochastic(forward)) @@ -822,7 +822,7 @@ def ellison_fig15_bidirectional(): Built from the separate forward and reverse presentations in Fig.~9 via Eq.~(15) in the same paper. """ - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine return BidirectionalEpsilonMachine.from_pair( ellison_fig9_forward(), diff --git a/pensive/examples/processes.py b/sofic/examples/processes.py similarity index 98% rename from pensive/examples/processes.py rename to sofic/examples/processes.py index 6dce6f8..a8d7883 100644 --- a/pensive/examples/processes.py +++ b/sofic/examples/processes.py @@ -1,7 +1,7 @@ """cmpy-compatible process constructors. This module ports the public constructors from ``cmpy.machines.processes`` to -pensive-native objects. Quantum and elementary-cellular-automaton helpers are +sofic-native objects. Quantum and elementary-cellular-automaton helpers are intentionally out of scope. """ @@ -14,11 +14,11 @@ import numpy as np -from pensive.automata.transducers import MealyMachine -from pensive.generators.base import QuasiStochasticModel -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_QUASIPROB, ATTR_SYMBOL +from sofic.automata.transducers import MealyMachine +from sofic.generators.base import QuasiStochasticModel +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_OUTPUT, ATTR_PROB, ATTR_QUASIPROB, ATTR_SYMBOL RecurrentEpsilonMachine = EpsilonMachine @@ -50,7 +50,7 @@ def _stationary_initial( states: Sequence[Hashable], edges: Sequence[tuple[Hashable, Hashable, Any, float]], ) -> dict[Hashable, float]: - from pensive.generators.stationary import stationary_distribution_from_transition + from sofic.generators.stationary import stationary_distribution_from_transition if not states: return {} @@ -950,7 +950,7 @@ def NoisyPeriod2(noise: float = 0.5) -> EpsilonMachine: def NRPS() -> EpsilonMachine: - from pensive.examples.epsilon_machines import noisy_random_phase_slip + from sofic.examples.epsilon_machines import noisy_random_phase_slip return noisy_random_phase_slip() diff --git a/pensive/examples/shifts.py b/sofic/examples/shifts.py similarity index 99% rename from pensive/examples/shifts.py rename to sofic/examples/shifts.py index 9917066..ab3a61e 100644 --- a/pensive/examples/shifts.py +++ b/sofic/examples/shifts.py @@ -4,7 +4,7 @@ from collections.abc import Hashable, Sequence -from pensive.shifts.sofic_dyck import SoficDyckShift +from sofic.shifts.sofic_dyck import SoficDyckShift def dyck_shift_order( diff --git a/pensive/exceptions.py b/sofic/exceptions.py similarity index 50% rename from pensive/exceptions.py rename to sofic/exceptions.py index 6a5c440..ec0cfc7 100644 --- a/pensive/exceptions.py +++ b/sofic/exceptions.py @@ -1,33 +1,33 @@ -"""Exceptions raised by pensive.""" +"""Exceptions raised by sofic.""" -class PensiveError(Exception): - """Base class for pensive errors.""" +class SoficError(Exception): + """Base class for sofic errors.""" -class PensiveValidationError(PensiveError): +class SoficValidationError(SoficError): """Raised when a model fails structural or semantic validation.""" -class NonDeterministicError(PensiveValidationError): +class NonDeterministicError(SoficValidationError): """Raised when a DFA determinism invariant is violated.""" -class StochasticValidationError(PensiveValidationError): +class StochasticValidationError(SoficValidationError): """Raised when probability masses are invalid.""" -class UnifilarityError(PensiveValidationError): +class UnifilarityError(SoficValidationError): """Raised when a unifilarity invariant is violated.""" -class QuasiStochasticValidationError(PensiveValidationError): +class QuasiStochasticValidationError(SoficValidationError): """Raised when quasi-stochastic invariants are violated.""" -class LumpabilityError(PensiveValidationError): +class LumpabilityError(SoficValidationError): """Raised when a partition is not strongly lumpable for a model.""" -class InfiniteTransductionError(PensiveError): +class InfiniteTransductionError(SoficError): """Raised when a finite input has infinitely many transducer outputs.""" diff --git a/pensive/generators/__init__.py b/sofic/generators/__init__.py similarity index 64% rename from pensive/generators/__init__.py rename to sofic/generators/__init__.py index b9c8c4e..1e586c8 100644 --- a/pensive/generators/__init__.py +++ b/sofic/generators/__init__.py @@ -1,10 +1,10 @@ """Stochastic and quasiprobabilistic generators.""" -from pensive.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.generators.block_convergence import BlockConvergenceDiagram, BlockConvergenceEstimates -from pensive.generators.block_entropy import BlockEntropyDiagram, BlockEntropyEstimates -from pensive.generators.directional_flow import ( +from sofic.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.block_convergence import BlockConvergenceDiagram, BlockConvergenceEstimates +from sofic.generators.block_entropy import BlockEntropyDiagram, BlockEntropyEstimates +from sofic.generators.directional_flow import ( directed_information, independent_pair_generator, intrinsic_information_flow, @@ -12,12 +12,12 @@ synergistic_information_flow, transfer_entropy, ) -from pensive.generators.epsilon_inference import cssr, subtree_merge -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.lumping import LumpabilityError, is_lumpable, lump, normalize_partition -from pensive.generators.markov import MarkovChain -from pensive.generators.mealy import MealyHMM -from pensive.generators.minimal_generative_model import ( +from sofic.generators.epsilon_inference import cssr, subtree_merge +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.lumping import LumpabilityError, is_lumpable, lump, normalize_partition +from sofic.generators.markov import MarkovChain +from sofic.generators.mealy import MealyHMM +from sofic.generators.minimal_generative_model import ( FunctionalGenerativeModel, GacsKornerGenerativeModel, MinimalGenerativeModel, @@ -27,19 +27,19 @@ minimal_generative_model, wyner_generative_model, ) -from pensive.generators.mixed_state import MixedState, MixedStatePresentation -from pensive.generators.moore import MooreHMM -from pensive.generators.nmachine import NMachine -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.generators.quasi_realization import QuasiRealization -from pensive.generators.stack_hmm import HiddenMarkovStackModel -from pensive.generators.stack_inference import ( +from sofic.generators.mixed_state import MixedState, MixedStatePresentation +from sofic.generators.moore import MooreHMM +from sofic.generators.nmachine import NMachine +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.generators.quasi_realization import QuasiRealization +from sofic.generators.stack_hmm import HiddenMarkovStackModel +from sofic.generators.stack_inference import ( fit_stack_hmm_mle, learn_stack_hmm_papni, stack_cssr, stack_subtree_merge, ) -from pensive.generators.topological_epsilon_enumeration import ( +from sofic.generators.topological_epsilon_enumeration import ( count_topological_epsilon_machines, epsilon_machine_to_idfa_string, idfa_string_to_epsilon_machine, diff --git a/pensive/generators/_word_measures.py b/sofic/generators/_word_measures.py similarity index 88% rename from pensive/generators/_word_measures.py rename to sofic/generators/_word_measures.py index d5fda3e..3266a24 100644 --- a/pensive/generators/_word_measures.py +++ b/sofic/generators/_word_measures.py @@ -1,8 +1,8 @@ """Dit-backed word-distribution measures for block curves. -Leaf module shared by :mod:`pensive.generators.block_entropy` and -:mod:`pensive.generators.block_convergence`. It depends only on ``dit`` (via -:func:`pensive.generators.measures.require_dit`) and must not import either +Leaf module shared by :mod:`sofic.generators.block_entropy` and +:mod:`sofic.generators.block_convergence`. It depends only on ``dit`` (via +:func:`sofic.generators.measures.require_dit`) and must not import either block module, so that both can depend on it without a circular import. """ @@ -11,13 +11,13 @@ from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine _TOL = 1e-15 def _require_dit() -> Any: - from pensive.generators.measures import require_dit + from sofic.generators.measures import require_dit return require_dit("block convergence measures") diff --git a/pensive/generators/alternative_complexity.py b/sofic/generators/alternative_complexity.py similarity index 88% rename from pensive/generators/alternative_complexity.py rename to sofic/generators/alternative_complexity.py index 4c0de6d..c65d118 100644 --- a/pensive/generators/alternative_complexity.py +++ b/sofic/generators/alternative_complexity.py @@ -7,12 +7,12 @@ import numpy as np -from pensive.generators.stochastic import shannon_entropy -from pensive.graph import ATTR_PROB -from pensive.properties import transition_matrix +from sofic.generators.stochastic import shannon_entropy +from sofic.graph import ATTR_PROB +from sofic.properties import transition_matrix if TYPE_CHECKING: - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine def structural_information(machine: EpsilonMachine) -> float: @@ -54,7 +54,7 @@ def spectral_complexity(machine: EpsilonMachine) -> float: Builds the recurrent mixed-state presentation and summarizes the modulus spectrum of its transition operator (Riechers & Crutchfield, 2017). """ - from pensive.generators.mixed_state_construction import build_mixed_state_presentation + from sofic.generators.mixed_state_construction import build_mixed_state_presentation msp = build_mixed_state_presentation(machine) states = tuple(msp.recurrent_states) @@ -96,7 +96,7 @@ def _mean_first_passage_time(transition: np.ndarray, start: np.ndarray, target: def _stationary_vector(transition: np.ndarray) -> np.ndarray | None: - from pensive.generators.stationary import stationary_distribution_from_transition + from sofic.generators.stationary import stationary_distribution_from_transition try: return stationary_distribution_from_transition(transition) diff --git a/pensive/generators/base.py b/sofic/generators/base.py similarity index 75% rename from pensive/generators/base.py rename to sofic/generators/base.py index 69c03b9..0480a02 100644 --- a/pensive/generators/base.py +++ b/sofic/generators/base.py @@ -7,15 +7,15 @@ import numpy as np -from pensive.base import StateMachine -from pensive.exceptions import QuasiStochasticValidationError, StochasticValidationError +from sofic.base import StateMachine +from sofic.exceptions import QuasiStochasticValidationError, StochasticValidationError if TYPE_CHECKING: - from pensive.automata.dfa import DFA - from pensive.automata.nfa import NFA - from pensive.generators.mealy import MealyHMM - from pensive.generators.prob import SymbolConstraints - from pensive.shifts.sofic import SoficShift + from sofic.automata.dfa import DFA + from sofic.automata.nfa import NFA + from sofic.generators.mealy import MealyHMM + from sofic.generators.prob import SymbolConstraints + from sofic.shifts.sofic import SoficShift class StochasticModel(StateMachine): @@ -39,7 +39,7 @@ def validate(self) -> None: self.validate_stochastic() def validate_stochastic(self) -> None: - from pensive.generators.prob import is_symbolic, row_sums_to_one + from sofic.generators.prob import is_symbolic, row_sums_to_one probs = list(self.initial_distribution.values()) if probs and not row_sums_to_one(probs): @@ -54,38 +54,38 @@ def validate_stochastic(self) -> None: self._require(self.graph.has_state(state), f"unknown initial state {state!r}") def stationary_distribution(self) -> np.ndarray: - from pensive.generators.stationary import stationary_distribution_hmm + from sofic.generators.stationary import stationary_distribution_hmm return stationary_distribution_hmm(self) def state_distribution(self) -> Any: - from pensive.generators.measures import state_distribution + from sofic.generators.measures import state_distribution return state_distribution(self) def state_entropy(self) -> float: - from pensive.generators.measures import state_entropy + from sofic.generators.measures import state_entropy return state_entropy(self) def reverse(self) -> Self: """Return the time-reversed generator (stochastic reversal, not a graph transpose). - Overrides :meth:`pensive.base.StateMachine.reverse` (which merely + Overrides :meth:`sofic.base.StateMachine.reverse` (which merely transposes the transition graph): for a stochastic process the reversal must reweight edges by the stationary distribution - (:func:`~pensive.generators.reversal.time_reverse_stochastic`). Emission + (:func:`~sofic.generators.reversal.time_reverse_stochastic`). Emission machines are routed through :meth:`EpsilonMachine.from_hmm`. """ - from pensive.generators.reversal import is_markov_like, time_reverse_stochastic + from sofic.generators.reversal import is_markov_like, time_reverse_stochastic if not is_markov_like(self): - from pensive.generators.epsilon_machine import EpsilonMachine - from pensive.generators.mealy import MealyHMM - from pensive.generators.moore import MooreHMM + from sofic.generators.epsilon_machine import EpsilonMachine + from sofic.generators.mealy import MealyHMM + from sofic.generators.moore import MooreHMM if isinstance(self, (MealyHMM, MooreHMM)): - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine if isinstance(self, EpsilonMachine): return EpsilonMachine.from_time_reversed(self) @@ -104,27 +104,27 @@ def __init__(self, observation_alphabet: frozenset[Any] | None = None, **kwargs: self.observation_alphabet = observation_alphabet if observation_alphabet is not None else frozenset() def sample(self, n: int, rng: np.random.Generator | None = None) -> tuple[list[Any], list[Hashable]]: - from pensive.generators.hmm_inference import sample + from sofic.generators.hmm_inference import sample return sample(self, n, rng) def log_likelihood(self, observations: Sequence[Any]) -> float: - from pensive.generators.hmm_inference import log_likelihood + from sofic.generators.hmm_inference import log_likelihood return log_likelihood(self, observations) def forward(self, observations: Sequence[Any], *, scaled: bool = False) -> np.ndarray: - from pensive.generators.hmm_inference import forward + from sofic.generators.hmm_inference import forward return forward(self, observations, scaled=scaled) def backward(self, observations: Sequence[Any], *, scaled: bool = False) -> np.ndarray: - from pensive.generators.hmm_inference import backward + from sofic.generators.hmm_inference import backward return backward(self, observations, scaled=scaled) def viterbi(self, observations: Sequence[Any]) -> list[Hashable]: - from pensive.generators.hmm_inference import viterbi + from sofic.generators.hmm_inference import viterbi return viterbi(self, observations) @@ -133,18 +133,18 @@ def to_mealy(self) -> MealyHMM: raise NotImplementedError(f"{type(self).__name__} must implement to_mealy()") def entropy_rate(self) -> float: - from pensive.generators.measures import entropy_rate_hmm + from sofic.generators.measures import entropy_rate_hmm return entropy_rate_hmm(self) def joint_block_distribution(self, history_length: int = 1) -> Any: - from pensive.generators.measures import joint_block_distribution + from sofic.generators.measures import joint_block_distribution return joint_block_distribution(self, history_length=history_length) def words_of_length(self, length: int) -> dict[tuple[Any, ...], float]: """Return observed words of ``length`` and their probabilities.""" - from pensive.generators.words import hmm_words_of_length + from sofic.generators.words import hmm_words_of_length return hmm_words_of_length(self, length) @@ -155,7 +155,7 @@ def word_probability( start: Hashable | Mapping[Hashable, float] | Sequence[float] | np.ndarray | None = None, ) -> float: """Return the probability of an observed finite word.""" - from pensive.generators.words import hmm_word_probability + from sofic.generators.words import hmm_word_probability return hmm_word_probability(self, word, start=start) @@ -166,7 +166,7 @@ def log_word_probability( start: Hashable | Mapping[Hashable, float] | Sequence[float] | np.ndarray | None = None, ) -> float: """Return ``log2`` of an observed finite-word probability.""" - from pensive.generators.words import hmm_log_word_probability + from sofic.generators.words import hmm_log_word_probability return hmm_log_word_probability(self, word, start=start) @@ -178,7 +178,7 @@ def word_probabilities( sparse: bool = True, ) -> dict[tuple[Any, ...], float]: """Return observed-word probabilities for one or more lengths.""" - from pensive.generators.words import hmm_word_probabilities + from sofic.generators.words import hmm_word_probabilities return hmm_word_probabilities(self, lengths, start=start, sparse=sparse) @@ -190,7 +190,7 @@ def conditional_word_probability( start: Hashable | Mapping[Hashable, float] | Sequence[float] | np.ndarray | None = None, ) -> float: """Return ``P(word | condition)``.""" - from pensive.generators.words import hmm_conditional_word_probability + from sofic.generators.words import hmm_conditional_word_probability return hmm_conditional_word_probability(self, word, condition, start=start) @@ -204,25 +204,25 @@ def is_equal_process( atol: float | None = None, ) -> bool: """Return whether two HMMs generate the same finite-word process.""" - from pensive.generators.process_equivalence import is_equal_process + from sofic.generators.process_equivalence import is_equal_process return is_equal_process(self, other, start1=start1, start2=start2, rtol=rtol, atol=atol) def to_sofic_shift(self) -> SoficShift: """Strip probabilities and return a sofic shift with the same support.""" - from pensive.generators.conversions import hmm_to_sofic_shift + from sofic.generators.conversions import hmm_to_sofic_shift return hmm_to_sofic_shift(self) def to_support_nfa(self) -> NFA: """Return an NFA for the support language of this HMM.""" - from pensive.generators.conversions import hmm_to_support_nfa + from sofic.generators.conversions import hmm_to_support_nfa return hmm_to_support_nfa(self) def to_support_dfa(self) -> DFA: """Return a determinized automaton for the support language of this HMM.""" - from pensive.generators.conversions import hmm_to_support_dfa + from sofic.generators.conversions import hmm_to_support_dfa return hmm_to_support_dfa(self) @@ -247,32 +247,32 @@ def validate_quasistochastic(self) -> None: self._require(self.graph.has_state(state), f"unknown initial state {state!r}") def word_probability(self, word: Sequence[Any]) -> float: - from pensive.generators.quasi_inference import word_probability + from sofic.generators.quasi_inference import word_probability return word_probability(self, word) def stationary_quasidistribution(self) -> np.ndarray: - from pensive.generators.quasi_inference import stationary_quasidistribution + from sofic.generators.quasi_inference import stationary_quasidistribution return stationary_quasidistribution(self) def transition_matrices(self) -> dict[Any, np.ndarray]: - from pensive.generators.quasi_inference import transition_matrices + from sofic.generators.quasi_inference import transition_matrices return transition_matrices(self) def words_of_length(self, length: int) -> dict[tuple[Any, ...], float]: """Return words of ``length`` and their signed quasiprobabilities.""" - from pensive.generators.words import quasi_words_of_length + from sofic.generators.words import quasi_words_of_length return quasi_words_of_length(self, length) def collision_entropy(self) -> float: - from pensive.generators.measures import collision_entropy + from sofic.generators.measures import collision_entropy return collision_entropy(self) def process_negativity(self) -> float: - from pensive.generators.measures import process_negativity + from sofic.generators.measures import process_negativity return process_negativity(self) diff --git a/pensive/generators/bidirectional_construction.py b/sofic/generators/bidirectional_construction.py similarity index 96% rename from pensive/generators/bidirectional_construction.py rename to sofic/generators/bidirectional_construction.py index c9fe230..cd25da4 100644 --- a/pensive/generators/bidirectional_construction.py +++ b/sofic/generators/bidirectional_construction.py @@ -8,10 +8,10 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.prob import ( +from sofic.exceptions import StochasticValidationError +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.prob import ( as_prob, has_symbolic, is_positive_mass, @@ -23,13 +23,13 @@ sum_probs, zeros, ) -from pensive.generators.reversal import time_reverse_stochastic -from pensive.generators.stationary import ( +from sofic.generators.reversal import time_reverse_stochastic +from sofic.generators.stationary import ( stationary_distribution_from_transition, stationary_distribution_hmm, ) -from pensive.graph import ATTR_EMISSION, ATTR_FUTURE_SYMBOL, ATTR_PROB, TransitionGraph -from pensive.states import next_sequential_label_index, sequential_labels +from sofic.graph import ATTR_EMISSION, ATTR_FUTURE_SYMBOL, ATTR_PROB, TransitionGraph +from sofic.states import next_sequential_label_index, sequential_labels def build_bidirectional_epsilon_machine( @@ -215,8 +215,7 @@ def _compatible_pairs( def _forward_emits(forward: EpsilonMachine, state: Hashable, symbol: Any) -> bool: return any( - transition.data.get(ATTR_EMISSION) == symbol - and is_positive_mass(as_prob(transition.data.get(ATTR_PROB, 0.0))) + transition.data.get(ATTR_EMISSION) == symbol and is_positive_mass(as_prob(transition.data.get(ATTR_PROB, 0.0))) for transition in forward.graph.out_transitions(state) ) @@ -254,9 +253,7 @@ def _joint_pi_on_pair_subset( index = {state: i for i, state in enumerate(states)} n = len(states) edge_probs = [ - as_prob(transition.data.get(ATTR_PROB, 0.0)) - for state in states - for transition in graph.out_transitions(state) + as_prob(transition.data.get(ATTR_PROB, 0.0)) for state in states for transition in graph.out_transitions(state) ] symbolic = has_symbolic(edge_probs) matrix = zeros((n, n), symbolic=symbolic) @@ -281,11 +278,7 @@ def _joint_pi_on_pair_subset( return None if symbolic: - joint = { - states[i]: simplify_prob(as_prob(pi[i])) - for i in range(n) - if is_positive_mass(pi[i]) - } + joint = {states[i]: simplify_prob(as_prob(pi[i])) for i in range(n) if is_positive_mass(pi[i])} else: joint = {states[i]: float(pi[i]) for i in range(n) if float(pi[i]) > tol} return joint or None @@ -474,7 +467,7 @@ def _relabel_epsilon_machine( machine: EpsilonMachine, mapping: Mapping[Hashable, Hashable], ) -> EpsilonMachine: - from pensive.generators.stochastic import normalize_row_weights + from sofic.generators.stochastic import normalize_row_weights graph = TransitionGraph() for state in machine.states(): @@ -639,7 +632,7 @@ def bidirectional_step_distribution(bidir: BidirectionalEpsilonMachine) -> Any: def _require_dit_for_step(): - from pensive.generators.measures import require_dit + from sofic.generators.measures import require_dit return require_dit("bidirectional step distributions") diff --git a/pensive/generators/bidirectional_epsilon_machine.py b/sofic/generators/bidirectional_epsilon_machine.py similarity index 87% rename from pensive/generators/bidirectional_epsilon_machine.py rename to sofic/generators/bidirectional_epsilon_machine.py index 32fcfe3..32006f5 100644 --- a/pensive/generators/bidirectional_epsilon_machine.py +++ b/sofic/generators/bidirectional_epsilon_machine.py @@ -5,9 +5,9 @@ from collections.abc import Hashable from typing import Any, Self -from pensive.exceptions import PensiveValidationError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM +from sofic.exceptions import SoficValidationError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM _STEP_S_PLUS_0 = 0 _STEP_S_MINUS_0 = 1 @@ -17,7 +17,7 @@ def _require_dit(): - from pensive.generators.measures import require_dit + from sofic.generators.measures import require_dit return require_dit("entropy measures") @@ -38,7 +38,7 @@ class BidirectionalEpsilonMachine(MealyHMM): Examples -------- - >>> from pensive.examples import golden_mean_bidirectional + >>> from sofic.examples import golden_mean_bidirectional >>> bidir = golden_mean_bidirectional(0.5) >>> bidir.entropy_rate() > 0 True @@ -56,7 +56,7 @@ def __init__( ) -> None: super().__init__(**kwargs) if forward_machine is None or reverse_machine is None: - raise PensiveValidationError("forward_machine and reverse_machine are required") + raise SoficValidationError("forward_machine and reverse_machine are required") self.forward_machine = forward_machine self.reverse_machine = reverse_machine @@ -64,11 +64,11 @@ def validate(self) -> None: super().validate_stochastic() for state in self.states(): if not isinstance(state, tuple) or len(state) != 2: - raise PensiveValidationError(f"bidirectional state must be (forward, reverse) pair, got {state!r}") + raise SoficValidationError(f"bidirectional state must be (forward, reverse) pair, got {state!r}") def is_unifilar(self) -> bool: """Return whether joint emissions are row-unifilar (usually ``False``).""" - from pensive.properties import is_unifilar_emissions + from sofic.properties import is_unifilar_emissions return is_unifilar_emissions(self) @@ -87,13 +87,13 @@ def from_pair( forward: EpsilonMachine, reverse: EpsilonMachine, ) -> BidirectionalEpsilonMachine: - from pensive.generators.bidirectional_construction import build_bidirectional_epsilon_machine + from sofic.generators.bidirectional_construction import build_bidirectional_epsilon_machine return build_bidirectional_epsilon_machine(forward, reverse) @classmethod def from_forward(cls, forward: EpsilonMachine) -> BidirectionalEpsilonMachine: - from pensive.generators.bidirectional_construction import infer_reverse_epsilon_machine + from sofic.generators.bidirectional_construction import infer_reverse_epsilon_machine reverse = infer_reverse_epsilon_machine(forward) return cls.from_pair(forward, reverse) @@ -101,22 +101,22 @@ def from_forward(cls, forward: EpsilonMachine) -> BidirectionalEpsilonMachine: def joint_distribution(self) -> dict[tuple[Hashable, Hashable], Any]: if self._joint_pi is not None: return dict(self._joint_pi) - from pensive.generators.bidirectional_construction import joint_distribution + from sofic.generators.bidirectional_construction import joint_distribution return joint_distribution(self) def forward_epsilon_machine(self) -> EpsilonMachine: - from pensive.generators.bidirectional_construction import forward_epsilon_machine + from sofic.generators.bidirectional_construction import forward_epsilon_machine return forward_epsilon_machine(self) def reverse_epsilon_machine(self) -> EpsilonMachine: - from pensive.generators.bidirectional_construction import reverse_epsilon_machine + from sofic.generators.bidirectional_construction import reverse_epsilon_machine return reverse_epsilon_machine(self) def step_distribution(self) -> Any: - from pensive.generators.bidirectional_construction import bidirectional_step_distribution + from sofic.generators.bidirectional_construction import bidirectional_step_distribution return bidirectional_step_distribution(self) @@ -274,7 +274,7 @@ def excess_entropy(self) -> Any: if not joint: return 0.0 - from pensive.generators.prob import as_prob, has_symbolic, sum_probs + from sofic.generators.prob import as_prob, has_symbolic, sum_probs pi_plus: dict[Any, Any] = {} pi_minus: dict[Any, Any] = {} @@ -294,17 +294,11 @@ def excess_entropy(self) -> Any: plus_dist = symbolic_distribution(plus_outcomes, plus_pmf) minus_dist = symbolic_distribution(minus_outcomes, minus_pmf) joint_dist = symbolic_distribution(joint_outcomes, joint_pmf) - return ( - dit.shannon.entropy(plus_dist) - + dit.shannon.entropy(minus_dist) - - dit.shannon.entropy(joint_dist) - ) + return dit.shannon.entropy(plus_dist) + dit.shannon.entropy(minus_dist) - dit.shannon.entropy(joint_dist) plus_dist = dit.Distribution(plus_outcomes, plus_pmf) minus_dist = dit.Distribution(minus_outcomes, minus_pmf) joint_dist = dit.Distribution(joint_outcomes, joint_pmf) - return float( - dit.shannon.entropy(plus_dist) + dit.shannon.entropy(minus_dist) - dit.shannon.entropy(joint_dist) - ) + return float(dit.shannon.entropy(plus_dist) + dit.shannon.entropy(minus_dist) - dit.shannon.entropy(joint_dist)) def statistical_complexity(self) -> Any: """C± = H[S⁺, S⁻] under the bidirectional stationary distribution.""" @@ -314,7 +308,7 @@ def statistical_complexity(self) -> Any: return 0.0 outcomes = list(joint.keys()) probs = [joint[outcome] for outcome in outcomes] - from pensive.generators.prob import has_symbolic + from sofic.generators.prob import has_symbolic if has_symbolic(probs): from dit.symbolic import symbolic_distribution @@ -328,25 +322,25 @@ def crypticity(self) -> Any: def minimal_generative_model(self, **kwargs: Any) -> Any: """Construct the minimal-state-entropy generative presentation.""" - from pensive.generators.minimal_generative_model import minimal_generative_model + from sofic.generators.minimal_generative_model import minimal_generative_model return minimal_generative_model(self, **kwargs) def wyner_generative_model(self, **kwargs: Any) -> Any: """Construct the Wyner-common-information generative presentation.""" - from pensive.generators.minimal_generative_model import wyner_generative_model + from sofic.generators.minimal_generative_model import wyner_generative_model return wyner_generative_model(self, **kwargs) def functional_generative_model(self, **kwargs: Any) -> Any: """Construct the functional-common-information generative presentation.""" - from pensive.generators.minimal_generative_model import functional_generative_model + from sofic.generators.minimal_generative_model import functional_generative_model return functional_generative_model(self, **kwargs) def gacs_korner_generative_model(self, **kwargs: Any) -> Any: """Construct the Gács-Körner (deterministic meet) generative presentation.""" - from pensive.generators.minimal_generative_model import gacs_korner_generative_model + from sofic.generators.minimal_generative_model import gacs_korner_generative_model return gacs_korner_generative_model(self, **kwargs) diff --git a/pensive/generators/block_convergence.py b/sofic/generators/block_convergence.py similarity index 99% rename from pensive/generators/block_convergence.py rename to sofic/generators/block_convergence.py index 72e09fe..7800981 100644 --- a/pensive/generators/block_convergence.py +++ b/sofic/generators/block_convergence.py @@ -8,14 +8,14 @@ import numpy as np -from pensive.generators._word_measures import ( +from sofic.generators._word_measures import ( _block_caekl, _block_coinformation, _block_residual_entropy, _block_total_correlation, _block_word_distribution, ) -from pensive.generators.block_entropy import ( +from sofic.generators.block_entropy import ( CMExtensionEstimates, _block_entropy_curves, _cm_extension_curves, @@ -26,7 +26,7 @@ ) if TYPE_CHECKING: - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine _TOL = 1e-15 _CAEKL_RATE_STABLE_STEPS = 2 diff --git a/pensive/generators/block_entropy.py b/sofic/generators/block_entropy.py similarity index 98% rename from pensive/generators/block_entropy.py rename to sofic/generators/block_entropy.py index ff9f380..64a8ca0 100644 --- a/pensive/generators/block_entropy.py +++ b/sofic/generators/block_entropy.py @@ -10,7 +10,7 @@ import numpy as np if TYPE_CHECKING: - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine _TOL = 1e-15 @@ -183,7 +183,7 @@ class CMExtensionEstimates: These nine finite-block curves are computed identically for :class:`BlockEntropyEstimates` and - :class:`~pensive.generators.block_convergence.BlockConvergenceEstimates` + :class:`~sofic.generators.block_convergence.BlockConvergenceEstimates` (see :func:`_cm_extension_curves`). """ @@ -423,7 +423,7 @@ def _block_entropy_curves( def _stationary_symbol_matrices(machine: EpsilonMachine) -> tuple[np.ndarray, dict[Any, np.ndarray]]: - from pensive.generators.hmm_inference import _emission_transition_tensors + from sofic.generators.hmm_inference import _emission_transition_tensors pi = machine.stationary_distribution() _, raw_matrices = _emission_transition_tensors(machine) @@ -560,7 +560,7 @@ def _cm_extension_curves( def _residual_entropy_curve(machine: EpsilonMachine, max_length: int) -> np.ndarray: - from pensive.generators._word_measures import residual_entropy_from_distribution + from sofic.generators._word_measures import residual_entropy_from_distribution residual = np.zeros(max_length + 1, dtype=float) for length in range(1, max_length + 1): @@ -569,7 +569,7 @@ def _residual_entropy_curve(machine: EpsilonMachine, max_length: int) -> np.ndar def _entropy(probabilities: Iterable[float]) -> float: - from pensive.generators.stochastic import shannon_entropy + from sofic.generators.stochastic import shannon_entropy return shannon_entropy(probabilities, normalize=True, atol=_TOL) diff --git a/pensive/generators/conversions.py b/sofic/generators/conversions.py similarity index 87% rename from pensive/generators/conversions.py rename to sofic/generators/conversions.py index 05f23f3..b5abf5b 100644 --- a/pensive/generators/conversions.py +++ b/sofic/generators/conversions.py @@ -8,17 +8,17 @@ import numpy as np if TYPE_CHECKING: - from pensive.generators.synchronization import TopologicalUnifilarGraph - -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM -from pensive.generators.nmachine import NMachine -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.generators.quasi_realization import QuasiRealization -from pensive.graph import ( + from sofic.generators.synchronization import TopologicalUnifilarGraph + +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM +from sofic.generators.nmachine import NMachine +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.generators.quasi_realization import QuasiRealization +from sofic.graph import ( ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, @@ -27,8 +27,8 @@ EPSILON, TransitionGraph, ) -from pensive.shifts.sofic import SoficShift -from pensive.states import sequential_labels +from sofic.shifts.sofic import SoficShift +from sofic.states import sequential_labels def moore_to_mealy(moore: MooreHMM) -> MealyHMM: @@ -122,11 +122,11 @@ def _support_graph(hmm: MealyHMM, *, edge_attr: str) -> TransitionGraph: def _fresh_start_state(states: frozenset[Hashable]) -> Hashable: - start: Hashable = ("__pensive_hmm_start__",) + start: Hashable = ("__sofic_hmm_start__",) suffix = 0 while start in states: suffix += 1 - start = ("__pensive_hmm_start__", suffix) + start = ("__sofic_hmm_start__", suffix) return start @@ -170,13 +170,13 @@ def nmachine_from_quasi_realization( def hmm_to_edge_machine(hmm: MealyHMM | MooreHMM, iterations: int = 1, style: int = 0) -> MealyHMM: """Convert an HMM to its edge (generator) presentation.""" - from pensive.generators.edge_machine import hmm_to_edge_machine as _build + from sofic.generators.edge_machine import hmm_to_edge_machine as _build return _build(hmm, iterations=iterations, style=style) def epsilon_machine_to_unifilar_graph(eps: MealyHMM) -> TopologicalUnifilarGraph: """Strip emission-labeled transitions to a topological unifilar graph.""" - from pensive.generators.synchronization import graph_from_epsilon_machine + from sofic.generators.synchronization import graph_from_epsilon_machine return graph_from_epsilon_machine(eps) diff --git a/pensive/generators/directional_flow.py b/sofic/generators/directional_flow.py similarity index 95% rename from pensive/generators/directional_flow.py rename to sofic/generators/directional_flow.py index a48b3c9..e5e9f6c 100644 --- a/pensive/generators/directional_flow.py +++ b/sofic/generators/directional_flow.py @@ -7,19 +7,19 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel, StochasticModel -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.generators.base import HiddenMarkovModel, StochasticModel +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _require_dit(): - from pensive.generators.measures import require_dit + from sofic.generators.measures import require_dit return require_dit("directional flow") def _pair_block_distribution(generator: HiddenMarkovModel, *, history: int) -> Any: """Joint law over flattened ``(x0, y0, x1, y1, ...)`` windows.""" - from pensive.generators.hmm_inference import _stationary_emission_tensors + from sofic.generators.hmm_inference import _stationary_emission_tensors dit = _require_dit() # Directional-flow statistics describe the stationary joint process, so weight @@ -194,8 +194,8 @@ def independent_pair_generator( right: StochasticModel, ) -> HiddenMarkovModel: """Build an independent pair generator emitting ``(x, y)`` tuple symbols.""" - from pensive.generators.mealy import MealyHMM - from pensive.graph import TransitionGraph + from sofic.generators.mealy import MealyHMM + from sofic.graph import TransitionGraph left_states = tuple(left.states()) right_states = tuple(right.states()) diff --git a/pensive/generators/edge_emissions.py b/sofic/generators/edge_emissions.py similarity index 88% rename from pensive/generators/edge_emissions.py rename to sofic/generators/edge_emissions.py index 1a87b9b..47e6bbd 100644 --- a/pensive/generators/edge_emissions.py +++ b/sofic/generators/edge_emissions.py @@ -4,9 +4,9 @@ from typing import Any -from pensive.exceptions import StochasticValidationError -from pensive.generators.prob import is_symbolic, row_sums_to_one -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.prob import is_symbolic, row_sums_to_one +from sofic.graph import ATTR_EMISSION, ATTR_PROB def validate_stochastic_edge_emissions( diff --git a/pensive/generators/edge_machine.py b/sofic/generators/edge_machine.py similarity index 97% rename from pensive/generators/edge_machine.py rename to sofic/generators/edge_machine.py index 4089111..93b3de4 100644 --- a/pensive/generators/edge_machine.py +++ b/sofic/generators/edge_machine.py @@ -10,9 +10,9 @@ from collections import defaultdict from typing import Any -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph EdgeState = tuple[Any, ...] diff --git a/pensive/generators/epsilon_construction.py b/sofic/generators/epsilon_construction.py similarity index 88% rename from pensive/generators/epsilon_construction.py rename to sofic/generators/epsilon_construction.py index 1dca474..490e444 100644 --- a/pensive/generators/epsilon_construction.py +++ b/sofic/generators/epsilon_construction.py @@ -14,12 +14,12 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.generators.mixed_state import MixedStatePresentation -from pensive.generators.prob import ( +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.generators.mixed_state import MixedStatePresentation +from sofic.generators.prob import ( array_sum, as_prob, canonical_prob_key, @@ -29,8 +29,8 @@ simplify_prob, sum_probs, ) -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph -from pensive.states import sequential_labels +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.states import sequential_labels TransitionSignature = tuple[tuple[Any, int, Any], ...] @@ -42,9 +42,7 @@ def build_epsilon_machine(hmm: HiddenMarkovModel) -> EpsilonMachine: if is_zero(array_sum(stationary)): raise StochasticValidationError("generator must have a stationary distribution") - constraints = getattr(presentation, "symbol_constraints", None) or getattr( - hmm, "symbol_constraints", None - ) + constraints = getattr(presentation, "symbol_constraints", None) or getattr(hmm, "symbol_constraints", None) partitions = _refine_probabilistic_partitions(presentation, constraints=constraints) return _quotient_machine(presentation, partitions, stationary, constraints=constraints) @@ -167,9 +165,7 @@ def _quotient_machine( if is_zero(sum_probs(initial)): idx = hmm.reindex() for state, mass in zip(idx.states, stationary, strict=False): - initial[state_map[state]] = simplify_prob( - as_prob(initial[state_map[state]]) + as_prob(mass) - ) + initial[state_map[state]] = simplify_prob(as_prob(initial[state_map[state]]) + as_prob(mass)) total = sum_probs(initial) initial_dist = { label_for_index[i]: simplify_prob(as_prob(initial[i]) / total) @@ -185,9 +181,7 @@ def _quotient_machine( for state, mass in zip(idx.states, stationary, strict=False): initial[state_map[state]] += float(mass) initial /= initial.sum() - initial_dist = { - label_for_index[i]: float(initial[i]) for i in range(len(partitions)) if initial[i] > 0.0 - } + initial_dist = {label_for_index[i]: float(initial[i]) for i in range(len(partitions)) if initial[i] > 0.0} eps = EpsilonMachine( graph=graph, diff --git a/pensive/generators/epsilon_inference.py b/sofic/generators/epsilon_inference.py similarity index 99% rename from pensive/generators/epsilon_inference.py rename to sofic/generators/epsilon_inference.py index 36a2968..0d4ff4e 100644 --- a/pensive/generators/epsilon_inference.py +++ b/sofic/generators/epsilon_inference.py @@ -14,9 +14,9 @@ import numpy as np from scipy import stats -from pensive.exceptions import StochasticValidationError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.exceptions import StochasticValidationError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph History = tuple[Any, ...] diff --git a/pensive/generators/epsilon_machine.py b/sofic/generators/epsilon_machine.py similarity index 86% rename from pensive/generators/epsilon_machine.py rename to sofic/generators/epsilon_machine.py index 45ed8f8..2b54246 100644 --- a/pensive/generators/epsilon_machine.py +++ b/sofic/generators/epsilon_machine.py @@ -5,13 +5,13 @@ from collections.abc import Hashable, Sequence from typing import TYPE_CHECKING, Any, Literal, Self -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM if TYPE_CHECKING: - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.block_entropy import BlockEntropyDiagram - from pensive.generators.minimal_generative_model import ( + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.block_entropy import BlockEntropyDiagram + from sofic.generators.minimal_generative_model import ( FunctionalGenerativeModel, GacsKornerGenerativeModel, MinimalGenerativeModel, @@ -27,7 +27,7 @@ class EpsilonMachine(MealyHMM): Examples -------- - >>> from pensive.examples import golden_mean + >>> from sofic.examples import golden_mean >>> eps = golden_mean(0.5) >>> eps.markov_order() 1 @@ -42,7 +42,7 @@ def validate(self) -> None: @classmethod def from_hmm(cls, hmm: MealyHMM | MooreHMM, **kwargs: Any) -> EpsilonMachine: - from pensive.generators.epsilon_construction import build_epsilon_machine + from sofic.generators.epsilon_construction import build_epsilon_machine return build_epsilon_machine(hmm) @@ -64,15 +64,15 @@ def from_sequence( ``"cssr"`` for Causal-State Splitting Reconstruction, or ``"subtree"`` for depth-``L`` subtree merging (pass ``L=...``). **kwargs - Forwarded to :func:`~pensive.generators.epsilon_inference.cssr` or - :func:`~pensive.generators.epsilon_inference.subtree_merge`. + Forwarded to :func:`~sofic.generators.epsilon_inference.cssr` or + :func:`~sofic.generators.epsilon_inference.subtree_merge`. """ if method == "cssr": - from pensive.generators.epsilon_inference import cssr + from sofic.generators.epsilon_inference import cssr return cssr(sequence, **kwargs) if method == "subtree": - from pensive.generators.epsilon_inference import subtree_merge + from sofic.generators.epsilon_inference import subtree_merge return subtree_merge(sequence, **kwargs) raise ValueError(f"unknown inference method {method!r}") @@ -92,7 +92,7 @@ def to_bidirectional(self) -> BidirectionalEpsilonMachine: """Return the bidirectional presentation, building and caching on first use.""" fingerprint = self._bidirectional_cache_fingerprint() if self._bidirectional_machine is None or self._bidirectional_machine_fingerprint != fingerprint: - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine self._bidirectional_machine = BidirectionalEpsilonMachine.from_forward(self) self._bidirectional_machine_fingerprint = fingerprint @@ -130,7 +130,7 @@ def bidirectional_statistical_complexity(self) -> float: def excess_entropy(self) -> float: """Excess entropy ``E`` from the bidirectional machine when available.""" - from pensive.generators.block_entropy import _excess_entropy + from sofic.generators.block_entropy import _excess_entropy return _excess_entropy(self) @@ -172,7 +172,7 @@ def caekl_causal_information(self) -> float: def block_entropy_diagram(self, max_length: int) -> BlockEntropyDiagram: """Compute finite-block entropy convergence curves up to ``max_length``.""" - from pensive.generators.block_entropy import block_entropy_diagram + from sofic.generators.block_entropy import block_entropy_diagram return block_entropy_diagram(self, max_length) @@ -190,7 +190,7 @@ def block_entropy_estimates( block-convergence entry points. Pass ``use_exact=False`` for genuinely finite-length estimates (see :meth:`approximate_entropy_rate`). """ - from pensive.generators.block_entropy import block_entropy_estimates + from sofic.generators.block_entropy import block_entropy_estimates return block_entropy_estimates(self, max_length, entropy_rate=entropy_rate, use_exact=use_exact) @@ -213,13 +213,13 @@ def approximate_information_anatomy( def plot_block_entropy_diagram(self, max_length: int, ax: Any | None = None, **kwargs: Any) -> Any: """Compute and plot finite-block entropy convergence curves.""" - from pensive.generators.block_entropy import plot_block_entropy_diagram + from sofic.generators.block_entropy import plot_block_entropy_diagram return plot_block_entropy_diagram(self, max_length, ax=ax, **kwargs) def block_convergence_diagram(self, max_length: int, **kwargs: Any) -> Any: """James et al. (2011) block convergence curves up to ``max_length``.""" - from pensive.generators.block_convergence import block_convergence_diagram + from sofic.generators.block_convergence import block_convergence_diagram return block_convergence_diagram(self, max_length, **kwargs) @@ -232,7 +232,7 @@ def block_convergence_estimates( max_caekl_length: int | None = None, ) -> Any: """Finite-block anatomy estimates including TC, DTC, coinformation, and CAEKL.""" - from pensive.generators.block_convergence import block_convergence_estimates + from sofic.generators.block_convergence import block_convergence_estimates return block_convergence_estimates( self, @@ -244,14 +244,14 @@ def block_convergence_estimates( def caekl_block_information(self, length: int) -> float: """Block CAEKL mutual information ``J(ℓ)`` from exact word probabilities.""" - from pensive.generators.block_convergence import block_caekl + from sofic.generators.block_convergence import block_caekl return block_caekl(self, length) def caekl_rate(self, max_length: int, **kwargs: Any) -> float: """Asymptotic CAEKL rate ``j_μ`` from finite-block convergence. - When :attr:`~pensive.generators.block_convergence.BlockConvergenceEstimates.caekl_rate_converged` + When :attr:`~sofic.generators.block_convergence.BlockConvergenceEstimates.caekl_rate_converged` is ``True``, the returned rate equals ``J(ℓ) - J(ℓ-1)`` for all sufficiently large ``ℓ`` in the affine tail. """ @@ -267,7 +267,7 @@ def caekl_rate_converged(self, max_length: int, **kwargs: Any) -> bool: def plot_block_convergence_diagram(self, max_length: int, ax: Any | None = None, **kwargs: Any) -> Any: """Compute and plot James et al. (2011) block convergence curves.""" - from pensive.generators.block_convergence import plot_block_convergence_diagram + from sofic.generators.block_convergence import plot_block_convergence_diagram return plot_block_convergence_diagram(self, max_length, ax=ax, **kwargs) @@ -343,19 +343,19 @@ def predictability_gain(self, max_length: int) -> float: def structural_information(self) -> float: """Asymptotic structural information (Feldman & Crutchfield, 1998).""" - from pensive.generators.alternative_complexity import structural_information + from sofic.generators.alternative_complexity import structural_information return structural_information(self) def thermodynamic_depth(self) -> float: """Thermodynamic depth of causal states (Shalizi & Crutchfield, 1999).""" - from pensive.generators.alternative_complexity import thermodynamic_depth + from sofic.generators.alternative_complexity import thermodynamic_depth return thermodynamic_depth(self) def spectral_complexity(self) -> float: """Spectral entropy of mixed-state transition eigenvalues (Riechers & Crutchfield, 2017).""" - from pensive.generators.alternative_complexity import spectral_complexity + from sofic.generators.alternative_complexity import spectral_complexity return spectral_complexity(self) @@ -365,7 +365,7 @@ def markov_order(self) -> int | float: Topological (probability-independent); see James et al., arXiv:1010.5545. Returns ``math.inf`` when no finite ``R`` exists. """ - from pensive.generators.synchronization import graph_from_epsilon_machine, markov_order_from_graph + from sofic.generators.synchronization import graph_from_epsilon_machine, markov_order_from_graph return markov_order_from_graph(graph_from_epsilon_machine(self)) @@ -380,24 +380,24 @@ def cryptic_order(self) -> int | float: """Cryptic order ``k_chi``: retrodiction depth after synchronization. Distinct from :meth:`crypticity` (``χ = C_μ − E``) and from - :meth:`~pensive.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.crypticity` + :meth:`~sofic.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.crypticity` (``χ = C± − E``). See James et al., arXiv:1010.5545. """ - from pensive.generators.synchronization import cryptic_order_from_graph, graph_from_epsilon_machine + from sofic.generators.synchronization import cryptic_order_from_graph, graph_from_epsilon_machine return cryptic_order_from_graph(graph_from_epsilon_machine(self)) def is_exactly_synchronizable(self) -> bool: """Return whether the presentation has finite Markov order.""" - from pensive.generators.synchronization import graph_from_epsilon_machine, is_exactly_synchronizable + from sofic.generators.synchronization import graph_from_epsilon_machine, is_exactly_synchronizable return is_exactly_synchronizable(graph_from_epsilon_machine(self)) @classmethod def from_time_reversed(cls, forward: EpsilonMachine) -> EpsilonMachine: """Build a reverse ε-machine presentation from ``forward``.""" - from pensive.exceptions import StochasticValidationError, UnifilarityError - from pensive.generators.reversal import time_reverse_stochastic + from sofic.exceptions import StochasticValidationError, UnifilarityError + from sofic.generators.reversal import time_reverse_stochastic rev_hmm = time_reverse_stochastic(forward) try: @@ -407,9 +407,9 @@ def from_time_reversed(cls, forward: EpsilonMachine) -> EpsilonMachine: def _row_normalized_presentation(hmm: MealyHMM) -> EpsilonMachine: - from pensive.generators.prob import as_prob, simplify_prob - from pensive.generators.stochastic import normalize_row_weights - from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph + from sofic.generators.prob import as_prob, simplify_prob + from sofic.generators.stochastic import normalize_row_weights + from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph graph = TransitionGraph() for state in hmm.states(): diff --git a/pensive/generators/hmm_inference.py b/sofic/generators/hmm_inference.py similarity index 96% rename from pensive/generators/hmm_inference.py rename to sofic/generators/hmm_inference.py index 386d76a..5aaeaec 100644 --- a/pensive/generators/hmm_inference.py +++ b/sofic/generators/hmm_inference.py @@ -7,8 +7,8 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.generators.base import HiddenMarkovModel +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _as_mealy_hmm(hmm: HiddenMarkovModel) -> Any: @@ -20,7 +20,7 @@ def _emission_transition_tensors_from_mealy( hmm: Any, ) -> tuple[np.ndarray, dict[Any, np.ndarray]]: """Return initial vector ``pi`` and symbol -> joint transition matrices.""" - from pensive.generators.prob import as_prob, has_symbolic, zeros + from sofic.generators.prob import as_prob, has_symbolic, zeros idx = hmm.reindex() n = len(idx) @@ -64,8 +64,8 @@ def _stationary_emission_tensors( indexing; it falls back to the initial vector only when no stationary law can be found (e.g. a degenerate generator). """ - from pensive.generators.prob import zeros - from pensive.generators.stationary import stationary_distribution_from_transition + from sofic.generators.prob import zeros + from sofic.generators.stationary import stationary_distribution_from_transition pi_initial, joint = _emission_transition_tensors(hmm) n = len(pi_initial) diff --git a/pensive/generators/lumping.py b/sofic/generators/lumping.py similarity index 91% rename from pensive/generators/lumping.py rename to sofic/generators/lumping.py index bd21e9d..766cf1b 100644 --- a/pensive/generators/lumping.py +++ b/sofic/generators/lumping.py @@ -10,13 +10,13 @@ Hidden Markov models extend the condition to each emitted symbol so that the lumped model generates the same observed process: -* :class:`~pensive.generators.mealy.MealyHMM` -- the joint block-and-symbol mass +* :class:`~sofic.generators.mealy.MealyHMM` -- the joint block-and-symbol mass ``sum_{t in B_j} P(t, o | s)`` must be constant across ``s`` in a block. -* :class:`~pensive.generators.moore.MooreHMM` -- additionally the state emission +* :class:`~sofic.generators.moore.MooreHMM` -- additionally the state emission law ``P(o | s)`` must be identical across a block. The public entry points are :func:`is_lumpable` (predicate) and :func:`lump` -(constructor). ``lump`` raises :class:`~pensive.exceptions.LumpabilityError` when +(constructor). ``lump`` raises :class:`~sofic.exceptions.LumpabilityError` when the partition is not strongly lumpable unless ``check=False``. """ @@ -27,14 +27,14 @@ import numpy as np -from pensive.exceptions import LumpabilityError -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, TransitionGraph +from sofic.exceptions import LumpabilityError +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, TransitionGraph if TYPE_CHECKING: - from pensive.base import StateMachine - from pensive.generators.markov import MarkovChain - from pensive.generators.mealy import MealyHMM - from pensive.generators.moore import MooreHMM + from sofic.base import StateMachine + from sofic.generators.markov import MarkovChain + from sofic.generators.mealy import MealyHMM + from sofic.generators.moore import MooreHMM PartitionLike = Iterable[Iterable[Hashable]] | Mapping[Hashable, Hashable] LabelsLike = Mapping[frozenset[Hashable], Hashable] | Callable[[frozenset[Hashable]], Hashable] @@ -211,7 +211,7 @@ def _lumped_initial( def _lump_markov(model: MarkovChain, blocks: list[frozenset[Hashable]], labels: LabelsLike | None) -> MarkovChain: - from pensive.generators.markov import MarkovChain + from sofic.generators.markov import MarkovChain resolved = _resolve_labels(blocks, labels) block_of = _block_of(blocks) @@ -238,7 +238,7 @@ def _lump_markov(model: MarkovChain, blocks: list[frozenset[Hashable]], labels: def _lump_mealy(model: MealyHMM, blocks: list[frozenset[Hashable]], labels: LabelsLike | None) -> MealyHMM: - from pensive.generators.mealy import MealyHMM + from sofic.generators.mealy import MealyHMM resolved = _resolve_labels(blocks, labels) block_of = _block_of(blocks) @@ -266,7 +266,7 @@ def _lump_mealy(model: MealyHMM, blocks: list[frozenset[Hashable]], labels: Labe def _lump_moore(model: MooreHMM, blocks: list[frozenset[Hashable]], labels: LabelsLike | None) -> MooreHMM: - from pensive.generators.moore import MooreHMM + from sofic.generators.moore import MooreHMM resolved = _resolve_labels(blocks, labels) block_of = _block_of(blocks) @@ -296,9 +296,9 @@ def _lump_moore(model: MooreHMM, blocks: list[frozenset[Hashable]], labels: Labe def _dispatch(model: StateMachine) -> tuple[Callable[..., bool], Callable[..., Any]]: - from pensive.generators.markov import MarkovChain - from pensive.generators.mealy import MealyHMM - from pensive.generators.moore import MooreHMM + from sofic.generators.markov import MarkovChain + from sofic.generators.mealy import MealyHMM + from sofic.generators.moore import MooreHMM if isinstance(model, MarkovChain): return _is_lumpable_markov, _lump_markov @@ -320,10 +320,10 @@ def is_lumpable(model: StateMachine, partition: PartitionLike, *, rtol: float = Parameters ---------- model - A :class:`~pensive.generators.markov.MarkovChain`, - :class:`~pensive.generators.mealy.MealyHMM` (including - :class:`~pensive.generators.epsilon_machine.EpsilonMachine`), or - :class:`~pensive.generators.moore.MooreHMM`. + A :class:`~sofic.generators.markov.MarkovChain`, + :class:`~sofic.generators.mealy.MealyHMM` (including + :class:`~sofic.generators.epsilon_machine.EpsilonMachine`), or + :class:`~sofic.generators.moore.MooreHMM`. partition Blocks (iterable of iterables) or a state-to-block mapping; must cover every state exactly. @@ -404,10 +404,10 @@ def lump( Builds the coarser lumped model (Kemeny & Snell, :cite:`KemenySnell1976`): block-to-block transition masses are read from a block representative, and initial masses are summed within blocks. A - :class:`~pensive.generators.markov.MarkovChain` lumps to a ``MarkovChain``, a - :class:`~pensive.generators.moore.MooreHMM` to a ``MooreHMM``, and any - :class:`~pensive.generators.mealy.MealyHMM` (including an - :class:`~pensive.generators.epsilon_machine.EpsilonMachine`) to a plain + :class:`~sofic.generators.markov.MarkovChain` lumps to a ``MarkovChain``, a + :class:`~sofic.generators.moore.MooreHMM` to a ``MooreHMM``, and any + :class:`~sofic.generators.mealy.MealyHMM` (including an + :class:`~sofic.generators.epsilon_machine.EpsilonMachine`) to a plain ``MealyHMM`` -- lumping may break unifilarity, so the stricter subtype is not preserved. @@ -419,7 +419,7 @@ def lump( Blocks (iterable of iterables) or a state-to-block mapping; must cover every state exactly. check - When ``True`` (default), raise :class:`~pensive.exceptions.LumpabilityError` + When ``True`` (default), raise :class:`~sofic.exceptions.LumpabilityError` if ``partition`` is not strongly lumpable. When ``False``, build the model anyway from each block's representative row (the result is exact only when the partition is in fact lumpable). diff --git a/pensive/generators/markov.py b/sofic/generators/markov.py similarity index 83% rename from pensive/generators/markov.py rename to sofic/generators/markov.py index 5c14885..e2d61dd 100644 --- a/pensive/generators/markov.py +++ b/sofic/generators/markov.py @@ -7,12 +7,12 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import StochasticModel -from pensive.graph import ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import StochasticModel +from sofic.graph import ATTR_PROB if TYPE_CHECKING: - from pensive.generators.lumping import LabelsLike, PartitionLike + from sofic.generators.lumping import LabelsLike, PartitionLike class MarkovChain(StochasticModel): @@ -24,13 +24,13 @@ def add_transition(self, source: Hashable, target: Hashable, prob: float, **attr def is_deterministic(self) -> bool: """Return whether each state has a single successor with probability 1.""" - from pensive.properties import is_deterministic_markov + from sofic.properties import is_deterministic_markov return is_deterministic_markov(self) def is_lumpable(self, partition: PartitionLike, *, rtol: float = 1e-8, atol: float = 1e-10) -> bool: """Return whether ``partition`` is strongly lumpable for this chain.""" - from pensive.generators.lumping import is_lumpable + from sofic.generators.lumping import is_lumpable return is_lumpable(self, partition, rtol=rtol, atol=atol) @@ -44,7 +44,7 @@ def lump( atol: float = 1e-10, ) -> MarkovChain: """Aggregate states into blocks, returning the lumped chain.""" - from pensive.generators.lumping import lump + from sofic.generators.lumping import lump return lump(self, partition, check=check, labels=labels, rtol=rtol, atol=atol) @@ -61,8 +61,8 @@ def validate_stochastic(self) -> None: raise StochasticValidationError(f"negative transition probability on {transition}") def stationary_distribution(self) -> np.ndarray: - from pensive.generators.stationary import stationary_distribution_from_transition - from pensive.properties import transition_matrix + from sofic.generators.stationary import stationary_distribution_from_transition + from sofic.properties import transition_matrix idx = self.reindex() if len(idx) == 0: @@ -71,13 +71,13 @@ def stationary_distribution(self) -> np.ndarray: return stationary_distribution_from_transition(transition) def entropy_rate(self) -> float: - from pensive.generators.measures import entropy_rate_markov + from sofic.generators.measures import entropy_rate_markov return entropy_rate_markov(self) def words_of_length(self, length: int) -> dict[tuple[Hashable, ...], float]: """Return visible state paths of ``length`` and their probabilities.""" - from pensive.generators.words import markov_words_of_length + from sofic.generators.words import markov_words_of_length return markov_words_of_length(self, length) diff --git a/pensive/generators/mealy.py b/sofic/generators/mealy.py similarity index 77% rename from pensive/generators/mealy.py rename to sofic/generators/mealy.py index c4cf004..24e300d 100644 --- a/pensive/generators/mealy.py +++ b/sofic/generators/mealy.py @@ -5,14 +5,14 @@ from collections.abc import Hashable, Mapping, Sequence from typing import TYPE_CHECKING, Any -from pensive.exceptions import UnifilarityError -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.edge_emissions import validate_stochastic_edge_emissions -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.exceptions import UnifilarityError +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.edge_emissions import validate_stochastic_edge_emissions +from sofic.graph import ATTR_EMISSION, ATTR_PROB if TYPE_CHECKING: - from pensive.generators.lumping import LabelsLike, PartitionLike - from pensive.generators.mixed_state import MixedState, MixedStatePresentation + from sofic.generators.lumping import LabelsLike, PartitionLike + from sofic.generators.mixed_state import MixedState, MixedStatePresentation class MealyHMM(HiddenMarkovModel): @@ -20,12 +20,12 @@ class MealyHMM(HiddenMarkovModel): Each outgoing edge carries an emission symbol and a probability. Row sums at every state must equal 1. Use :meth:`mixed_state_presentation` to obtain - belief-state dynamics, or :meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_hmm` + belief-state dynamics, or :meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm` for the causal ε-machine presentation. Examples -------- - >>> from pensive.examples import golden_mean + >>> from sofic.examples import golden_mean >>> eps = golden_mean(0.5) >>> eps.entropy_rate() > 0 True @@ -35,9 +35,9 @@ def add_transition(self, source: Hashable, target: Hashable, symbol: Any, prob: """Add an edge carrying joint emission probability ``P(target, symbol | source)``. ``prob`` may be a Python float or an exact sympy expression (see - :mod:`pensive.generators.prob`). + :mod:`sofic.generators.prob`). """ - from pensive.generators.prob import as_prob + from sofic.generators.prob import as_prob return self.graph.add_transition( source, @@ -74,19 +74,19 @@ def _check_unifilar(self) -> None: def is_unifilar(self) -> bool: """Return whether each state emits at most one edge per symbol.""" - from pensive.properties import is_unifilar_emissions + from sofic.properties import is_unifilar_emissions return is_unifilar_emissions(self) def is_counifilar(self) -> bool: """Return whether each ``(target, emission)`` identifies a unique source.""" - from pensive.properties import is_counifilar_emissions + from sofic.properties import is_counifilar_emissions return is_counifilar_emissions(self) def is_lumpable(self, partition: PartitionLike, *, rtol: float = 1e-8, atol: float = 1e-10) -> bool: """Return whether ``partition`` is strongly lumpable for this HMM.""" - from pensive.generators.lumping import is_lumpable + from sofic.generators.lumping import is_lumpable return is_lumpable(self, partition, rtol=rtol, atol=atol) @@ -100,43 +100,43 @@ def lump( atol: float = 1e-10, ) -> MealyHMM: """Aggregate states into blocks, returning the lumped Mealy HMM.""" - from pensive.generators.lumping import lump + from sofic.generators.lumping import lump return lump(self, partition, check=check, labels=labels, rtol=rtol, atol=atol) def is_irreducible(self) -> bool: """Return whether the internal state graph is strongly connected.""" - from pensive.properties import is_irreducible + from sofic.properties import is_irreducible return is_irreducible(self) def is_ergodic(self, *, weak: bool = True) -> bool: """Return weak/strong ergodicity of the internal finite-state dynamics.""" - from pensive.properties import is_ergodic + from sofic.properties import is_ergodic return is_ergodic(self, weak=weak) def is_stationary(self, *, rtol: float = 1e-8, atol: float = 1e-10) -> bool: """Return whether the initial distribution is internally stationary.""" - from pensive.properties import is_stationary + from sofic.properties import is_stationary return is_stationary(self, rtol=rtol, atol=atol) def is_detailed_balance(self, *, rtol: float = 1e-8, atol: float = 1e-10) -> bool: """Return whether stationary labeled flows satisfy detailed balance.""" - from pensive.properties import is_detailed_balance + from sofic.properties import is_detailed_balance return is_detailed_balance(self, rtol=rtol, atol=atol) def is_periodic(self) -> bool: """Return whether terminal internal components have graph period greater than one.""" - from pensive.properties import is_periodic + from sofic.properties import is_periodic return is_periodic(self) def is_strictly_sofic(self) -> bool: """Return whether this generator's support is strictly sofic.""" - from pensive.properties import is_strictly_sofic + from sofic.properties import is_strictly_sofic return is_strictly_sofic(self) @@ -146,11 +146,11 @@ def mixed_state_presentation( initial_mixed_state: MixedState | Mapping[Hashable, float] | Sequence[float] | None = None, ) -> MixedStatePresentation: """Build the mixed-state presentation (observer belief dynamics).""" - from pensive.generators.mixed_state import MixedStatePresentation + from sofic.generators.mixed_state import MixedStatePresentation return MixedStatePresentation.from_presentation(self, initial_mixed_state=initial_mixed_state) def to_edge_machine(self, iterations: int = 1, style: int = 0) -> MealyHMM: - from pensive.generators.edge_machine import hmm_to_edge_machine + from sofic.generators.edge_machine import hmm_to_edge_machine return hmm_to_edge_machine(self, iterations=iterations, style=style) diff --git a/pensive/generators/measures.py b/sofic/generators/measures.py similarity index 89% rename from pensive/generators/measures.py rename to sofic/generators/measures.py index 8b5ae86..62bb5f0 100644 --- a/pensive/generators/measures.py +++ b/sofic/generators/measures.py @@ -6,9 +6,9 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel -from pensive.generators.markov import MarkovChain -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel +from sofic.generators.markov import MarkovChain +from sofic.graph import ATTR_EMISSION, ATTR_PROB def require_dit(feature: str = "entropy measures") -> Any: @@ -35,11 +35,11 @@ def dit_state_label(state: Any) -> Any: values, so a tuple-valued state (e.g. an edge-machine state like ``("A", "0", "A")``) is misread as multi-dimensional coordinate data and raises ``MissingDimensionsError``. Tuple states are encoded to a lossless - string via :func:`pensive.generators.edge_machine.edge_state_label` (invert + string via :func:`sofic.generators.edge_machine.edge_state_label` (invert with ``parse_edge_state_label``); scalar states pass through unchanged. """ if isinstance(state, tuple): - from pensive.generators.edge_machine import edge_state_label + from sofic.generators.edge_machine import edge_state_label return edge_state_label(state) return state @@ -55,7 +55,7 @@ def state_distribution(model: StochasticModel) -> Any: idx = model.reindex() pi = model.stationary_distribution() outcomes = [(dit_state_label(idx.state(i)),) for i in range(len(idx))] - from pensive.generators.prob import as_prob, has_symbolic, simplify_prob + from sofic.generators.prob import as_prob, has_symbolic, simplify_prob probs = [as_prob(pi[i]) for i in range(len(idx))] if has_symbolic(probs): @@ -86,7 +86,7 @@ def joint_block_distribution( """ from itertools import product - from pensive.generators.hmm_inference import _stationary_emission_tensors + from sofic.generators.hmm_inference import _stationary_emission_tensors dit = _require_dit() # Blocks of a stationary process are weighted by the stationary state law, not @@ -121,13 +121,12 @@ def _entropy_rate_from_transitions( model); the target state stands in as the emitted symbol when no emission is set. """ dit = _require_dit() - from pensive.generators.prob import ( + from sofic.generators.prob import ( as_prob, has_symbolic, is_positive_mass, is_symbolic, simplify_prob, - sum_probs, ) symbolic = pi.dtype == object or has_symbolic(pi.ravel()) @@ -169,10 +168,10 @@ def entropy_rate_hmm(hmm: HiddenMarkovModel) -> Any: Returns a sympy :class:`~sympy.Expr` when the stationary law or emission tensors are symbolic; otherwise a Python ``float``. """ - from pensive.generators.hmm_inference import _emission_transition_tensors - from pensive.generators.prob import ( - as_prob, + from sofic.generators.hmm_inference import _emission_transition_tensors + from sofic.generators.prob import ( array_sum, + as_prob, has_symbolic, is_positive_mass, is_symbolic, @@ -202,10 +201,7 @@ def entropy_rate_hmm(hmm: HiddenMarkovModel) -> Any: label = dit_state_label(state) for symbol, matrix in joint.items(): row_mass = as_prob(pi[i]) * array_sum(matrix[i]) - if symbolic or is_symbolic(row_mass): - row_mass = simplify_prob(row_mass) - else: - row_mass = float(row_mass) + row_mass = simplify_prob(row_mass) if symbolic or is_symbolic(row_mass) else float(row_mass) if not is_positive_mass(row_mass): continue outcomes.append((label, symbol)) @@ -225,9 +221,7 @@ def entropy_rate_hmm(hmm: HiddenMarkovModel) -> Any: outcomes, [simplify_prob(as_prob(p) / as_prob(total)) for p in probs], ) - return simplify_prob( - as_prob(dit.shannon.entropy(joint_dist)) - as_prob(dit.shannon.entropy(state_dist)) - ) + return simplify_prob(as_prob(dit.shannon.entropy(joint_dist)) - as_prob(dit.shannon.entropy(state_dist))) joint_dist = dit.Distribution(outcomes, [float(p) / float(total) for p in probs]) return float(dit.shannon.entropy(joint_dist) - dit.shannon.entropy(state_dist)) diff --git a/pensive/generators/minimal_generative_model.py b/sofic/generators/minimal_generative_model.py similarity index 98% rename from pensive/generators/minimal_generative_model.py rename to sofic/generators/minimal_generative_model.py index 2d15d8a..d922df7 100644 --- a/pensive/generators/minimal_generative_model.py +++ b/sofic/generators/minimal_generative_model.py @@ -8,12 +8,12 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.exceptions import StochasticValidationError +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph if TYPE_CHECKING: - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine class _CommonInformationGenerativeModel(MealyHMM): @@ -400,7 +400,7 @@ def _functional_state_channel( def _require_dit(): - from pensive.generators.measures import require_dit + from sofic.generators.measures import require_dit return require_dit("minimal generative models") @@ -608,7 +608,7 @@ def _matching_support_channel( def _entropy(masses: Any) -> float: - from pensive.generators.stochastic import shannon_entropy + from sofic.generators.stochastic import shannon_entropy return shannon_entropy(masses, atol=0.0) diff --git a/pensive/generators/mixed_state.py b/sofic/generators/mixed_state.py similarity index 92% rename from pensive/generators/mixed_state.py rename to sofic/generators/mixed_state.py index 52fddb7..79cb43a 100644 --- a/pensive/generators/mixed_state.py +++ b/sofic/generators/mixed_state.py @@ -8,9 +8,9 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.mealy import MealyHMM -from pensive.generators.prob import ( +from sofic.exceptions import StochasticValidationError +from sofic.generators.mealy import MealyHMM +from sofic.generators.prob import ( as_prob, has_symbolic, is_positive_mass, @@ -19,7 +19,7 @@ simplify_prob, sum_probs, ) -from pensive.graph import TransitionGraph +from sofic.graph import TransitionGraph @dataclass(frozen=True, slots=True) @@ -79,7 +79,7 @@ def is_pure_mixed_state( belief = state.belief if isinstance(state, MixedState) else tuple(state) if has_symbolic(belief): positives = [value for value in belief if is_positive_mass(value, atol=atol)] - from pensive.generators.prob import probs_equal + from sofic.generators.prob import probs_equal return len(positives) == 1 and probs_equal(sum_probs(belief), 1) positives = [value for value in belief if value > atol] @@ -98,7 +98,7 @@ def pure_state_index(state: MixedState, *, atol: float = 1e-9) -> int | None: def mixed_state_entropy(state: MixedState, *, atol: float = 1e-12) -> float: """Shannon entropy of a mixed state in bits.""" - from pensive.generators.stochastic import shannon_entropy + from sofic.generators.stochastic import shannon_entropy if state.is_symbolic(): raise NotImplementedError("mixed_state_entropy is numeric-only; evaluate beliefs first") @@ -138,7 +138,7 @@ def from_presentation( *, initial_mixed_state: MixedState | Mapping[Hashable, float] | Sequence[float] | None = None, ) -> MixedStatePresentation: - from pensive.generators.mixed_state_construction import build_mixed_state_presentation + from sofic.generators.mixed_state_construction import build_mixed_state_presentation return build_mixed_state_presentation(hmm, initial_mixed_state=initial_mixed_state) @@ -146,8 +146,8 @@ def to_recurrent(self) -> MealyHMM: """Return the recurrent component with stationary initial weights. Pure recurrent mixed states are relabeled by their basis states and returned - as an :class:`~pensive.generators.epsilon_machine.EpsilonMachine`. Otherwise - the recurrent component remains a unifilar :class:`~pensive.generators.mealy.MealyHMM` + as an :class:`~sofic.generators.epsilon_machine.EpsilonMachine`. Otherwise + the recurrent component remains a unifilar :class:`~sofic.generators.mealy.MealyHMM` over mixed states. """ keep = frozenset(self.recurrent_states) @@ -170,7 +170,7 @@ def to_recurrent(self) -> MealyHMM: initial_distribution = self._recurrent_initial_distribution(keep, state_map) constraints = getattr(self, "symbol_constraints", None) if is_epsilon_machine: - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine recurrent = EpsilonMachine( graph=graph, @@ -208,7 +208,7 @@ def _recurrent_initial_distribution( keep: frozenset[MixedState], state_map: Mapping[MixedState, Hashable], ) -> dict[Hashable, Any]: - from pensive.generators.prob import simplify_prob + from sofic.generators.prob import simplify_prob idx = self.reindex() stationary = self.stationary_distribution() diff --git a/pensive/generators/mixed_state_construction.py b/sofic/generators/mixed_state_construction.py similarity index 88% rename from pensive/generators/mixed_state_construction.py rename to sofic/generators/mixed_state_construction.py index 1b39fc6..d59adf1 100644 --- a/pensive/generators/mixed_state_construction.py +++ b/sofic/generators/mixed_state_construction.py @@ -8,15 +8,15 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.generators.mixed_state import ( +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.generators.mixed_state import ( MixedState, MixedStatePresentation, is_pure_mixed_state, ) -from pensive.generators.prob import ( +from sofic.generators.prob import ( as_prob, has_symbolic, is_positive_mass, @@ -25,7 +25,7 @@ sum_probs, zeros, ) -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph def _terminal_recurrent_states(graph: TransitionGraph) -> frozenset[Any]: @@ -84,7 +84,7 @@ def build_mixed_state_presentation( if not isinstance(hmm, MealyHMM): raise TypeError(f"mixed-state presentation requires a MealyHMM, not {type(hmm)!r}") - from pensive.generators.hmm_inference import _emission_transition_tensors + from sofic.generators.hmm_inference import _emission_transition_tensors constraints = getattr(hmm, "symbol_constraints", None) @@ -107,9 +107,7 @@ def register(state: MixedState) -> MixedState: discovered[state] = known return known if len(discovered) >= max_states: - raise StochasticValidationError( - f"mixed-state presentation exceeded max_states={max_states}" - ) + raise StochasticValidationError(f"mixed-state presentation exceeded max_states={max_states}") discovered[state] = state graph.add_state(state) queue.append(state) @@ -158,11 +156,8 @@ def register(state: MixedState) -> MixedState: def _beliefs_equal(left: MixedState, right: MixedState, *, constraints: Any = None) -> bool: - from pensive.generators.prob import probs_equal + from sofic.generators.prob import probs_equal if len(left.belief) != len(right.belief): return False - return all( - probs_equal(a, b, constraints=constraints) - for a, b in zip(left.belief, right.belief, strict=True) - ) + return all(probs_equal(a, b, constraints=constraints) for a, b in zip(left.belief, right.belief, strict=True)) diff --git a/pensive/generators/moore.py b/sofic/generators/moore.py similarity index 85% rename from pensive/generators/moore.py rename to sofic/generators/moore.py index 5b183f2..eeb5af4 100644 --- a/pensive/generators/moore.py +++ b/sofic/generators/moore.py @@ -7,13 +7,13 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION_DIST, ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION_DIST, ATTR_PROB if TYPE_CHECKING: - from pensive.generators.lumping import LabelsLike, PartitionLike + from sofic.generators.lumping import LabelsLike, PartitionLike class MooreHMM(HiddenMarkovModel): @@ -32,13 +32,13 @@ def is_unifilar(self) -> bool: return self.to_mealy().is_unifilar() def to_mealy(self) -> MealyHMM: - from pensive.generators.conversions import moore_to_mealy + from sofic.generators.conversions import moore_to_mealy return moore_to_mealy(self) def is_lumpable(self, partition: PartitionLike, *, rtol: float = 1e-8, atol: float = 1e-10) -> bool: """Return whether ``partition`` is strongly lumpable for this HMM.""" - from pensive.generators.lumping import is_lumpable + from sofic.generators.lumping import is_lumpable return is_lumpable(self, partition, rtol=rtol, atol=atol) @@ -52,7 +52,7 @@ def lump( atol: float = 1e-10, ) -> MooreHMM: """Aggregate states into blocks, returning the lumped Moore HMM.""" - from pensive.generators.lumping import lump + from sofic.generators.lumping import lump return lump(self, partition, check=check, labels=labels, rtol=rtol, atol=atol) diff --git a/pensive/generators/nmachine.py b/sofic/generators/nmachine.py similarity index 80% rename from pensive/generators/nmachine.py rename to sofic/generators/nmachine.py index c1dee8e..b26aa94 100644 --- a/pensive/generators/nmachine.py +++ b/sofic/generators/nmachine.py @@ -7,13 +7,13 @@ import numpy as np -from pensive.exceptions import QuasiStochasticValidationError -from pensive.generators.base import QuasiStochasticModel -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.graph import ATTR_EMISSION, ATTR_QUASIPROB +from sofic.exceptions import QuasiStochasticValidationError +from sofic.generators.base import QuasiStochasticModel +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.graph import ATTR_EMISSION, ATTR_QUASIPROB if TYPE_CHECKING: - from pensive.generators.quasi_realization import QuasiRealization + from sofic.generators.quasi_realization import QuasiRealization class NMachine(QuasiStochasticModel): @@ -47,7 +47,7 @@ def validate_quasistochastic(self) -> None: def is_unifilar(self) -> bool: """Return whether each state emits at most one edge per symbol.""" - from pensive.properties import is_unifilar_emissions + from sofic.properties import is_unifilar_emissions return is_unifilar_emissions(self) @@ -58,12 +58,12 @@ def from_epsilon_machine( splits: Mapping[Hashable, int] | None = None, **kwargs: Any, ) -> NMachine: - from pensive.generators.nmachine_construction import build_nmachine + from sofic.generators.nmachine_construction import build_nmachine return build_nmachine(eps, splits) def coarse_grained_distribution(self) -> dict[Hashable, float]: - from pensive.generators.nmachine_construction import coarse_grained_distribution + from sofic.generators.nmachine_construction import coarse_grained_distribution eps_states = tuple( substate[0] for substate in self.states() if isinstance(substate, tuple) and len(substate) == 2 @@ -73,6 +73,6 @@ def coarse_grained_distribution(self) -> dict[Hashable, float]: return coarse_grained_distribution(self, eps_states) def to_quasi_realization(self) -> QuasiRealization: - from pensive.generators.quasi_realization import QuasiRealization + from sofic.generators.quasi_realization import QuasiRealization return QuasiRealization.from_nmachine(self) diff --git a/pensive/generators/nmachine_construction.py b/sofic/generators/nmachine_construction.py similarity index 92% rename from pensive/generators/nmachine_construction.py rename to sofic/generators/nmachine_construction.py index dba8fb4..0285abd 100644 --- a/pensive/generators/nmachine_construction.py +++ b/sofic/generators/nmachine_construction.py @@ -4,9 +4,9 @@ from collections.abc import Hashable, Mapping -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.nmachine import NMachine -from pensive.graph import ATTR_EMISSION, ATTR_PROB, ATTR_QUASIPROB, TransitionGraph +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.nmachine import NMachine +from sofic.graph import ATTR_EMISSION, ATTR_PROB, ATTR_QUASIPROB, TransitionGraph def build_nmachine( diff --git a/pensive/generators/pfa.py b/sofic/generators/pfa.py similarity index 89% rename from pensive/generators/pfa.py rename to sofic/generators/pfa.py index 6cea968..71c9e2c 100644 --- a/pensive/generators/pfa.py +++ b/sofic/generators/pfa.py @@ -7,10 +7,10 @@ import numpy as np -from pensive.generators.base import StochasticModel -from pensive.generators.edge_emissions import validate_stochastic_edge_emissions -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.generators.base import StochasticModel +from sofic.generators.edge_emissions import validate_stochastic_edge_emissions +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB class ProbabilisticFiniteAutomaton(StochasticModel): @@ -42,12 +42,12 @@ def validate_stochastic(self) -> None: def is_unifilar(self) -> bool: """Return whether each state emits at most one edge per symbol.""" - from pensive.properties import is_unifilar_emissions + from sofic.properties import is_unifilar_emissions return is_unifilar_emissions(self) def to_mealy(self) -> MealyHMM: - from pensive.generators.conversions import pfa_to_mealy + from sofic.generators.conversions import pfa_to_mealy return pfa_to_mealy(self) @@ -72,7 +72,7 @@ def string_probability(self, word: Sequence[Any]) -> float: def words_of_length(self, length: int) -> dict[tuple[Any, ...], float]: """Return output words of ``length`` and their probabilities.""" - from pensive.generators.words import pfa_words_of_length + from sofic.generators.words import pfa_words_of_length return pfa_words_of_length(self, length) diff --git a/pensive/generators/prob.py b/sofic/generators/prob.py similarity index 98% rename from pensive/generators/prob.py rename to sofic/generators/prob.py index 4d06ac3..8e8ed8e 100644 --- a/pensive/generators/prob.py +++ b/sofic/generators/prob.py @@ -14,9 +14,7 @@ def _sympy(): try: import sympy except ImportError as exc: # pragma: no cover - optional dependency - raise ImportError( - "Symbolic probabilities require sympy. Install with: pip install pensive[symbolic]" - ) from exc + raise ImportError("Symbolic probabilities require sympy. Install with: pip install sofic[symbolic]") from exc return sympy diff --git a/pensive/generators/process_equivalence.py b/sofic/generators/process_equivalence.py similarity index 97% rename from pensive/generators/process_equivalence.py rename to sofic/generators/process_equivalence.py index 393b97b..f1c22d8 100644 --- a/pensive/generators/process_equivalence.py +++ b/sofic/generators/process_equivalence.py @@ -13,9 +13,9 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.hmm_inference import _emission_transition_tensors_from_mealy -from pensive.generators.words import _start_vector +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.hmm_inference import _emission_transition_tensors_from_mealy +from sofic.generators.words import _start_vector _DEFAULT_RTOL = 1e-9 _DEFAULT_ATOL = 1e-12 diff --git a/pensive/generators/quasi_inference.py b/sofic/generators/quasi_inference.py similarity index 92% rename from pensive/generators/quasi_inference.py rename to sofic/generators/quasi_inference.py index 0e5d802..2a467d2 100644 --- a/pensive/generators/quasi_inference.py +++ b/sofic/generators/quasi_inference.py @@ -7,9 +7,9 @@ import numpy as np -from pensive.exceptions import QuasiStochasticValidationError -from pensive.generators.base import QuasiStochasticModel -from pensive.graph import ATTR_EMISSION, ATTR_QUASIPROB +from sofic.exceptions import QuasiStochasticValidationError +from sofic.generators.base import QuasiStochasticModel +from sofic.graph import ATTR_EMISSION, ATTR_QUASIPROB def transition_matrices(model: QuasiStochasticModel) -> dict[Any, np.ndarray]: diff --git a/pensive/generators/quasi_realization.py b/sofic/generators/quasi_realization.py similarity index 91% rename from pensive/generators/quasi_realization.py rename to sofic/generators/quasi_realization.py index 4cc7254..c7825fd 100644 --- a/pensive/generators/quasi_realization.py +++ b/sofic/generators/quasi_realization.py @@ -7,8 +7,8 @@ import numpy as np -from pensive.exceptions import QuasiStochasticValidationError -from pensive.generators.base import QuasiStochasticModel +from sofic.exceptions import QuasiStochasticValidationError +from sofic.generators.base import QuasiStochasticModel class QuasiRealization(QuasiStochasticModel): @@ -56,12 +56,12 @@ def word_probability(self, word: Sequence[Any]) -> float: @classmethod def from_nmachine(cls, nm: Any) -> QuasiRealization: - from pensive.generators.conversions import quasi_realization_from_nmachine + from sofic.generators.conversions import quasi_realization_from_nmachine return quasi_realization_from_nmachine(nm) def to_nmachine(self) -> Any: - from pensive.generators.conversions import nmachine_from_quasi_realization + from sofic.generators.conversions import nmachine_from_quasi_realization return nmachine_from_quasi_realization(self) diff --git a/pensive/generators/reversal.py b/sofic/generators/reversal.py similarity index 93% rename from pensive/generators/reversal.py rename to sofic/generators/reversal.py index 427d35f..02eea65 100644 --- a/pensive/generators/reversal.py +++ b/sofic/generators/reversal.py @@ -4,15 +4,15 @@ from typing import Any, TypeVar -from pensive.base import StateMachine -from pensive.generators.prob import ( +from sofic.base import StateMachine +from sofic.generators.prob import ( as_prob, has_symbolic, is_positive_mass, is_zero, simplify_prob, ) -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, TransitionGraph +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, TransitionGraph S = TypeVar("S", bound=StateMachine) diff --git a/pensive/generators/stack_hmm.py b/sofic/generators/stack_hmm.py similarity index 98% rename from pensive/generators/stack_hmm.py rename to sofic/generators/stack_hmm.py index e41a012..37ea6cc 100644 --- a/pensive/generators/stack_hmm.py +++ b/sofic/generators/stack_hmm.py @@ -7,10 +7,10 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import StochasticModel -from pensive.generators.stationary import stationary_distribution_from_transition -from pensive.graph import ( +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import StochasticModel +from sofic.generators.stationary import stationary_distribution_from_transition +from sofic.graph import ( ATTR_KIND, ATTR_PROB, ATTR_SYMBOL, @@ -20,7 +20,7 @@ Transition, TransitionGraph, ) -from pensive.shifts.sofic_dyck import MatchedEdge, SoficDyckShift, TransitionRef, transition_ref +from sofic.shifts.sofic_dyck import MatchedEdge, SoficDyckShift, TransitionRef, transition_ref Configuration = tuple[Hashable, tuple[TransitionRef, ...]] _KINDS = frozenset({KIND_CALL, KIND_RETURN, KIND_INTERNAL}) diff --git a/pensive/generators/stack_inference.py b/sofic/generators/stack_inference.py similarity index 97% rename from pensive/generators/stack_inference.py rename to sofic/generators/stack_inference.py index 7c01fa0..8a6173a 100644 --- a/pensive/generators/stack_inference.py +++ b/sofic/generators/stack_inference.py @@ -6,9 +6,9 @@ from collections.abc import Callable, Hashable, Sequence from typing import Any, ClassVar, Literal -from pensive.automata.papni import DyckAlphabet, is_well_matched, learn_sofic_dyck_shift_papni -from pensive.exceptions import StochasticValidationError -from pensive.generators.epsilon_inference import ( +from sofic.automata.papni import DyckAlphabet, is_well_matched, learn_sofic_dyck_shift_papni +from sofic.exceptions import StochasticValidationError +from sofic.generators.epsilon_inference import ( History, SuffixCounts, _cluster_histories_by_morph, @@ -18,9 +18,9 @@ _drop_transient_states, _merge_similar_states, ) -from pensive.generators.stack_hmm import HiddenMarkovStackModel -from pensive.graph import ATTR_SYMBOL -from pensive.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref +from sofic.generators.stack_hmm import HiddenMarkovStackModel +from sofic.graph import ATTR_SYMBOL +from sofic.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref __all__ = [ "ConfigurationHistory", @@ -449,7 +449,7 @@ def fit_stack_hmm_mle( smoothing: float = 1e-6, ) -> HiddenMarkovStackModel: """Assign MLE edge probabilities to a fixed Dyck topology from one sample.""" - from pensive.shifts.dyck_algorithms import _successors + from sofic.shifts.dyck_algorithms import _successors seq = tuple(sequence) edge_counts: Counter[TransitionRef] = Counter() diff --git a/pensive/generators/stationary.py b/sofic/generators/stationary.py similarity index 94% rename from pensive/generators/stationary.py rename to sofic/generators/stationary.py index 6fb05ec..fdccc53 100644 --- a/pensive/generators/stationary.py +++ b/sofic/generators/stationary.py @@ -4,20 +4,18 @@ import numpy as np -from pensive.exceptions import StochasticValidationError -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.prob import ( - as_prob, +from sofic.exceptions import StochasticValidationError +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.prob import ( has_symbolic, - is_symbolic, simplify_prob, zeros, ) -from pensive.graph import ATTR_PROB +from sofic.graph import ATTR_PROB def stationary_distribution_hmm(hmm: HiddenMarkovModel) -> np.ndarray: - from pensive.properties import transition_matrix + from sofic.properties import transition_matrix idx = hmm.reindex() if len(idx) == 0: diff --git a/pensive/generators/stochastic.py b/sofic/generators/stochastic.py similarity index 94% rename from pensive/generators/stochastic.py rename to sofic/generators/stochastic.py index bee82fd..43a4dad 100644 --- a/pensive/generators/stochastic.py +++ b/sofic/generators/stochastic.py @@ -3,10 +3,11 @@ from __future__ import annotations from collections.abc import Iterable +from typing import Any import numpy as np -from pensive.exceptions import StochasticValidationError +from sofic.exceptions import StochasticValidationError def shannon_entropy(values: Iterable[float], *, normalize: bool = False, atol: float = 0.0) -> float: @@ -30,10 +31,9 @@ def shannon_entropy(values: Iterable[float], *, normalize: bool = False, atol: f def normalize_row_weights(weights: dict[tuple, Any], *, atol: float = 1e-9) -> dict[tuple, Any]: """Return ``weights`` scaled to sum to 1 when the total is positive.""" - from pensive.generators.prob import ( + from sofic.generators.prob import ( as_prob, has_symbolic, - is_positive_mass, is_zero, probs_equal, simplify_prob, diff --git a/pensive/generators/synchronization.py b/sofic/generators/synchronization.py similarity index 97% rename from pensive/generators/synchronization.py rename to sofic/generators/synchronization.py index d5d1947..29ba4bd 100644 --- a/pensive/generators/synchronization.py +++ b/sofic/generators/synchronization.py @@ -12,8 +12,8 @@ from dataclasses import dataclass, field from typing import Any -from pensive.exceptions import UnifilarityError -from pensive.graph import ATTR_EMISSION, ATTR_SYMBOL +from sofic.exceptions import UnifilarityError +from sofic.graph import ATTR_EMISSION, ATTR_SYMBOL @dataclass(frozen=True, slots=True) @@ -248,7 +248,7 @@ def is_exactly_synchronizable(graph: TopologicalUnifilarGraph) -> bool: def graph_from_epsilon_machine(eps: Any) -> TopologicalUnifilarGraph: """Strip probabilities from an ε-machine into a topological graph.""" - from pensive.properties import is_unifilar_emissions + from sofic.properties import is_unifilar_emissions if not is_unifilar_emissions(eps): for transition in eps.transitions(): @@ -283,8 +283,8 @@ def graph_from_epsilon_machine(eps: Any) -> TopologicalUnifilarGraph: def graph_from_unifilar_automaton(aut: Any) -> TopologicalUnifilarGraph: - """Build a topological graph from a :class:`~pensive.automata.unifilar.UnifilarAutomaton`.""" - from pensive.graph import EPSILON + """Build a topological graph from a :class:`~sofic.automata.unifilar.UnifilarAutomaton`.""" + from sofic.graph import EPSILON transitions: dict[tuple[Hashable, Any], Hashable] = {} alphabet: set[Any] = set() diff --git a/pensive/generators/topological_epsilon_enumeration.py b/sofic/generators/topological_epsilon_enumeration.py similarity index 94% rename from pensive/generators/topological_epsilon_enumeration.py rename to sofic/generators/topological_epsilon_enumeration.py index 846a017..4e24939 100644 --- a/pensive/generators/topological_epsilon_enumeration.py +++ b/sofic/generators/topological_epsilon_enumeration.py @@ -11,7 +11,7 @@ import numpy as np -from pensive.automata.idfa import ( +from sofic.automata.idfa import ( MISSING_TRANSITION, IDFAEnumerationError, _delta_table, @@ -22,9 +22,9 @@ transition_count, validate_idfa_string, ) -from pensive.exceptions import PensiveValidationError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.exceptions import SoficValidationError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.graph import ATTR_EMISSION, ATTR_PROB __all__ = [ "TopologicalEpsilonEnumerationError", @@ -40,7 +40,7 @@ ] -class TopologicalEpsilonEnumerationError(PensiveValidationError): +class TopologicalEpsilonEnumerationError(SoficValidationError): """Raised when topological ε-machine enumeration fails.""" @@ -81,7 +81,7 @@ def idfa_string_to_epsilon_machine( k: int, alphabet: Sequence[object] | None = None, ) -> EpsilonMachine: - """Decode an IDFA string into an :class:`~pensive.generators.epsilon_machine.EpsilonMachine`. + """Decode an IDFA string into an :class:`~sofic.generators.epsilon_machine.EpsilonMachine`. Outgoing edges from each state receive uniform probability ``1 / outdegree``, matching the topological ε-machine convention of Johnson et al. (2010). @@ -128,11 +128,11 @@ def epsilon_machine_to_idfa_string( """Encode an ε-machine as an incomplete accessible DFA transition string. Probabilities are ignored. Missing symbol transitions are encoded with - :data:`pensive.automata.idfa.MISSING_TRANSITION`. If ``canonical`` is true, + :data:`sofic.automata.idfa.MISSING_TRANSITION`. If ``canonical`` is true, all states are tried as roots and the rank-minimal IDFA string is returned. Otherwise, the first state in deterministic label order is used as the root. """ - from pensive.generators.synchronization import graph_from_epsilon_machine + from sofic.generators.synchronization import graph_from_epsilon_machine graph = graph_from_epsilon_machine(eps) states = tuple(graph.states) @@ -339,7 +339,7 @@ def iter_topological_epsilon_machines( alphabet: Sequence[object] | None = None, check_minimal: bool = True, ) -> Iterator[EpsilonMachine]: - """Yield uniform-probability :class:`~pensive.generators.epsilon_machine.EpsilonMachine` objects.""" + """Yield uniform-probability :class:`~sofic.generators.epsilon_machine.EpsilonMachine` objects.""" for transitions in iter_topological_epsilon_strings(k, n, check_minimal=check_minimal): yield idfa_string_to_epsilon_machine(transitions, n=n, k=k, alphabet=alphabet) diff --git a/pensive/generators/words.py b/sofic/generators/words.py similarity index 96% rename from pensive/generators/words.py rename to sofic/generators/words.py index 225eef9..171a999 100644 --- a/pensive/generators/words.py +++ b/sofic/generators/words.py @@ -8,11 +8,11 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel, QuasiStochasticModel -from pensive.generators.hmm_inference import _emission_transition_tensors -from pensive.generators.markov import MarkovChain -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.graph import ATTR_PROB +from sofic.generators.base import HiddenMarkovModel, QuasiStochasticModel +from sofic.generators.hmm_inference import _emission_transition_tensors +from sofic.generators.markov import MarkovChain +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.graph import ATTR_PROB _TOL = 1e-15 diff --git a/pensive/graph.py b/sofic/graph.py similarity index 97% rename from pensive/graph.py rename to sofic/graph.py index e50bfdc..879695f 100644 --- a/pensive/graph.py +++ b/sofic/graph.py @@ -44,14 +44,14 @@ class Transition: class TransitionGraph: - """NetworkX-backed directed multigraph for pensive models. + """NetworkX-backed directed multigraph for sofic models. States are nodes; transitions are edges with attribute dictionaries keyed by module constants such as :data:`ATTR_PROB` and :data:`ATTR_EMISSION`. Examples -------- - >>> from pensive.graph import TransitionGraph, ATTR_PROB + >>> from sofic.graph import TransitionGraph, ATTR_PROB >>> g = TransitionGraph() >>> g.add_state("A") >>> g.add_transition("A", "A", **{ATTR_PROB: 1.0}) diff --git a/pensive/indexing.py b/sofic/indexing.py similarity index 100% rename from pensive/indexing.py rename to sofic/indexing.py diff --git a/pensive/inference/__init__.py b/sofic/inference/__init__.py similarity index 66% rename from pensive/inference/__init__.py rename to sofic/inference/__init__.py index 88289b3..450d319 100644 --- a/pensive/inference/__init__.py +++ b/sofic/inference/__init__.py @@ -1,5 +1,5 @@ """Inference algorithms for stochastic generators.""" -from pensive.inference import bayesian +from sofic.inference import bayesian __all__ = ["bayesian"] diff --git a/pensive/inference/bayesian/__init__.py b/sofic/inference/bayesian/__init__.py similarity index 76% rename from pensive/inference/bayesian/__init__.py rename to sofic/inference/bayesian/__init__.py index 0468f18..faef0d8 100644 --- a/pensive/inference/bayesian/__init__.py +++ b/sofic/inference/bayesian/__init__.py @@ -5,8 +5,8 @@ when requested. """ -from pensive.inference.bayesian.comparison import ModelComparisonEM, ModelComparisonMC, ModelComparisonMC2 -from pensive.inference.bayesian.counts import ( +from sofic.inference.bayesian.comparison import ModelComparisonEM, ModelComparisonMC, ModelComparisonMC2 +from sofic.inference.bayesian.counts import ( BayesianInferenceError, PathCountEM, WordCountsMC, @@ -14,7 +14,7 @@ pretty_word, split_word, ) -from pensive.inference.bayesian.diversity import ( +from sofic.inference.bayesian.diversity import ( PosteriorDiversityResult, machine_diversity, posterior_mean_word_distribution, @@ -22,9 +22,9 @@ process_identification_word_length, word_distribution_to_pmf, ) -from pensive.inference.bayesian.epsilon import DirichletDistributionEM, EpsilonMachinePosterior, InferEM -from pensive.inference.bayesian.markov import DirichletPriorMC, InferMC, MarkovChainPosterior -from pensive.inference.bayesian.stack_hmm import ( +from sofic.inference.bayesian.epsilon import DirichletDistributionEM, EpsilonMachinePosterior, InferEM +from sofic.inference.bayesian.markov import DirichletPriorMC, InferMC, MarkovChainPosterior +from sofic.inference.bayesian.stack_hmm import ( DirichletDistributionStackHMM, ModelComparisonStackHMM, PathCountStackHMM, diff --git a/pensive/inference/bayesian/comparison.py b/sofic/inference/bayesian/comparison.py similarity index 89% rename from pensive/inference/bayesian/comparison.py rename to sofic/inference/bayesian/comparison.py index 16c48f2..a8653c9 100644 --- a/pensive/inference/bayesian/comparison.py +++ b/sofic/inference/bayesian/comparison.py @@ -7,12 +7,12 @@ import numpy as np -from pensive.generators.mealy import MealyHMM -from pensive.inference.bayesian.counts import BayesianInferenceError, posterior_weights -from pensive.inference.bayesian.markov import MarkovChainPosterior +from sofic.generators.mealy import MealyHMM +from sofic.inference.bayesian.counts import BayesianInferenceError, posterior_weights +from sofic.inference.bayesian.markov import MarkovChainPosterior if TYPE_CHECKING: - from pensive.inference.bayesian.diversity import PosteriorDiversityResult + from sofic.inference.bayesian.diversity import PosteriorDiversityResult class ModelComparisonMC: @@ -97,7 +97,7 @@ def __init__( beta: float = 0.0, state_path: bool = False, ): - from pensive.inference.bayesian.epsilon import EpsilonMachinePosterior + from sofic.inference.bayesian.epsilon import EpsilonMachinePosterior self.beta = float(beta) self.em_dict: dict[str, EpsilonMachinePosterior] = {} @@ -139,9 +139,9 @@ def generate_sample(self, rng: np.random.Generator | None = None) -> tuple[Any, def machine_diversity(self) -> float: """Shannon entropy (bits) of the topology posterior weights. - See :func:`~pensive.inference.bayesian.diversity.machine_diversity`. + See :func:`~sofic.inference.bayesian.diversity.machine_diversity`. """ - from pensive.inference.bayesian.diversity import machine_diversity + from sofic.inference.bayesian.diversity import machine_diversity return machine_diversity(self) @@ -156,9 +156,9 @@ def process_diversity( ) -> PosteriorDiversityResult: """Weighted JSD over length-:math:`L` word distributions in the posterior. - See :func:`~pensive.inference.bayesian.diversity.posterior_process_diversity`. + See :func:`~sofic.inference.bayesian.diversity.posterior_process_diversity`. """ - from pensive.inference.bayesian.diversity import posterior_process_diversity + from sofic.inference.bayesian.diversity import posterior_process_diversity return posterior_process_diversity( self, diff --git a/pensive/inference/bayesian/counts.py b/sofic/inference/bayesian/counts.py similarity index 98% rename from pensive/inference/bayesian/counts.py rename to sofic/inference/bayesian/counts.py index 9e43afd..e757945 100644 --- a/pensive/inference/bayesian/counts.py +++ b/sofic/inference/bayesian/counts.py @@ -10,8 +10,8 @@ import numpy as np from scipy.special import gammaln -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION class BayesianInferenceError(ValueError): diff --git a/pensive/inference/bayesian/diversity.py b/sofic/inference/bayesian/diversity.py similarity index 94% rename from pensive/inference/bayesian/diversity.py rename to sofic/inference/bayesian/diversity.py index 79ab84f..d473c4d 100644 --- a/pensive/inference/bayesian/diversity.py +++ b/sofic/inference/bayesian/diversity.py @@ -3,7 +3,7 @@ Two complementary notions of posterior spread are tracked: * **Machine diversity** — Shannon entropy of the topology weights returned by - :meth:`~pensive.inference.bayesian.comparison.ModelComparisonEM.model_probabilities`. + :meth:`~sofic.inference.bayesian.comparison.ModelComparisonEM.model_probabilities`. This measures uncertainty over *presentations* (topologies), not processes. * **Process diversity** — weighted Jensen–Shannon divergence (JSD) over length-:math:`L` @@ -17,7 +17,7 @@ Finesso 1991) uses the full length-:math:`(2n-1)` word distribution. Some minimal-realization algorithms use a conservative window of :math:`2n+1`. Upper's rank-growing history/future word lists (see -:func:`~pensive.generators.process_equivalence.is_equal_process`) provide a +:func:`~sofic.generators.process_equivalence.is_equal_process`) provide a data-driven alternative. All topologies in a comparison share the same :math:`L`, chosen from the largest state count in the posterior. @@ -33,14 +33,14 @@ import numpy as np -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.process_equivalence import _HistoryFutureWordList -from pensive.generators.words import hmm_words_of_length -from pensive.inference.bayesian.counts import BayesianInferenceError -from pensive.inference.bayesian.epsilon import EpsilonMachinePosterior +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.process_equivalence import _HistoryFutureWordList +from sofic.generators.words import hmm_words_of_length +from sofic.inference.bayesian.counts import BayesianInferenceError +from sofic.inference.bayesian.epsilon import EpsilonMachinePosterior if TYPE_CHECKING: - from pensive.inference.bayesian.comparison import ModelComparisonEM + from sofic.inference.bayesian.comparison import ModelComparisonEM _TOL = 1e-15 WordLengthConvention = Literal["paz", "conservative", "upper_list"] diff --git a/pensive/inference/bayesian/epsilon.py b/sofic/inference/bayesian/epsilon.py similarity index 97% rename from pensive/inference/bayesian/epsilon.py rename to sofic/inference/bayesian/epsilon.py index 1004d47..b3e1f4a 100644 --- a/pensive/inference/bayesian/epsilon.py +++ b/sofic/inference/bayesian/epsilon.py @@ -8,10 +8,10 @@ import numpy as np from scipy.special import logsumexp -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB -from pensive.inference.bayesian.counts import ( +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB +from sofic.inference.bayesian.counts import ( BayesianInferenceError, PathCountEM, dirichlet_multinomial_log_evidence, @@ -262,7 +262,7 @@ def posterior_mean_machine(self, start_node: Hashable | None = None) -> MealyHMM return self.dirichlet.posterior_mean_machine(start_node) def as_pymc_model(self, start_node: Hashable | None = None) -> Any: - from pensive.inference.bayesian.pymc_backend import epsilon_machine_model + from sofic.inference.bayesian.pymc_backend import epsilon_machine_model return epsilon_machine_model(self, start_node=start_node) diff --git a/pensive/inference/bayesian/markov.py b/sofic/inference/bayesian/markov.py similarity index 98% rename from pensive/inference/bayesian/markov.py rename to sofic/inference/bayesian/markov.py index 509a78e..15922b9 100644 --- a/pensive/inference/bayesian/markov.py +++ b/sofic/inference/bayesian/markov.py @@ -9,9 +9,9 @@ import numpy as np from scipy.special import polygamma -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB -from pensive.inference.bayesian.counts import ( +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB +from sofic.inference.bayesian.counts import ( BayesianInferenceError, WordCountsMC, dirichlet_multinomial_log_evidence, @@ -268,7 +268,7 @@ def sample_mealy_hmms( yield hmm def as_pymc_model(self, *, observed_as_counts: bool = False) -> Any: - from pensive.inference.bayesian.pymc_backend import markov_chain_model + from sofic.inference.bayesian.pymc_backend import markov_chain_model return markov_chain_model(self, observed_as_counts=observed_as_counts) diff --git a/pensive/inference/bayesian/pymc_backend.py b/sofic/inference/bayesian/pymc_backend.py similarity index 100% rename from pensive/inference/bayesian/pymc_backend.py rename to sofic/inference/bayesian/pymc_backend.py diff --git a/pensive/inference/bayesian/stack_hmm.py b/sofic/inference/bayesian/stack_hmm.py similarity index 96% rename from pensive/inference/bayesian/stack_hmm.py rename to sofic/inference/bayesian/stack_hmm.py index faa7c16..5b3b6af 100644 --- a/pensive/inference/bayesian/stack_hmm.py +++ b/sofic/inference/bayesian/stack_hmm.py @@ -8,14 +8,14 @@ import numpy as np -from pensive.generators.stack_hmm import Configuration, HiddenMarkovStackModel -from pensive.graph import ATTR_SYMBOL -from pensive.inference.bayesian.counts import ( +from sofic.generators.stack_hmm import Configuration, HiddenMarkovStackModel +from sofic.graph import ATTR_SYMBOL +from sofic.inference.bayesian.counts import ( BayesianInferenceError, dirichlet_multinomial_log_evidence, posterior_weights, ) -from pensive.shifts.sofic_dyck import TransitionRef, transition_ref +from sofic.shifts.sofic_dyck import TransitionRef, transition_ref class PathCountStackHMM: @@ -143,7 +143,7 @@ def posterior_mean_probabilities(self) -> dict[TransitionRef, float]: return {edge: edge_totals[edge] / edge_counts[edge] for edge in edge_totals if edge_counts[edge] > 0} def posterior_mean_model(self) -> HiddenMarkovStackModel: - from pensive.shifts.sofic_dyck import SoficDyckShift + from sofic.shifts.sofic_dyck import SoficDyckShift shift: SoficDyckShift = self.model.to_sofic_dyck_shift() probabilities = self.posterior_mean_probabilities() diff --git a/pensive/operations.py b/sofic/operations.py similarity index 59% rename from pensive/operations.py rename to sofic/operations.py index 6c90071..d81269a 100644 --- a/pensive/operations.py +++ b/sofic/operations.py @@ -1,16 +1,16 @@ -"""Cross-cutting operations on pensive state-machine models.""" +"""Cross-cutting operations on sofic state-machine models.""" from __future__ import annotations -from pensive.base import StateMachine +from sofic.base import StateMachine def reverse(model: StateMachine) -> StateMachine: """Reverse ``model`` using its concrete :meth:`~StateMachine.reverse` implementation. - This is the canonical top-level ``reverse`` for all :class:`~pensive.base.StateMachine` + This is the canonical top-level ``reverse`` for all :class:`~sofic.base.StateMachine` subtypes (HMMs, ε-machines, automata, shifts). For finite automata, prefer - :meth:`~pensive.automata.nfa.NFA.reverse` or import - :func:`~pensive.automata.algorithms.reverse` directly. + :meth:`~sofic.automata.nfa.NFA.reverse` or import + :func:`~sofic.automata.algorithms.reverse` directly. """ return model.reverse() diff --git a/pensive/properties.py b/sofic/properties.py similarity index 95% rename from pensive/properties.py rename to sofic/properties.py index b0e8b27..11aee3c 100644 --- a/pensive/properties.py +++ b/sofic/properties.py @@ -1,4 +1,4 @@ -"""Structural predicates for pensive state-machine models.""" +"""Structural predicates for sofic state-machine models.""" from __future__ import annotations @@ -8,8 +8,8 @@ import networkx as nx import numpy as np -from pensive.base import StateMachine -from pensive.graph import ATTR_EMISSION, ATTR_PROB, ATTR_SYMBOL, EPSILON +from sofic.base import StateMachine +from sofic.graph import ATTR_EMISSION, ATTR_PROB, ATTR_SYMBOL, EPSILON def is_unifilar_labeled( @@ -191,8 +191,8 @@ def is_strictly_sofic(model: Any) -> bool: For finite right-resolving support presentations, finite Markov order is the finite-type case; infinite Markov order is strictly sofic. """ - from pensive.generators.conversions import hmm_to_support_dfa - from pensive.generators.synchronization import ( + from sofic.generators.conversions import hmm_to_support_dfa + from sofic.generators.synchronization import ( build_topological_graph_from_transitions, graph_from_unifilar_automaton, markov_order_from_graph, @@ -240,7 +240,7 @@ def transition_matrix( ordered = list(states) if states is not None else list(model.states()) index = {state: i for i, state in enumerate(ordered)} n = len(ordered) - from pensive.generators.prob import as_prob, has_symbolic, zeros + from sofic.generators.prob import as_prob, has_symbolic, zeros edge_probs = [] for state in ordered: @@ -273,7 +273,7 @@ def _labeled_or_internal_matrices(model: StateMachine) -> list[np.ndarray]: observation_alphabet = getattr(model, "observation_alphabet", None) to_mealy = getattr(model, "to_mealy", None) if observation_alphabet is not None and to_mealy is not None: - from pensive.generators.hmm_inference import _emission_transition_tensors_from_mealy + from sofic.generators.hmm_inference import _emission_transition_tensors_from_mealy _pi, matrices = _emission_transition_tensors_from_mealy(to_mealy()) return list(matrices.values()) diff --git a/pensive/serialization.py b/sofic/serialization.py similarity index 83% rename from pensive/serialization.py rename to sofic/serialization.py index 9ec7b53..def9140 100644 --- a/pensive/serialization.py +++ b/sofic/serialization.py @@ -1,4 +1,4 @@ -"""YAML serialization for pensive state-machine models.""" +"""YAML serialization for sofic state-machine models.""" from __future__ import annotations @@ -12,8 +12,8 @@ import numpy as np import yaml -from pensive.base import StateMachine -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.graph import ( ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_KIND, @@ -25,9 +25,9 @@ TransitionGraph, ) -SCHEMA = "pensive.model" +SCHEMA = "sofic.model" VERSION = 1 -_TYPE_KEY = "__pensive_type__" +_TYPE_KEY = "__sofic_type__" @dataclass(frozen=True, slots=True) @@ -43,10 +43,10 @@ def model_to_yaml(model: StateMachine) -> str: def model_from_yaml(text: str, *, validate: bool = True) -> StateMachine: - """Reconstruct a pensive model from YAML text.""" + """Reconstruct a sofic model from YAML text.""" loaded = yaml.safe_load(text) if not isinstance(loaded, dict): - raise TypeError("pensive model YAML must load to a mapping") + raise TypeError("sofic model YAML must load to a mapping") return model_from_dict(loaded, validate=validate) @@ -56,7 +56,7 @@ def from_yaml(text: str, *, validate: bool = True) -> StateMachine: def read_yaml(path: str | Path, *, validate: bool = True) -> StateMachine: - """Read a pensive model from a YAML file.""" + """Read a sofic model from a YAML file.""" return model_from_yaml(Path(path).read_text(encoding="utf-8"), validate=validate) @@ -78,22 +78,22 @@ def model_to_dict(model: StateMachine) -> dict[str, Any]: def model_from_dict(data: Mapping[str, Any], *, validate: bool = True) -> StateMachine: - """Reconstruct a pensive model from a decoded YAML mapping.""" + """Reconstruct a sofic model from a decoded YAML mapping.""" if data.get("schema") != SCHEMA: - raise ValueError(f"unsupported pensive YAML schema {data.get('schema')!r}") + raise ValueError(f"unsupported sofic YAML schema {data.get('schema')!r}") if data.get("version") != VERSION: - raise ValueError(f"unsupported pensive YAML version {data.get('version')!r}") + raise ValueError(f"unsupported sofic YAML version {data.get('version')!r}") class_path = data.get("class") if not isinstance(class_path, str): - raise TypeError("pensive model YAML requires a string class path") + raise TypeError("sofic model YAML requires a string class path") spec = _registry_by_path().get(class_path) if spec is None: - raise ValueError(f"unregistered pensive model class {class_path!r}") + raise ValueError(f"unregistered sofic model class {class_path!r}") graph = _graph_from_data(data.get("graph", {}), validate=validate) metadata = _decode(data.get("metadata", {}), validate=validate) if not isinstance(metadata, dict): - raise TypeError("pensive model metadata must decode to a mapping") + raise TypeError("sofic model metadata must decode to a mapping") model = _build_model(spec, graph, metadata) if validate: @@ -184,7 +184,7 @@ def _graph_to_data(graph: TransitionGraph) -> dict[str, Any]: def _graph_from_data(data: Any, *, validate: bool = True) -> TransitionGraph: if not isinstance(data, Mapping): - raise TypeError("pensive model graph must be a mapping") + raise TypeError("sofic model graph must be a mapping") graph = nx.MultiDiGraph() for node in data.get("nodes", []): if not isinstance(node, Mapping): @@ -208,7 +208,7 @@ def _graph_from_data(data: Any, *, validate: bool = True) -> TransitionGraph: def _encode(value: Any) -> Any: - from pensive.generators.mixed_state import MixedState + from sofic.generators.mixed_state import MixedState if value is EPSILON: return {_TYPE_KEY: "epsilon"} @@ -244,7 +244,7 @@ def _encode(value: Any) -> Any: def _decode(value: Any, *, validate: bool = True) -> Any: - from pensive.generators.mixed_state import MixedState + from sofic.generators.mixed_state import MixedState if isinstance(value, list): return [_decode(item, validate=validate) for item in value] @@ -273,7 +273,7 @@ def _decode(value: Any, *, validate: bool = True) -> Any: _decode(item["key"], validate=validate): _decode(item["value"], validate=validate) for item in value["items"] } - raise ValueError(f"unknown pensive YAML value tag {tag!r}") + raise ValueError(f"unknown sofic YAML value tag {tag!r}") def _stable_iterable(values: set[Any] | frozenset[Any]) -> list[Any]: @@ -300,15 +300,15 @@ def _spec(cls: type[StateMachine], fields: tuple[str, ...], builder: str = "defa @cache def _specs() -> tuple[_ModelSpec, ...]: - from pensive.automata.atomaton import Atomaton, AtomicAutomaton, MaximizedPrimeAtomaton - from pensive.automata.buchi import BuchiAutomaton - from pensive.automata.dfa import DFA - from pensive.automata.nfa import NFA - from pensive.automata.nwa import NestedWordAutomaton - from pensive.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton - from pensive.automata.transducers import MealyMachine, MooreMachine - from pensive.automata.unifilar import UnifilarAutomaton - from pensive.automata.vpa import ( + from sofic.automata.atomaton import Atomaton, AtomicAutomaton, MaximizedPrimeAtomaton + from sofic.automata.buchi import BuchiAutomaton + from sofic.automata.dfa import DFA + from sofic.automata.nfa import NFA + from sofic.automata.nwa import NestedWordAutomaton + from sofic.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton + from sofic.automata.transducers import MealyMachine, MooreMachine + from sofic.automata.unifilar import UnifilarAutomaton + from sofic.automata.vpa import ( CallDrivenAutomaton, CanonicalVisiblyPushdownAutomaton, CompositeVisiblyPushdownAutomaton, @@ -317,24 +317,24 @@ def _specs() -> tuple[_ModelSpec, ...]: SingleEntryVisiblyPushdownAutomaton, VisiblyPushdownAutomaton, ) - from pensive.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.epsilon_machine import EpsilonMachine - from pensive.generators.markov import MarkovChain - from pensive.generators.mealy import MealyHMM - from pensive.generators.mixed_state import MixedStatePresentation - from pensive.generators.moore import MooreHMM - from pensive.generators.nmachine import NMachine - from pensive.generators.pfa import ProbabilisticFiniteAutomaton - from pensive.generators.quasi_realization import QuasiRealization - from pensive.generators.stack_hmm import HiddenMarkovStackModel - from pensive.shifts.base import SymbolicModel - from pensive.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover - from pensive.shifts.markov_dyck import MarkovDyckShift - from pensive.shifts.sft import ShiftOfFiniteType - from pensive.shifts.sofic import SoficShift - from pensive.shifts.sofic_dyck import SoficDyckShift - from pensive.shifts.tmc import TopologicalMarkovChain + from sofic.generators.base import HiddenMarkovModel, QuasiStochasticModel, StochasticModel + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine + from sofic.generators.markov import MarkovChain + from sofic.generators.mealy import MealyHMM + from sofic.generators.mixed_state import MixedStatePresentation + from sofic.generators.moore import MooreHMM + from sofic.generators.nmachine import NMachine + from sofic.generators.pfa import ProbabilisticFiniteAutomaton + from sofic.generators.quasi_realization import QuasiRealization + from sofic.generators.stack_hmm import HiddenMarkovStackModel + from sofic.shifts.base import SymbolicModel + from sofic.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover + from sofic.shifts.markov_dyck import MarkovDyckShift + from sofic.shifts.sft import ShiftOfFiniteType + from sofic.shifts.sofic import SoficShift + from sofic.shifts.sofic_dyck import SoficDyckShift + from sofic.shifts.tmc import TopologicalMarkovChain labeled = ("input_alphabet", "initial_states", "accepting_states") transducer = ("input_alphabet", "output_alphabet", "initial_states") diff --git a/pensive/shifts/__init__.py b/sofic/shifts/__init__.py similarity index 66% rename from pensive/shifts/__init__.py rename to sofic/shifts/__init__.py index b23f1bf..be7a89b 100644 --- a/pensive/shifts/__init__.py +++ b/sofic/shifts/__init__.py @@ -1,13 +1,13 @@ """Symbolic shifts and subshifts.""" -from pensive.shifts.base import SymbolicModel -from pensive.shifts.covers import ( +from sofic.shifts.base import SymbolicModel +from sofic.shifts.covers import ( LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover, ) -from pensive.shifts.dyck_enumeration import ( +from sofic.shifts.dyck_enumeration import ( DyckGraphString, count_dyck_graph_strings, dyck_graph_string_to_shift, @@ -15,11 +15,11 @@ iter_sofic_dyck_topologies, shift_to_dyck_graph_string, ) -from pensive.shifts.markov_dyck import MarkovDyckShift -from pensive.shifts.sft import ShiftOfFiniteType -from pensive.shifts.sofic import SoficShift -from pensive.shifts.sofic_dyck import SoficDyckShift -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.shifts.markov_dyck import MarkovDyckShift +from sofic.shifts.sft import ShiftOfFiniteType +from sofic.shifts.sofic import SoficShift +from sofic.shifts.sofic_dyck import SoficDyckShift +from sofic.shifts.tmc import TopologicalMarkovChain __all__ = [ "DyckGraphString", diff --git a/pensive/shifts/algorithms.py b/sofic/shifts/algorithms.py similarity index 96% rename from pensive/shifts/algorithms.py rename to sofic/shifts/algorithms.py index 95fe703..2214cf6 100644 --- a/pensive/shifts/algorithms.py +++ b/sofic/shifts/algorithms.py @@ -8,8 +8,8 @@ import numpy as np -from pensive.graph import ATTR_SYMBOL -from pensive.shifts.base import SymbolicModel +from sofic.graph import ATTR_SYMBOL +from sofic.shifts.base import SymbolicModel def trim_transient(model: SymbolicModel) -> SymbolicModel: diff --git a/pensive/shifts/base.py b/sofic/shifts/base.py similarity index 86% rename from pensive/shifts/base.py rename to sofic/shifts/base.py index f4965e9..0442def 100644 --- a/pensive/shifts/base.py +++ b/sofic/shifts/base.py @@ -5,8 +5,8 @@ from collections.abc import Hashable, Iterator from typing import Any, Self -from pensive.base import StateMachine -from pensive.graph import ATTR_SYMBOL +from sofic.base import StateMachine +from sofic.graph import ATTR_SYMBOL class SymbolicModel(StateMachine): @@ -29,7 +29,7 @@ def validate(self) -> None: self._require(symbol in self.symbol_alphabet, f"symbol {symbol!r} not in alphabet") def factor_language(self, length: int) -> Iterator[tuple[Any, ...]]: - from pensive.shifts.algorithms import factor_language + from sofic.shifts.algorithms import factor_language yield from factor_language(self, length) @@ -39,11 +39,11 @@ def words_of_length(self, length: int) -> Iterator[tuple[Any, ...]]: def is_unifilar(self) -> bool: """Return whether this presentation is right-resolving (unifilar).""" - from pensive.properties import is_unifilar_symbols + from sofic.properties import is_unifilar_symbols return is_unifilar_symbols(self) def trim_transient(self) -> Self: - from pensive.shifts.algorithms import trim_transient + from sofic.shifts.algorithms import trim_transient return trim_transient(self) diff --git a/pensive/shifts/cover_construction.py b/sofic/shifts/cover_construction.py similarity index 91% rename from pensive/shifts/cover_construction.py rename to sofic/shifts/cover_construction.py index 891dc50..cb4daf1 100644 --- a/pensive/shifts/cover_construction.py +++ b/sofic/shifts/cover_construction.py @@ -5,10 +5,10 @@ from collections import defaultdict from typing import Any -from pensive.graph import ATTR_SYMBOL, TransitionGraph -from pensive.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover -from pensive.shifts.sofic import SoficShift -from pensive.states import sequential_labels +from sofic.graph import ATTR_SYMBOL, TransitionGraph +from sofic.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover +from sofic.shifts.sofic import SoficShift +from sofic.states import sequential_labels def _follower_language(shift: SoficShift, vertex: Any, max_len: int = 8) -> frozenset[tuple[Any, ...]]: diff --git a/pensive/shifts/covers.py b/sofic/shifts/covers.py similarity index 74% rename from pensive/shifts/covers.py rename to sofic/shifts/covers.py index 7634b55..617467a 100644 --- a/pensive/shifts/covers.py +++ b/sofic/shifts/covers.py @@ -4,7 +4,7 @@ from typing import Any -from pensive.shifts.sofic import SoficShift +from sofic.shifts.sofic import SoficShift class LeftFischerCover(SoficShift): @@ -12,7 +12,7 @@ class LeftFischerCover(SoficShift): @classmethod def from_sofic(cls, shift: SoficShift, **kwargs: Any) -> LeftFischerCover: - from pensive.shifts.cover_construction import left_fischer_from_sofic + from sofic.shifts.cover_construction import left_fischer_from_sofic return left_fischer_from_sofic(shift) @@ -22,7 +22,7 @@ class RightFischerCover(SoficShift): @classmethod def from_sofic(cls, shift: SoficShift, **kwargs: Any) -> RightFischerCover: - from pensive.shifts.cover_construction import right_fischer_from_sofic + from sofic.shifts.cover_construction import right_fischer_from_sofic return right_fischer_from_sofic(shift) @@ -32,7 +32,7 @@ class LeftKriegerCover(SoficShift): @classmethod def from_sofic(cls, shift: SoficShift, **kwargs: Any) -> LeftKriegerCover: - from pensive.shifts.cover_construction import left_krieger_from_sofic + from sofic.shifts.cover_construction import left_krieger_from_sofic return left_krieger_from_sofic(shift) @@ -42,6 +42,6 @@ class RightKriegerCover(SoficShift): @classmethod def from_sofic(cls, shift: SoficShift, **kwargs: Any) -> RightKriegerCover: - from pensive.shifts.cover_construction import right_krieger_from_sofic + from sofic.shifts.cover_construction import right_krieger_from_sofic return right_krieger_from_sofic(shift) diff --git a/pensive/shifts/dyck_algorithms.py b/sofic/shifts/dyck_algorithms.py similarity index 93% rename from pensive/shifts/dyck_algorithms.py rename to sofic/shifts/dyck_algorithms.py index 3fded37..a752097 100644 --- a/pensive/shifts/dyck_algorithms.py +++ b/sofic/shifts/dyck_algorithms.py @@ -6,11 +6,11 @@ from collections.abc import Hashable, Iterator, Sequence from typing import TYPE_CHECKING, Any -from pensive.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN, Transition -from pensive.shifts.sofic_dyck import TransitionRef, transition_ref +from sofic.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN, Transition +from sofic.shifts.sofic_dyck import TransitionRef, transition_ref if TYPE_CHECKING: - from pensive.shifts.sofic_dyck import SoficDyckShift + from sofic.shifts.sofic_dyck import SoficDyckShift Configuration = tuple[Hashable, tuple[TransitionRef, ...]] diff --git a/pensive/shifts/dyck_enumeration.py b/sofic/shifts/dyck_enumeration.py similarity index 96% rename from pensive/shifts/dyck_enumeration.py rename to sofic/shifts/dyck_enumeration.py index d2d4332..bbe2eed 100644 --- a/pensive/shifts/dyck_enumeration.py +++ b/sofic/shifts/dyck_enumeration.py @@ -8,10 +8,10 @@ from itertools import product from typing import Any -from pensive.exceptions import PensiveValidationError -from pensive.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN -from pensive.shifts.dyck_algorithms import is_admissible_word -from pensive.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref +from sofic.exceptions import SoficValidationError +from sofic.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.shifts.dyck_algorithms import is_admissible_word +from sofic.shifts.sofic_dyck import SoficDyckShift, TransitionRef, transition_ref __all__ = [ "DyckGraphString", @@ -27,7 +27,7 @@ _KIND_INTERNAL = 2 -class DyckEnumerationError(PensiveValidationError): +class DyckEnumerationError(SoficValidationError): """Raised when Dyck-graph enumeration fails.""" @@ -245,7 +245,7 @@ def iter_sofic_dyck_topologies( ): try: shift = dyck_graph_string_to_shift(spec) - except (PensiveValidationError, ValueError): + except (SoficValidationError, ValueError): continue if not is_admissible_word(shift, ()): continue diff --git a/pensive/shifts/markov_dyck.py b/sofic/shifts/markov_dyck.py similarity index 97% rename from pensive/shifts/markov_dyck.py rename to sofic/shifts/markov_dyck.py index c997d65..05424a9 100644 --- a/pensive/shifts/markov_dyck.py +++ b/sofic/shifts/markov_dyck.py @@ -7,8 +7,8 @@ import numpy as np -from pensive.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_RETURN, TransitionGraph -from pensive.shifts.sofic_dyck import MatchedEdge, SoficDyckShift, TransitionRef, transition_ref +from sofic.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_RETURN, TransitionGraph +from sofic.shifts.sofic_dyck import MatchedEdge, SoficDyckShift, TransitionRef, transition_ref GraphKind = Literal["edge", "vertex"] diff --git a/pensive/shifts/parry_construction.py b/sofic/shifts/parry_construction.py similarity index 90% rename from pensive/shifts/parry_construction.py rename to sofic/shifts/parry_construction.py index a891013..a1362b7 100644 --- a/pensive/shifts/parry_construction.py +++ b/sofic/shifts/parry_construction.py @@ -4,10 +4,10 @@ import numpy as np -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_MULTIPLICITY, ATTR_PROB -from pensive.shifts.algorithms import adjacency_matrix -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_MULTIPLICITY, ATTR_PROB +from sofic.shifts.algorithms import adjacency_matrix +from sofic.shifts.tmc import TopologicalMarkovChain def _perron_pair(matrix: np.ndarray) -> tuple[float, np.ndarray, np.ndarray]: @@ -69,7 +69,7 @@ def parry_measure(tmc: TopologicalMarkovChain) -> MealyHMM: continue symbol = transition_edge.data.get(ATTR_EMISSION) if symbol is None: - from pensive.graph import ATTR_SYMBOL + from sofic.graph import ATTR_SYMBOL symbol = transition_edge.data.get(ATTR_SYMBOL) weight = transition[i, j] * (multiplicity / total) diff --git a/pensive/shifts/sft.py b/sofic/shifts/sft.py similarity index 95% rename from pensive/shifts/sft.py rename to sofic/shifts/sft.py index e54640a..c41a516 100644 --- a/pensive/shifts/sft.py +++ b/sofic/shifts/sft.py @@ -5,8 +5,8 @@ from itertools import product from typing import Any -from pensive.graph import TransitionGraph -from pensive.shifts.base import SymbolicModel +from sofic.graph import TransitionGraph +from sofic.shifts.base import SymbolicModel class ShiftOfFiniteType(SymbolicModel): @@ -31,7 +31,7 @@ def from_forbidden_words( symbol_alphabet: frozenset[Any], **kwargs: Any, ) -> ShiftOfFiniteType: - from pensive.shifts.sft_construction import from_forbidden_words + from sofic.shifts.sft_construction import from_forbidden_words return from_forbidden_words(forbidden, symbol_alphabet, **kwargs) diff --git a/pensive/shifts/sft_construction.py b/sofic/shifts/sft_construction.py similarity index 93% rename from pensive/shifts/sft_construction.py rename to sofic/shifts/sft_construction.py index 148ea5b..05fe2a9 100644 --- a/pensive/shifts/sft_construction.py +++ b/sofic/shifts/sft_construction.py @@ -4,8 +4,8 @@ from typing import Any -from pensive.graph import ATTR_SYMBOL, TransitionGraph -from pensive.shifts.sft import ShiftOfFiniteType +from sofic.graph import ATTR_SYMBOL, TransitionGraph +from sofic.shifts.sft import ShiftOfFiniteType def from_forbidden_words( diff --git a/pensive/shifts/sofic.py b/sofic/shifts/sofic.py similarity index 68% rename from pensive/shifts/sofic.py rename to sofic/shifts/sofic.py index 9dbc253..86b7a1a 100644 --- a/pensive/shifts/sofic.py +++ b/sofic/shifts/sofic.py @@ -4,17 +4,17 @@ from typing import TYPE_CHECKING -from pensive.shifts.base import SymbolicModel +from sofic.shifts.base import SymbolicModel if TYPE_CHECKING: - from pensive.generators.mealy import MealyHMM + from sofic.generators.mealy import MealyHMM class SoficShift(SymbolicModel): """Sofic subshift given by a labeled directed graph presentation.""" def topological_entropy(self) -> float: - from pensive.shifts.tmc_construction import topological_entropy + from sofic.shifts.tmc_construction import topological_entropy return topological_entropy(self) @@ -24,9 +24,9 @@ def parry_measure(self) -> MealyHMM: The unique measure attaining ``h_top`` (Parry 1964): built from the Perron data of a right-resolving presentation of this shift, with each edge's emitted symbol preserved. See - :func:`~pensive.shifts.topological_anatomy.parry_measure_sofic`. + :func:`~sofic.shifts.topological_anatomy.parry_measure_sofic`. """ - from pensive.shifts.topological_anatomy import parry_measure_sofic + from sofic.shifts.topological_anatomy import parry_measure_sofic return parry_measure_sofic(self) @@ -37,25 +37,25 @@ def topological_anatomy(self) -> dict[str, float]: observed symbol process at the measure of maximal entropy, returning ``{h_top, b_top, r_top, excess_entropy}``. Here ``r_top`` is the MME erasure entropy rate (Verdu & Weissman 2008). See - :func:`~pensive.shifts.topological_anatomy.topological_anatomy`. + :func:`~sofic.shifts.topological_anatomy.topological_anatomy`. """ - from pensive.shifts.topological_anatomy import topological_anatomy + from sofic.shifts.topological_anatomy import topological_anatomy return topological_anatomy(self) def markov_order(self) -> int | float: """Markov order ``R`` when the presentation is right-resolving (unifilar).""" - from pensive.generators.synchronization import graph_from_sofic_shift, markov_order_from_graph + from sofic.generators.synchronization import graph_from_sofic_shift, markov_order_from_graph return markov_order_from_graph(graph_from_sofic_shift(self)) def cryptic_order(self) -> int | float: """Cryptic order ``k_chi`` when the presentation is right-resolving.""" - from pensive.generators.synchronization import cryptic_order_from_graph, graph_from_sofic_shift + from sofic.generators.synchronization import cryptic_order_from_graph, graph_from_sofic_shift return cryptic_order_from_graph(graph_from_sofic_shift(self)) def is_exactly_synchronizable(self) -> bool: - from pensive.generators.synchronization import graph_from_sofic_shift, is_exactly_synchronizable + from sofic.generators.synchronization import graph_from_sofic_shift, is_exactly_synchronizable return is_exactly_synchronizable(graph_from_sofic_shift(self)) diff --git a/pensive/shifts/sofic_dyck.py b/sofic/shifts/sofic_dyck.py similarity index 94% rename from pensive/shifts/sofic_dyck.py rename to sofic/shifts/sofic_dyck.py index 13e730f..4a0d6cd 100644 --- a/pensive/shifts/sofic_dyck.py +++ b/sofic/shifts/sofic_dyck.py @@ -5,8 +5,8 @@ from collections.abc import Hashable, Iterator, Sequence from typing import Any -from pensive.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN, Transition -from pensive.shifts.base import SymbolicModel +from sofic.graph import ATTR_KIND, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN, Transition +from sofic.shifts.base import SymbolicModel TransitionRef = tuple[Hashable, Hashable, int] MatchedEdge = tuple[TransitionRef, TransitionRef] @@ -100,11 +100,11 @@ def add_matched_pair(self, call_ref: TransitionRef, return_ref: TransitionRef) - def is_admissible_word(self, word: Sequence[Any]) -> bool: """Return whether ``word`` is a finite factor of this Dyck presentation.""" - from pensive.shifts.dyck_algorithms import is_admissible_word + from sofic.shifts.dyck_algorithms import is_admissible_word return is_admissible_word(self, word) def factor_language(self, length: int) -> Iterator[tuple[Any, ...]]: - from pensive.shifts.dyck_algorithms import admissible_words + from sofic.shifts.dyck_algorithms import admissible_words yield from admissible_words(self, length) diff --git a/pensive/shifts/tmc.py b/sofic/shifts/tmc.py similarity index 68% rename from pensive/shifts/tmc.py rename to sofic/shifts/tmc.py index b8e1e2f..b18a7e8 100644 --- a/pensive/shifts/tmc.py +++ b/sofic/shifts/tmc.py @@ -7,10 +7,10 @@ import numpy as np if TYPE_CHECKING: - from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_MULTIPLICITY -from pensive.shifts.base import SymbolicModel -from pensive.shifts.sofic import SoficShift + from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_MULTIPLICITY +from sofic.shifts.base import SymbolicModel +from sofic.shifts.sofic import SoficShift class TopologicalMarkovChain(SymbolicModel): @@ -20,17 +20,17 @@ class TopologicalMarkovChain(SymbolicModel): def from_adjacency( cls, matrix: np.ndarray, symbol_alphabet: frozenset[Any] | None = None, **kwargs: Any ) -> TopologicalMarkovChain: - from pensive.shifts.tmc_construction import from_adjacency + from sofic.shifts.tmc_construction import from_adjacency return from_adjacency(matrix, symbol_alphabet) def to_sofic_shift(self) -> SoficShift: - from pensive.shifts.tmc_construction import to_sofic_shift + from sofic.shifts.tmc_construction import to_sofic_shift return to_sofic_shift(self) def topological_entropy(self) -> float: - from pensive.shifts.tmc_construction import topological_entropy + from sofic.shifts.tmc_construction import topological_entropy return topological_entropy(self) @@ -41,6 +41,6 @@ def validate(self) -> None: self._require(isinstance(mult, (int, float)) and mult >= 1, "multiplicity must be >= 1") def parry_measure(self) -> MealyHMM: - from pensive.shifts.parry_construction import parry_measure + from sofic.shifts.parry_construction import parry_measure return parry_measure(self) diff --git a/pensive/shifts/tmc_construction.py b/sofic/shifts/tmc_construction.py similarity index 85% rename from pensive/shifts/tmc_construction.py rename to sofic/shifts/tmc_construction.py index 7209c01..a4250a3 100644 --- a/pensive/shifts/tmc_construction.py +++ b/sofic/shifts/tmc_construction.py @@ -6,11 +6,11 @@ import numpy as np -from pensive.graph import ATTR_MULTIPLICITY, ATTR_SYMBOL, TransitionGraph -from pensive.shifts.algorithms import adjacency_matrix, topological_entropy_from_matrix -from pensive.shifts.sofic import SoficShift -from pensive.shifts.tmc import TopologicalMarkovChain -from pensive.states import sequential_labels +from sofic.graph import ATTR_MULTIPLICITY, ATTR_SYMBOL, TransitionGraph +from sofic.shifts.algorithms import adjacency_matrix, topological_entropy_from_matrix +from sofic.shifts.sofic import SoficShift +from sofic.shifts.tmc import TopologicalMarkovChain +from sofic.states import sequential_labels def from_adjacency( diff --git a/pensive/shifts/topological_anatomy.py b/sofic/shifts/topological_anatomy.py similarity index 85% rename from pensive/shifts/topological_anatomy.py rename to sofic/shifts/topological_anatomy.py index d60d257..4615b62 100644 --- a/pensive/shifts/topological_anatomy.py +++ b/sofic/shifts/topological_anatomy.py @@ -29,14 +29,14 @@ 1. Put the presentation in right-resolving (unifilar) form (:func:`_right_resolving`) -- required so ``h_top`` is exact rather than the path-overcount of a nondeterministic presentation. -2. Build the Parry MME as a labeled :class:`~pensive.generators.mealy.MealyHMM` +2. Build the Parry MME as a labeled :class:`~sofic.generators.mealy.MealyHMM` (:func:`parry_measure_sofic`). 3. Minimize to the causal presentation - (:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.from_hmm`), pair it + (:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.from_hmm`), pair it with its time reverse - (:meth:`~pensive.generators.epsilon_machine.EpsilonMachine.to_bidirectional`), + (:meth:`~sofic.generators.epsilon_machine.EpsilonMachine.to_bidirectional`), and read the anatomy - (:meth:`~pensive.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.information_anatomy`). + (:meth:`~sofic.generators.bidirectional_epsilon_machine.BidirectionalEpsilonMachine.information_anatomy`). References: Parry (1964), *Intrinsic Markov chains*; James, Ellison & Crutchfield (2013), *Anatomy of a bit*; Verdu & Weissman (2008), *The information lost in @@ -47,12 +47,12 @@ from typing import TYPE_CHECKING -from pensive.exceptions import UnifilarityError -from pensive.graph import ATTR_SYMBOL +from sofic.exceptions import UnifilarityError +from sofic.graph import ATTR_SYMBOL if TYPE_CHECKING: - from pensive.generators.mealy import MealyHMM - from pensive.shifts.sofic import SoficShift + from sofic.generators.mealy import MealyHMM + from sofic.shifts.sofic import SoficShift def _dedup_symbol_edges(shift: SoficShift) -> SoficShift: @@ -65,7 +65,7 @@ def _dedup_symbol_edges(shift: SoficShift) -> SoficShift: correct; genuine right-resolving parallelism (same ``(source, target)``, *different* symbols) is preserved. """ - from pensive.shifts.sofic import SoficShift + from sofic.shifts.sofic import SoficShift result = SoficShift(symbol_alphabet=shift.symbol_alphabet) for state in shift.states(): @@ -85,16 +85,16 @@ def _right_resolving(shift: SoficShift) -> SoficShift: """Return a right-resolving (unifilar) presentation of ``shift``. If ``shift`` is already unifilar it is returned unchanged. Otherwise the right - Fischer cover (:meth:`~pensive.shifts.covers.RightFischerCover.from_sofic`) is + Fischer cover (:meth:`~sofic.shifts.covers.RightFischerCover.from_sofic`) is built and its duplicate labeled edges merged (:func:`_dedup_symbol_edges`). The cover construction uses a bounded follower language, so it is not guaranteed to determinize every presentation; if the result is still not - unifilar a :class:`~pensive.exceptions.UnifilarityError` is raised asking for a + unifilar a :class:`~sofic.exceptions.UnifilarityError` is raised asking for a right-resolving input. """ if shift.is_unifilar(): return shift - from pensive.shifts.covers import RightFischerCover + from sofic.shifts.covers import RightFischerCover cover = _dedup_symbol_edges(RightFischerCover.from_sofic(shift)) if not cover.is_unifilar(): @@ -115,7 +115,7 @@ def parry_measure_sofic(shift: SoficShift) -> MealyHMM: ``log2(lambda) = h_top`` and its observed process is the shift's MME symbol process. """ - from pensive.shifts.parry_construction import parry_measure + from sofic.shifts.parry_construction import parry_measure return parry_measure(_right_resolving(shift)) @@ -135,7 +135,7 @@ def topological_anatomy(shift: SoficShift) -> dict[str, float]: with ``h_top = b_top + r_top`` exactly. Requires ``dit`` for the anatomy entropies. """ - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine parry = parry_measure_sofic(shift) if not list(parry.states()): diff --git a/pensive/states.py b/sofic/states.py similarity index 100% rename from pensive/states.py rename to sofic/states.py diff --git a/sofic/testing/__init__.py b/sofic/testing/__init__.py new file mode 100644 index 0000000..2c3cbec --- /dev/null +++ b/sofic/testing/__init__.py @@ -0,0 +1,8 @@ +"""Optional Hypothesis strategies for sofic models.""" + +from sofic.testing.strategies import dfas, epsilon_machines + +__all__ = [ + "dfas", + "epsilon_machines", +] diff --git a/pensive/testing/strategies.py b/sofic/testing/strategies.py similarity index 91% rename from pensive/testing/strategies.py rename to sofic/testing/strategies.py index dc37694..cab8e14 100644 --- a/pensive/testing/strategies.py +++ b/sofic/testing/strategies.py @@ -1,7 +1,7 @@ -"""Hypothesis strategies for pensive models. +"""Hypothesis strategies for sofic models. These helpers live outside the main package imports so Hypothesis remains a -test-only dependency. Install ``pensive[test]`` to use them. +test-only dependency. Install ``sofic[test]`` to use them. """ from __future__ import annotations @@ -10,10 +10,10 @@ from functools import cache from typing import Any -from pensive.automata.dfa import DFA -from pensive.automata.icdfa import icdfa_string_to_dfa, iter_icdfa_empty_strings -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.topological_epsilon_enumeration import ( +from sofic.automata.dfa import DFA +from sofic.automata.icdfa import icdfa_string_to_dfa, iter_icdfa_empty_strings +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.topological_epsilon_enumeration import ( idfa_string_to_epsilon_machine, iter_topological_epsilon_strings, ) @@ -104,7 +104,7 @@ def _hypothesis_strategies() -> Any: try: from hypothesis import strategies as st except ImportError as exc: # pragma: no cover - exercised only without test extra - raise ImportError("pensive.testing.strategies requires Hypothesis; install pensive[test].") from exc + raise ImportError("sofic.testing.strategies requires Hypothesis; install sofic[test].") from exc return st diff --git a/sofic/viz/__init__.py b/sofic/viz/__init__.py new file mode 100644 index 0000000..2bcbc47 --- /dev/null +++ b/sofic/viz/__init__.py @@ -0,0 +1,14 @@ +"""Visualization for sofic models (Graphviz and TikZ).""" + +from sofic.viz.graphviz import draw, model_to_graphviz, model_to_svg +from sofic.viz.tikz import compile_tikz, draw_tikz, model_to_tikz, model_to_tikz_image + +__all__ = [ + "compile_tikz", + "draw", + "draw_tikz", + "model_to_graphviz", + "model_to_svg", + "model_to_tikz", + "model_to_tikz_image", +] diff --git a/pensive/viz/_context.py b/sofic/viz/_context.py similarity index 87% rename from pensive/viz/_context.py rename to sofic/viz/_context.py index 2aa2db4..cd203fe 100644 --- a/pensive/viz/_context.py +++ b/sofic/viz/_context.py @@ -6,8 +6,8 @@ from dataclasses import dataclass, field from typing import Any -from pensive.base import StateMachine -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.graph import ( ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_FUTURE_SYMBOL, @@ -18,7 +18,7 @@ KIND_RETURN, Transition, ) -from pensive.viz._edge import ( +from sofic.viz._edge import ( PART_KIND, PART_MATCH_TAG, PART_MULTIPLICITY, @@ -29,7 +29,7 @@ EdgeSpec, edge_spec, ) -from pensive.viz._format import ( +from sofic.viz._format import ( format_belief, format_distribution, format_prob_label, @@ -40,7 +40,7 @@ @dataclass(frozen=True, slots=True) class VizContext: - """Rendering policy for one :class:`~pensive.base.StateMachine`.""" + """Rendering policy for one :class:`~sofic.base.StateMachine`.""" title: str initial_states: frozenset[Hashable] @@ -83,7 +83,7 @@ def _state_label_with_attrs(state: Hashable, attrs: Mapping[str, Any], extras: l def _edge_state_label_with_attrs(attrs: Mapping[str, Any]) -> str | None: - from pensive.generators.edge_machine import ATTR_EDGE_SOURCE, ATTR_EDGE_TARGET + from sofic.generators.edge_machine import ATTR_EDGE_SOURCE, ATTR_EDGE_TARGET if ATTR_EDGE_SOURCE not in attrs or ATTR_EMISSION not in attrs or ATTR_EDGE_TARGET not in attrs: return None @@ -134,9 +134,9 @@ def _recurrence_fill_sets( initial_states: frozenset[Hashable], ) -> tuple[frozenset[Hashable], frozenset[Hashable]]: """Return ``(transient, recurrent_highlight)`` state sets for node fill colors.""" - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.epsilon_machine import EpsilonMachine - from pensive.generators.mixed_state import MixedStatePresentation + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine + from sofic.generators.mixed_state import MixedStatePresentation if isinstance(model, MixedStatePresentation): return model.transient_states, model.pure_states @@ -144,7 +144,7 @@ def _recurrence_fill_sets( if isinstance(model, (EpsilonMachine, BidirectionalEpsilonMachine)): recurrent = model.graph.terminal_recurrent_states() if isinstance(model, BidirectionalEpsilonMachine): - from pensive.generators.prob import is_positive_mass + from sofic.generators.prob import is_positive_mass starts = { state @@ -165,17 +165,17 @@ def _recurrence_fill_sets( def viz_context(model: StateMachine, *, style: str = "auto") -> VizContext: - from pensive.automata.base import LabeledAutomaton - from pensive.automata.transducers import Transducer - from pensive.automata.vpa import VisiblyPushdownAutomaton - from pensive.generators.base import QuasiStochasticModel, StochasticModel - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.epsilon_machine import EpsilonMachine - from pensive.generators.mealy import MealyHMM - from pensive.generators.mixed_state import MixedState, MixedStatePresentation, pure_state_index - from pensive.generators.moore import MooreHMM - from pensive.generators.nmachine import NMachine - from pensive.shifts.sofic_dyck import SoficDyckShift + from sofic.automata.base import LabeledAutomaton + from sofic.automata.transducers import Transducer + from sofic.automata.vpa import VisiblyPushdownAutomaton + from sofic.generators.base import QuasiStochasticModel, StochasticModel + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.epsilon_machine import EpsilonMachine + from sofic.generators.mealy import MealyHMM + from sofic.generators.mixed_state import MixedState, MixedStatePresentation, pure_state_index + from sofic.generators.moore import MooreHMM + from sofic.generators.nmachine import NMachine + from sofic.shifts.sofic_dyck import SoficDyckShift paper_style = style == "paper" or (style == "auto" and isinstance(model, BidirectionalEpsilonMachine)) epsilon_paper = style == "paper" diff --git a/pensive/viz/_edge.py b/sofic/viz/_edge.py similarity index 89% rename from pensive/viz/_edge.py rename to sofic/viz/_edge.py index f0fbc45..a2ed27b 100644 --- a/pensive/viz/_edge.py +++ b/sofic/viz/_edge.py @@ -14,8 +14,8 @@ from dataclasses import dataclass from typing import Any -from pensive.base import StateMachine -from pensive.graph import ( +from sofic.base import StateMachine +from sofic.graph import ( ATTR_EMISSION, ATTR_KIND, ATTR_MULTIPLICITY, @@ -93,18 +93,18 @@ def _prob_part(prob: Any, quasiprob: Any) -> EdgePart | None: def edge_spec(model: StateMachine, transition: Transition) -> EdgeSpec: """Decompose ``transition`` for ``model`` into a backend-agnostic spec.""" - from pensive.automata.base import LabeledAutomaton - from pensive.automata.transducers import MooreMachine, Transducer - from pensive.automata.vpa import VisiblyPushdownAutomaton - from pensive.generators.base import QuasiStochasticModel, StochasticModel - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.mealy import MealyHMM - from pensive.generators.mixed_state import MixedStatePresentation - from pensive.generators.moore import MooreHMM - from pensive.generators.nmachine import NMachine - from pensive.shifts.base import SymbolicModel - from pensive.shifts.sofic_dyck import SoficDyckShift, transition_ref - from pensive.shifts.tmc import TopologicalMarkovChain + from sofic.automata.base import LabeledAutomaton + from sofic.automata.transducers import MooreMachine, Transducer + from sofic.automata.vpa import VisiblyPushdownAutomaton + from sofic.generators.base import QuasiStochasticModel, StochasticModel + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.mealy import MealyHMM + from sofic.generators.mixed_state import MixedStatePresentation + from sofic.generators.moore import MooreHMM + from sofic.generators.nmachine import NMachine + from sofic.shifts.base import SymbolicModel + from sofic.shifts.sofic_dyck import SoficDyckShift, transition_ref + from sofic.shifts.tmc import TopologicalMarkovChain data = transition.data symbol = data.get(ATTR_SYMBOL) diff --git a/pensive/viz/_format.py b/sofic/viz/_format.py similarity index 94% rename from pensive/viz/_format.py rename to sofic/viz/_format.py index 5d016bd..7b8ed91 100644 --- a/pensive/viz/_format.py +++ b/sofic/viz/_format.py @@ -5,8 +5,8 @@ from collections.abc import Mapping, Sequence from typing import Any -from pensive.viz import _labels -from pensive.viz._rational import two_digit_rational +from sofic.viz import _labels +from sofic.viz._rational import two_digit_rational def dot_escape(text: str) -> str: @@ -51,7 +51,7 @@ def format_prob_rational(value: float, *, precision: int = 3) -> str: def format_prob_label(value: Any, *, precision: int = 3) -> str: """Format a probability for Graphviz edge/π labels (float or sympy Expr).""" try: - from pensive.generators.prob import is_symbolic, simplify_prob + from sofic.generators.prob import is_symbolic, simplify_prob except ImportError: # pragma: no cover def is_symbolic(_v: Any) -> bool: diff --git a/pensive/viz/_labels.py b/sofic/viz/_labels.py similarity index 97% rename from pensive/viz/_labels.py rename to sofic/viz/_labels.py index 7bc7f08..0cf3b3a 100644 --- a/pensive/viz/_labels.py +++ b/sofic/viz/_labels.py @@ -11,7 +11,7 @@ from collections.abc import Callable from typing import Any -from pensive.graph import EPSILON +from sofic.graph import EPSILON def format_state(state: Any, *, escape: Callable[[str], str], epsilon: str) -> str: diff --git a/pensive/viz/_names.py b/sofic/viz/_names.py similarity index 100% rename from pensive/viz/_names.py rename to sofic/viz/_names.py diff --git a/pensive/viz/_rational.py b/sofic/viz/_rational.py similarity index 100% rename from pensive/viz/_rational.py rename to sofic/viz/_rational.py diff --git a/pensive/viz/_tikz_compile.py b/sofic/viz/_tikz_compile.py similarity index 93% rename from pensive/viz/_tikz_compile.py rename to sofic/viz/_tikz_compile.py index 237624c..b214f03 100644 --- a/pensive/viz/_tikz_compile.py +++ b/sofic/viz/_tikz_compile.py @@ -84,10 +84,10 @@ def compile_tikz_fragment(fragment: str, *, format: str = "png") -> bytes: raise ValueError(f"unsupported compile format {format!r}; expected pdf, png, or svg") pdflatex = _require_pdflatex() - with tempfile.TemporaryDirectory(prefix="pensive-tikz-") as tmp: + with tempfile.TemporaryDirectory(prefix="sofic-tikz-") as tmp: workdir = Path(tmp) shutil.copy2(_VAUCANSON_ASSET, workdir / "vaucanson.tikz") - tex_path = workdir / "pensive_tikz.tex" + tex_path = workdir / "sofic_tikz.tex" tex_path.write_text(compilation_document(fragment), encoding="utf-8") result = subprocess.run( @@ -97,16 +97,16 @@ def compile_tikz_fragment(fragment: str, *, format: str = "png") -> bytes: text=True, check=False, ) - pdf_path = workdir / "pensive_tikz.pdf" + pdf_path = workdir / "sofic_tikz.pdf" if result.returncode != 0 or not pdf_path.is_file(): - log_tail = (workdir / "pensive_tikz.log").read_text(encoding="utf-8", errors="replace") + log_tail = (workdir / "sofic_tikz.log").read_text(encoding="utf-8", errors="replace") raise TikzCompileError("pdflatex failed to compile TikZ figure.\n" + log_tail[-4000:]) if normalized == "pdf": return pdf_path.read_bytes() if normalized == "png": - return _pdf_to_png(pdf_path, workdir / "pensive_tikz.png") - return _pdf_to_svg(pdf_path, workdir / "pensive_tikz.svg") + return _pdf_to_png(pdf_path, workdir / "sofic_tikz.png") + return _pdf_to_svg(pdf_path, workdir / "sofic_tikz.svg") def _pdf_to_png(pdf_path: Path, png_path: Path) -> bytes: diff --git a/pensive/viz/_tikz_format.py b/sofic/viz/_tikz_format.py similarity index 95% rename from pensive/viz/_tikz_format.py rename to sofic/viz/_tikz_format.py index 4474463..5a5a8f1 100644 --- a/pensive/viz/_tikz_format.py +++ b/sofic/viz/_tikz_format.py @@ -5,8 +5,8 @@ from collections.abc import Sequence from typing import Any -from pensive.viz import _labels -from pensive.viz._rational import _TWO_DIGIT_RATIONAL_ATOL, two_digit_rational +from sofic.viz import _labels +from sofic.viz._rational import _TWO_DIGIT_RATIONAL_ATOL, two_digit_rational _LATEX_SPECIAL = { "\\": r"\textbackslash{}", @@ -62,7 +62,7 @@ def format_symbol_latex(symbol: Any) -> str: def format_prob_latex(value: Any, *, precision: int = 3) -> str: """Format a probability for Vaucanson edge labels (float or sympy Expr).""" try: - from pensive.generators.prob import is_symbolic, simplify_prob + from sofic.generators.prob import is_symbolic, simplify_prob except ImportError: # pragma: no cover is_symbolic = lambda _v: False # noqa: E731 simplify_prob = lambda v: v # noqa: E731 diff --git a/pensive/viz/_tikz_layout.py b/sofic/viz/_tikz_layout.py similarity index 98% rename from pensive/viz/_tikz_layout.py rename to sofic/viz/_tikz_layout.py index 5ca6dcb..ada56b3 100644 --- a/pensive/viz/_tikz_layout.py +++ b/sofic/viz/_tikz_layout.py @@ -8,8 +8,8 @@ from collections.abc import Hashable, Mapping from typing import Any -from pensive.base import StateMachine -from pensive.viz._names import node_name +from sofic.base import StateMachine +from sofic.viz._names import node_name _LOOP_STYLES = ("loop above", "loop right", "loop below", "loop left") _BEND_STYLES = ("bend left", "bend right") @@ -66,7 +66,7 @@ def layout_graphviz( rankdir: str | None = None, ) -> dict[Hashable, str]: """Return TikZ ``at (...)`` clauses using Graphviz node positions.""" - from pensive.viz.graphviz import model_to_graphviz + from sofic.viz.graphviz import model_to_graphviz dot = model_to_graphviz(model, style=style, rankdir=rankdir) plain = dot.pipe(format="plain").decode("utf-8") diff --git a/pensive/viz/assets/vaucanson.tikz b/sofic/viz/assets/vaucanson.tikz similarity index 100% rename from pensive/viz/assets/vaucanson.tikz rename to sofic/viz/assets/vaucanson.tikz diff --git a/pensive/viz/graphviz.py b/sofic/viz/graphviz.py similarity index 90% rename from pensive/viz/graphviz.py rename to sofic/viz/graphviz.py index 11ed276..425b0b4 100644 --- a/pensive/viz/graphviz.py +++ b/sofic/viz/graphviz.py @@ -1,13 +1,13 @@ -"""Graphviz rendering for pensive state-machine models.""" +"""Graphviz rendering for sofic state-machine models.""" from __future__ import annotations from typing import TYPE_CHECKING, Any -from pensive.base import StateMachine -from pensive.viz._context import VizContext, viz_context -from pensive.viz._format import format_state -from pensive.viz._names import node_name as _node_name +from sofic.base import StateMachine +from sofic.viz._context import VizContext, viz_context +from sofic.viz._format import format_state +from sofic.viz._names import node_name as _node_name if TYPE_CHECKING: import graphviz @@ -18,14 +18,14 @@ def _require_graphviz() -> Any: import graphviz except ImportError as exc: raise ImportError( - "Graphviz rendering requires the optional pensive[viz] extra " - "(pip install 'pensive[viz]') and the Graphviz system binaries." + "Graphviz rendering requires the optional sofic[viz] extra " + "(pip install 'sofic[viz]') and the Graphviz system binaries." ) from exc return graphviz def _model_for_viz(model: StateMachine) -> StateMachine: - from pensive.generators.quasi_realization import QuasiRealization + from sofic.generators.quasi_realization import QuasiRealization if isinstance(model, QuasiRealization) and not any(model.states()): return model.to_nmachine() diff --git a/pensive/viz/tikz.py b/sofic/viz/tikz.py similarity index 92% rename from pensive/viz/tikz.py rename to sofic/viz/tikz.py index 481bcfc..9c73abb 100644 --- a/pensive/viz/tikz.py +++ b/sofic/viz/tikz.py @@ -1,4 +1,4 @@ -"""TikZ / Vaucanson rendering for pensive state-machine models.""" +"""TikZ / Vaucanson rendering for sofic state-machine models.""" from __future__ import annotations @@ -10,10 +10,10 @@ from pathlib import Path from typing import Any -from pensive.base import StateMachine -from pensive.graph import Transition -from pensive.viz._context import VizContext, viz_context -from pensive.viz._edge import ( +from sofic.base import StateMachine +from sofic.graph import Transition +from sofic.viz._context import VizContext, viz_context +from sofic.viz._edge import ( PART_EMISSION, PART_KIND, PART_MATCH_TAG, @@ -33,8 +33,8 @@ edge_spec, part_value, ) -from pensive.viz._names import node_name -from pensive.viz._tikz_format import ( +from sofic.viz._names import node_name +from sofic.viz._tikz_format import ( format_belief_tikz_node, format_edge_latex, format_prob_latex, @@ -45,18 +45,18 @@ format_transducer_edge_latex, latex_escape, ) -from pensive.viz._tikz_layout import ( +from sofic.viz._tikz_layout import ( edge_style, layout_circle, layout_graphviz, placement_to_xy, plan_loop_styles, ) -from pensive.viz.graphviz import _model_for_viz +from sofic.viz.graphviz import _model_for_viz def _tikz_state_label(context: VizContext, state: Hashable) -> str: - from pensive.generators.mixed_state import MixedState, pure_state_index + from sofic.generators.mixed_state import MixedState, pure_state_index if isinstance(state, MixedState): if pure_state_index(state) is not None: @@ -147,7 +147,7 @@ def _tikz_edge_label(model: StateMachine, transition: Transition) -> str: def _tikz_display_kwargs(model: StateMachine) -> dict[str, Any]: """Kwargs for notebook / default TikZ rendering.""" - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine if isinstance(model, BidirectionalEpsilonMachine): return {"style": "auto", "layout": "graphviz"} @@ -192,8 +192,8 @@ def model_to_tikz( ] lines.append(r"\begin{tikzpicture}[" + ",\n ".join(picture_options) + "]") - from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine - from pensive.generators.mixed_state import MixedState, pure_state_index + from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine + from sofic.generators.mixed_state import MixedState, pure_state_index for state in sorted(model.states(), key=repr): node = node_name(state) @@ -263,7 +263,7 @@ def model_to_tikz( def _standalone_document(body: str) -> str: - from pensive.viz._tikz_compile import compilation_document + from sofic.viz._tikz_compile import compilation_document return compilation_document(body) @@ -274,7 +274,7 @@ def compile_tikz( format: str = "png", ) -> bytes: """Compile a TikZ fragment to PDF, PNG, or SVG bytes.""" - from pensive.viz._tikz_compile import compile_tikz_fragment + from sofic.viz._tikz_compile import compile_tikz_fragment return compile_tikz_fragment(fragment, format=format) diff --git a/tests/conftest.py b/tests/conftest.py index 8a8d5b7..926667f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,5 +4,5 @@ from hypothesis import settings -settings.register_profile("pensive", deadline=None) -settings.load_profile("pensive") +settings.register_profile("sofic", deadline=None) +settings.load_profile("sofic") diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 22d1d75..8ae3ddd 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -6,7 +6,7 @@ from hypothesis import given, settings from hypothesis import strategies as st -from pensive.automata.algorithms import ( +from sofic.automata.algorithms import ( complete, determinize, equivalent, @@ -14,10 +14,10 @@ reverse, trim, ) -from pensive.automata.dfa import DFA -from pensive.automata.languages.base import AutomatonLanguage -from pensive.automata.languages.operations import reverse as reverse_language -from pensive.automata.nfa import NFA +from sofic.automata.dfa import DFA +from sofic.automata.languages.base import AutomatonLanguage +from sofic.automata.languages.operations import reverse as reverse_language +from sofic.automata.nfa import NFA def _epsilon_nfa() -> NFA: diff --git a/tests/test_atomaton.py b/tests/test_atomaton.py index f76762f..19f88a2 100644 --- a/tests/test_atomaton.py +++ b/tests/test_atomaton.py @@ -1,8 +1,8 @@ """Tests for átomaton skeletons.""" -from pensive.automata.atomaton import Atomaton, MaximizedPrimeAtomaton -from pensive.automata.dfa import DFA -from pensive.automata.rfsa import CanonicalRFSA +from sofic.automata.atomaton import Atomaton, MaximizedPrimeAtomaton +from sofic.automata.dfa import DFA +from sofic.automata.rfsa import CanonicalRFSA def test_atomaton_validate(): diff --git a/tests/test_bayesian_inference.py b/tests/test_bayesian_inference.py index 67f58c6..c3e6c24 100644 --- a/tests/test_bayesian_inference.py +++ b/tests/test_bayesian_inference.py @@ -5,8 +5,8 @@ import numpy as np import pytest -from pensive.examples.processes import SNS, Even -from pensive.inference.bayesian import ( +from sofic.examples.processes import SNS, Even +from sofic.inference.bayesian import ( BayesianInferenceError, InferEM, InferMC, diff --git a/tests/test_bidirectional_epsilon.py b/tests/test_bidirectional_epsilon.py index d915c5f..bc6791b 100644 --- a/tests/test_bidirectional_epsilon.py +++ b/tests/test_bidirectional_epsilon.py @@ -6,7 +6,7 @@ import pytest -from pensive.examples import ( +from sofic.examples import ( ellison_fig9_forward, ellison_fig9_reverse, ellison_fig15_bidirectional, @@ -16,11 +16,11 @@ golden_mean_reverse, tent_map_misiurewicz_bidirectional, ) -from pensive.generators.bidirectional_construction import infer_reverse_epsilon_machine -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.reversal import time_reverse_stochastic -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.generators.bidirectional_construction import infer_reverse_epsilon_machine +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.reversal import time_reverse_stochastic +from sofic.graph import ATTR_EMISSION, ATTR_PROB def test_bidirectional_reversible_coin(): @@ -69,7 +69,7 @@ def test_from_forward_golden_mean_forward_three_states(): def test_from_forward_golden_mean_shift_three_states(): import networkx as nx - from pensive.examples import golden_mean + from sofic.examples import golden_mean forward = golden_mean(0.5) bidir = BidirectionalEpsilonMachine.from_forward(forward) @@ -256,7 +256,7 @@ def test_nemo_bidirectional_selects_true_recurrent_component(): pytest.importorskip("dit") import networkx as nx - from pensive.examples import nemo_process + from sofic.examples import nemo_process forward = nemo_process(0.5, 0.5) bidir = forward.to_bidirectional() @@ -288,7 +288,7 @@ def test_nemo_bidirectional_selects_true_recurrent_component(): def test_bidirectional_tent_map_fig8_edges(): """Supplement Fig.~8 topology with symbolic ``1/2`` and ``a/(a+1)`` weights.""" - from pensive.examples.epsilon_machines import _tent_map_misiurewicz_fig8_edges, tent_map_misiurewicz_a + from sofic.examples.epsilon_machines import _tent_map_misiurewicz_fig8_edges, tent_map_misiurewicz_a a = tent_map_misiurewicz_a() half = 0.5 diff --git a/tests/test_bidirectional_hypothesis.py b/tests/test_bidirectional_hypothesis.py index 5e8d1c5..3ffc84d 100644 --- a/tests/test_bidirectional_hypothesis.py +++ b/tests/test_bidirectional_hypothesis.py @@ -6,8 +6,8 @@ from hypothesis import given, settings from hypothesis import strategies as st -from pensive.examples import golden_mean_forward, golden_mean_reverse -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.examples import golden_mean_forward, golden_mean_reverse +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine @given(p=st.floats(min_value=0.05, max_value=0.95)) diff --git a/tests/test_block_convergence.py b/tests/test_block_convergence.py index 7c4dbb3..e37c58e 100644 --- a/tests/test_block_convergence.py +++ b/tests/test_block_convergence.py @@ -4,8 +4,8 @@ import pytest -from pensive.examples import even_process, fair_coin, golden_mean, noisy_random_phase_slip -from pensive.generators.block_convergence import block_caekl +from sofic.examples import even_process, fair_coin, golden_mean, noisy_random_phase_slip +from sofic.generators.block_convergence import block_caekl def test_fair_coin_block_convergence_independent(): diff --git a/tests/test_block_entropy.py b/tests/test_block_entropy.py index 070e267..4849f05 100644 --- a/tests/test_block_entropy.py +++ b/tests/test_block_entropy.py @@ -9,10 +9,10 @@ import pytest from hypothesis import given, settings -from pensive.examples import fair_coin, golden_mean -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.topological_epsilon_enumeration import idfa_string_to_epsilon_machine -from pensive.testing.strategies import epsilon_machines +from sofic.examples import fair_coin, golden_mean +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.topological_epsilon_enumeration import idfa_string_to_epsilon_machine +from sofic.testing.strategies import epsilon_machines def test_fair_coin_block_entropy_diagram_is_linear(): diff --git a/tests/test_buchi.py b/tests/test_buchi.py index 86d1b4d..16041d2 100644 --- a/tests/test_buchi.py +++ b/tests/test_buchi.py @@ -2,8 +2,8 @@ import pytest -from pensive.automata.buchi import BuchiAutomaton -from pensive.graph import ATTR_SYMBOL +from sofic.automata.buchi import BuchiAutomaton +from sofic.graph import ATTR_SYMBOL def _accepting_loop_ba() -> BuchiAutomaton: diff --git a/tests/test_cm_gap_measures.py b/tests/test_cm_gap_measures.py index c2e600b..2b75569 100644 --- a/tests/test_cm_gap_measures.py +++ b/tests/test_cm_gap_measures.py @@ -4,13 +4,13 @@ import pytest -from pensive.examples import even_process, fair_coin, golden_mean -from pensive.generators.directional_flow import ( +from sofic.examples import even_process, fair_coin, golden_mean +from sofic.generators.directional_flow import ( directed_information, independent_pair_generator, transfer_entropy, ) -from pensive.generators.epsilon_machine import EpsilonMachine +from sofic.generators.epsilon_machine import EpsilonMachine def test_causal_irreversibility_even_process_is_zero(): @@ -68,7 +68,7 @@ def test_independent_pair_has_zero_transfer_entropy(): def test_information_flow_measures_on_independent_pair(): pytest.importorskip("dit") - from pensive.generators.directional_flow import ( + from sofic.generators.directional_flow import ( intrinsic_information_flow, shared_information_flow, synergistic_information_flow, diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 1db07df..c401650 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -1,13 +1,13 @@ """Tests for generator conversions.""" -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.generators.base import HiddenMarkovModel -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_SYMBOL -from pensive.shifts.sofic import SoficShift +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.generators.base import HiddenMarkovModel +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_SYMBOL +from sofic.shifts.sofic import SoficShift def _golden_mean_support_hmm() -> MealyHMM: diff --git a/tests/test_covers.py b/tests/test_covers.py index 56da1a2..9172871 100644 --- a/tests/test_covers.py +++ b/tests/test_covers.py @@ -2,9 +2,9 @@ import pytest -from pensive.graph import ATTR_SYMBOL -from pensive.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover -from pensive.shifts.sofic import SoficShift +from sofic.graph import ATTR_SYMBOL +from sofic.shifts.covers import LeftFischerCover, LeftKriegerCover, RightFischerCover, RightKriegerCover +from sofic.shifts.sofic import SoficShift def _golden_mean() -> SoficShift: diff --git a/tests/test_dfa.py b/tests/test_dfa.py index f86908c..9f136e2 100644 --- a/tests/test_dfa.py +++ b/tests/test_dfa.py @@ -4,10 +4,10 @@ import pytest -from pensive.automata import automaton_to_regex -from pensive.automata.dfa import DFA -from pensive.exceptions import NonDeterministicError -from pensive.graph import EPSILON +from sofic.automata import automaton_to_regex +from sofic.automata.dfa import DFA +from sofic.exceptions import NonDeterministicError +from sofic.graph import EPSILON def _dfa() -> DFA: diff --git a/tests/test_edge_machine.py b/tests/test_edge_machine.py index 0b87fea..62da4e5 100644 --- a/tests/test_edge_machine.py +++ b/tests/test_edge_machine.py @@ -4,10 +4,10 @@ import pytest -from pensive.examples import fair_coin, golden_mean -from pensive.generators.edge_machine import ATTR_EDGE_SOURCE, ATTR_EDGE_TARGET, hmm_to_edge_machine -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.examples import fair_coin, golden_mean +from sofic.generators.edge_machine import ATTR_EDGE_SOURCE, ATTR_EDGE_TARGET, hmm_to_edge_machine +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _count_labeled_transitions(hmm) -> int: diff --git a/tests/test_epsilon_inference.py b/tests/test_epsilon_inference.py index db5035c..00c4ca3 100644 --- a/tests/test_epsilon_inference.py +++ b/tests/test_epsilon_inference.py @@ -9,11 +9,11 @@ import numpy as np import pytest -from pensive.examples.epsilon_machines import bernoulli, even_process, golden_mean -from pensive.generators.epsilon_inference import cssr, subtree_merge -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.hmm_inference import sample -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.examples.epsilon_machines import bernoulli, even_process, golden_mean +from sofic.generators.epsilon_inference import cssr, subtree_merge +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.hmm_inference import sample +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _transition_signature(hmm: EpsilonMachine) -> dict[Hashable, tuple[tuple[Any, Hashable, float], ...]]: diff --git a/tests/test_epsilon_machine.py b/tests/test_epsilon_machine.py index 0584dcf..ff30176 100644 --- a/tests/test_epsilon_machine.py +++ b/tests/test_epsilon_machine.py @@ -1,10 +1,10 @@ """Tests for epsilon machine construction.""" -from pensive.examples.epsilon_machines import ellison_fig9_forward, golden_mean -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.generators.reversal import time_reverse_stochastic -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.examples.epsilon_machines import ellison_fig9_forward, golden_mean +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.generators.reversal import time_reverse_stochastic +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _unifilar_mealy() -> MealyHMM: @@ -60,10 +60,10 @@ def test_from_hmm_via_msp_on_nonunifilar_reverse(): def test_row_normalized_presentation_fallback(): from unittest.mock import patch - from pensive.examples import golden_mean_forward - from pensive.exceptions import UnifilarityError - from pensive.generators.epsilon_machine import _row_normalized_presentation - from pensive.generators.reversal import time_reverse_stochastic + from sofic.examples import golden_mean_forward + from sofic.exceptions import UnifilarityError + from sofic.generators.epsilon_machine import _row_normalized_presentation + from sofic.generators.reversal import time_reverse_stochastic forward = golden_mean_forward(0.5) rev_hmm = time_reverse_stochastic(forward) diff --git a/tests/test_examples.py b/tests/test_examples.py index 5b475b1..60b2f2f 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from pensive.examples import ( +from sofic.examples import ( alternating_biased_coins, bernoulli, butterfly_process, @@ -19,8 +19,8 @@ nemo_process, restricted_golden_mean, ) -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.shifts.tmc import TopologicalMarkovChain @pytest.mark.parametrize( diff --git a/tests/test_examples_nrps.py b/tests/test_examples_nrps.py index 4338373..8f5f643 100644 --- a/tests/test_examples_nrps.py +++ b/tests/test_examples_nrps.py @@ -4,8 +4,8 @@ import pytest -from pensive.examples import even_process, golden_mean, noisy_random_phase_slip -from pensive.examples.processes import NRPS +from sofic.examples import even_process, golden_mean, noisy_random_phase_slip +from sofic.examples.processes import NRPS def test_nrps_alias_matches_canonical_constructor(): diff --git a/tests/test_graph.py b/tests/test_graph.py index da85b23..86b4327 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,6 +1,6 @@ """Tests for TransitionGraph and Transition.""" -from pensive.graph import ( +from sofic.graph import ( ATTR_PROB, ATTR_SYMBOL, EPSILON, diff --git a/tests/test_hmm_inference.py b/tests/test_hmm_inference.py index 5f046d6..b780716 100644 --- a/tests/test_hmm_inference.py +++ b/tests/test_hmm_inference.py @@ -5,10 +5,10 @@ import numpy as np import pytest -from pensive.examples import fair_coin -from pensive.generators.hmm_inference import backward, forward, log_likelihood, sample, viterbi -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.examples import fair_coin +from sofic.generators.hmm_inference import backward, forward, log_likelihood, sample, viterbi +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB def test_forward_coin_initial_and_likelihood(): diff --git a/tests/test_icdfa.py b/tests/test_icdfa.py index 4bf5dec..87ed1b8 100644 --- a/tests/test_icdfa.py +++ b/tests/test_icdfa.py @@ -6,8 +6,8 @@ import pytest -from pensive.automata.dfa import DFA -from pensive.automata.icdfa import ( +from sofic.automata.dfa import DFA +from sofic.automata.icdfa import ( ICDFAEnumerationError, _upper_bound_at, count_flag_sequences, diff --git a/tests/test_information_anatomy.py b/tests/test_information_anatomy.py index e3b33c8..243a0ab 100644 --- a/tests/test_information_anatomy.py +++ b/tests/test_information_anatomy.py @@ -4,7 +4,7 @@ import pytest -from pensive.examples import ( +from sofic.examples import ( bernoulli, butterfly_process, even_process, @@ -15,7 +15,7 @@ tent_map_misiurewicz_forward, tent_map_misiurewicz_information_expected, ) -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine def test_fair_coin_predicted_information_near_zero(): @@ -238,8 +238,8 @@ def test_tent_map_misiurewicz_bidirectional_regression(): def test_tent_forward_matches_generator_path(): pytest.importorskip("dit") - from pensive.examples.epsilon_machines import tent_map_misiurewicz_hmm - from pensive.generators.epsilon_machine import EpsilonMachine + from sofic.examples.epsilon_machines import tent_map_misiurewicz_hmm + from sofic.generators.epsilon_machine import EpsilonMachine forward = tent_map_misiurewicz_forward() from_hmm = EpsilonMachine.from_hmm(tent_map_misiurewicz_hmm()) diff --git a/tests/test_information_anatomy_hypothesis.py b/tests/test_information_anatomy_hypothesis.py index 515845d..9030d97 100644 --- a/tests/test_information_anatomy_hypothesis.py +++ b/tests/test_information_anatomy_hypothesis.py @@ -7,8 +7,8 @@ from hypothesis import strategies as st from hypothesis.errors import Unsatisfiable -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.testing.strategies import epsilon_machines +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.testing.strategies import epsilon_machines THREE_STATE_ALPHABET = (0, 1, 2) THREE_BY_THREE_POOL = 32 diff --git a/tests/test_languages.py b/tests/test_languages.py index a61207b..05aaff0 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -1,9 +1,9 @@ """Tests for regular-language wrappers and quotients.""" -from pensive.automata.dfa import DFA -from pensive.automata.languages.base import AutomatonLanguage, ExplicitLanguage, as_language -from pensive.automata.languages.quotients import left_quotient, right_quotient -from pensive.automata.languages.residuals import is_composed_residual +from sofic.automata.dfa import DFA +from sofic.automata.languages.base import AutomatonLanguage, ExplicitLanguage, as_language +from sofic.automata.languages.quotients import left_quotient, right_quotient +from sofic.automata.languages.residuals import is_composed_residual def _simple_dfa() -> DFA: @@ -66,7 +66,7 @@ def test_is_composed_residual(): def test_atoms_from_dfa(): - from pensive.automata.languages.atoms import atoms + from sofic.automata.languages.atoms import atoms dfa = _simple_dfa() result = atoms(AutomatonLanguage(dfa)) diff --git a/tests/test_learning.py b/tests/test_learning.py index 8e9954a..7bfe988 100644 --- a/tests/test_learning.py +++ b/tests/test_learning.py @@ -1,8 +1,8 @@ """Tests for NL* learning.""" -from pensive.automata.dfa import DFA -from pensive.automata.languages.base import AutomatonLanguage -from pensive.automata.learning import learn_maximized_prime_atomaton +from sofic.automata.dfa import DFA +from sofic.automata.languages.base import AutomatonLanguage +from sofic.automata.learning import learn_maximized_prime_atomaton def _teacher_dfa() -> AutomatonLanguage: diff --git a/tests/test_lumping.py b/tests/test_lumping.py index d2115d8..1c9f816 100644 --- a/tests/test_lumping.py +++ b/tests/test_lumping.py @@ -3,12 +3,12 @@ import numpy as np import pytest -from pensive.exceptions import LumpabilityError -from pensive.generators.lumping import is_lumpable, lump, normalize_partition -from pensive.generators.markov import MarkovChain -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM -from pensive.properties import transition_matrix +from sofic.exceptions import LumpabilityError +from sofic.generators.lumping import is_lumpable, lump, normalize_partition +from sofic.generators.markov import MarkovChain +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM +from sofic.properties import transition_matrix def _symmetric_chain() -> MarkovChain: @@ -161,7 +161,7 @@ def test_custom_labels_mapping(): def test_unsupported_type_raises(): - from pensive.automata.nfa import NFA + from sofic.automata.nfa import NFA nfa = NFA(initial_states={"q0"}, accepting_states={"q0"}) nfa.graph.add_state("q0") @@ -189,7 +189,7 @@ def test_mealy_not_lumpable(): def test_epsilon_machine_lumps_to_plain_mealy(): - from pensive.examples import golden_mean + from sofic.examples import golden_mean eps = golden_mean(0.5) partition = [{state} for state in eps.states()] diff --git a/tests/test_markov.py b/tests/test_markov.py index ca41bdb..5b5a012 100644 --- a/tests/test_markov.py +++ b/tests/test_markov.py @@ -2,9 +2,9 @@ import pytest -from pensive.exceptions import StochasticValidationError -from pensive.generators.markov import MarkovChain -from pensive.graph import ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.markov import MarkovChain +from sofic.graph import ATTR_PROB def _chain() -> MarkovChain: diff --git a/tests/test_markov_dyck.py b/tests/test_markov_dyck.py index 168d59a..6fe338b 100644 --- a/tests/test_markov_dyck.py +++ b/tests/test_markov_dyck.py @@ -4,8 +4,8 @@ import numpy as np import pytest -from pensive.graph import KIND_CALL, KIND_RETURN, TransitionGraph -from pensive.shifts.markov_dyck import MarkovDyckShift +from sofic.graph import KIND_CALL, KIND_RETURN, TransitionGraph +from sofic.shifts.markov_dyck import MarkovDyckShift def _call(label): diff --git a/tests/test_mealy_hmm.py b/tests/test_mealy_hmm.py index c41d233..5e41341 100644 --- a/tests/test_mealy_hmm.py +++ b/tests/test_mealy_hmm.py @@ -2,10 +2,10 @@ import pytest -from pensive.exceptions import StochasticValidationError, UnifilarityError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.exceptions import StochasticValidationError, UnifilarityError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _mealy_hmm() -> MealyHMM: @@ -35,7 +35,7 @@ def test_to_mealy_returns_self(): def test_mixed_state_presentation_on_mealy_hmm(): - from pensive.generators.mixed_state import MixedStatePresentation + from sofic.generators.mixed_state import MixedStatePresentation msp = _mealy_hmm().mixed_state_presentation() assert isinstance(msp, MixedStatePresentation) diff --git a/tests/test_measures.py b/tests/test_measures.py index 91fae29..8fb4c43 100644 --- a/tests/test_measures.py +++ b/tests/test_measures.py @@ -2,7 +2,7 @@ import pytest -from pensive.examples.epsilon_machines import ( +from sofic.examples.epsilon_machines import ( bernoulli, butterfly_process, fair_coin, @@ -10,12 +10,12 @@ golden_mean_reverse, golden_mean_shift_parry, ) -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.moore import MooreHMM -from pensive.generators.nmachine import NMachine -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_QUASIPROB -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.moore import MooreHMM +from sofic.generators.nmachine import NMachine +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_QUASIPROB +from sofic.shifts.tmc import TopologicalMarkovChain pytest.importorskip("dit") @@ -53,7 +53,7 @@ def test_state_distribution_matches_stationary_vector(): def test_state_distribution_edge_machine_tuple_states_roundtrip(): """Edge-machine states are tuples; state_distribution must encode them losslessly.""" - from pensive.generators.edge_machine import parse_edge_state_label + from sofic.generators.edge_machine import parse_edge_state_label edge = fair_coin().to_edge_machine() dist = edge.state_distribution() diff --git a/tests/test_minimal_generative_model.py b/tests/test_minimal_generative_model.py index a2879d6..ab7acca 100644 --- a/tests/test_minimal_generative_model.py +++ b/tests/test_minimal_generative_model.py @@ -8,16 +8,16 @@ import numpy as np import pytest -from pensive.examples.epsilon_machines import bernoulli, from_symbol_matrices, golden_mean_bidirectional -from pensive.exceptions import StochasticValidationError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.minimal_generative_model import ( +from sofic.examples.epsilon_machines import bernoulli, from_symbol_matrices, golden_mean_bidirectional +from sofic.exceptions import StochasticValidationError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.minimal_generative_model import ( MinimalGenerativeModel, _auxiliary_state_channel, _model_from_channel, _reproduction_error, ) -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.graph import ATTR_EMISSION, ATTR_PROB pytest.importorskip("dit") @@ -259,7 +259,7 @@ def test_nemo_minimal_generative_model_reproduces_process(): here; the reproduction guard falls back to the deterministic functional realization, which is isomorphic to the forward epsilon-machine. """ - from pensive.examples import nemo_process + from sofic.examples import nemo_process process = nemo_process(0.5, 0.5) mgm = process.minimal_generative_model(niter=2, rng=np.random.default_rng(0)) @@ -273,8 +273,8 @@ def test_nemo_minimal_generative_model_reproduces_process(): def test_ensure_reproducing_falls_back_to_functional_channel(): """A deliberately non-reproducing channel is replaced by the functional fallback.""" - from pensive.examples import nemo_process - from pensive.generators.minimal_generative_model import ( + from sofic.examples import nemo_process + from sofic.generators.minimal_generative_model import ( _ensure_reproducing, _normalized_joint_distribution, ) diff --git a/tests/test_mixed_state.py b/tests/test_mixed_state.py index 5e985f6..e93c8fe 100644 --- a/tests/test_mixed_state.py +++ b/tests/test_mixed_state.py @@ -4,10 +4,10 @@ import pytest -from pensive.examples.epsilon_machines import bernoulli, even_process, golden_mean -from pensive.generators.mixed_state import MixedState, MixedStatePresentation, mixed_state_entropy -from pensive.generators.mixed_state_construction import build_mixed_state_presentation -from pensive.viz.graphviz import model_to_graphviz +from sofic.examples.epsilon_machines import bernoulli, even_process, golden_mean +from sofic.generators.mixed_state import MixedState, MixedStatePresentation, mixed_state_entropy +from sofic.generators.mixed_state_construction import build_mixed_state_presentation +from sofic.viz.graphviz import model_to_graphviz graphviz = pytest.importorskip("graphviz") @@ -70,8 +70,8 @@ def test_mixed_state_canonicalization_merges_near_duplicates(): def test_msp_deduplicates_canonical_beliefs(): - from pensive.generators.mealy import MealyHMM - from pensive.graph import ATTR_EMISSION, ATTR_PROB + from sofic.generators.mealy import MealyHMM + from sofic.graph import ATTR_EMISSION, ATTR_PROB hmm = MealyHMM(observation_alphabet=frozenset({0, 1})) for state in ("A", "B"): diff --git a/tests/test_mixed_state_recurrent.py b/tests/test_mixed_state_recurrent.py index 9f67bd9..53d9fb5 100644 --- a/tests/test_mixed_state_recurrent.py +++ b/tests/test_mixed_state_recurrent.py @@ -4,11 +4,11 @@ import pytest -from pensive.examples.epsilon_machines import golden_mean -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.mealy import MealyHMM -from pensive.generators.mixed_state import MixedState, MixedStatePresentation -from pensive.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph +from sofic.examples.epsilon_machines import golden_mean +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.mealy import MealyHMM +from sofic.generators.mixed_state import MixedState, MixedStatePresentation +from sofic.graph import ATTR_EMISSION, ATTR_PROB, TransitionGraph def test_to_recurrent_returns_epsilon_machine_for_pure_recurrent_msp(): diff --git a/tests/test_moore_hmm.py b/tests/test_moore_hmm.py index 7f4cdd1..19845ba 100644 --- a/tests/test_moore_hmm.py +++ b/tests/test_moore_hmm.py @@ -2,9 +2,9 @@ import pytest -from pensive.exceptions import StochasticValidationError -from pensive.generators.moore import MooreHMM -from pensive.graph import ATTR_EMISSION_DIST, ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.moore import MooreHMM +from sofic.graph import ATTR_EMISSION_DIST, ATTR_PROB def _moore_hmm() -> MooreHMM: diff --git a/tests/test_nfa.py b/tests/test_nfa.py index e34eed1..347d10a 100644 --- a/tests/test_nfa.py +++ b/tests/test_nfa.py @@ -1,6 +1,6 @@ """Tests for NFA and epsilon closure.""" -from pensive.automata.nfa import NFA +from sofic.automata.nfa import NFA def _nfa_with_epsilon() -> NFA: diff --git a/tests/test_nmachine.py b/tests/test_nmachine.py index 9a259ea..d35313f 100644 --- a/tests/test_nmachine.py +++ b/tests/test_nmachine.py @@ -2,10 +2,10 @@ import pytest -from pensive.exceptions import QuasiStochasticValidationError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.nmachine import NMachine -from pensive.graph import ATTR_EMISSION, ATTR_PROB, ATTR_QUASIPROB +from sofic.exceptions import QuasiStochasticValidationError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.nmachine import NMachine +from sofic.graph import ATTR_EMISSION, ATTR_PROB, ATTR_QUASIPROB def _nmachine() -> NMachine: diff --git a/tests/test_nwa.py b/tests/test_nwa.py index 369be26..019f896 100644 --- a/tests/test_nwa.py +++ b/tests/test_nwa.py @@ -2,10 +2,10 @@ import pytest -from pensive.automata.nwa import NestedWord, NestedWordAutomaton -from pensive.automata.vpa import VisiblyPushdownAutomaton -from pensive.exceptions import PensiveValidationError -from pensive.graph import ( +from sofic.automata.nwa import NestedWord, NestedWordAutomaton +from sofic.automata.vpa import VisiblyPushdownAutomaton +from sofic.exceptions import SoficValidationError +from sofic.graph import ( ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, @@ -41,7 +41,7 @@ def _visible(symbols: tuple[str, ...]) -> NestedWord: def test_nested_word_rejects_crossing_matches(): - with pytest.raises(PensiveValidationError): + with pytest.raises(SoficValidationError): NestedWord( symbols=("a", "b", "c", "d"), kinds=(KIND_CALL, KIND_CALL, KIND_RETURN, KIND_RETURN), @@ -50,7 +50,7 @@ def test_nested_word_rejects_crossing_matches(): def test_nested_word_rejects_bad_call_return_pair(): - with pytest.raises(PensiveValidationError): + with pytest.raises(SoficValidationError): NestedWord( symbols=("a", "b"), kinds=(KIND_CALL, KIND_INTERNAL), diff --git a/tests/test_observation.py b/tests/test_observation.py index 9d272b7..5d1753b 100644 --- a/tests/test_observation.py +++ b/tests/test_observation.py @@ -1,7 +1,7 @@ """Tests for observation tables.""" -from pensive.automata.languages.base import AutomatonLanguage -from pensive.automata.observation import ObservationTable +from sofic.automata.languages.base import AutomatonLanguage +from sofic.automata.observation import ObservationTable def test_defaults(): diff --git a/tests/test_pfa.py b/tests/test_pfa.py index 88c1b0e..12207bd 100644 --- a/tests/test_pfa.py +++ b/tests/test_pfa.py @@ -2,9 +2,9 @@ import pytest -from pensive.exceptions import StochasticValidationError -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.graph import ATTR_EMISSION, ATTR_PROB +from sofic.exceptions import StochasticValidationError +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.graph import ATTR_EMISSION, ATTR_PROB def _pfa() -> ProbabilisticFiniteAutomaton: diff --git a/tests/test_posterior_diversity.py b/tests/test_posterior_diversity.py index 962704e..32cee03 100644 --- a/tests/test_posterior_diversity.py +++ b/tests/test_posterior_diversity.py @@ -5,9 +5,9 @@ import numpy as np import pytest -from pensive.examples import fair_coin -from pensive.examples.processes import Even, EvenRedundant -from pensive.inference.bayesian import ( +from sofic.examples import fair_coin +from sofic.examples.processes import Even, EvenRedundant +from sofic.inference.bayesian import ( InferEM, ModelComparisonEM, machine_diversity, @@ -123,7 +123,7 @@ def test_posterior_mean_and_monte_carlo_same_order_of_magnitude(): def test_infer_em_posterior_mean_word_distribution_matches_machine(): pytest.importorskip("dit") - from pensive.inference.bayesian.diversity import posterior_mean_word_distribution + from sofic.inference.bayesian.diversity import posterior_mean_word_distribution data = list("1111101100") posterior = InferEM(Even(), data) diff --git a/tests/test_processes_port.py b/tests/test_processes_port.py index b91e865..5c3137f 100644 --- a/tests/test_processes_port.py +++ b/tests/test_processes_port.py @@ -4,10 +4,10 @@ import pytest -import pensive.examples.processes as processes -from pensive.automata.transducers import MealyMachine -from pensive.generators.base import HiddenMarkovModel -from pensive.graph import EPSILON +import sofic.examples.processes as processes +from sofic.automata.transducers import MealyMachine +from sofic.generators.base import HiddenMarkovModel +from sofic.graph import EPSILON def test_cmpy_process_constructor_names_are_exported(): diff --git a/tests/test_properties.py b/tests/test_properties.py index ee023ce..56bf5ca 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -4,18 +4,18 @@ import pytest -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.automata.transducers import MealyMachine -from pensive.automata.unifilar import UnifilarAutomaton -from pensive.examples.epsilon_machines import ellison_fig15_bidirectional, even_process, golden_mean -from pensive.exceptions import NonDeterministicError, UnifilarityError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.markov import MarkovChain -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_SYMBOL, EPSILON -from pensive.shifts.sofic import SoficShift +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.automata.transducers import MealyMachine +from sofic.automata.unifilar import UnifilarAutomaton +from sofic.examples.epsilon_machines import ellison_fig15_bidirectional, even_process, golden_mean +from sofic.exceptions import NonDeterministicError, UnifilarityError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.markov import MarkovChain +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_SYMBOL, EPSILON +from sofic.shifts.sofic import SoficShift def _dfa() -> DFA: diff --git a/tests/test_quasi_realization.py b/tests/test_quasi_realization.py index 8c2f7fc..85878b5 100644 --- a/tests/test_quasi_realization.py +++ b/tests/test_quasi_realization.py @@ -3,9 +3,9 @@ import numpy as np import pytest -from pensive.generators.nmachine import NMachine -from pensive.generators.quasi_realization import QuasiRealization -from pensive.graph import ATTR_EMISSION, ATTR_QUASIPROB +from sofic.generators.nmachine import NMachine +from sofic.generators.quasi_realization import QuasiRealization +from sofic.graph import ATTR_EMISSION, ATTR_QUASIPROB def _nmachine() -> NMachine: diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 04d2f76..c448932 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -4,11 +4,11 @@ import numpy as np -from pensive.automata.nfa import NFA -from pensive.generators.markov import MarkovChain -from pensive.graph import ATTR_PROB, ATTR_SYMBOL, TransitionGraph -from pensive.operations import reverse as reverse_model -from pensive.shifts.sofic import SoficShift +from sofic.automata.nfa import NFA +from sofic.generators.markov import MarkovChain +from sofic.graph import ATTR_PROB, ATTR_SYMBOL, TransitionGraph +from sofic.operations import reverse as reverse_model +from sofic.shifts.sofic import SoficShift def test_transition_graph_reverse(): @@ -95,8 +95,8 @@ def test_operations_reverse_dispatches(): def test_mealy_hmm_reverse(): - from pensive.generators.mealy import MealyHMM - from pensive.graph import ATTR_EMISSION, ATTR_PROB + from sofic.generators.mealy import MealyHMM + from sofic.graph import ATTR_EMISSION, ATTR_PROB hmm = MealyHMM( initial_distribution={"q0": 1.0}, diff --git a/tests/test_rfsa.py b/tests/test_rfsa.py index 6192994..b0c3cde 100644 --- a/tests/test_rfsa.py +++ b/tests/test_rfsa.py @@ -1,8 +1,8 @@ """Tests for residual and canonical RFSA skeletons.""" -from pensive.automata.dfa import DFA -from pensive.automata.observation import ObservationTable -from pensive.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton +from sofic.automata.dfa import DFA +from sofic.automata.observation import ObservationTable +from sofic.automata.rfsa import CanonicalRFSA, ResidualFiniteStateAutomaton def test_rfsa_validate(): diff --git a/tests/test_sft.py b/tests/test_sft.py index 6a73518..cd0c4ad 100644 --- a/tests/test_sft.py +++ b/tests/test_sft.py @@ -1,7 +1,7 @@ """Tests for shifts of finite type.""" -from pensive.graph import ATTR_SYMBOL, TransitionGraph -from pensive.shifts.sft import ShiftOfFiniteType +from sofic.graph import ATTR_SYMBOL, TransitionGraph +from sofic.shifts.sft import ShiftOfFiniteType def test_from_presentation(): diff --git a/tests/test_shift_examples.py b/tests/test_shift_examples.py index f551846..3b98d16 100644 --- a/tests/test_shift_examples.py +++ b/tests/test_shift_examples.py @@ -4,14 +4,14 @@ import pytest -from pensive.examples import ( +from sofic.examples import ( dyck_shift_order, motzkin_shift, sofic_dyck_fig1_shift, sofic_dyck_nondeterminizable_shift, sofic_dyck_zeta_example_shift, ) -from pensive.shifts.sofic_dyck import SoficDyckShift +from sofic.shifts.sofic_dyck import SoficDyckShift @pytest.mark.parametrize( diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 360b671..1489adc 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -1,7 +1,7 @@ -"""Smoke tests for the pensive package.""" +"""Smoke tests for the sofic package.""" -import pensive +import sofic def test_import(): - assert pensive.__version__ + assert sofic.__version__ diff --git a/tests/test_sofic.py b/tests/test_sofic.py index 64af451..da7e9f7 100644 --- a/tests/test_sofic.py +++ b/tests/test_sofic.py @@ -1,7 +1,7 @@ """Tests for SoficShift.""" -from pensive.graph import ATTR_SYMBOL -from pensive.shifts.sofic import SoficShift +from sofic.graph import ATTR_SYMBOL +from sofic.shifts.sofic import SoficShift def test_validate(): diff --git a/tests/test_sofic_dyck.py b/tests/test_sofic_dyck.py index 6307915..920b1c0 100644 --- a/tests/test_sofic_dyck.py +++ b/tests/test_sofic_dyck.py @@ -2,9 +2,9 @@ import pytest -from pensive.exceptions import PensiveValidationError -from pensive.graph import KIND_CALL, KIND_INTERNAL, KIND_RETURN -from pensive.shifts.sofic_dyck import SoficDyckShift +from sofic.exceptions import SoficValidationError +from sofic.graph import KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.shifts.sofic_dyck import SoficDyckShift def _dyck2() -> SoficDyckShift: @@ -72,7 +72,7 @@ def test_validate_rejects_missing_matched_edge(): call_ref = next(iter(call for call, _return in shift.matched_edges)) shift.matched_edges = frozenset({(call_ref, ("missing", "missing", 0))}) - with pytest.raises(PensiveValidationError, match="missing"): + with pytest.raises(SoficValidationError, match="missing"): shift.validate() @@ -88,14 +88,14 @@ def test_validate_rejects_matched_internal_edge(): shift.add_return_transition("q", "q", "r") shift.add_matched_pair(call, internal) - with pytest.raises(PensiveValidationError, match="not a return"): + with pytest.raises(SoficValidationError, match="not a return"): shift.validate() def test_validate_rejects_bad_role_alphabet(): shift = SoficDyckShift(call_alphabet=frozenset({"x"}), return_alphabet=frozenset({"x"})) - with pytest.raises(PensiveValidationError, match="disjoint"): + with pytest.raises(SoficValidationError, match="disjoint"): shift.validate() diff --git a/tests/test_stack_hmm.py b/tests/test_stack_hmm.py index 040b1a2..a208c5e 100644 --- a/tests/test_stack_hmm.py +++ b/tests/test_stack_hmm.py @@ -3,9 +3,9 @@ import numpy as np import pytest -from pensive.exceptions import StochasticValidationError -from pensive.generators.stack_hmm import HiddenMarkovStackModel -from pensive.shifts.sofic_dyck import SoficDyckShift +from sofic.exceptions import StochasticValidationError +from sofic.generators.stack_hmm import HiddenMarkovStackModel +from sofic.shifts.sofic_dyck import SoficDyckShift def _dyck2(*, allow_empty_stack_returns: bool = True) -> HiddenMarkovStackModel: diff --git a/tests/test_stack_inference.py b/tests/test_stack_inference.py index f8bf66b..86f3c83 100644 --- a/tests/test_stack_inference.py +++ b/tests/test_stack_inference.py @@ -5,22 +5,22 @@ import numpy as np import pytest -from pensive.automata.papni import DyckAlphabet, is_well_matched, learn_sofic_dyck_shift_papni, papni_encode -from pensive.automata.rpni import learn_dfa_rpni -from pensive.examples.shifts import dyck_shift_order, motzkin_shift -from pensive.generators.epsilon_inference import cssr -from pensive.generators.stack_hmm import HiddenMarkovStackModel -from pensive.generators.stack_inference import ( +from sofic.automata.papni import DyckAlphabet, is_well_matched, learn_sofic_dyck_shift_papni, papni_encode +from sofic.automata.rpni import learn_dfa_rpni +from sofic.examples.shifts import dyck_shift_order, motzkin_shift +from sofic.generators.epsilon_inference import cssr +from sofic.generators.stack_hmm import HiddenMarkovStackModel +from sofic.generators.stack_inference import ( fit_stack_hmm_mle, learn_stack_hmm_papni, stack_cssr, stack_subtree_merge, ) -from pensive.inference.bayesian.stack_hmm import ( +from sofic.inference.bayesian.stack_hmm import ( ModelComparisonStackHMM, StackHMMPosterior, ) -from pensive.shifts.dyck_enumeration import ( +from sofic.shifts.dyck_enumeration import ( count_dyck_graph_strings, dyck_graph_string_to_shift, iter_sofic_dyck_topologies, @@ -37,7 +37,7 @@ def _balanced_dyck_alphabet() -> DyckAlphabet: def _uniform_probabilities(shift): - from pensive.shifts.sofic_dyck import transition_ref + from sofic.shifts.sofic_dyck import transition_ref refs = [transition_ref(transition) for transition in shift.transitions()] return {ref: 1.0 / len(refs) for ref in refs} @@ -203,7 +203,7 @@ def test_benchmark_passive_paths(method: str): inferred.add_return_transition("s0", "s0", symbol, prob) else: inferred.add_internal_transition("s0", "s0", symbol, prob) - from pensive.graph import KIND_CALL, KIND_RETURN + from sofic.graph import KIND_CALL, KIND_RETURN for call in inferred.transitions(): if call.data.get("kind") != KIND_CALL: diff --git a/tests/test_stochastic.py b/tests/test_stochastic.py index 4317072..a883853 100644 --- a/tests/test_stochastic.py +++ b/tests/test_stochastic.py @@ -5,8 +5,8 @@ import numpy as np import pytest -from pensive.exceptions import StochasticValidationError -from pensive.generators.stochastic import assert_stochastic_rows, normalize_row_weights +from sofic.exceptions import StochasticValidationError +from sofic.generators.stochastic import assert_stochastic_rows, normalize_row_weights def test_normalize_row_weights(): diff --git a/tests/test_symbolic_hmm.py b/tests/test_symbolic_hmm.py index 4da4ef9..142ec95 100644 --- a/tests/test_symbolic_hmm.py +++ b/tests/test_symbolic_hmm.py @@ -11,21 +11,21 @@ import sympy as sp -from pensive.examples.epsilon_machines import ( +from sofic.examples.epsilon_machines import ( tent_map_misiurewicz_a, tent_map_misiurewicz_bidirectional, tent_map_misiurewicz_forward, tent_map_misiurewicz_hmm, tent_map_misiurewicz_information_expected, ) -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.prob import ( +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.prob import ( SymbolConstraints, canonical_prob_key, is_symbolic, probs_equal, ) -from pensive.generators.words import hmm_word_probability +from sofic.generators.words import hmm_word_probability def test_symbolic_edge_probabilities_preserved(): @@ -140,15 +140,9 @@ def test_symbolic_fig8_matches_numeric_anatomy(): num = tent_map_misiurewicz_bidirectional(a_num) expected = tent_map_misiurewicz_information_expected(a_num) - assert float(sym.ephemeral_information().subs(a, a_num)) == pytest.approx( - num.ephemeral_information(), abs=1e-10 - ) - assert float(sym.bound_information().subs(a, a_num)) == pytest.approx( - num.bound_information(), abs=1e-10 - ) - assert float(sym.entropy_rate().subs(a, a_num)) == pytest.approx( - expected["entropy_rate"], abs=1e-10 - ) + assert float(sym.ephemeral_information().subs(a, a_num)) == pytest.approx(num.ephemeral_information(), abs=1e-10) + assert float(sym.bound_information().subs(a, a_num)) == pytest.approx(num.bound_information(), abs=1e-10) + assert float(sym.entropy_rate().subs(a, a_num)) == pytest.approx(expected["entropy_rate"], abs=1e-10) assert expected["entropy_rate"] == pytest.approx(math.log2(a_num), abs=1e-12) diff --git a/tests/test_synchronization.py b/tests/test_synchronization.py index 7c81737..aa8f4a3 100644 --- a/tests/test_synchronization.py +++ b/tests/test_synchronization.py @@ -5,7 +5,7 @@ import pytest from hypothesis import given, settings -from pensive.examples.epsilon_machines import ( +from sofic.examples.epsilon_machines import ( bernoulli, butterfly_process, golden_mean, @@ -14,34 +14,34 @@ phase_slip_backtrack, restricted_golden_mean, ) -from pensive.generators.synchronization import graph_from_epsilon_machine, markov_order_from_graph -from pensive.serialization import model_from_yaml -from pensive.testing.strategies import epsilon_machines +from sofic.generators.synchronization import graph_from_epsilon_machine, markov_order_from_graph +from sofic.serialization import model_from_yaml +from sofic.testing.strategies import epsilon_machines INFINITE_ORDER_EPSILON_MACHINE_YAML = """ -schema: pensive.model +schema: sofic.model version: 1 -class: pensive.generators.epsilon_machine.EpsilonMachine +class: sofic.generators.epsilon_machine.EpsilonMachine graph: nodes: - id: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: [] - id: 1 attrs: - __pensive_type__: dict + __sofic_type__: dict items: [] - id: 2 attrs: - __pensive_type__: dict + __sofic_type__: dict items: [] edges: - source: 0 target: 1 key: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: - key: prob value: 0.5 @@ -51,7 +51,7 @@ target: 2 key: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: - key: prob value: 0.5 @@ -61,7 +61,7 @@ target: 0 key: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: - key: prob value: 0.5 @@ -71,7 +71,7 @@ target: 2 key: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: - key: prob value: 0.5 @@ -81,18 +81,18 @@ target: 0 key: 0 attrs: - __pensive_type__: dict + __sofic_type__: dict items: - key: prob value: 1.0 - key: emission value: 1 metadata: - __pensive_type__: dict + __sofic_type__: dict items: - key: initial_distribution value: - __pensive_type__: dict + __sofic_type__: dict items: - key: 0 value: 0.44444444444444464 diff --git a/tests/test_testing_strategies.py b/tests/test_testing_strategies.py index 224fd77..0db589e 100644 --- a/tests/test_testing_strategies.py +++ b/tests/test_testing_strategies.py @@ -5,8 +5,8 @@ import pytest from hypothesis import given, settings -from pensive.automata.icdfa import dfa_to_icdfa_string -from pensive.testing.strategies import dfas, epsilon_machines +from sofic.automata.icdfa import dfa_to_icdfa_string +from sofic.testing.strategies import dfas, epsilon_machines @given(dfa=dfas(max_states=3)) diff --git a/tests/test_tikz.py b/tests/test_tikz.py index a7b0089..aae38c9 100644 --- a/tests/test_tikz.py +++ b/tests/test_tikz.py @@ -4,9 +4,9 @@ import pytest -from pensive.automata.dfa import DFA -from pensive.examples.epsilon_machines import golden_mean_bidirectional, golden_mean_forward -from pensive.viz.tikz import compile_tikz, draw_tikz, model_to_tikz +from sofic.automata.dfa import DFA +from sofic.examples.epsilon_machines import golden_mean_bidirectional, golden_mean_forward +from sofic.viz.tikz import compile_tikz, draw_tikz, model_to_tikz def _dfa() -> DFA: @@ -40,7 +40,7 @@ def test_dfa_tikz_symbol_only(): def test_sofic_dyck_tikz_marks_matched_edges(): - from pensive.examples import sofic_dyck_nondeterminizable_shift + from sofic.examples import sofic_dyck_nondeterminizable_shift tikz = model_to_tikz(sofic_dyck_nondeterminizable_shift()) @@ -60,7 +60,7 @@ def test_bidirectional_tikz_uses_edge_labels(): def test_loop_avoids_outgoing_corridor(): - from pensive.viz._tikz_layout import plan_loop_styles + from sofic.viz._tikz_layout import plan_loop_styles positions = {"A": (0.0, 2.0), "B": (0.0, -2.0)} grouped = { @@ -73,9 +73,9 @@ def test_loop_avoids_outgoing_corridor(): def test_msp_self_loop_avoids_reciprocal_edge(): - from pensive.examples.epsilon_machines import golden_mean - from pensive.viz._names import node_name - from pensive.viz.tikz import model_to_tikz + from sofic.examples.epsilon_machines import golden_mean + from sofic.viz._names import node_name + from sofic.viz.tikz import model_to_tikz msp = golden_mean(0.5).mixed_state_presentation() a_state = next(state for state in msp.states() if msp.causal_state(state) == "A") @@ -87,8 +87,8 @@ def test_msp_self_loop_avoids_reciprocal_edge(): def test_reciprocal_edges_bend_same_direction(): - from pensive.examples.epsilon_machines import golden_mean_forward - from pensive.viz._tikz_layout import edge_style + from sofic.examples.epsilon_machines import golden_mean_forward + from sofic.viz._tikz_layout import edge_style assert edge_style("A", "B", parallel_index=0, total_parallel=1, has_reverse=True) == "bend left" assert edge_style("B", "A", parallel_index=0, total_parallel=1, has_reverse=True) == "bend left" @@ -103,7 +103,7 @@ def test_reciprocal_edges_bend_same_direction(): def test_graphviz_layout_spreads_nodes(): graphviz = pytest.importorskip("graphviz") del graphviz - from pensive.viz._tikz_layout import layout_graphviz + from sofic.viz._tikz_layout import layout_graphviz coords = layout_graphviz(golden_mean_bidirectional(0.5), style="paper") positions = [] @@ -119,8 +119,8 @@ def test_graphviz_layout_spreads_nodes(): def test_mixed_state_presentation_tikz_compiles(): - from pensive.examples.epsilon_machines import golden_mean - from pensive.viz._tikz_compile import find_executable + from sofic.examples.epsilon_machines import golden_mean + from sofic.viz._tikz_compile import find_executable if find_executable("pdflatex") is None: pytest.skip("pdflatex not available") @@ -136,8 +136,8 @@ def test_mixed_state_presentation_tikz_compiles(): def test_bidirectional_tikz_png(): - from pensive.viz._tikz_compile import find_executable - from pensive.viz.tikz import model_to_tikz_image + from sofic.viz._tikz_compile import find_executable + from sofic.viz.tikz import model_to_tikz_image if find_executable("pdflatex") is None: pytest.skip("pdflatex not available") @@ -179,7 +179,7 @@ def test_standalone_document_includes_preamble(): def test_epsilon_machine_tikz_fillcolor(): - from pensive.examples.epsilon_machines import golden_mean_forward + from sofic.examples.epsilon_machines import golden_mean_forward tikz = model_to_tikz(golden_mean_forward(0.5)) assert "fill=honeydew" in tikz diff --git a/tests/test_tmc.py b/tests/test_tmc.py index 272cd71..69d3b8a 100644 --- a/tests/test_tmc.py +++ b/tests/test_tmc.py @@ -2,8 +2,8 @@ import pytest -from pensive.graph import ATTR_MULTIPLICITY, ATTR_SYMBOL -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.graph import ATTR_MULTIPLICITY, ATTR_SYMBOL +from sofic.shifts.tmc import TopologicalMarkovChain def _tmc() -> TopologicalMarkovChain: diff --git a/tests/test_topological_anatomy.py b/tests/test_topological_anatomy.py index 883b5e7..56855da 100644 --- a/tests/test_topological_anatomy.py +++ b/tests/test_topological_anatomy.py @@ -7,11 +7,11 @@ import numpy as np import pytest -from pensive.exceptions import UnifilarityError -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.shifts.sofic import SoficShift -from pensive.shifts.tmc import TopologicalMarkovChain -from pensive.shifts.topological_anatomy import _right_resolving +from sofic.exceptions import UnifilarityError +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.shifts.sofic import SoficShift +from sofic.shifts.tmc import TopologicalMarkovChain +from sofic.shifts.topological_anatomy import _right_resolving PHI = (1.0 + 5.0**0.5) / 2.0 LOG2_PHI = log2(PHI) diff --git a/tests/test_topological_epsilon.py b/tests/test_topological_epsilon.py index 817a146..840f1df 100644 --- a/tests/test_topological_epsilon.py +++ b/tests/test_topological_epsilon.py @@ -4,7 +4,7 @@ import pytest -from pensive.automata.idfa import ( +from sofic.automata.idfa import ( MISSING_TRANSITION, count_accessible_idfa, first_idfa_string, @@ -13,9 +13,9 @@ unrank_idfa_string, validate_idfa_string, ) -from pensive.generators.epsilon_machine import EpsilonMachine -from pensive.generators.synchronization import graph_from_epsilon_machine -from pensive.generators.topological_epsilon_enumeration import ( +from sofic.generators.epsilon_machine import EpsilonMachine +from sofic.generators.synchronization import graph_from_epsilon_machine +from sofic.generators.topological_epsilon_enumeration import ( count_topological_epsilon_machines, epsilon_machine_to_idfa_string, idfa_string_to_epsilon_machine, @@ -125,6 +125,6 @@ def test_accessible_idfa_count_grows() -> None: def count_icdfa_placeholder(k: int, n: int) -> int: - from pensive.automata.icdfa import count_icdfa_empty + from sofic.automata.icdfa import count_icdfa_empty return count_icdfa_empty(k, n) diff --git a/tests/test_transducer_composition.py b/tests/test_transducer_composition.py index 932e22e..15cc1bf 100644 --- a/tests/test_transducer_composition.py +++ b/tests/test_transducer_composition.py @@ -4,8 +4,8 @@ import pytest -import pensive.examples.processes as processes -from pensive.automata.transducer_operations import compose_tg, compose_tt, transduce_generator +import sofic.examples.processes as processes +from sofic.automata.transducer_operations import compose_tg, compose_tt, transduce_generator def test_bitflip_composed_with_bitflip_is_identity(): diff --git a/tests/test_transducers.py b/tests/test_transducers.py index 62b64ca..f1ea9e2 100644 --- a/tests/test_transducers.py +++ b/tests/test_transducers.py @@ -2,9 +2,9 @@ import pytest -from pensive.automata.transducers import MealyMachine, MooreMachine -from pensive.exceptions import InfiniteTransductionError -from pensive.graph import ATTR_OUTPUT, ATTR_SYMBOL, EPSILON +from sofic.automata.transducers import MealyMachine, MooreMachine +from sofic.exceptions import InfiniteTransductionError +from sofic.graph import ATTR_OUTPUT, ATTR_SYMBOL, EPSILON def _mealy() -> MealyMachine: diff --git a/tests/test_viz.py b/tests/test_viz.py index e87db73..6c39937 100644 --- a/tests/test_viz.py +++ b/tests/test_viz.py @@ -4,21 +4,21 @@ import pytest -from pensive.automata.dfa import DFA -from pensive.examples.epsilon_machines import golden_mean, golden_mean_bidirectional -from pensive.generators.markov import MarkovChain -from pensive.graph import ATTR_PROB -from pensive.viz._context import viz_context -from pensive.viz._format import ( +from sofic.automata.dfa import DFA +from sofic.examples.epsilon_machines import golden_mean, golden_mean_bidirectional +from sofic.generators.markov import MarkovChain +from sofic.graph import ATTR_PROB +from sofic.viz._context import viz_context +from sofic.viz._format import ( format_belief, format_distribution, format_prob_label, format_prob_rational, format_state, ) -from pensive.viz._tikz_format import format_state_tikz_node -from pensive.viz.graphviz import model_to_graphviz -from pensive.viz.tikz import model_to_tikz +from sofic.viz._tikz_format import format_state_tikz_node +from sofic.viz.graphviz import model_to_graphviz +from sofic.viz.tikz import model_to_tikz graphviz = pytest.importorskip("graphviz") @@ -77,7 +77,7 @@ def test_model_to_graphviz_contains_states_and_edges(): def test_sofic_dyck_graphviz_marks_matched_edges(): - from pensive.examples import sofic_dyck_nondeterminizable_shift + from sofic.examples import sofic_dyck_nondeterminizable_shift source = model_to_graphviz(sofic_dyck_nondeterminizable_shift()).source @@ -185,7 +185,7 @@ def test_repr_png_method(): def test_find_executable_discovers_mactex_without_path(): import os - from pensive.viz._tikz_compile import find_executable + from sofic.viz._tikz_compile import find_executable mactex = "/Library/TeX/texbin/pdflatex" if not os.path.isfile(mactex): @@ -213,7 +213,7 @@ def _fail_svg(_self): def test_epsilon_machine_recurrent_states_colored(): - from pensive.examples.epsilon_machines import golden_mean_bidirectional, golden_mean_forward + from sofic.examples.epsilon_machines import golden_mean_bidirectional, golden_mean_forward forward = model_to_graphviz(golden_mean_forward(0.5)).source assert "honeydew" in forward @@ -224,7 +224,7 @@ def test_epsilon_machine_recurrent_states_colored(): def test_msp_state_fillcolors(): - from pensive.examples.epsilon_machines import golden_mean + from sofic.examples.epsilon_machines import golden_mean source = model_to_graphviz(golden_mean(0.5).mixed_state_presentation()).source assert "mistyrose" in source diff --git a/tests/test_vpa.py b/tests/test_vpa.py index 0298aaf..fe5bcf5 100644 --- a/tests/test_vpa.py +++ b/tests/test_vpa.py @@ -2,8 +2,8 @@ import pytest -from pensive.automata.dfa import DFA -from pensive.automata.vpa import ( +from sofic.automata.dfa import DFA +from sofic.automata.vpa import ( CallDrivenAutomaton, CanonicalVisiblyPushdownAutomaton, DeterministicVisiblyPushdownAutomaton, @@ -11,8 +11,8 @@ SingleEntryVisiblyPushdownAutomaton, VisiblyPushdownAutomaton, ) -from pensive.exceptions import NonDeterministicError, PensiveValidationError -from pensive.graph import ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN +from sofic.exceptions import NonDeterministicError, SoficValidationError +from sofic.graph import ATTR_KIND, ATTR_STACK_SYMBOL, ATTR_SYMBOL, KIND_CALL, KIND_INTERNAL, KIND_RETURN def _vpa() -> VisiblyPushdownAutomaton: @@ -198,7 +198,7 @@ def test_sevpa_validate_and_reject_bad_stack_symbol(): bad = sevpa.copy() bad.stack_alphabet = frozenset({("wrong", "c"), ("m", "c")}) bad.graph.add_transition("m", "e", **{ATTR_KIND: KIND_CALL, ATTR_SYMBOL: "c", ATTR_STACK_SYMBOL: ("wrong", "c")}) - with pytest.raises((PensiveValidationError, NonDeterministicError)): + with pytest.raises((SoficValidationError, NonDeterministicError)): bad.validate() diff --git a/tests/test_words.py b/tests/test_words.py index fcbf2b2..d6f5a3b 100644 --- a/tests/test_words.py +++ b/tests/test_words.py @@ -2,13 +2,13 @@ import pytest -from pensive.examples import bernoulli, fair_coin, golden_mean -from pensive.generators.markov import MarkovChain -from pensive.generators.moore import MooreHMM -from pensive.generators.nmachine import NMachine -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.generators.quasi_realization import QuasiRealization -from pensive.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_QUASIPROB +from sofic.examples import bernoulli, fair_coin, golden_mean +from sofic.generators.markov import MarkovChain +from sofic.generators.moore import MooreHMM +from sofic.generators.nmachine import NMachine +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.generators.quasi_realization import QuasiRealization +from sofic.graph import ATTR_EMISSION, ATTR_EMISSION_DIST, ATTR_PROB, ATTR_QUASIPROB def _mealy_like_pfa() -> ProbabilisticFiniteAutomaton: diff --git a/tests/test_yaml.py b/tests/test_yaml.py index d725278..da8bc67 100644 --- a/tests/test_yaml.py +++ b/tests/test_yaml.py @@ -5,12 +5,12 @@ import numpy as np import pytest -from pensive.automata.atomaton import Atomaton -from pensive.automata.dfa import DFA -from pensive.automata.nfa import NFA -from pensive.automata.nwa import NestedWordAutomaton -from pensive.automata.transducers import MealyMachine, MooreMachine -from pensive.automata.vpa import ( +from sofic.automata.atomaton import Atomaton +from sofic.automata.dfa import DFA +from sofic.automata.nfa import NFA +from sofic.automata.nwa import NestedWordAutomaton +from sofic.automata.transducers import MealyMachine, MooreMachine +from sofic.automata.vpa import ( CallDrivenAutomaton, CanonicalVisiblyPushdownAutomaton, CompositeVisiblyPushdownAutomaton, @@ -19,16 +19,16 @@ SingleEntryVisiblyPushdownAutomaton, VisiblyPushdownAutomaton, ) -from pensive.examples.epsilon_machines import bernoulli, golden_mean -from pensive.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine -from pensive.generators.markov import MarkovChain -from pensive.generators.mealy import MealyHMM -from pensive.generators.moore import MooreHMM -from pensive.generators.nmachine import NMachine -from pensive.generators.pfa import ProbabilisticFiniteAutomaton -from pensive.generators.quasi_realization import QuasiRealization -from pensive.generators.stack_hmm import HiddenMarkovStackModel -from pensive.graph import ( +from sofic.examples.epsilon_machines import bernoulli, golden_mean +from sofic.generators.bidirectional_epsilon_machine import BidirectionalEpsilonMachine +from sofic.generators.markov import MarkovChain +from sofic.generators.mealy import MealyHMM +from sofic.generators.moore import MooreHMM +from sofic.generators.nmachine import NMachine +from sofic.generators.pfa import ProbabilisticFiniteAutomaton +from sofic.generators.quasi_realization import QuasiRealization +from sofic.generators.stack_hmm import HiddenMarkovStackModel +from sofic.graph import ( ATTR_EMISSION, ATTR_KIND, ATTR_OUTPUT, @@ -40,12 +40,12 @@ KIND_INTERNAL, KIND_RETURN, ) -from pensive.serialization import model_from_yaml, model_to_dict -from pensive.shifts.markov_dyck import MarkovDyckShift -from pensive.shifts.sft import ShiftOfFiniteType -from pensive.shifts.sofic import SoficShift -from pensive.shifts.sofic_dyck import SoficDyckShift -from pensive.shifts.tmc import TopologicalMarkovChain +from sofic.serialization import model_from_yaml, model_to_dict +from sofic.shifts.markov_dyck import MarkovDyckShift +from sofic.shifts.sft import ShiftOfFiniteType +from sofic.shifts.sofic import SoficShift +from sofic.shifts.sofic_dyck import SoficDyckShift +from sofic.shifts.tmc import TopologicalMarkovChain def _round_trip(model): diff --git a/uv.lock b/uv.lock index df301bf..424cc85 100644 --- a/uv.lock +++ b/uv.lock @@ -1399,109 +1399,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] -[[package]] -name = "pensive" -source = { editable = "." } -dependencies = [ - { name = "dit" }, - { name = "networkx" }, - { name = "numpy" }, - { name = "pyyaml" }, - { name = "scipy" }, -] - -[package.optional-dependencies] -bayes = [ - { name = "arviz", version = "0.23.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "arviz", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pymc", version = "5.28.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "pymc", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, -] -dev = [ - { name = "graphviz" }, - { name = "hypothesis", extra = ["numpy"] }, - { name = "ipython" }, - { name = "matplotlib" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-rerunfailures" }, - { name = "pytest-sugar" }, - { name = "pytest-xdist" }, - { name = "ruff" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "sphinx-rtd-theme" }, - { name = "sphinxcontrib-bibtex" }, - { name = "ty" }, -] -docs = [ - { name = "ipython" }, - { name = "matplotlib" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "sphinx-rtd-theme" }, - { name = "sphinxcontrib-bibtex" }, -] -symbolic = [ - { name = "dit", extra = ["symbolic"] }, - { name = "sympy" }, -] -test = [ - { name = "graphviz" }, - { name = "hypothesis", extra = ["numpy"] }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pytest-rerunfailures" }, - { name = "pytest-sugar" }, - { name = "pytest-xdist" }, -] -viz = [ - { name = "graphviz" }, -] - -[package.metadata] -requires-dist = [ - { name = "arviz", marker = "extra == 'bayes'" }, - { name = "dit", specifier = ">=2.2" }, - { name = "dit", extras = ["symbolic"], marker = "extra == 'symbolic'", specifier = ">=2.2" }, - { name = "graphviz", marker = "extra == 'dev'", specifier = ">=0.20" }, - { name = "graphviz", marker = "extra == 'test'", specifier = ">=0.20" }, - { name = "graphviz", marker = "extra == 'viz'", specifier = ">=0.20" }, - { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.0" }, - { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.0" }, - { name = "hypothesis", extras = ["numpy"], marker = "extra == 'dev'" }, - { name = "hypothesis", extras = ["numpy"], marker = "extra == 'test'" }, - { name = "ipython", marker = "extra == 'dev'" }, - { name = "ipython", marker = "extra == 'docs'" }, - { name = "matplotlib", marker = "extra == 'dev'" }, - { name = "matplotlib", marker = "extra == 'docs'" }, - { name = "networkx", specifier = ">=2.6" }, - { name = "numpy", specifier = ">=1.22" }, - { name = "pymc", marker = "extra == 'bayes'", specifier = ">=5" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.3" }, - { name = "pytest-cov", marker = "extra == 'dev'" }, - { name = "pytest-cov", marker = "extra == 'test'" }, - { name = "pytest-rerunfailures", marker = "extra == 'dev'" }, - { name = "pytest-rerunfailures", marker = "extra == 'test'" }, - { name = "pytest-sugar", marker = "extra == 'dev'" }, - { name = "pytest-sugar", marker = "extra == 'test'" }, - { name = "pytest-xdist", marker = "extra == 'dev'" }, - { name = "pytest-xdist", marker = "extra == 'test'" }, - { name = "pyyaml", specifier = ">=6.0" }, - { name = "ruff", marker = "extra == 'dev'" }, - { name = "scipy", specifier = ">=1.7" }, - { name = "sphinx", marker = "extra == 'dev'" }, - { name = "sphinx", marker = "extra == 'docs'" }, - { name = "sphinx-rtd-theme", marker = "extra == 'dev'" }, - { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, - { name = "sphinxcontrib-bibtex", marker = "extra == 'dev'" }, - { name = "sphinxcontrib-bibtex", marker = "extra == 'docs'" }, - { name = "sympy", marker = "extra == 'symbolic'", specifier = ">=1.12" }, - { name = "ty", marker = "extra == 'dev'" }, -] -provides-extras = ["bayes", "dev", "docs", "symbolic", "test", "viz"] - [[package]] name = "pexpect" version = "4.9.0" @@ -2163,6 +2060,117 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, ] +[[package]] +name = "sofic" +source = { editable = "." } +dependencies = [ + { name = "dit" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "pyyaml" }, + { name = "scipy" }, +] + +[package.optional-dependencies] +bayes = [ + { name = "arviz", version = "0.23.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "arviz", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pymc", version = "5.28.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "pymc", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +dev = [ + { name = "dit", extra = ["symbolic"] }, + { name = "graphviz" }, + { name = "hypothesis", extra = ["numpy"] }, + { name = "ipython" }, + { name = "matplotlib" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-rerunfailures" }, + { name = "pytest-sugar" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-bibtex" }, + { name = "sympy" }, + { name = "ty" }, +] +docs = [ + { name = "dit", extra = ["symbolic"] }, + { name = "ipython" }, + { name = "matplotlib" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-rtd-theme" }, + { name = "sphinxcontrib-bibtex" }, + { name = "sympy" }, +] +symbolic = [ + { name = "dit", extra = ["symbolic"] }, + { name = "sympy" }, +] +test = [ + { name = "graphviz" }, + { name = "hypothesis", extra = ["numpy"] }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-rerunfailures" }, + { name = "pytest-sugar" }, + { name = "pytest-xdist" }, +] +viz = [ + { name = "graphviz" }, +] + +[package.metadata] +requires-dist = [ + { name = "arviz", marker = "extra == 'bayes'" }, + { name = "dit", specifier = ">=2.2" }, + { name = "dit", extras = ["symbolic"], marker = "extra == 'dev'", specifier = ">=2.2" }, + { name = "dit", extras = ["symbolic"], marker = "extra == 'docs'", specifier = ">=2.2" }, + { name = "dit", extras = ["symbolic"], marker = "extra == 'symbolic'", specifier = ">=2.2" }, + { name = "graphviz", marker = "extra == 'dev'", specifier = ">=0.20" }, + { name = "graphviz", marker = "extra == 'test'", specifier = ">=0.20" }, + { name = "graphviz", marker = "extra == 'viz'", specifier = ">=0.20" }, + { name = "hypothesis", marker = "extra == 'dev'", specifier = ">=6.0" }, + { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.0" }, + { name = "hypothesis", extras = ["numpy"], marker = "extra == 'dev'" }, + { name = "hypothesis", extras = ["numpy"], marker = "extra == 'test'" }, + { name = "ipython", marker = "extra == 'dev'" }, + { name = "ipython", marker = "extra == 'docs'" }, + { name = "matplotlib", marker = "extra == 'dev'" }, + { name = "matplotlib", marker = "extra == 'docs'" }, + { name = "networkx", specifier = ">=2.6" }, + { name = "numpy", specifier = ">=1.22" }, + { name = "pymc", marker = "extra == 'bayes'", specifier = ">=5" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.3" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, + { name = "pytest-cov", marker = "extra == 'test'" }, + { name = "pytest-rerunfailures", marker = "extra == 'dev'" }, + { name = "pytest-rerunfailures", marker = "extra == 'test'" }, + { name = "pytest-sugar", marker = "extra == 'dev'" }, + { name = "pytest-sugar", marker = "extra == 'test'" }, + { name = "pytest-xdist", marker = "extra == 'dev'" }, + { name = "pytest-xdist", marker = "extra == 'test'" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "scipy", specifier = ">=1.7" }, + { name = "sphinx", marker = "extra == 'dev'" }, + { name = "sphinx", marker = "extra == 'docs'" }, + { name = "sphinx-rtd-theme", marker = "extra == 'dev'" }, + { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, + { name = "sphinxcontrib-bibtex", marker = "extra == 'dev'" }, + { name = "sphinxcontrib-bibtex", marker = "extra == 'docs'" }, + { name = "sympy", marker = "extra == 'dev'", specifier = ">=1.12" }, + { name = "sympy", marker = "extra == 'docs'", specifier = ">=1.12" }, + { name = "sympy", marker = "extra == 'symbolic'", specifier = ">=1.12" }, + { name = "ty", marker = "extra == 'dev'" }, +] +provides-extras = ["bayes", "dev", "docs", "symbolic", "test", "viz"] + [[package]] name = "sortedcontainers" version = "2.4.0"