Skip to content

Restrict Wyner/exact/functional generative models to generator states - #29

Merged
Autoplectic merged 2 commits into
mainfrom
lifted-common-information
Oct 22, 2026
Merged

Autoplectic merged 2 commits into
mainfrom
lifted-common-information

Conversation

@Autoplectic

@Autoplectic Autoplectic commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

A channel p(g | s+, s-) that renders the forward and reverse causal states conditionally independent need not be the hidden state of a generator. The HMM sofic builds from it (the channel applied at both ends of every bidirectional edge) generates the process, with G jointly distributed with past and future as the channel prescribes, if and only if

  • I[G_0 X_0 : S-_1 | G_1] = 0 (the next state screens what was emitted from the reverse causal state), and
  • I[S+_0 : X_0 G_1 | G_0] = 0 (its time reverse).

Previously the exact/Wyner constructors ignored these, checked reproduction afterwards, and silently returned the functional model under the unconstrained value. The functional model itself was returned without any check and can fail too.

Changes

  • minimal_generative_model / wyner_generative_model optimize subject to both conditions (new sofic/generators/lifted_common_information.py): SLSQP with analytic constraint Jacobians, continuation from the unconstrained problem, starts at S+, S-, (S+, S-), the dit optimum and random channels, then refinement on the channel's support. The reported value is the model's own; the exact value upper-bounds the generative complexity. The fallback is removed and models carry markov_residual (largest constraint information, bits).
  • functional_generative_model searches deterministic partitions under the same conditions (exhaustive up to nine joint states, greedy merging from (S+, S-) beyond), so it always reproduces the process. dit's partition is kept when it already satisfies them (it is then optimal).
  • New measures BidirectionalEpsilonMachine.wyner_common_information() / exact_common_information() return the unconstrained C[S+:S-], G[S+:S-] (= the common informations of past and future).
  • generative_gap(measure="exact" | "wyner") (also BidirectionalEpsilonMachine.generative_gap()) returns GenerativeGap(lower, upper, gap, model): the unconstrained common information vs. its value over generator states. For the exact measure G[S+:S-] <= C_g <= H[G], so gap bounds C_g - G[S+:S-]. On the (2,∞) process: exact 1.406 → 1.5 (gap 0.094), Wyner 1.129 → 1.311 (gap 0.18); zero for first-order Markov processes.
  • Bug fix: dit numbers each variable's symbols by first appearance, so the optimum was read with the reverse-state axis permuted whenever the support listed a higher-indexed reverse state first. The resulting channels did not separate S+ from S- (this is why the golden mean appeared to fail and fell back).

Motivating example

Every 1 followed by at least two 0s (3-state ε-machine, one ½/½ branch, second-order Markov) is the simplest case where the conditions bind. Its unconstrained exact optimum is a deterministic W with H[W] = 1.406; one value of W ("past ends in 10, future starts with 01") can only be preceded by a value that does not know the past ended in 10, so no HMM has W as its state. Constrained values: exact 1.5, Wyner 1.311 (vs 1.129 unconstrained). For first-order Markov processes the constrained and unconstrained values coincide.

Breaking changes

Default behavior of the three constructors and generative_complexity() changes (see CHANGELOG). There is no unconstrained-model mode; use the new measures for the unconstrained values.

Tests

  • New tests/test_lifted_common_information.py: golden mean and binary Markov chains vs closed-form C_g, Odd process (interior Wyner generator), the (2,∞) process, functional model always reproducing (exhaustive and greedy paths), set-partition enumeration vs Bell numbers, Jacobian vs finite differences, Hypothesis bounds on Markov chains.
  • Regression test for the dit symbol-order alignment.
  • Full suite: 2019 passed, 15 skipped; ruff clean. Sphinx build not run locally.

Ryan James added 2 commits October 21, 2026 21:43
A channel p(g | s+, s-) that renders S+ and S- conditionally independent need
not be the state of a generator: the HMM built by applying it at both ends of
every bidirectional edge generates the process only if
I[G_0 X_0 : S-_1 | G_1] = 0 and I[S+_0 : X_0 G_1 | G_0] = 0.

- minimal_generative_model and wyner_generative_model now optimize subject to
  both conditions (new module sofic.generators.lifted_common_information:
  SLSQP with analytic constraint Jacobians, continuation from the
  unconstrained problem, support-restricted refinement), so the reported value
  is the returned model's own and the exact value upper-bounds C_g. The silent
  functional fallback is removed; models carry markov_residual.
- functional_generative_model searches deterministic partitions under the same
  conditions (exhaustive up to nine joint states, greedy merging beyond), so it
  always reproduces the process; dit's partition is kept when it already lifts.
- BidirectionalEpsilonMachine.wyner_common_information() and
  exact_common_information() expose the unconstrained C[S+:S-] and G[S+:S-].
- Fix: the dit optimum was read with the reverse-state axis permuted whenever
  the support listed a higher-indexed reverse state first (dit numbers symbols
  by first appearance), giving channels that did not separate S+ and S-.

The process in which every 1 is followed by at least two 0s is the simplest
case where the conditions bind: its unconstrained exact optimum (1.406) is not
the state of any HMM; the constrained value is 1.5.
Refine candidates an extra decade below the acceptance tolerance so rounding
cannot tip a good channel over markov_atol (seen on macOS/Python 3.11 CI).
Test the Odd-process property (its Wyner optimum is an interior channel that
already lifts) directly instead of through the stochastic optimizer, and keep
an end-to-end check that the constructed model generates the process.
@Autoplectic
Autoplectic merged commit 20f75f7 into main Oct 22, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant