Skip to content
Open
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
A second correctness review: every fix below was reproduced against a
brute-force reference or a closed-form value and has a regression test.

### Added

- `generative_model` / `GenerativeModelResult`: bracket search for generative
complexity with support, flow, and heuristic coverage lower bounds;
enumeration of exact-common-information optima with `generator_kernel`;
optional `bilevel_search`; coupled and memory-lift uppers; gap flags
(`cg_exceeds_g`, `n_g_optima_generating`). `BidirectionalEpsilonMachine.generative_model()`.
- `coverage_bound` helpers for partial-sum (coverage) lower bounds on full supports.

### Breaking changes

- `minimal_generative_model`, `wyner_generative_model`,
Expand Down
101 changes: 101 additions & 0 deletions docs/generators/generative_models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,89 @@ only, so ``gap`` can be positive even when some non-lifted generator attains
:math:`\op{G}{S^+ : S^-}` (the interleaved process above). The gap vanishes for
first-order Markov processes.

Coupled generators
==================

:mod:`sofic.generators.coupled_generators` drops the lifting restriction. A
generator with :math:`k` states is described, jointly with the causal states,
by a state law :math:`\pi`, beliefs :math:`Q_w(s^+)` and :math:`R_w(s^-)`, and a
free kernel :math:`K(x, w' \mid w)`; the product
:math:`\pi_w Q_w(s^+_0) K(x_0, w_1 \mid w_0) R_{w_1}(s^-_1)` must reproduce the
bidirectional edge law :cite:`Ellison2009`, be stationary on the past side and
consistent on the future side. :func:`~sofic.generators.coupled_generators.coupled_search`
minimizes :math:`\H{\pi}` subject to these equations (an augmented Lagrangian
with analytic gradients) from the ε-machines, the bidirectional machine, extra
seeds and random starts; :func:`~sofic.generators.coupled_generators.coupled_generative_model`
returns the best candidate that reproduces the source process. For the
interleaved process above it reaches :math:`2 h(\tfrac49) = 1.98215`, below the
best lifted generator (1.99108); for the :math:`(2, \infty)` process it finds
the ε-machine (1.5).

Two narrower tools share the same equations:

* :func:`~sofic.generators.coupled_generators.memory_lift` builds the
*memory-1 lift* of a channel :math:`\nu(v \mid s^+, s^-)`: draw
:math:`V_t \sim \nu(\cdot \mid B_t)` independently at each time and take
:math:`W_t = (V_t, V_{t+1})`, so consecutive states share randomness. The
interleaved process's minimal generator is the memory-1 lift of a single
flag, and :func:`~sofic.generators.coupled_generators.memory_lift_search`
finds it.
* :func:`~sofic.generators.coupled_generators.generator_kernel` decides whether
a given channel's variable is the state of *any* generator: for a fixed channel
the equations are linear in :math:`K`, and a linear program returns the
smallest violation and the kernel. The exact-common-information optimum of the
:math:`(2, \infty)` process has violation 0.0625, so no generator has it as
its state. :func:`~sofic.generators.coupled_generators.bilevel_search`
(experimental) searches channels against this violation.

All three searches are local and return upper bounds on :math:`C_g` for the
given number of states.

Searching for a generator
=========================

:func:`~sofic.generators.generative_search.generative_model` combines these
tools into one pipeline and returns the best generator it can verify together
with a rigorous bracket on :math:`C_g`:

* lower bounds: the excess entropy, support-type and flow bounds, and (on large
supports) an optional heuristic coverage bound;
* the persistent-crypticity criteria; when both vanish every separating variable
is a generator state and :math:`C_g = \op{G}{S^+ : S^-}`;
* the exact-common-information optima (dit, several starts, since the optimum
need not be unique and only some optima generate), each polished to exact
separation with :func:`~sofic.generators.coupled_generators.polish_separating_channel`
and tested with :func:`~sofic.generators.coupled_generators.generator_kernel`;
when none generate, :func:`~sofic.generators.coupled_generators.bilevel_search`
may propose feasible channels;
* upper bounds over nested classes, cheapest first: the ε-machines and
bidirectional machine, memory-1 lifts, deterministic and lifted channels (for
small machines), and the coupled search seeded with everything found so far.

.. code-block:: python

