Skip to main content

certlim

What any Certify / Reject rule can achieve from the evidence you actually have.

certlim answers a question that accuracy metrics cannot: given two registered classes of systems — those that are safe under a proposed change and those that are unsafe — and the measurement you can afford to take, how often can any procedure return a reliable verdict while controlling both kinds of error?

And when the answer is "not often enough", it tells you which of the two remedies applies:

  • buy more of the same evidence — the obstruction is temporary and closes at a computable block length; or
  • change the measurement — the obstruction is permanent, and no amount of the same channel, nor any re-encoding of it, will ever help.

Distinguishing those two cases is the point. They look identical to a single-sample detector and they cost very different amounts of money.

pip install certlim

Sixty seconds

import certlim as cl

# Two safe populations and one unsafe one, over a two-symbol evidence alphabet.
exp = cl.Experiment(safe=[[0.25, 0.75], [0.75, 0.25]],
                    unsafe=[[0.50, 0.50]])

d = exp.diagnose()
print(d.region)            # 'delayed'
print(d.n_star)            # 2
print(d.explain())

Delayed separation: the one-unit hulls intersect but no exact pair collides, so repetition alone opens the gate at n = 2. Buying more of the same evidence is the correct move here.

Note what just happened. Every safe/unsafe pair in this example is well separated — the minimum pairwise total variation is 0.25 — yet one unit of evidence certifies nothing at all:

exp.pairwise_min_tv()      # 0.25   "looks separable"
exp.delta(1)               # 0.0    actually is not
exp.decisiveness(1)        # 0.10 = alpha + beta, i.e. no better than a coin
exp.delta(2)               # 0.125  two units break the tie

A criterion built on pairwise distances would have declared success here. The composite geometry is what governs, and it is what certlim computes.


The frontier

f = cl.frontier(exp, n_grid=[1, 2, 3, 5, 10], alpha=0.05, beta=0.05)
print(f)
   n   states     Delta         D     bound     slack  gate
   1        2  0.000000  0.100000  0.100000  0.000000     1
   2        3  0.125000  0.112500  0.225000  0.112500     0
   3        4  0.187500  0.116667  0.287500  0.170833     0
   5        6  0.273438  0.138889  0.373438  0.234549     0
  10       11  0.409561  0.196970  0.509561  0.312591     0

Delta is the composite separation, D the best worst-case decisiveness any valid rule attains, bound the ceiling min(1, alpha + beta + Delta), and gate flags block lengths where the two hulls still meet.

f.units_for(0.9) returns the smallest gridded block reaching 90 % decisiveness, or None if the grid does not get there.


The deployable rule

rule = cl.build_rule(exp, n=3, alpha=0.05, beta=0.05)
rule.decisiveness          # what it actually attains
rule.alert_rate_floor()    # the fraction of blocks that must come back Alert

decision = rule.decide([2, 1], rng=np.random.default_rng(7))
decision.outcome           # 'certify' | 'alert' | 'reject'

rule.error_profile(exp)    # realised worst-case side errors, self-check

The optimal rule is genuinely randomised, so pass a seeded generator whenever the verdict has to be reproducible — in a certification workflow it usually does.

When the hulls still intersect, the optimum is a rule that ignores the data and answers at the allowed error rates. certlim says so plainly rather than dressing it up. That is the signal to change the measurement.


From raw measurements

Start from per-unit symbol counts, one row per physical unit:

exp = cl.Experiment.from_counts(safe_counts, unsafe_counts, smoothing=0.5)

Building the evidence menu is where most mistakes happen, so the quantiser enforces the one property that makes menus comparable:

q = cl.NestedQuantizer.from_quantiles(features, bins=2, drop_order=(2, 1))
q.chain()                  # [8, 4, 2] — strictly nested, by construction
counts = q.counts(features, level=0)

Quantising each menu independently produces partitions that are not nested, and the resulting comparison can show a coarse menu beating its own refinement. That is a pipeline artefact. cl.check_data_processing(fine, coarse) will tell you when you have hit it.


Finite-sample calibration

A plug-in Delta from observed counts is an estimate:

cl.permutation_test(unit_counts, labels, blocks=chemistry, n_permutations=999)
cl.bootstrap_delta(unit_counts, labels, n_resamples=2000)

Both resample whole physical units. Resampling rows, cycles or time points instead will understate the interval and inflate significance by orders of magnitude, because repeated measurements of one unit are not independent evidence about the population of units.

Neither is a lower confidence bound on the population separation. A significant permutation p-value rejects label exchangeability; it does not certify anything with coverage. certlim keeps that distinction in the result objects.


Deployment and procurement

report = cl.transport_margin(source_exp, deployment_exp)
report.certified           # separation survives the registered shift?

The margin is Delta_source - drift_safe - drift_unsafe, measured as class geometry rather than point-prediction error. A non-positive margin means the source evidence does not certify transport — not that the deployment domain collides.

