Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Changelog

## 0.3.0 (unreleased)

A correctness review and breaking refactor. Old names are **not** kept as
aliases; use the tables below to migrate.

### Behavior changes

- **All information quantities are in bits**, including log-likelihoods
(`log_likelihood`, the Baum-Welch trace, cross-validated log-likelihood,
HDP-HMM traces), topological entropy (was nats), and collision entropy (was
nats). AIC, AICc, BIC, and WAIC keep their standard deviance scale (computed
from the natural log-likelihood); the MDL code length is in bits. `score` and
`observed_information` remain derivatives of the natural log-likelihood, so
standard errors are unchanged.
- **Covers follow Lind & Marcus**: *right* means right-resolving.
`RightFischerCover` is now the exact minimal right-resolving presentation
(it was built from follower sets truncated at length 8 and labeled *left*);
reducible shifts raise `SoficValidationError`. `LeftFischerCover` is its
mirror image.
- `MaximizedPrimeAtomaton` subclasses `NFA`, not `AtomicAutomaton`: it is the
reverse of the canonical RFSA of the reversed language and need not be atomic.
- `CanonicalVisiblyPushdownAutomaton.from_vpa` raises
`NonWellMatchedLanguageError` for languages with pending calls or returns.
- `DeterministicVisiblyPushdownAutomaton.from_vpa` determinizes
nondeterministic input instead of raising.
- VPA boolean and structural operations return concrete automata.
- Wildcard VPA returns fire on every stack symbol and, with a bottom symbol, on
the empty stack (the simulator's behavior; the docstring said otherwise).
- `ShiftOfFiniteType.from_forbidden_words` raises instead of silently
truncating at `max_states`; an empty forbidden set builds the full shift.
- `equivalent()` compares automata over the union of both alphabets.
- `MarkovChain.words_of_length(0)` and `sample_path` respect the initial
distribution; sampling from an initial law with no mass raises `ValueError`.
- `block_entropy_estimates(use_exact=True)` reports crypticity as `C_mu - E`.
- `SlidingBlockCode.apply` keeps constraints longer than the window and raises
when the block map misses an allowed block.
- Baum-Welch raises when every sequence is impossible and warns when some are.
- `BuchiAutomaton.accepts_lasso` rejects an empty loop.
- Stack CSSR no longer drops histories during determinization (which produced
zero-mass states and validation errors).
- TikZ labels: fixed uncompilable `\midcall` / `\uparrowA` and escaped
`\times` / sympy LaTeX.
- cmpy example factories validate parameters like their curated counterparts
(e.g. a coin bias of 1 raises). Processes previously built by `Even`, `Nemo`,
and `ABC` now come from the curated functions and emit integer symbols `0`/`1`;
`alternating_biased_coins(p, p)` keeps its two-state presentation.
- `joint_block_distribution(block_length=n)` counts blocks of `n` symbols
(`history_length=h` meant `h + 1` symbols); the default `2` is unchanged.

### New

- Exact canonical RFSA (`CanonicalRFSA.from_language`), maximized prime
átomaton, `CanonicalRFSA.dual` / `MaximizedPrimeAtomaton.dual`, exact
`prime_residuals`, `atoms`, `prime_atoms`, and `ResidualTable`.
- NL\*: `learn_rfsa_nlstar`, `learn_prime_atomaton_nlstar`,
`learn_rfsa_from_language`, and `AutomatonEquivalenceOracle`.
- VPA: `determinize`, `is_empty`, `accepted_word`, `is_universal`, `includes`,
`equivalent`, `has_unmatched_word`; `sofic.automata.vpa.to_single_entry` /
`to_multiple_entry`; modular `minimize` converts automatically when no
modules are given. `NestedWordAutomaton` gains the same operations.
- Exact left/right Krieger covers.
- `sofic.generators.matrices` (joint matrices and start-vector policies) and
`sofic.generators.sampling`.

### Module moves

| Old module | New module |
|---|---|
| `sofic.generators.hmm_inference` | `sofic.inference.hmm` (`filtering`, `em`, `information`); `sample` → `sofic.generators.sampling` |
| `sofic.generators.epsilon_inference` | `sofic.inference.cssr` (`process`, `subtree`, `counts`, `significance`); `spectral` → `sofic.inference.spectral` |
| `sofic.generators.epsilon_transducer_inference` | `sofic.inference.cssr.transducer` |
| `sofic.generators.stack_inference` | `sofic.inference.cssr.stack` |
| `sofic.automata.{active,rpni,edsm,dfasat,alergia,papni,observation}` | `sofic.automata.learning.*` |
| `sofic.automata.learning` (NL\*) | `sofic.automata.learning.nlstar` |
| `sofic.automata.vpa`, `vpa_simulation` | `sofic.automata.vpa` package (`base`, `operations`, `deterministic`, `modular`, `canonical`, `simulation`) |
| `sofic.automata.{icdfa,idfa,enumeration}` | `sofic.automata.enumeration.{icdfa,idfa,words}` |
| `sofic.automata.{canonical_extraction,rfsa,atomaton,canonical_dual}` | `sofic.automata.canonical.{residual,rfsa,atomaton,dual}` |
| `sofic.shifts.sofic_relation` | `sofic.shifts.product_alphabet_shift` |

### Renames and removals

| Old | New |
|---|---|
| `cssr` | `learn_epsilon_machine_cssr` |
| `subtree_merge` | `learn_epsilon_machine_subtree` |
| `spectral` (wrapper) | `learn_epsilon_machine_spectral` |
| `transcssr` | `learn_epsilon_transducer_cssr` |
| `stack_cssr` / `stack_subtree_merge` / `fit_stack_hmm_mle` | `learn_stack_hmm_cssr` / `learn_stack_hmm_subtree` / `learn_stack_hmm_mle` |
| `suggest_lmax` | `suggest_max_history` |
| `Lmax=`, `L=` (CSSR family and subtree learners) | `max_history=` |
| `reconstruction_sweep(lmaxes=)` | `reconstruction_sweep(max_histories=)` |
| `GoodnessOfFit.L`, `goodness_of_fit(L=)` | `block_length` |
| `forward(scaled=)`, `backward(scaled=)` | `normalize=` |
| `joint_block_distribution(history_length=h)` | `joint_block_distribution(block_length=h + 1)` |
| `QuasiStochasticModel.transition_matrices`, `quasi_inference.transition_matrices` | `symbol_matrices` |
| `sofic.generators.words.hmm_*`, `pfa_*`, `quasi_*`, `markov_*` functions | private; use the model methods |
| `cartesian_product_gg` / `cartesian_product_tt` | `generator_product` / `transducer_product` |
| `compose_tt` / `compose_tg` | `compose_transducers` / `compose_transducer_generator` |
| `wnfa_to_wdfa` / `minimum_wdfa` | `determinize_wheeler` / `minimize_wheeler` |
| `papni_encode` / `papni_encode_samples` | `encode_dyck_word` / `encode_dyck_samples` |
| ICDFA helpers `next_flags`, `string_from_flags`, `flags_from_string`, `count_flag_sequences` | `icdfa_next_flags`, `icdfa_string_from_flags`, `icdfa_flags_from_string`, `icdfa_count_flag_sequences` |
| IDFA helpers `string_from_flags`, `extended_flags`, `transition_count` | `idfa_string_from_flags`, `idfa_extended_flags`, `idfa_transition_count` |
| `CallDrivenAutomaton` | `ModularVisiblyPushdownAutomaton` |
| `CompositeVisiblyPushdownAutomaton`, `union_vpa`, `intersection_vpa`, `complement_vpa`, `difference_vpa`, `concat_vpa`, `kleene_star_vpa` | removed; use the VPA methods |
| `LabeledAutomaton.intersect` / `concatenate` / `star` | `intersection` / `concat` / `kleene_star` |
| `learn_maximized_prime_atomaton` | `learn_prime_atomaton_nlstar` (or `learn_rfsa_nlstar`) |
| `SoficRelation`, `to_sofic_relation` | `ProductAlphabetShift`, `to_product_alphabet_shift` |
| cover `from_sofic` | `from_presentation` |
| `{left,right}_{fischer,krieger}_from_sofic` | `{left,right}_{fischer,krieger}_cover` |
| `sofic.from_yaml`, `serialization.from_yaml` | `model_from_yaml` |
| `examples.processes`: `BiasedCoin`, `FairCoin`, `Even`, `Nemo`, `NRPS`, `ABC` | removed: `bernoulli`, `fair_coin`, `even_process`, `nemo_process`, `noisy_random_phase_slip`, `alternating_biased_coins(1 - p, 1 - q)` |
| `GoldenMean` / `Butterfly` / `PSB` | `golden_mean_forbid_00` / `butterfly_two_branch` / `phase_slip_backtrack_cmpy` |
| other PascalCase `examples.processes` factories (e.g. `BitFlip`, `Delay`, `RestrictedGM`, `IrreversibleTwoState`) | snake_case (`bit_flip`, `delay`, `restricted_gm`, `irreversible_two_state`) |
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ read off computational-mechanics quantities:
from sofic import EpsilonMachine

eps = EpsilonMachine.from_hmm(gm) # minimize an HMM presentation
# eps = EpsilonMachine.from_sequence(data, method="cssr", Lmax=4) # infer
# eps = EpsilonMachine.from_sequence(data, method="cssr", max_history=4) # infer
# eps = EpsilonMachine.from_sequence(data, method="spectral", prefix_length=3, rank=2)

eps.statistical_complexity() # 0.9183 bits (C_mu)
Expand Down
4 changes: 2 additions & 2 deletions docs/automata/learning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,6 @@ For fitting probabilities on the learned topology, see

.. autofunction:: sofic.automata.learning.papni.learn_sofic_dyck_shift_papni
.. autofunction:: sofic.automata.learning.papni.is_well_matched
.. autofunction:: sofic.automata.learning.papni.papni_encode
.. autofunction:: sofic.automata.learning.papni.papni_encode_samples
.. autofunction:: sofic.automata.learning.papni.encode_dyck_word
.. autofunction:: sofic.automata.learning.papni.encode_dyck_samples
.. autofunction:: sofic.automata.learning.papni.sofic_dyck_shift_from_papni_dfa
4 changes: 2 additions & 2 deletions docs/automata/subsequential.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ hierarchy :cite:`Mohri2009`.

In [5]: from sofic.automata.subsequential import WeightedFiniteStateTransducer

In [6]: from sofic.examples.processes import BinaryChannel
In [6]: from sofic.examples.processes import binary_channel

In [7]: w = WeightedFiniteStateTransducer.from_transducer(BinaryChannel(0.1, 0.2))
In [7]: w = WeightedFiniteStateTransducer.from_transducer(binary_channel(0.1, 0.2))

@doctest float
In [8]: w.weight(['0'], ['0'])
Expand Down
16 changes: 8 additions & 8 deletions docs/automata/transducers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ Composition

The composition helpers mirror the common ``cmpy`` transducer operations:

* :func:`sofic.automata.transducer_operations.compose_tt` serially composes
* :func:`sofic.automata.transducer_operations.compose_transducers` serially composes
transducers.
* :func:`sofic.automata.transducer_operations.compose_tg` composes a
* :func:`sofic.automata.transducer_operations.compose_transducer_generator` composes a
transducer with a stochastic generator and returns a joint input/output
generator.
* :func:`sofic.automata.transducer_operations.transduce_generator` returns
the output-only generator induced by driving a transducer with a generator.
* :func:`sofic.automata.transducer_operations.cartesian_product_tt` and
:func:`sofic.automata.transducer_operations.cartesian_product_gg` build
* :func:`sofic.automata.transducer_operations.transducer_product` and
:func:`sofic.automata.transducer_operations.generator_product` build
tuple-symbol Cartesian products.

For convenience, :class:`MealyMachine` also exposes ``compose``,
Expand All @@ -55,8 +55,8 @@ API

.. 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.compose_transducers
.. autofunction:: sofic.automata.transducer_operations.compose_transducer_generator
.. autofunction:: sofic.automata.transducer_operations.transduce_generator
.. autofunction:: sofic.automata.transducer_operations.cartesian_product_tt
.. autofunction:: sofic.automata.transducer_operations.cartesian_product_gg
.. autofunction:: sofic.automata.transducer_operations.transducer_product
.. autofunction:: sofic.automata.transducer_operations.generator_product
4 changes: 2 additions & 2 deletions docs/automata/vpa.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ forms exist for well-matched languages, or once calls are assigned to modules
A k-module MEVPA. A module may have several entries, and the pushed symbol
depends only on the caller state.

``CallDrivenAutomaton``
``ModularVisiblyPushdownAutomaton``
The shared modular generalization: a call's target depends only on the call
symbol.

Expand Down Expand Up @@ -108,7 +108,7 @@ API
.. autoclass:: MultipleEntryVisiblyPushdownAutomaton
:members: minimize

.. autoclass:: CallDrivenAutomaton
.. autoclass:: ModularVisiblyPushdownAutomaton
:members: minimize

.. autoclass:: CanonicalVisiblyPushdownAutomaton
Expand Down
6 changes: 3 additions & 3 deletions docs/automata/wheeler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ What the order buys
uses this to replace ``2^n`` subsets with ``n(n+1)/2`` intervals, which makes
the Markov order, cryptic order, and reset threshold polynomial.
* Determinization to at most ``2n - 1 - |Sigma|`` states
(:func:`wnfa_to_wdfa`) and a unique minimal WDFA (:func:`minimum_wdfa`),
(:func:`determinize_wheeler`) and a unique minimal WDFA (:func:`minimize_wheeler`),
both of which fail for general automata.
* A succinct index — see below.

Expand Down Expand Up @@ -100,8 +100,8 @@ Width
Canonical forms and minimization
================================

.. autofunction:: minimum_wdfa
.. autofunction:: wnfa_to_wdfa
.. autofunction:: minimize_wheeler
.. autofunction:: determinize_wheeler
.. autofunction:: wheeler_canonical_form
.. autofunction:: wheeler_isomorphic
.. autofunction:: wheeler_state_index
Expand Down
3 changes: 1 addition & 2 deletions docs/core/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ 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`
functions below. The polymorphic :func:`model_from_yaml` / :func:`read_yaml`
readers reconstruct the correct subclass from the serialized class tag, so they
are convenient when the concrete type is not known in advance.

Expand All @@ -35,7 +35,6 @@ API

.. autofunction:: model_to_yaml
.. autofunction:: model_from_yaml
.. autofunction:: from_yaml
.. autofunction:: read_yaml
.. autofunction:: model_to_dict
.. autofunction:: model_from_dict
Expand Down
46 changes: 27 additions & 19 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,33 +165,41 @@ Process library
---------------

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:`~sofic.generators.epsilon_machine.EpsilonMachine`
but accepting a ``machine_type`` argument:
ports a large library of parametrized process factories from cmpy
(``golden_mean_forbid_00``, ``iid``, ``ising``, ``ehrenfest``, the periodic and
``misiurewicz`` families, and many more). Every factory name is snake_case. Each
is a function that returns a generator, defaulting to an
:class:`~sofic.generators.epsilon_machine.EpsilonMachine` but accepting a
``machine_type`` argument:

.. ipython::

In [1]: from sofic.examples import GoldenMean, Even, Nemo
In [1]: from sofic.examples import golden_mean_forbid_00

In [2]: gm = GoldenMean(bias=0.5)
In [2]: gm = golden_mean_forbid_00(bias=0.5)

@doctest float
In [3]: gm.entropy_rate()
Out[3]: 0.6666666666666665

Several factories share one implementation with a curated example and differ only
in string symbols (``"0"``/``"1"``), state names, or parametrization:
``BiasedCoin(b)`` is :func:`bernoulli` ``(b)``, ``Even`` is :func:`even_process`,
``Nemo`` is :func:`nemo_process`, ``GoldenMean(b)`` is
:func:`golden_mean_forward` ``(1 - b)`` (and :func:`golden_mean_markov` ``(b)``
with ``A``/``B`` swapped), ``ABC(p, q)`` is :func:`alternating_biased_coins`
``(1 - p, 1 - q)`` for ``p != q``, ``RestrictedGM`` is
:func:`restricted_golden_mean`, and ``IrreversibleTwoState()`` is
cmpy factories that merely duplicated a curated example are not ported; use the
curated function instead: cmpy's ``BiasedCoin(b)`` is :func:`bernoulli` ``(b)``,
``FairCoin`` is :func:`fair_coin`, ``Even(bias=b)`` is :func:`even_process`
``(b)``, ``Nemo(p=p, q=q)`` is :func:`nemo_process` ``(p, q)``, ``NRPS`` is
:func:`noisy_random_phase_slip`, and ``ABC(p, q)`` is
:func:`alternating_biased_coins` ``(1 - p, 1 - q)``. The curated versions emit
integer symbols ``0``/``1`` (except :func:`bernoulli`), whereas the ported
factories emit strings ``"0"``/``"1"``.

Several remaining factories share an implementation with a curated example and
differ only in string symbols, state names, or parametrization:
``golden_mean_forbid_00(b)`` is :func:`golden_mean_forward` ``(1 - b)`` (and
:func:`golden_mean_markov` ``(b)`` with ``A``/``B`` swapped), ``restricted_gm``
is :func:`restricted_golden_mean`, and ``irreversible_two_state()`` is
:func:`ellison_fig9_forward`. Similar names do not always mean the same process:
:func:`golden_mean` is the ``0 <-> 1`` mirror of ``GoldenMean``, and ``Butterfly``
and ``PSB`` differ from :func:`butterfly_process` and
:func:`golden_mean` (forbids ``11``) is the ``0 <-> 1`` mirror of
``golden_mean_forbid_00``, and ``butterfly_two_branch`` and
``phase_slip_backtrack_cmpy`` differ from :func:`butterfly_process` and
:func:`~sofic.examples.epsilon_machines.phase_slip_backtrack`.

The module also exposes registries — ``processes.process_list`` and
Expand All @@ -205,8 +213,8 @@ parametrized tests and sweeps:
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
A parallel set of transducer factories (``bit_flip``, ``parity``, ``delay``,
``binary_channel``, …) lives alongside the processes and produces
:class:`~sofic.automata.transducers.MealyMachine` instances.

Symbolic-shift examples (:mod:`sofic.examples.shifts`) provide the
Expand Down
6 changes: 3 additions & 3 deletions docs/generators/epsilon_transducer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ Minimize a joint-unifilar stochastic transducer to its causal states:

In [1]: from sofic import EpsilonTransducer

In [2]: from sofic.examples.processes import BinaryChannel, GMtoEven
In [2]: from sofic.examples.processes import binary_channel, gm_to_even

In [3]: eps = EpsilonTransducer.from_channel(BinaryChannel(0.1, 0.2))
In [3]: eps = EpsilonTransducer.from_channel(binary_channel(0.1, 0.2))

@doctest
In [4]: len(list(eps.states()))
Out[4]: 1

@doctest
In [5]: EpsilonTransducer.from_channel(GMtoEven()).is_unifilar()
In [5]: EpsilonTransducer.from_channel(gm_to_even()).is_unifilar()
Out[5]: True

The channel can also be read off a joint ``(input, output)`` generator with
Expand Down
4 changes: 2 additions & 2 deletions docs/generators/information_anatomy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ forward and reverse structural ephemeral branches match.

.. ipython::

In [1]: from sofic.examples import nemo_process, NRPS
In [1]: from sofic.examples import nemo_process, noisy_random_phase_slip

In [2]: nemo = nemo_process().to_bidirectional()

# Reversible branching (r_fwd == r_rev): the two chain rates agree.
In [3]: round(nemo.internal_markov_entropy_rate(), 6), round(nemo.reverse_internal_markov_entropy_rate(), 6)
Out[3]: (0.5, 0.5)

In [4]: nrps = NRPS().to_bidirectional()
In [4]: nrps = noisy_random_phase_slip().to_bidirectional()

# An arrow of time (r_fwd != r_rev): forward and reverse chain rates differ.
In [5]: round(nrps.internal_markov_entropy_rate(), 6), round(nrps.reverse_internal_markov_entropy_rate(), 6)
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/quasi_realization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ API
.. autoclass:: QuasiRealization
.. autoclass:: QuasiStochasticModel

.. autofunction:: sofic.generators.quasi_inference.transition_matrices
.. autofunction:: sofic.generators.quasi_inference.symbol_matrices
.. autofunction:: sofic.generators.quasi_inference.stationary_quasidistribution
.. autofunction:: sofic.generators.quasi_inference.word_probability
Loading
Loading