bistable-cert
bistable-cert is the reference Python implementation of the computable
certificates in Certified branch-safe selective writing in diffusively
coupled bistable graph memories (A. Kubański, 2026; prepared for
submission to Opuscula Mathematica). The preprint is available at
https://doi.org/10.5281/zenodo.22903156.
The package certifies storage of a prescribed binary pattern (Theorem A) and branch-safe switching of one selected node by a constant pulse (Theorem B). Certificate decisions use closed-form sufficient conditions; they do not depend on an ODE simulation. Simulation utilities are included only for inspection and numerical cross-checking.
Installation
From the extracted release directory:
python -m pip install .
For development and test execution:
python -m pip install -e ".[dev]"
pytest
The distribution name is bistable-cert; the Python import name is
bistable_cert.
Reproducibility
The paper's numerical verification script for Section 6 is included at
reference/reproduce_section6.py. Every formula in certificates.py and
dynamics.py is checked against its printed output by
tests/test_against_paper.py. The complete release suite contains 83 tests.
Pre-release audit and correction history
Versions 0.1.0–0.1.5 were internal audit builds under the temporary name
rlds-network. The public 1.0.0 release was renamed to avoid confusion
with unrelated RLDS software. The rename and release packaging do not
change the mathematical formulas or certificate logic. The detailed
record below is retained so earlier defects and their regression tests
remain visible.
Erratum (v0.1.1)
An external review of v0.1.0 found that certify_storage checked the
wrong condition: the a-independent eps*q(sigma) < C_beta (the
paper's eq:Acert) instead of the a-specific eps*(1+a)*q(sigma) < f_beta(a) (eq:amarg) that Theorem B's own hypothesis requires ("let a
satisfy eq:common"). eq:Acert only proves some floor near a_star is
admissible for a pattern — not that the floor a caller actually asks for
(every function defaulted to the paper's own a=0.65) is one of them.
Concretely, v0.1.0 could report a write CERTIFIED when a non-target
node's true resting equilibrium was already below the claimed floor, with
zero pulse applied — confirmed here by direct ODE integration on a
constructed 6-node counterexample, independently of the reviewer's own.
This was a real bug in the package's API layer, not in the paper: the
paper's own reproduce_section6.py checked the correct condition all
along. It is fixed in v0.1.1 — certify_storage now checks eq:amarg
directly, admissible_floor_bound() is added to compute which floors a
pattern actually supports, and every number in this README below was
re-measured after the fix (they differ from v0.1.0's numbers, sometimes
substantially — see each subsection). The review also flagged a grid-
search precision concern (replaced with an exact critical-point solver,
see certificates.py) and an invalid a=0.30 floor used in one probe
script (a_star≈0.4127 for beta=2.5, so 0.30 was never a valid floor
to begin with — fixed). Full detail is in certificates.py's module
docstring and tests/test_against_paper.py's comments, which keep the
old, now-invalid examples on record as regression checks rather than
deleting them quietly.
Erratum (v0.1.2)
A third external review, of the v0.1.1 bundle, found the eq:amarg fix and the exact pulse-margin solver both hold up, but the package's edges were not fail-closed: several inputs outside what Theorem A/B actually cover were silently accepted rather than rejected, and a "CERTIFIED" verdict was reachable a few float64 ULPs from a true zero. All confirmed by independent reproduction (not just taken on the review's word) before fixing:
amplitude=nanreturnedfeasible=Truewithmargin=nan,T_cert=nan(nancompares false against everything, so the oldmargin <= 0guard never caught it);amplitude=infwas accepted outright. Both now raise.- A negative
target_nodewas silently accepted via Python's own negative-indexing (reinterpreted as a node counted from the end, not rejected). Now validated to0 <= target_node < n. Graph's symmetry check used an explicitatol=1e-12but leftnp.allclose's defaultrtol=1e-5in effect, making the effective tolerance ~1e-5, not 1e-12 — a 1e-6 asymmetric matrix was accepted. Negativity and the zero-diagonal check used a1e-12tolerance that let a-5e-13weight and a5e-13diagonal entry through. All three are now exact (negativity, diagonal) or pinned tortol=0, atol=1e-9(symmetry, a tolerance for genuine float noise only, not for a 1e-6 error).- A pattern with two entries both within tolerance of
-1but not float-equal to each other (e.g.-1+3e-13and-1-4e-13) passed validation, then was silently miscomputed downstream by exact==/!=comparisons — confirmed to actually flipopposed_degree's output on a 4-node ring. Validated patterns are now snapped to the exact±1they were checked against. certify_storage/certify_single_node_writecould reportCERTIFIEDon a margin only a few float64 ULPs above zero — independently reconstructed from scratch here (not the reviewer's own example, which didn't come with enough parameters to replicate directly): a random graph's margin(U) root, found by exact bisection, then sampled at float64 ULPs around that root and cross-checked against an 80-digit arbitrary-precision recomputation of the identical formula — 3 sign disagreements found out of ~6000 points tested, all with|margin|under 1.2e-16. Both functions now require clearing anumerical_safety_tolerance(default1e-9— negligible next to any real margin seen in this project, from 0.076 up, but far above float noise), not just> 0.admissible_floor_bound()crashed withValueError: f(a) and f(b) must have different signson a homogeneous pattern (q_sigma=0, where the true answer is exactlys_plus) and on a load within ~1e-12 ofC_beta(where the true root sits closer toa_starthan the old fixed bracket padding). Rewritten to evaluate the (removable) endpoint singularities directly instead of padding around them —h_beta(a_star)agrees withC_betabit-for-bit, so the true, unpadded bracket is safe to use.CellModel(beta=20)(a value the model accepts — onlybeta > 1is required) crashed computings_plus: forbetaabove ~19–20,tanh(beta*(1-1e-15))itself rounds to exactly1.0in float64, so the old bracket no longer contained a sign change. Fixed by using exactly1.0as the upper bracket (scipy.optimize.brentqaccepts an exact zero at an endpoint as the root itself, with no sign-change needed) — confirmed correct through beta=100; unchanged at the paper's beta=2.5.
All of the above are now regression-tested in tests/test_validation.py
against the pre-fix behavior, not just the post-fix one. Two wording/
methodology issues in the probe scripts (not the package) were fixed at
the same time — the "roughly 4x" floor-vs-d_perp* claim below, and the
sensitivity-sweep's mechanism attribution below — see those two
subsections directly for the corrected numbers and reasoning rather than
restating them here. T_true/"branch preserved" wording in the
probe scripts now says "observed equilibrium-start hitting time" and "no
violation found in the sampled points" at least once before reverting to
shorthand, since both are narrower claims than the old phrasing implied
(see probe_common.py's module docstring). One more probe-side check was
added while fixing this: whether the target node, once it first crosses
the floor, stays above it through T_cert — which briefly appeared to
fail on 39/179 certified writes on the first re-run, traced to a 1-ULP
float artifact in the new check itself (evaluating the crossing instant a
second time, through a different code path, can round a hair either side
of the floor) rather than anything physical, and fixed with the same
numerical-tolerance treatment as above before the number below was
reported.
Erratum (v0.1.3)
A fourth external review, of the v0.1.2 bundle, confirmed the eq:amarg fix, the exact pulse-margin solver, and every v0.1.2 fail-closed check hold up under the standard paper scenario (beta=2.5, a valid graph, default parameters) — but found six more edges, all independently reproduced before fixing:
- A negative
numerical_safety_tolerancewas accepted outright and could push a raw-negative margin over the CERTIFIED line — confirmed: a storage margin of-0.0546538, and separately a write withamplitude=0givingmargin=-0.257158and a negativeT_cert= -6.41628, both reportedfeasible=Trueattolerance=-1.0. Now validated finite and>= 0in bothcertify_storageandcertify_single_node_write(0itself stays legal, as the documented opt-out to the raw condition). Graphstill stored a raw, slightly-asymmetric matrix within the v0.1.2 tolerance — confirmed:[[0,0],[9e-10,0]](just insideatol=1e-9) was accepted as-is, soopposed_degreedisagreed with itself by row about whether the edge existed,reprundercounted edges (1 nonzero entry, not 2), andcertify_storagereportedCERTIFIEDoff the zeroed-out row. Tightening the tolerance further only moves the same problem to a smaller adversarial value, so the fix is structural: the matrix actually stored, once confirmed close enough to symmetric to accept, is always its exact symmetrization — never the caller's raw input.search_amplitude'stolwas unvalidated — confirmed:tol<0hung the bisection loop forever (the loop invariantb-a >= 0is always greater than a negative number), andtol=nan/infskipped the loop entirely and silently returnedhias though it were the minimal amplitude found. Now validated finite and> 0, plus a 200-iteration cap regardless (even a tiny-but-validtolcan stop making progress onceb-ahits float64's own precision floor).admissible_floor_boundcould return a floorcertify_storage's own default tolerance then rejected — confirmed: a load placed1e-12belowC_betagot a finite bound back, butcertify_storageat exactly that floor, default tolerance, said NOT CERTIFIED. Both functions now share the samenumerical_safety_toleranceparameter andadmissible_floor_boundsolves the correspondingly shifted condition, so a floor it returns is onecertify_storage(same tolerance) actually accepts. (Documentation fix alongside this: an earlier note claimedh_beta(a_star)matchesC_beta"bit-for-bit" as a general fact — checked across a wider beta range, it doesn't; it only happened to match exactly at the paper's own beta=2.5. The function's correctness never actually depended on that identity, only the docstring's explanation of it did, and that's now fixed.)Graph.from_edgesstill accepted a negative node index — confirmed:(-1, 0, 0.5)silently became an edge to the LAST node via plain Python negative-indexing, the same class of gap v0.1.2 fixed fortarget_nodebut this constructor's own loop had its own, separate index use. Now validated against0 <= i,j < nbefore any edge is built.betaright at 1 lost catastrophic precision — confirmed:beta=nextafter(1.0, 2.0)(accepted by the oldbeta > 1check alone) computeda_star=0.0EXACTLY, when it should be a tiny positive number;sqrt(1+1ULP)itself already rounds to exactly1.0in float64. Checked against an 80-digit reference across a beta range:C_beta's relative error (a subtraction of two quantities that both-> 0at the same rate asbeta -> 1, so a classic cancellation setup) is already ~1.6e-8 atbeta=1+1e-4and exceeds 25% bybeta=1+1e-8.CellModelnow requiresbeta - 1 >= 1e-3(documented onCellModel.MIN_BETA_MINUS_ONE, measured at ~1e-10 relative error there, comfortable margin to spare) rather than attempting an asymptotic rewrite of the closed forms near their own branch point — the paper's own working value, beta=2.5, is nowhere near this edge.
Two documentation-only fixes from the same review: simulation.py's
nontarget_margin docstring said a positive result means a non-target
node "stayed strictly inside its certified branch for the whole simulated
window" — overstated in exactly the way the probe scripts' own wording was
already corrected for in v0.1.2 (it's a dense-sample check, not a
continuous one; fixed to say so); and this file previously explained two
of the v0.1.2 fixes in full twice — once in that Erratum, once again at
each fix's own subsection — trimmed to a single explanation with a pointer
between them, here.
Erratum (v0.1.4)
A fifth external review, of the v0.1.3 bundle, again confirmed the standard paper scenario (beta=2.5, a valid graph, default parameters) passes cleanly, and characterized its three remaining findings as adversarial-parameter territory this README already disclaims (an ever-finer boundary value, an ever-more-extreme magnitude, an ever-less- standard input type — the chase has no natural end, and a research package accompanying a paper doesn't owe it one). That framing is right in general, but treating all three as equally out of scope wasn't checked, just asserted — so each was checked independently before accepting the recommendation to stop here:
Graph.from_edgesgiven a single-use iterator (a generator expression, amap/zipobject) instead of a list — confirmed this is not adversarial-parameter territory at all: it's a plain, idiomatic way to call the documented[(i, j, weight), ...]contract, and it silently built an all-zero, zero-edge graph with no error. The v0.1.3 fix for the negative-index bug made this function iterateedgestwice (validate, then buildW), which exhausts a single-use iterator on the first pass. Fixed by materializingedgesinto a list up front — see graph.py's own CORRECTNESS NOTE (v0.1.4) andtest_from_edges_accepts_a_single_use_iterator.- An edge weight near float64's max (~1e308) — checked directly: it
does trigger a
RuntimeWarning: overflow encountered in addfrom the symmetrization step (cosmetic; nothing catches or suppresses it), but the overflow propagates to the load term asinf, andcertify_storagecorrectly reportsNOT CERTIFIEDwithmargin=-infrather than anything resembling a falseCERTIFIED. Fail-safe direction confirmed; left as a documented limitation rather than a fix, since silencing the warning without addressing the underlying non-finite arithmetic would only hide it, not resolve it. - A value placed right at the v0.1.2 symmetry tolerance's own boundary
(
atol=1e-9, not the specific9e-10case v0.1.3 already fixed) — checked both sides directly: an asymmetry of9.999999e-10(just inside) is accepted and stored as its exact symmetrization, and1.0000001e-9(just outside) is correctly rejected. The v0.1.3 structural fix (always store the symmetrization, never the raw input) generalizes past the one originally-reported value, as intended.
admissible_floor_bound's own residual gap (documented in its
CORRECTNESS NOTE (v0.1.3)) was also re-checked against this review's
characterization of it as producing an excess rejection rather than a
dangerous false certificate: it does, and provably so, not just by
observation — the function shifts by the unscaled
numerical_safety_tolerance, while certify_storage only requires
clearing the smaller scaled tol/(1+floor), so the floor returned is
never looser than what certify_storage actually accepts, only
potentially tighter.
Net effect: two of the three remaining findings were legitimately out of scope for this package, confirmed rather than assumed; one was not, and is now fixed. v0.1.4 is the version this package stops at pending any further external review.
Erratum (v0.1.5)
This corrects the v0.1.4 bullet above about an edge weight near float64's max. That bullet checked the claim against one pattern (alternating signs) and reported the overflow fails closed. A follow-up to the fifth external review checked a second pattern and it isn't fail-closed there:
-
The same
~1e308overflow, with a HOMOGENEOUS pattern — confirmed reproducible:certify_storagereportedCERTIFIEDonW=[[0,1e308],[1e308,0]],pattern=[1,1].opposed_degreeselects a node's edges to opposite-signed neighbors via boolean masking (self.W[k][pattern != pattern[k]]); for a homogeneous pattern nothing is opposite-signed, so it selects nothing and returns an exact0.0— never touching theinfthatW+W.Toverflowed to. The certificate this produces isCERTIFIEDfor a graph that, per this package's own constructor docstring, should not have been constructible at all ("W must contain only finite weights"). Root cause:Graph.__init__checked finiteness on the caller's raw input, before theW = (W + W.T) / 2symmetrization step that can itself overflow two large-but-finite entries intoinf— so the check and the thing actually stored could disagree. This is the same class of gap v0.1.3 fixed for the symmetry check itself (checking the pre-symmetrization input let an asymmetric raw matrix through; here, checking pre-symmetrization finiteness let a matrix that overflows during symmetrization through). Fixed the same way: finiteness is now checked on the matrix actually stored, after symmetrization.Unlike the previous bullet's alternating-pattern case, or either of the two v0.1.4 findings correctly left as documented limitations, this one is a false
CERTIFIED— the exact failure mode this package exists to rule out — so it's fixed rather than documented, regardless of how unusual~1e308is as an edge weight. A documented "don't use extreme magnitudes" caveat is a reasonable thing to ask of a caller; silently handing back a wrong CERTIFIED when they do isn't.
Also from the same follow-up: admissible_floor_bound's own docstring
called its return value "the supremum a_max" outright. That overclaims
what the tolerance-padded computation actually guarantees — because of
the padding (see its CORRECTNESS NOTE (v0.1.3)), the true supremum of the
raw condition can be larger than what this function returns, though never
smaller. Reworded to "a conservative admissible floor bound" throughout,
with the guarantee stated in one direction only (every floor it returns
is admissible; it does not claim to find the largest one). No
computational change, only what the docstring claims.
Lesson taken from this round specifically: "checked directly" (v0.1.4's own words, one paragraph up) meant one pattern, not all reachable ones — worth naming plainly rather than quietly fixing and moving on. 82 → 83 tests. Filed as v0.1.5 rather than amending v0.1.4 in place, so this correction has its own paper trail instead of rewriting what v0.1.4 claimed.
What this is
Given a finite weighted graph of diffusively coupled bistable cells, this package answers, before applying anything:
certify_storage(graph, pattern)— ispatterna certified stable memory state? (Theorem A)certify_single_node_write(graph, pattern, target_node, amplitude)— does a constant pulse of the given amplitude, applied totarget_nodealone, provably switch it while every other node provably keeps its own branch throughout the transient? (Theorem B)
Both answers come from the theorems' closed-form sufficient conditions,
evaluated without integrating the network. Numerical integration
(bistable_cert.simulation) is available separately, to inspect or
cross-check a certificate — it never decides one.
What this is not
Version 1.0.0 deliberately implements the Theorem A/B certificate layer only. It does not implement:
- Theorem B* (robustness of a certified write over an interval of
admissible amplitudes, not just one fixed
amplitude) - Section 5's crosstalk bounds (Theorems C/C*) — the non-target displacement envelope
- multi-node writes, write-path planning, or anything that chains several single-node writes together
These are natural next layers, but each is its own scope decision, not a missing feature of this release. See "Status" below for the validation boundary of version 1.0.0.
The central caveat
feasible=False never means "impossible." Theorem A and Theorem B are
sufficient conditions. When a certificate comes back infeasible, it means
this theorem does not certify the write at these parameters — not that no
pulse, no amplitude, and no duration could ever do it. search_amplitude()
is the first thing to try before concluding a write is out of reach.
How conservative is the sufficient condition, really?
Before writing this package, two probe scripts measured this empirically
(not derived — measured, on top of the already-proved theorem). All
numbers below are from v0.1.1's corrected certify_storage (see the
Erratum above); they differ from v0.1.0's, in one case substantially.
On 500 random connected graphs (4–12 nodes): of the 215 with a
storage-certified source pattern, 83.3% of attempted single-node
writes were certified by Theorem B at all (v0.1.0 claimed 92.6% — the gap
is cases the corrected check now correctly excludes). Of the 179 that
were, the certified duration T_cert was typically 3.28x the actual
time needed (median — essentially unchanged from v0.1.0's 3.3x), with
82.7% under 5x and 96.6% under 10x (also essentially unchanged). The
tail is where the correction actually shows up: the maximum ratio
observed dropped from v0.1.0's ~8765x to 22.7x. The astronomical
outliers were, on inspection, exactly the cases the old a-independent
check let through despite the pattern being right at the edge of (or past)
real storage certification at a=0.65 — border cases that are now
rejected outright as NOT CERTIFIED rather than accepted with a wildly
inflated bound.
Following up on that tail across four explicit graph families
(chain, ring, star, complete, every node as write target): chain and ring
stay essentially flat regardless of size (ratio 2.9x–3.3x from n=4 to
n=20 — low, bounded degree, nothing to trigger). Star and complete are
where the correction changes the story, not just the numbers: v0.1.0
reported a 10-node star hub as CERTIFIED with margin≈0.0096 and a 64x
ratio; that source/target pair is not actually storage-certified at
a=0.65 under the real eq:amarg condition, so v0.1.0's number was
itself a false positive, not just a loose-but-valid bound. Under the
fix, the star family stops producing ANY certified write past n=6 (source
pattern itself fails storage certification at n=8+, at weight=0.5), and
complete stops past n=4 (n=6's pattern is still storage-certified, but
every one of its 6 possible single-node writes fails the pulse-margin or
target-certificate step; n=8+ fails storage certification outright). The
worst ratio among writes that ARE genuinely certified in this sweep is
6.05x (star, n=4, a leaf — not even the hub). In short: high written-
node degree still predicts trouble, but under the corrected check that
trouble now shows up mostly as outright rejection, not as a certified-but-
extremely-loose bound — the earlier "conservativeness blows up with
degree" framing undersold how much of that blowup was actually invalid
certificates. The mechanism identified for the ones that ARE valid is
unchanged: the margin formula's eps*(a*d_par - d_perp) term still grows
with the written node's degree.
Practical consequence for this API: every WriteCertificate carries
degree_i, d_par, and d_perp alongside margin and T_cert, so a
caller can see why a bound is loose, not just that it is. Treat a
certificate on a high-degree written node as correct but possibly very
conservative, and check margin before trusting T_cert as a useful
(not just valid) bound.
Does raising the pulse amplitude fix a bad margin? Only below a hard,
closed-form ceiling — and the ceiling depends on ONE thing, not degree.
As U -> infinity, tanh(beta(r+U)) -> 1 pointwise, so the margin
function converges to an amplitude-independent limit:
m_inf(a) = 1 - (1+eps*d_i)*a + eps*(a*d_par - d_perp)
A first pass found this ceiling crossed zero at the same total degree on
both the star and complete-graph families and concluded "degree drives
it." That was the wrong mechanism, caught by isolating degree from the
d_par/d_perp split directly: fixing a star hub's degree at 4.50 and
varying only how those neighbours split between same-side (d_par) and
opposite-side (d_perp) moved the ceiling from -0.393 to +0.020 —
same degree, different split, different verdict. Substituting
d_i = d_par + d_perp into the formula shows why: the d_par terms
cancel completely, leaving
m_inf(a) = 1 - a - eps*(1+a)*d_perp
— a function of d_perp alone. This gives a universal,
topology-independent threshold, opposite_side_degree_threshold():
d_perp* = (1-a) / (eps*(1+a))
A written node's OWN margin is capable of being pushed positive by some
finite amplitude if and only if its opposite-side weighted degree is below
d_perp* — regardless of same-side degree, total degree, or graph family.
At the paper's own a=0.65, eps=0.10, d_perp* ≈ 2.121. Confirmed against
certify_single_node_write at U=1e4 (exact match, on examples that pass
storage certification — see the scope note just below) and against the
formula's predicted sign on 800 random connected weighted graphs (raised
from v0.1.0's 300, since the corrected storage check passes fewer of them,
see the Erratum) — 267/267 storage-certified cases matched exactly
(it's an algebraic identity, not a statistical tendency, so 100% is the
expected result, not a lucky sample).
Scope note, sharpened in v0.1.1: margin_ceiling/d_perp* describe
only the written node's OWN drift — they say nothing about whether the two
endpoint patterns are storage-certified in the first place (a separate,
required condition, checked by certify_storage). A positive ceiling is
therefore necessary but not sufficient for certify_single_node_write
to succeed. v0.1.0's demonstration examples (a 9- and a 10-node star)
conflated the two: their ceiling is positive, but neither pattern is
actually storage-certified at a=0.65 under the real check, so
certify_single_node_write correctly reports NOT CERTIFIED at any
amplitude regardless of the ceiling's sign — those examples were replaced
with ones that genuinely pass both conditions (tests/test_against_paper.py
keeps the old ones too, now as an explicit "positive ceiling does not
imply certified" regression check).
That separation turns out to resolve cleanly: whenever the TARGET pattern
of a write is storage-certified at floor a, margin_ceiling at that
same a is provably positive — proved directly from the two closed
forms (storage certification bounds the target node's own post-write
opposed degree, which is exactly d_perp, and tanh(beta*a) < 1 always),
not just observed. So "both endpoints storage-certified, yet no finite
amplitude can ever certify the write" cannot actually happen — confirmed
on 3000 random graphs (752 storage-certified pairs, zero exceptions,
smallest ceiling seen 0.076) on top of the proof. search_amplitude()
still checks storage certification and margin_ceiling() in that order,
so a write that fails storage certification is reported as such — "no
finite amplitude can certify this write" is now understood to be a
defensive branch for a case the model's own algebra rules out once
storage certification already passed, not a scenario this package's
random-graph testing ever exercises.
How does the threshold itself move with eps and the floor a?
d_perp* = (1-a)/(eps*(1+a)) is closed-form, so this is direct
substitution, not a new measurement — but it's worth stating plainly
rather than leaving a reader to work it out:
- In
eps: exactly inverse —d_perp*ateps=0.02is 5x the value ateps=0.10. Weaker coupling always widens the certifiable range. - In the floor
a: about 7.2x tighter across the grid used throughout this README (d_perp*=3.79ata=0.45down tod_perp*=0.53ata=0.90, at the paper'seps=0.10) — a stricter (higher) certified floor shrinks the budget for opposite-side neighbours faster than typicalepschoices would. (An earlier version of this README compareda=0.30againsta=0.90and called it "roughly 4x" —a=0.30is belowa_star≈0.4127and was never a valid floor to begin with, the same invalid grid point the Erratum above already removed elsewhere; the raw formula still evaluates there without complaint since nothing in this one-line computation calls_validate_floor, which is exactly how the error slipped in unnoticed. The two valid numbers give ~10.2x if you insist on comparinga=0.30's number anyway — correct arithmetic on an invalid input is still not a fact about this model — so ~7.2x across the actually-valid[0.45, 0.90]grid is the number to use.) The paper's owna=0.65sits roughly in the middle of the model's valid range(a_star≈0.413, s_plus≈0.986), not near either edge. - This holds across the entire valid floor range, not just near 0.65:
the derivation rests on
g_inf(r,a)'s minimum always landing atr=a, which requires the coefficient-(1+eps*d_i)to stay negative — true unconditionally for everyd_i >= 0, so nothing about the argument is specific to the paper's chosen floor.
Practical reading: if a write is infeasible at every amplitude
(margin_ceiling <= 0), lowering the certified floor a moves the
threshold more than adjusting eps typically would — worth knowing before
concluding a graph is simply unwritable at that node.
Does the ~3.3x typical T_cert/T_true conservativeness itself move with
a and eps? A paired sweep — the same 120 random graphs/patterns/
write-nodes re-evaluated at each parameter setting, so differences reflect
the swept parameter and not sampling noise — under the corrected storage
check now reads noticeably differently from v0.1.0's version:
a |
certified / 120 | median ratio |
|---|---|---|
| 0.45 | 89 | 3.62x |
| 0.55 | 72 | 3.30x |
| 0.65 | 40 | 3.28x |
| 0.80 | 9 | 3.70x |
| 0.90 | 3 | 3.29x |
(a=0.30, used in v0.1.0, is removed — it is below a_star≈0.4127 for
beta=2.5 and was never a valid floor; see the Erratum.)
eps |
certified / 120 | median ratio |
|---|---|---|
| 0.02 | 120 | 4.08x |
| 0.05 | 115 | 3.63x |
| 0.10 | 40 | 3.28x |
| 0.20 | 7 | 3.53x |
| 0.30 | 3 | 3.15x |
- The median ratio is stable across the entire valid range, 3.15x–4.08x
— actually more stable than v0.1.0 reported. v0.1.0 claimed a distinct
jump to 5.8x at
a=0.90, attributed to highafiltering the sample down to near-threshold survivors. That jump does not survive the fix: ata=0.90the median is 3.29x, unremarkable. The near-threshold cases that drove the old 5.8x figure were disproportionately exactly the ones the corrected eq:amarg check now excludes as invalid rather than measures as loose — the same pattern as the topology-probe tail above. - The far bigger, and still-correct, effect is on how many writes are
certifiable at all — this part of the original finding holds up:
raising
asharply shrinks the certified count (89 → 3 froma=0.45toa=0.90), and so does raisingeps(120 → 3 fromeps=0.02toeps=0.30). Correction: an earlier version of this README attributed thea-sweep's drop partly to the pulse margin /opposite_side_degree_thresholdshrinking. A third external review checked the saved sweep data directly and found zero margin-failure outcomes anywhere in either sweep's JSON — every non-certified case issource_not_certifiedortarget_not_certified, i.e. storage certification (Theorem A, eq:amarg), never the pulse margin. That is the actual, single mechanism behind both sweeps, and it makes sense directly from eq:amarg's own shape, not just from the data:eps*(1+a)*q(sigma) < f_beta(a)gets harder on BOTH sides asagrows (the left side grows linearly ina, the right sidef_beta(a)shrinks to 0 asa -> s_plus), and obviously harder asepsgrows on the left side alone. The pulse margin /d_perp*story earlier in this README is a real, separate, and correctly-derived effect (confirmed above against 800 random graphs) — it governs whether a storage-certified write's target-node drift alone can be pushed positive by amplitude; it just isn't what is driving this particular sweep's certified-count numbers, since every write reaching the margin check in this sweep already passed the margin check that lets it (see the JSON:not_certified, the margin-failure bucket, never appears at any point in either table). - Sample size caveat: the
a=0.90andeps=0.30rows rest on only 3 certified writes each (out of 120 paired specs) — the median at those two settings is reported for completeness, not as a stable estimate; treat the "stable 3.15x–4.08x across the whole range" reading above as solid for the well-populated interior of the grid (40–120 certified writes) and merely suggestive at its two sparse ends. - Independent cross-check: the external review that found the
eq:amarg bug separately re-filtered v0.1.0's own saved specs by hand and
reported 89 / 40 / 9 / 3 certified at
a=0.45/0.65/0.80/0.90— an exact match to this rewritten probe's own from-scratch count at every point, despite being produced independently (their manual re-filter vs. this script's full re-run through the fixed package). That agreement is strong mutual confirmation that both the diagnosis and the fix are right.
The probe scripts that produced these numbers are not part of the package API. They are included in the release bundle as supporting research material, not as hidden package functionality.
Example
from bistable_cert import Graph, CellModel, certify_single_node_write
g = Graph.from_edges(7, [
(0,1,0.70),(0,2,0.60),(0,4,0.50),(1,2,0.40),(1,3,0.30),(1,4,0.60),
(1,5,0.20),(2,3,0.50),(2,4,0.30),(2,5,0.40),(3,6,0.30),(4,5,0.50),
(4,6,0.40),(5,6,0.60),
]) # the paper's own worked example (Section 6), 0-indexed
model = CellModel(beta=2.5, eps=0.10)
pattern = [-1, 1, 1, 1, -1, -1, -1]
cert = certify_single_node_write(g, pattern, target_node=0, amplitude=0.40, model=model)
print(cert)
# WriteCertificate(CERTIFIED, node=0, T_cert=7.6801, margin=0.21484, degree_i=1.800)
Status
Version 1.0.0 is the first public release. Its scientific scope is frozen to Theorems A and B; the 0.1.x identifiers below refer to the retained pre-release audit history.
Every formula is transcribed from, and numerically cross-checked against,
reference/reproduce_section6.py (a copy of the script behind the paper's
own Section 6, included here specifically so this claim is checkable) —
see tests/test_against_paper.py, which pins exact printed values from
that script plus star-hub and complete-graph cases genuinely re-verified
under the corrected storage check (see the Erratum). Run pytest before
trusting any change to certificates.py, graph.py, or dynamics.py
against this suite (83 tests across test_against_paper.py, test_api.py,
and test_validation.py); a mismatch there means a transcription error,
not a new numerical result.
Input validation (tests/test_validation.py): Graph rejects
non-finite weights, non-square matrices, and — as of v0.1.2, tightened
after a third review found the old tolerances let through inputs Theorem
A/B doesn't cover — any negative weight or nonzero diagonal entry (exact,
no tolerance) and any asymmetry beyond atol=1e-9 (pinned rtol=0; the
old check's unset rtol defaulted to 1e-5, effectively ~1e-5 tight, not
the intended ~1e-12). A fourth review then found that within that
tolerance, v0.1.2 still stored the caller's raw, slightly-asymmetric
matrix rather than an exactly symmetric one, so row-indexed computations
(opposed_degree, the Laplacian, …) could disagree with each other about
whether a near-tolerance edge existed at all — as of v0.1.3, the matrix
actually stored (and used everywhere downstream) is always the exact
symmetrization of whatever passed the closeness check, never the raw
input, so this can no longer happen regardless of how tight any tolerance
is; Graph.from_edges (v0.1.3) also now rejects an out-of-range node
index instead of silently wrapping it via Python's own negative-indexing.
opposed_degree/split_degree reject non-{-1,+1}
patterns and (v0.1.2) snap accepted ones to the exact ±1 checked against,
so a near-binary pattern can no longer be silently miscomputed by an exact
==/!= comparison downstream; every node index (target_node, and
split_degree's own index) is checked against 0 <= i < n (v0.1.2 — a
negative index used to be silently accepted via Python's own
negative-indexing); certify_single_node_write (v0.1.2) rejects a
non-finite or negative amplitude outright, where it used to report
CERTIFIED with margin=nan/T_cert=nan for nan, or an
internally-inconsistent finite-looking certificate for inf;
certify_storage/certify_single_node_write (v0.1.2) require a margin to
clear a numerical_safety_tolerance (default 1e-9), not just be > 0,
since a margin within a few float64 ULPs of zero is not reliably signed at
all (see the Erratum above); CellModel rejects beta<=1 (no fold exists
there) and eps<0, both required finite, with eps=0 (no coupling)
handled as a genuine edge case rather than a division error; every floor
argument is checked against the model's valid domain (a_star, s_plus);
admissible_floor_bound() (v0.1.2) no longer crashes on a homogeneous
pattern or a load near C_beta; CellModel(beta=20) and beyond (v0.1.2)
no longer crashes computing s_plus; and
bistable_cert.simulation.equilibrium/simulate_write raise
RuntimeError (rather than returning silently) if the ODE solver reports
failure, or if equilibrium settles somewhere that isn't actually at rest
(checked via the residual ||rhs(Z)||, not just solver status).
Metadata
Release files for bistable-cert 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bistable_cert-1.0.0.tar.gz | 83.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bistable_cert-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 126.4 kB
Release files / bistable_cert-1.0.0.tar.gz
| Download URL | bistable_cert-1.0.0.tar.gz |
|---|---|
| Size | 83.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aaa6b6987b3e98c052b15f5e8470201733f170dfc178274c11a3d2566689ba03
|
|
BLAKE2b-256 checksum How to use checksums |
87bf0f97e63b8656f304241ba4d024c8730bc0879d3f97ad0e0c2cc27e1a2470
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / bistable_cert-1.0.0-py3-none-any.whl
| Download URL | bistable_cert-1.0.0-py3-none-any.whl |
|---|---|
| Size | 43.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c4dff894e11d6b37eed9e119cb0b82a4c4d6b1a91dfe5fb9ee7d74386503caeb
|
|
BLAKE2b-256 checksum How to use checksums |
b4c0a5646dfd43457dc510828aef798d6d73ac6acc2230ce137fa4413a8b7c00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|