twindex 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. twindex-0.2.0/CHANGELOG.md +54 -0
  2. twindex-0.2.0/CITATION.cff +19 -0
  3. twindex-0.2.0/CODE_OF_CONDUCT.md +12 -0
  4. twindex-0.2.0/CONTRIBUTING.md +25 -0
  5. twindex-0.2.0/LICENSE +21 -0
  6. twindex-0.2.0/MANIFEST.in +8 -0
  7. twindex-0.2.0/PKG-INFO +84 -0
  8. twindex-0.2.0/README.md +277 -0
  9. twindex-0.2.0/SECURITY.md +20 -0
  10. twindex-0.2.0/docs/DESIGN.md +237 -0
  11. twindex-0.2.0/docs/THEORY.md +299 -0
  12. twindex-0.2.0/docs/api.md +33 -0
  13. twindex-0.2.0/docs/assets/icon.svg +7 -0
  14. twindex-0.2.0/docs/assets/logo-dark.svg +11 -0
  15. twindex-0.2.0/docs/assets/logo-light.svg +11 -0
  16. twindex-0.2.0/docs/audit.md +65 -0
  17. twindex-0.2.0/docs/changelog.md +1 -0
  18. twindex-0.2.0/docs/examples.md +32 -0
  19. twindex-0.2.0/docs/getting-started.md +51 -0
  20. twindex-0.2.0/docs/index.md +44 -0
  21. twindex-0.2.0/docs/pypi.md +38 -0
  22. twindex-0.2.0/docs/study.md +164 -0
  23. twindex-0.2.0/docs/tutorial.md +81 -0
  24. twindex-0.2.0/examples/README.md +29 -0
  25. twindex-0.2.0/examples/box_inside.json +63 -0
  26. twindex-0.2.0/examples/box_outside.json +63 -0
  27. twindex-0.2.0/examples/generate.py +50 -0
  28. twindex-0.2.0/examples/inconclusive.json +56 -0
  29. twindex-0.2.0/examples/invalid_bc.json +72 -0
  30. twindex-0.2.0/examples/invalid_gap.json +72 -0
  31. twindex-0.2.0/examples/paving_regression.json +74 -0
  32. twindex-0.2.0/examples/paving_theta500.json +82 -0
  33. twindex-0.2.0/examples/regression_pair.json +72 -0
  34. twindex-0.2.0/examples/study/m17_eps100000_local_paving.json +106 -0
  35. twindex-0.2.0/examples/study/m17_eps100000_pair.json +144 -0
  36. twindex-0.2.0/examples/study/m17_eps100000_paving.json +211 -0
  37. twindex-0.2.0/examples/study/m17_eps1000_affine_local_paving.json +232 -0
  38. twindex-0.2.0/examples/study/m17_eps100_pair.json +144 -0
  39. twindex-0.2.0/examples/study/m17_eps100_paving.json +211 -0
  40. twindex-0.2.0/examples/study/m3_eps100000_pair.json +116 -0
  41. twindex-0.2.0/examples/study/m3_eps100000_paving.json +183 -0
  42. twindex-0.2.0/examples/study/m3_eps100_pair.json +116 -0
  43. twindex-0.2.0/examples/study/m3_eps100_paving.json +183 -0
  44. twindex-0.2.0/examples/theta500_pair.json +146 -0
  45. twindex-0.2.0/examples/witness_theta500.json +86 -0
  46. twindex-0.2.0/mkdocs.yml +64 -0
  47. twindex-0.2.0/pyproject.toml +62 -0
  48. twindex-0.2.0/scripts/verify_release.py +53 -0
  49. twindex-0.2.0/setup.cfg +4 -0
  50. twindex-0.2.0/tests/test_affine.py +130 -0
  51. twindex-0.2.0/tests/test_audit_models.py +106 -0
  52. twindex-0.2.0/tests/test_checker.py +314 -0
  53. twindex-0.2.0/tests/test_fit.py +64 -0
  54. twindex-0.2.0/tests/test_models.py +173 -0
  55. twindex-0.2.0/tests/test_paving.py +189 -0
  56. twindex-0.2.0/tests/test_poly.py +100 -0
  57. twindex-0.2.0/tests/test_rational.py +94 -0
  58. twindex-0.2.0/tests/test_release.py +43 -0
  59. twindex-0.2.0/tests/test_search_cli_examples.py +85 -0
  60. twindex-0.2.0/tests/test_sivia.py +181 -0
  61. twindex-0.2.0/tests/test_solver_candidate.py +100 -0
  62. twindex-0.2.0/tests/test_tutorial_study.py +92 -0
  63. twindex-0.2.0/twindex/__init__.py +3 -0
  64. twindex-0.2.0/twindex/affine.py +135 -0
  65. twindex-0.2.0/twindex/audit.py +103 -0
  66. twindex-0.2.0/twindex/candidate.py +66 -0
  67. twindex-0.2.0/twindex/certificate.py +688 -0
  68. twindex-0.2.0/twindex/checker.py +502 -0
  69. twindex-0.2.0/twindex/cli.py +127 -0
  70. twindex-0.2.0/twindex/data/box_inside.json +63 -0
  71. twindex-0.2.0/twindex/data/box_outside.json +63 -0
  72. twindex-0.2.0/twindex/data/inconclusive.json +56 -0
  73. twindex-0.2.0/twindex/data/invalid_bc.json +72 -0
  74. twindex-0.2.0/twindex/data/invalid_gap.json +72 -0
  75. twindex-0.2.0/twindex/data/paving_regression.json +74 -0
  76. twindex-0.2.0/twindex/data/paving_theta500.json +82 -0
  77. twindex-0.2.0/twindex/data/regression_pair.json +72 -0
  78. twindex-0.2.0/twindex/data/theta500_pair.json +146 -0
  79. twindex-0.2.0/twindex/data/witness_theta500.json +86 -0
  80. twindex-0.2.0/twindex/fit.py +73 -0
  81. twindex-0.2.0/twindex/model.py +269 -0
  82. twindex-0.2.0/twindex/poly.py +255 -0
  83. twindex-0.2.0/twindex/rational.py +98 -0
  84. twindex-0.2.0/twindex/search.py +264 -0
  85. twindex-0.2.0/twindex/solver.py +101 -0
  86. twindex-0.2.0/twindex.egg-info/PKG-INFO +84 -0
  87. twindex-0.2.0/twindex.egg-info/SOURCES.txt +89 -0
  88. twindex-0.2.0/twindex.egg-info/dependency_links.txt +1 -0
  89. twindex-0.2.0/twindex.egg-info/entry_points.txt +2 -0
  90. twindex-0.2.0/twindex.egg-info/requires.txt +23 -0
  91. twindex-0.2.0/twindex.egg-info/top_level.txt +1 -0
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0] - 2026-11-07
8
+
9
+ ### Added
10
+ - **General models.** `Model(degree, source, nonlinearity)`: Bernstein
11
+ coefficients of any degree up to 20, a polynomial source term, and
12
+ nonlinearities `Σ a_m u^m` with odd `m` and `a_m ≥ 0`. The original problem is
13
+ `V1_MODEL`. The theory is generalised accordingly (THEORY §1, §3–§5).
14
+ - **Certificate format v2** with an explicit `problem.model` block. Version 1
15
+ certificates are still read and checked unchanged.
16
+ - **Affine candidate families** (THEORY Theorem 3′): a box certificate can carry
17
+ sensitivity polynomials, so that one certificate covers a whole parameter box
18
+ with residual bound `Σ_α h^α ‖R_α‖`. Around `θ̄ = 500` this certifies boxes with
19
+ 5× the half-width (625× the volume) of the plain bound.
20
+ - **`twindex audit`**: certify how well a parameter can be recovered from your
21
+ own measurements (CSV), noise level, prior and model.
22
+ - Model-aware solver, twin search and SIVIA.
23
+ - Affine SIVIA: pavings use sensitivity families by default. On the full
24
+ `[0,1000]⁴` prior they now prove most leaves incompatible, but the compatible
25
+ set is too thin for axis-aligned boxes, so certified upper bounds remain the
26
+ prior width (see the study). A local example proves `L = U = 50` with one affine leaf.
27
+ - `twindex.fit`: certify any callable (a PINN, a solver) as an exact candidate.
28
+ - Inhomogeneous boundary values `u(0) = α`, `u(1) = β`.
29
+ - A redesigned landing page with a hero figure, typeset equations and a results gallery.
30
+ - Sensor/noise studies for targets θ₀, θ₁ and θ₂ with certified results, and an
31
+ executable tutorial.
32
+ - `mypy --strict` on the trusted core, a 90% coverage gate, and CI on Linux
33
+ (Python 3.10–3.13), macOS and Windows.
34
+
35
+ ## [0.1.0] - 2026-11-07
36
+
37
+ First public release.
38
+
39
+ ### Added
40
+ - Exact trusted core (standard library only, no floating point): rational
41
+ parsing, exact polynomials, certified Bernstein sup-norm bounds, the model
42
+ problem, a strict certificate format (v1), and the checker with VALID /
43
+ INVALID / INCONCLUSIVE verdicts.
44
+ - Certificate kinds: `witness`, `pair`, `box` and `paving` (SIVIA-style
45
+ two-sided bounds on the target diameter).
46
+ - Untrusted search side: a Chebyshev–Newton solver, exact candidate conversion,
47
+ a local twin search and an adaptive SIVIA driver.
48
+ - `twindex` command line: `check`, `example`, `search`, `sivia`.
49
+ - Ten example certificates covering every kind and every verdict, replayed in
50
+ CI on Python 3.10–3.13.
51
+ - Full proofs in `docs/THEORY.md`; interface contract in `docs/DESIGN.md`.
52
+
53
+ [0.2.0]: https://github.com/Arnav-M10/Twindex/releases/tag/v0.2.0
54
+ [0.1.0]: https://github.com/Arnav-M10/Twindex/releases/tag/v0.1.0
@@ -0,0 +1,19 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use Twindex in your research, please cite it as below."
3
+ title: "Twindex: exact, replayable certificates of data-compatible ambiguity for a PDE inverse problem"
4
+ type: software
5
+ authors:
6
+ - family-names: Mittal
7
+ given-names: Arnav
8
+ version: 0.2.0
9
+ date-released: 2026-11-07
10
+ license: MIT
11
+ repository-code: "https://github.com/Arnav-M10/Twindex"
12
+ url: "https://arnav-m10.github.io/Twindex/"
13
+ keywords:
14
+ - inverse problems
15
+ - identifiability
16
+ - certified computation
17
+ - exact arithmetic
18
+ - partial differential equations
19
+ - set inversion
@@ -0,0 +1,12 @@
1
+ # Code of conduct
2
+
3
+ This project follows the
4
+ [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
5
+
6
+ In short: be respectful, assume good faith, and keep discussion about the work.
7
+ Harassment of any kind is not tolerated.
8
+
9
+ Report unacceptable behaviour privately to the maintainer through
10
+ [GitHub private reporting](https://github.com/Arnav-M10/Twindex/security/advisories/new)
11
+ or by contacting the repository owner on GitHub. Reports are handled
12
+ confidentially.
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ * Read [docs/DESIGN.md](docs/DESIGN.md) first; it is the interface contract and says who owns which module.
4
+ * The trusted core (`rational`, `poly`, `model`, `certificate`, `checker`) uses the standard library only and never touches floats.
5
+ * Work on a branch, then open a pull request. Every pull request is reviewed by the owner of each module it touches.
6
+ * Before merging: `ruff check .` and `pytest` must pass.
7
+
8
+ Setup:
9
+
10
+ ```bash
11
+ python3 -m venv .venv
12
+ .venv/bin/pip install -e ".[dev]"
13
+ .venv/bin/pytest
14
+ ```
15
+
16
+ Docs (MkDocs Material, deployed to GitHub Pages from `main`):
17
+
18
+ ```bash
19
+ .venv/bin/pip install -e ".[docs]"
20
+ .venv/bin/mkdocs serve
21
+ ```
22
+
23
+ Releasing: bump `version` in `pyproject.toml` and `twindex/__init__.py`, update
24
+ `CHANGELOG.md` and `CITATION.cff`, then publish a GitHub release tagged `vX.Y.Z`.
25
+ The `release` workflow builds, verifies and uploads to PyPI through trusted publishing.
twindex-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arnav Mittal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ include LICENSE README.md CONTRIBUTING.md
2
+ include mkdocs.yml CHANGELOG.md CITATION.cff SECURITY.md CODE_OF_CONDUCT.md
3
+ recursive-include docs *.md
4
+ recursive-include docs/assets *.svg
5
+ recursive-include scripts *.py
6
+ recursive-include examples *.json *.md *.py
7
+ recursive-include tests *.py
8
+ recursive-include twindex/data *.json
twindex-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: twindex
3
+ Version: 0.2.0
4
+ Summary: Exact, replayable certificates that noisy PDE data cannot tell two parameter sets apart.
5
+ Author: Arnav Mittal
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Arnav-M10/Twindex
8
+ Project-URL: Documentation, https://arnav-m10.github.io/Twindex/
9
+ Project-URL: Source, https://github.com/Arnav-M10/Twindex
10
+ Project-URL: Issues, https://github.com/Arnav-M10/Twindex/issues
11
+ Keywords: inverse problems,identifiability,certified computation,computer-assisted proof,exact arithmetic,PDE,PINN,set inversion,optimal recovery
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Topic :: Scientific/Engineering :: Physics
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Provides-Extra: search
27
+ Requires-Dist: numpy>=1.24; extra == "search"
28
+ Provides-Extra: studies
29
+ Requires-Dist: numpy>=1.24; extra == "studies"
30
+ Requires-Dist: matplotlib>=3.7; extra == "studies"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
33
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
34
+ Requires-Dist: numpy>=1.24; extra == "docs"
35
+ Provides-Extra: dev
36
+ Requires-Dist: numpy>=1.24; extra == "dev"
37
+ Requires-Dist: pytest>=7; extra == "dev"
38
+ Requires-Dist: hypothesis>=6; extra == "dev"
39
+ Requires-Dist: ruff>=0.4; extra == "dev"
40
+ Requires-Dist: build>=1; extra == "dev"
41
+ Requires-Dist: twine>=5; extra == "dev"
42
+ Requires-Dist: setuptools>=77; extra == "dev"
43
+ Requires-Dist: mypy>=1.10; extra == "dev"
44
+ Requires-Dist: pytest-cov>=5; extra == "dev"
45
+ Dynamic: license-file
46
+
47
+ # Twindex
48
+
49
+ **Your inverse solver returned a parameter. Twindex proves whether the data could have determined it.**
50
+
51
+ Least squares, Bayesian inference and physics-informed neural networks always
52
+ *return* a parameter. None of them can tell you whether a completely different
53
+ parameter fits your measurements just as well. Twindex finds such *twins* for
54
+ the boundary value problem
55
+
56
+ -u''(x) + q(x; theta) f(u(x)) = g(x) on (0, 1), u(0) = alpha, u(1) = beta,
57
+ q(x; theta) = sum_j theta_j B_j(x) (Bernstein basis, theta >= 0),
58
+ f(u) = sum of a_m u^m over odd m, a_m >= 0,
59
+
60
+ and proves it: it writes a JSON certificate that a small exact checker
61
+ (standard-library Python, no floating point, type-checked with mypy --strict)
62
+ replays and verifies. A VALID pair certificate proves that **no method** can
63
+ recover the parameter from those data with worst-case error below half the
64
+ certified gap.
65
+
66
+ ```console
67
+ $ pip install twindex # exact checker + CLI, zero dependencies
68
+ $ pip install "twindex[search]" # + numerical search, audits and fitting (numpy)
69
+
70
+ $ twindex example theta500_pair > pair.json
71
+ $ twindex check pair.json
72
+ pair.json: VALID: ... every estimator of theta_0 has worst-case error >= 500 on these data
73
+
74
+ $ twindex audit --data measurements.csv --epsilon 1/1000 --prior 0,1000 --target 1 --output audit/
75
+ ```
76
+
77
+ * Certificates: witness, pair (twins), box, and paving (two-sided bounds).
78
+ * Your model: any coefficient degree, polynomial sources, odd-power
79
+ nonlinearities, inhomogeneous boundary values.
80
+ * `twindex.fit`: check any callable (a PINN, scipy, FEM) as an exact candidate.
81
+ * Every step is proved: https://arnav-m10.github.io/Twindex/THEORY/
82
+
83
+ Documentation: https://arnav-m10.github.io/Twindex/ ·
84
+ Source: https://github.com/Arnav-M10/Twindex
@@ -0,0 +1,277 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Arnav-M10/Twindex/main/docs/assets/logo-dark.svg">
4
+ <img alt="Twindex" src="https://raw.githubusercontent.com/Arnav-M10/Twindex/main/docs/assets/logo-light.svg" width="560">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://github.com/Arnav-M10/Twindex/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Arnav-M10/Twindex/actions/workflows/ci.yml/badge.svg"></a>
10
+ <a href="https://pypi.org/project/twindex/"><img alt="PyPI" src="https://img.shields.io/pypi/v/twindex.svg"></a>
11
+ <a href="https://pypi.org/project/twindex/"><img alt="Python" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue.svg"></a>
12
+ <a href="https://arnav-m10.github.io/Twindex/"><img alt="Docs" src="https://img.shields.io/badge/docs-online-informational.svg"></a>
13
+ <img alt="Typed core" src="https://img.shields.io/badge/core-mypy%20--strict-2f81f7.svg">
14
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green.svg"></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <b>Your inverse solver returned a parameter. Twindex proves whether the data could have determined it.</b><br>
19
+ <a href="https://arnav-m10.github.io/Twindex/">Documentation</a> ·
20
+ <a href="https://arnav-m10.github.io/Twindex/tutorial/">Tutorial</a> ·
21
+ <a href="https://arnav-m10.github.io/Twindex/audit/">Audit your data</a> ·
22
+ <a href="docs/THEORY.md">Theory</a> ·
23
+ <a href="examples/">Examples</a>
24
+ </p>
25
+
26
+ ---
27
+
28
+ <p align="center">
29
+ <img src="docs/assets/hero.png" alt="Two very different coefficients q(x) give solutions that agree at every sensor to within the noise" width="100%">
30
+ </p>
31
+
32
+ Least squares, Bayesian inference and physics-informed neural networks will
33
+ always *return* a parameter. None of them can tell you whether a **completely
34
+ different** parameter fits your measurements just as well. Twindex finds such
35
+ *twins* and proves it: it writes an exact certificate that a small checker,
36
+ standard-library Python with no floating point, replays and verifies.
37
+
38
+ > [!IMPORTANT]
39
+ > The figure above is a real certificate (`examples/theta500_pair.json`). Two
40
+ > coefficient fields that differ by up to 1000 produce solutions that agree at all
41
+ > five sensors to within the noise level $`\varepsilon = 10^{-3}`$. Therefore **no
42
+ > method** can recover $`\theta_0`$ from those data with worst-case error below 500.
43
+ > Anyone can re-check the claim with `twindex check`.
44
+
45
+ **Contents:**
46
+ [Why Twindex](#why-twindex) ·
47
+ [What it certifies](#what-twindex-certifies) ·
48
+ [Why you can trust a VALID](#why-you-can-trust-a-valid) ·
49
+ [Installation](#installation) ·
50
+ [Quick start](#quick-start) ·
51
+ [Features](#features) ·
52
+ [Results](#results) ·
53
+ [How it works](#how-it-works) ·
54
+ [Documentation](#documentation) ·
55
+ [Citing](#citing-twindex)
56
+
57
+ ## Why Twindex
58
+
59
+ PINN libraries such as DeepXDE and PhysicsNeMo, Bayesian samplers and classical
60
+ least squares are built to **estimate** parameters. Twindex answers a different
61
+ question, *could any estimate be trusted?*, and it answers with a proof.
62
+
63
+ | | returns | uncertainty | proves that the data cannot pin the parameter down |
64
+ |---|---|---|---|
65
+ | least squares, PINNs | one estimate $`\hat\theta`$ | none | no |
66
+ | Bayesian inference | a posterior | probabilistic, depends on the prior and likelihood | no |
67
+ | **Twindex** | **certified twins** $`\theta, \theta'`$ | **worst case, under bounded noise** | **yes, by an exact certificate** |
68
+
69
+ Twindex complements estimators rather than replacing them: run your solver, then
70
+ ask Twindex whether a very different answer fits the data just as well.
71
+
72
+ ## What Twindex certifies
73
+
74
+ Twindex works with the boundary value problem
75
+
76
+ $$
77
+ -u''(x) + q(x;\theta)\, f\big(u(x)\big) = g(x), \qquad x \in (0,1), \qquad u(0)=\alpha,\quad u(1)=\beta,
78
+ $$
79
+
80
+ $$
81
+ q(x;\theta) = \sum_{j=0}^{d} \theta_j \binom{d}{j} x^j (1-x)^{d-j}, \qquad
82
+ f(u) = \sum_{m \text{ odd}} a_m u^m \quad (a_m \ge 0), \qquad \theta \in \Theta \subseteq [0,\infty)^{d+1}.
83
+ $$
84
+
85
+ Here $`g`$ is any polynomial, and the coefficient degree $`d`$, the nonlinearity
86
+ and the boundary values are yours to choose. Given sensors $`x_i`$, measurements
87
+ $`y_i`$ and a noise bound $`\varepsilon`$, the **data-compatible set** is
88
+
89
+ $$
90
+ \mathcal{C}_y = \Big\lbrace\, \theta \in \Theta \;:\; \max_i \big\vert u_\theta(x_i) - y_i \big\vert \le \varepsilon \,\Big\rbrace.
91
+ $$
92
+
93
+ | certificate | proves |
94
+ |---|---|
95
+ | `witness` | one parameter $`\theta \in \mathcal{C}_y`$ |
96
+ | `pair` | twins $`\theta, \theta' \in \mathcal{C}_y`$, so every estimator $`T`$ satisfies $`\sup_{\vartheta \in \mathcal{C}_y} \lvert T(y) - \vartheta_k \rvert \ge \tfrac12 \lvert \theta_k - \theta'_k \rvert`$ |
97
+ | `box` | a whole box of parameters lies inside $`\mathcal{C}_y`$, or misses it entirely |
98
+ | `paving` | a covering of $`\Theta`$ with certified two-sided bounds $`L \le \mathrm{diam}_k \mathcal{C}_y \le U`$ |
99
+
100
+ ## Why you can trust a VALID
101
+
102
+ Every verdict follows from one theorem, proved in [docs/THEORY.md](docs/THEORY.md).
103
+ If a polynomial $`p`$ matches the boundary values exactly and its residual is
104
+ bounded,
105
+
106
+ $$
107
+ \rho \;\ge\; \max_{x \in [0,1]} \Big\vert -p''(x) + q(x;\theta)\, f\big(p(x)\big) - g(x) \Big\vert,
108
+ $$
109
+
110
+ then the **true** solution satisfies, for every $`x \in [0,1]`$,
111
+
112
+ $$
113
+ \big\vert u_\theta(x) - p(x) \big\vert \;\le\; \rho \, \frac{x(1-x)}{2}.
114
+ $$
115
+
116
+ <details>
117
+ <summary><b>Proof sketch</b> (complete proofs in <a href="docs/THEORY.md">docs/THEORY.md</a>)</summary>
118
+
119
+ <br>
120
+
121
+ Let $`e = u_\theta - p`$ and $`r = -p'' + q f(p) - g`$. Because $`f`$ is a sum of odd
122
+ powers with nonnegative coefficients, $`f(u) - f(p) = (u - p)\,H(u, p)`$ with
123
+ $`H \ge 0`$. Hence
124
+
125
+ $$
126
+ -e'' + c\,e = -r, \qquad c = q\,H(u_\theta, p) \ge 0, \qquad e(0) = e(1) = 0.
127
+ $$
128
+
129
+ The barrier $`b(x) = \rho\,x(1-x)/2`$ satisfies $`-b'' + c\,b \ge \rho \ge \lvert r \rvert`$, so
130
+ $`w_\pm = b \mp e`$ satisfy $`-w_\pm'' + c\,w_\pm \ge 0`$ with zero boundary values.
131
+ A one-dimensional maximum principle gives $`w_\pm \ge 0`$, that is, $`\lvert e \rvert \le b`$.
132
+
133
+ </details>
134
+
135
+ There is no convergence assumption and no floating point. $`\rho`$ is computed
136
+ from Bernstein coefficients with exact rational subdivision, and every sensor
137
+ inequality is decided in exact arithmetic. The checker, about 2,000 lines of
138
+ standard-library Python and type-checked with `mypy --strict`, gives one of
139
+ three verdicts:
140
+
141
+ * **VALID**: the claim is proved;
142
+ * **INVALID**: the certificate is malformed or its claim is false;
143
+ * **INCONCLUSIVE**: nothing was proved either way.
144
+
145
+ ## Installation
146
+
147
+ ```console
148
+ $ pip install twindex # exact checker + CLI, zero dependencies
149
+ $ pip install "twindex[search]" # + numerical search, audits and fitting (numpy)
150
+ ```
151
+
152
+ ## Quick start
153
+
154
+ **Replay a certificate.**
155
+
156
+ ```console
157
+ $ twindex example theta500_pair > pair.json
158
+ $ twindex check pair.json
159
+ pair.json: VALID: both witnesses are data-compatible; D_Q(y) >= 1000, so every estimator of theta_0 has worst-case error >= 500 on these data
160
+ ```
161
+
162
+ **Audit your own measurements** (a CSV file with columns `x,y`):
163
+
164
+ ```console
165
+ $ twindex audit --data measurements.csv --epsilon 1/1000 --prior 0,1000 --target 1 --output audit/
166
+ theta_1 cannot have a uniform absolute-error guarantee better than +/-500 from these data (certified).
167
+ ```
168
+
169
+ **Check a PINN** (or any solver) as a black box:
170
+
171
+ ```python
172
+ from twindex.fit import certify_callable
173
+
174
+ report = certify_callable(lambda x: my_pinn(x), problem, theta_hat)
175
+ print(report.verdict) # VALID: theta_hat really is consistent with the data
176
+ ```
177
+
178
+ ## Features
179
+
180
+ * **Exact, replayable certificates.** Plain JSON that anyone can re-check with
181
+ `twindex check`. Floats are rejected everywhere, even inside JSON.
182
+ * **Your model.** Bernstein coefficients of any degree, polynomial sources,
183
+ odd-power nonlinearities $`f(u) = \sum_m a_m u^m`$, and inhomogeneous boundary
184
+ values.
185
+ * **Audits.** `twindex audit` turns measurements, a noise level and a prior into
186
+ a certified statement about how well a parameter can be recovered.
187
+ * **Bring your own solver.** `twindex.fit` turns any callable (a PINN, scipy,
188
+ FEM) into an exact candidate.
189
+ * **Two-sided bounds.** SIVIA-style pavings, with affine candidate families that
190
+ certify whole parameter boxes at once.
191
+ * **Honest by construction.** The search is a heuristic; the checker is the
192
+ judge. INCONCLUSIVE is never reported as a disproof.
193
+
194
+ ## Results
195
+
196
+ <p align="center">
197
+ <img src="docs/assets/results.png" alt="Certified ambiguity of theta_0 against sensor count and noise level" width="80%">
198
+ </p>
199
+
200
+ How much do more sensors and less noise buy? Certified lower bounds on the
201
+ ambiguity of $`\theta_0`$, for data generated at $`\bar\theta = (500, 500, 500, 500)`$
202
+ with prior $`[0, 1000]^4`$, rounded down ([full study](https://arnav-m10.github.io/Twindex/study/)):
203
+
204
+ | sensors | $`\varepsilon = 10^{-2}`$ | $`\varepsilon = 10^{-3}`$ | $`\varepsilon = 10^{-4}`$ | $`\varepsilon = 10^{-5}`$ |
205
+ |---:|---:|---:|---:|---:|
206
+ | 3 | 1000 | 1000 | 749 | 718 |
207
+ | 5 | 1000 | 1000 | 763 | 85 |
208
+ | 9 | 1000 | 1000 | 766 | 97 |
209
+ | 17 | 1000 | 1000 | 761 | 85 |
210
+
211
+ Even 17 sensors at noise $`10^{-3}`$ cannot narrow $`\theta_0`$ below its entire
212
+ prior range. These are certified lower bounds from a local search; they are
213
+ not optimal.
214
+
215
+ ## How it works
216
+
217
+ 1. **Search** (untrusted, floating point). A Chebyshev–Newton solver and a
218
+ local search propose twins. Each solution is rounded to an exact rational
219
+ polynomial that matches the boundary values exactly.
220
+ 2. **Bound** (trusted, exact). The residual $`\rho`$ is bounded from Bernstein
221
+ coefficients with exact de Casteljau subdivision.
222
+ 3. **Transfer.** The barrier theorem moves the bound from the candidate to the
223
+ true PDE solution.
224
+ 4. **Decide.** At each sensor,
225
+ $`\lvert p(x_i) - y_i \rvert + \rho\, x_i(1-x_i)/2 \le \varepsilon`$ proves
226
+ compatibility, and $`\lvert p(x_i) - y_i \rvert - \rho\, x_i(1-x_i)/2 > \varepsilon`$
227
+ proves incompatibility.
228
+
229
+ ## Documentation
230
+
231
+ | | |
232
+ |---|---|
233
+ | [Getting started](https://arnav-m10.github.io/Twindex/getting-started/) | install, check, search |
234
+ | [Tutorial](https://arnav-m10.github.io/Twindex/tutorial/) | certify your own twins in five minutes |
235
+ | [Audit](https://arnav-m10.github.io/Twindex/audit/) | your data, your model, a certified answer |
236
+ | [Theory](docs/THEORY.md) | complete proofs of every step |
237
+ | [Certificate format](docs/DESIGN.md) | JSON schema and verdict rules |
238
+ | [API reference](https://arnav-m10.github.io/Twindex/api/) | every public function |
239
+
240
+ ## Scope
241
+
242
+ * One space dimension and steady problems with a monotone nonlinearity. The
243
+ barrier argument needs $`q \ge 0`$ and a nondecreasing $`f`$.
244
+ * Bounded (worst-case) noise, not a statistical model. A certificate speaks
245
+ about the data $`y`$ it declares.
246
+ * The search is a heuristic. A VALID result is a proof; failing to find twins
247
+ proves nothing.
248
+
249
+ ## Related work
250
+
251
+ Twindex combines known ideas: optimal recovery (Micchelli–Rivlin; Foucart–Liao),
252
+ state and parameter estimation for parametric PDEs (Cohen, Dahmen, Mula,
253
+ Nichols), set inversion by interval analysis (Jaulin–Walter), certified
254
+ reduced-basis possibility regions (Patera and coauthors), and maximum-principle
255
+ a posteriori bounds (Nochetto et al.). What it adds is an exact, independently
256
+ replayable checker. See [THEORY §9](docs/THEORY.md#9-related-work).
257
+
258
+ ## Contributing
259
+
260
+ Contributions are welcome; start with [CONTRIBUTING.md](CONTRIBUTING.md) and the
261
+ [design contract](docs/DESIGN.md). A soundness bug (VALID for a false claim) is
262
+ treated as a security issue; please report it privately ([SECURITY.md](SECURITY.md)).
263
+
264
+ ## Citing Twindex
265
+
266
+ ```bibtex
267
+ @software{twindex,
268
+ author = {Mittal, Arnav},
269
+ title = {Twindex: exact, replayable certificates of data-compatible ambiguity for a PDE inverse problem},
270
+ year = {2026},
271
+ url = {https://github.com/Arnav-M10/Twindex}
272
+ }
273
+ ```
274
+
275
+ ## License
276
+
277
+ [MIT](LICENSE). Built by Arnav Mittal with Claude Code and Codex.
@@ -0,0 +1,20 @@
1
+ # Security policy
2
+
3
+ Twindex is a verifier, so a **soundness bug** (any input that makes the checker
4
+ report VALID for a claim that is false) is treated as a security issue.
5
+
6
+ ## Reporting
7
+
8
+ Please report soundness bugs and other vulnerabilities privately through
9
+ [GitHub private vulnerability reporting](https://github.com/Arnav-M10/Twindex/security/advisories/new).
10
+ Include a certificate or a Python snippet that reproduces the problem. Do not
11
+ open a public issue for a soundness bug before a fix is released.
12
+
13
+ Expect an acknowledgement within a week. Fixes ship with a regression test and
14
+ a changelog entry.
15
+
16
+ ## Supported versions
17
+
18
+ | version | supported |
19
+ |---------|-----------|
20
+ | 0.1.x | yes |