result = bidir.generative_model()
result.lower, result.upper, result.certified # bracket on C_g
result.cg_exceeds_g # True when no G optimum generates
result.separating_variables_generate # some G optimum passed kernel LP
result.model # the generator, verified against the source

The searches are local; ``certified`` reports when the bracket closes (for
example the golden mean, where the support bound meets :math:`C_\mu`).

Lower bounds
============

:mod:`sofic.generators.generator_bounds` bounds :math:`\op{G}{S^+ : S^-}`, and
hence :math:`C_g` (Löhr & Ay :cite:`LohrAy2009`), from below using only the
support of the causal-state joint distribution. Each value of a separating
variable has a rectangular support; typing values by maximal rectangles gives a
polytope of type masses on which entropy is minimized at a vertex
(:func:`~sofic.generators.generator_bounds.support_type_bound`). The bound is
exact for the :math:`(2, \infty)` process (:math:`2 - \tfrac38 \log_2 3`) and
for the golden mean process (:math:`C_\mu`, proving :math:`C_g = C_\mu` there);
it is uninformative when the support is the full product.

API
===

Expand All @@ -152,6 +235,24 @@ Constructors
.. autofunction:: functional_generative_model
.. autofunction:: gacs_korner_generative_model

Coupled generators and bounds
-----------------------------

.. autofunction:: sofic.generators.generative_search.generative_model
.. autoclass:: sofic.generators.generative_search.GenerativeModelResult
:members: certified, gap, g_optima_generate
.. autofunction:: sofic.generators.coverage_bound.coverage_entropy_lower
.. autofunction:: sofic.generators.coverage_bound.mu1_search
.. autofunction:: sofic.generators.coupled_generators.coupled_generative_model
.. autofunction:: sofic.generators.coupled_generators.polish_separating_channel
.. autofunction:: sofic.generators.coupled_generators.coupled_search
.. autofunction:: sofic.generators.coupled_generators.memory_lift
.. autofunction:: sofic.generators.coupled_generators.memory_lift_search
.. autofunction:: sofic.generators.coupled_generators.generator_kernel
.. autofunction:: sofic.generators.coupled_generators.bilevel_search
.. autofunction:: sofic.generators.generator_bounds.support_type_bound
.. autofunction:: sofic.generators.generator_bounds.generator_support_bound

Measures
--------

Expand Down
7 changes: 7 additions & 0 deletions sofic/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from sofic.generators.complexity_dimension import StatisticalComplexityDimension, statistical_complexity_dimension
from sofic.generators.correlations import autocorrelation, mutual_information_function, power_spectrum
from sofic.generators.coverage_bound import coverage_entropy_lower, mu1_brute_2x2, mu1_search
from sofic.generators.directional_flow import (
directed_information,
independent_pair_generator,
Expand All @@ -21,6 +22,7 @@
)
from sofic.generators.epsilon_machine import EpsilonMachine
from sofic.generators.epsilon_transducer import EpsilonTransducer
from sofic.generators.generative_search import GenerativeModelResult, generative_model
from sofic.generators.lumping import LumpabilityError, is_lumpable, lump, normalize_partition
from sofic.generators.markov import MarkovChain
from sofic.generators.mealy import MealyHMM
Expand Down Expand Up @@ -74,6 +76,11 @@
"BlockEntropyEstimates",
"BlockConvergenceDiagram",
"BlockConvergenceEstimates",
"GenerativeModelResult",
"coverage_entropy_lower",
"generative_model",
"mu1_brute_2x2",
"mu1_search",
"EpsilonMachine",
"EpsilonTransducer",
"FiniteStateChannel",
Expand Down
9 changes: 9 additions & 0 deletions sofic/generators/bidirectional_epsilon_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,15 @@ def generative_complexity(self, **kwargs: Any) -> float:
"""H[G] of the minimal generative model, an upper bound on C_g."""
return self.minimal_generative_model(**kwargs).generative_complexity()

def generative_model(self, **kwargs: Any) -> Any:
"""Search for a verified minimal generator and a bracket on :math:`C_g`.

See :func:`~sofic.generators.generative_search.generative_model`.
"""
from sofic.generators.generative_search import generative_model

return generative_model(self, **kwargs)

def wyner_common_information(self, **kwargs: Any) -> float:
"""Wyner common information C[S+ : S-] = C[past : future] (no generator requirement)."""
from sofic.generators.minimal_generative_model import wyner_common_information
Expand Down
Loading
Loading