cl.screen([
    cl.Plan("passive",   n=1, decisiveness=0.63, cost={"seconds": 780, "c_rate": 0.0}),
    cl.Plan("active",    n=1, decisiveness=0.53, cost={"seconds": 940, "c_rate": 0.5}),
    cl.Plan("nameplate", n=6, decisiveness=0.10, cost={"seconds": 0,   "c_rate": 0.0}),
], target=0.4)

Costs stay a vector and are never collapsed into one currency. A plan is dropped only when another is no worse on every recorded axis. Plans measured on different ledgers raise rather than being ranked — an unmeasured cost is not a free cost.


The report

One command turns the whole analysis into a filing-ready page:

certlim report units.csv -o certification.html --subject "Lot 2026-08"
cl.write_report("certification.html", exp,
                unit_counts=counts, labels=labels, blocks=site,
                subject="Lot 2026-08",
                notes=("Grades come from the bench protocol, not from this channel.",))

The page carries the structural verdict and the action it implies, the frontier as a table and an inline chart, the finite-sample calibration, the decision table and the standing scope caveats. It is a single file with no external stylesheet, script, font or image, so it survives being archived or opened offline, and it is byte-deterministic unless you pass a timestamp — two runs of the same analysis produce the same file, and a diff means something actually changed.

Command line

certlim diagnose  units.csv                          # permanent, delayed, or open?
certlim frontier  units.csv --n 1 2 3 5 10 --target 0.9
certlim calibrate units.csv --block-col chemistry
certlim rule      units.csv --n 3 --json rule.json
certlim report    units.csv -o certification.html

Input is either a CSV of per-unit counts with a grade column holding safe / unsafe, or a JSON file of probability laws:

{"safe": [[0.25, 0.75], [0.75, 0.25]], "unsafe": [[0.5, 0.5]]}

Add --json out.json to any subcommand for a machine-readable report.


What certlim does not do

It computes population geometry exactly. It does not know whether:

  • your two classes were registered before you looked at the data;
  • your grades were identified by a reference experiment rather than by the channel you are evaluating — grading units with the channel under test makes every number here meaningless;
  • your sampling unit is the physical unit it should be;
  • your evidence is conditionally i.i.d. within a block;
  • your registered models are cohorts rather than individual units. If each model is one physical unit, two units can produce identical histograms by chance, which is an exact collision by definition. certlim flags that case rather than letting it read as a structural obstruction, but it cannot decide for you how the classes should have been registered.

Those are design decisions and the package will not make them for you. Every result is relative to the registered classes: enlarging them can only shrink the separation, never grow it.


Method notes

Block laws are enumerated over count vectors, the sufficient statistic for i.i.d. draws from a finite alphabet, so the problem is C(n+K-1, K-1) rather than K**n. The composite distance and the optimal decision rule are solved as two independent linear programs, so their agreement with the certification bound is a real check rather than a same-code identity. There is no sampling, no fitting and no tuning parameter anywhere in the core.

delta() is solved through the dual separation game, which is 10x to 450x faster than the primal hull program on large state spaces. The dual loses accuracy as the space grows, and a value near zero is exactly where the gate verdict turns, so anything below 1e-5 is re-solved with the exact program; delta(n, exact=True) forces it everywhere.

Exact enumeration has a ceiling (max_states, default 20 000). It is a time budget, not a memory one — measured cost is about 3 s at 6 000 states, 40-100 s at 20 000, and hours beyond, with run-to-run variation of a factor of two at the same size. Exceeding it raises with the offending (K, n) and what to do about it, rather than silently approximating or starting a job that never finishes.

For a finite registered class with r distinct one-unit laws and no exact collision, the gate must be open by r - 1; Experiment.stopping_bound() returns it, and n_star() searches exhaustively up to it. The bound is tight for small classes and loose for large ones, and it bounds the structural search only — never the number of physical units needed for a confidence statement.

Licence

MIT.

Metadata

Release files for certlim 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for certlim 0.1.1
File Size Uploaded
certlim-0.1.1.tar.gz 52.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for certlim 0.1.1
File Interpreter ABI Platform
certlim-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 97.4 kB

Release files / certlim-0.1.1.tar.gz

Download URL certlim-0.1.1.tar.gz
Size 52.6 kB
Tags Source
SHA-256 checksum
How to use checksums
e4a425b9879bc04b5cf8ecfe114fc9549eb4099285577f48858a3f0e9a6c046b
BLAKE2b-256 checksum
How to use checksums
59412830bc09eac14d66acc337f2be842aafd44d7f62198a46f1a1388dc80cb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.2

Release files / certlim-0.1.1-py3-none-any.whl

Download URL certlim-0.1.1-py3-none-any.whl
Size 44.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05cd901d9095f795ab541908dff90eda64c9bf362bf1c2f78bd537191e8586f0
BLAKE2b-256 checksum
How to use checksums
dd706de93b49067093fa776b9efbf3a23c23bdb56b0b011db0a6d9916f82fbb3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.2

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page