# Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning where practical.

## [Unreleased]
- Add ACCESSIBILITY.md and PHILOSOPHY.md with clear, plain-language design principles
- Add CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, SUPPORT, CITATION
- Add CLI `--trace` to emit process signals in the routing certificate
- Add Sphinx docs (MyST + Furo), sitemap/robots, OpenGraph metadata
- Add docs pages: Getting-Started, CLI, LLM-Usage, Invariants, API-Reference
- Add GitHub templates (issues/PR), CODEOWNERS, GOVERNANCE, RELEASE guide, FUNDING
- Add AGENTS.md for LLM/dev agents; add LLM context pack helper
- Improve README with badges and links; interlink docs throughout

## [0.1.0] - 2025-10-26
- Initial public release
- Docs: cleaned mojibake/encoding issues; Sphinx build clean
- Certificate schema: align `constraints.shadow_prices` to object map; `boundary`/`drift` field names
- Math-Brief: added summary box; clarified formulas (ASCII-safe)
- CLI/Control-Perspective/Results-Summary: rewrites for clarity and correctness
- Added FUTURE.md outlining dual shadow pricing (optional, post-launch)


# compitum

\n\nA production-ready, geometrically-aware AI router with SPD metric learning, constraint-aware
selection (approximate shadow prices), metric-aware KDE coherence, and Lyapunov-inspired trust-region updates.

Note on media: `tess.mp4` at the repo root is an optional, illustrative video (generated with Sora 2) that conveys the cost–quality–constraints intuition for broader audiences. It is not required for any experiments or results and is excluded from distributions.

## Project Links

- Website/Docs: `index.html` (root) and `docs/` (in-repo)
- Quick links: `docs/PEER_REVIEW.md`, `docs/Math-Brief.md`, `docs/Control-of-Error.md`, `docs/Mathematical-Bridge.md`, `docs/Media.md`
 - For stat.ML reviewers: `docs/Statistical-Notes.md`
 - For cs.SY reviewers: `docs/Control-Perspective.md`
 - For cs.LG reviewers: `docs/Learning-Perspective.md`
 - Trust framing: `docs/Trust-From-Regret.md`
- Community: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `ACCESSIBILITY.md`
- Philosophy: `PHILOSOPHY.md`
- Security: `SECURITY.md`
- Changes: `CHANGELOG.md`
- Citation: `CITATION.cff`

## Install
```bash\npython -m venv .venv && source .venv/bin/activate\npip install -e ".[dev]"\n```\n\n## Quick demo\n\n```bash\ncompitum route --prompt "Prove the binomial identity using generating functions."\n```\n\n## Run tests\n\n```bash\npytest\n```\n\nSee `configs/` and `examples/` for constraints and a synthetic benchmark.\n\n## Reviewer Artifacts\n\n- Consolidated HTML report: `reports/report_release.html`\n- RouterBench markdown summary: `reports/routerbench_report.md`\n- Mutation testing dump + summary: `reports/cr_report.json`, `reports/mutation_summary.json`\n\n## Peer Review Package\n\nFor top‑tier peer review, see `docs/PEER_REVIEW.md`.\n\nOne‑shot pipeline to reproduce tests, bounded baselines, Compitum evaluation, fixed‑WTP bootstrap CIs, consolidated HTML, and an artifact manifest:\n\n```bat\nscripts\run_peer_review.bat\n```\n\nThis writes:\n\n- `reports\report_release.html` (consolidated report)\n- `reports\fixed_wtp_summary.{json,md}` (WTP=0.1 and 1.0 with 95% CIs)\n- `reports\routerbench_report.md` (baseline summary)\n- `reports\artifact_manifest.json` (artifact list with SHA256)\n\n## Testing Strategy\n\nThe project maintains a rigorous, deterministic testing program.\n\n*   **CI Profile (default):** `pytest` runs with `HYPOTHESIS_PROFILE=ci`. This uses a fixed random seed and a moderate number of examples (`max_examples=100`) for fast, repeatable builds.\n*   **Mutation Profile:** For mutation testing with `cosmic-ray`, a dedicated `HYPOTHESIS_PROFILE=mutation` is used via a wrapper script. This allows for a different number of examples to balance thoroughness and speed.\n*   **Invariants Suite:** A dedicated property-based test suite in `tests/invariants/` validates the core mathematical and operational invariants of the system. These tests are marked with `@pytest.mark.invariants`.\n\nTo run the full verification suite, including mutation testing:\n```bat\nset HYPOTHESIS_PROFILE=ci && ruff check . && mypy src\\compitum && pytest --cov=compitum --cov-branch && CALL .\\.venv-routerbench\\Scripts\\activate.bat && python -m pytest --cov=compitum --cov-branch --cov-append src/routerbench && coverage report -m && del /q session.sqlite 2>nul && cosmic-ray init --force cosmic-ray.toml session.sqlite && cosmic-ray exec cosmic-ray.toml session.sqlite && cr-report session.sqlite\n```\n**Note:** The `mypy` check has been temporarily removed from this command due to a path resolution issue. See `plan3.txt` for details.\n\n## Export Control\n\nThis project is open-source research code (MIT). Use is subject to U.S. export laws and sanctions compliance. Do not use if you are a sanctioned person/region.\n\n## Benchmark Profiles\n\nThree run profiles are provided for benchmarking. They are configured via environment variables.\n\n*   **SMOKE:** A very fast run to ensure the benchmarks execute without error.\n    ```bat\n    set COMPITUM_SKIP_PERF_ASSERTIONS=1 && set COMPITUM_STEPS=400 && set COMPITUM_REFIT_POLICY=never && set COMPITUM_UPDATE_BATCH_SIZE=100000 && pytest -q -k "test_energy_drift or test_constraint_violation_rate or test_spd_det_and_trust_radius_bounds" --benchmark-min-time=0.01\n    ```\n\n*   **DEV:** A standard development run for quick performance checks.\n    ```bat\n    set COMPITUM_STEPS=800 && set COMPITUM_REFIT_POLICY=adaptive && set COMPITUM_UPDATE_BATCH_SIZE=4000 && pytest -q -k "test_energy_drift or test_constraint_violation_rate or test_spd_det_and_trust_radius_bounds or test_iso_utility_savings_vs_fixed_best or test_router_throughput_and_latency" --benchmark-autosave --benchmark-save=DEV\n    ```\n\n*   **FULL:** The complete diagnostic run, including the heavy benchmarks.\n    ```bat\n    set COMPITUM_STEPS=4000 && set COMPITUM_REFIT_POLICY=periodic && set COMPITUM_UPDATE_BATCH_SIZE=2000 && pytest -q -m heavy_bench --benchmark-autosave --benchmark-save=FULL\n    ```\n\n### A Note on Threading\n\nFor performance-critical benchmark runs, it is advisable to set the following environment variables to prevent thread over-subscription from libraries like NumPy, which can interfere with measurements:\n\n```bat\nset OMP_NUM_THREADS=1\nset MKL_NUM_THREADS=1\nset OPENBLAS_NUM_THREADS=1\nset NUMEXPR_NUM_THREADS=1\n```\n\n## Running RouterBench Evaluation\n\nTo run the full `routerbench` evaluation, follow these steps:\n\n### 1. Setup RouterBench Environment\n\nCreate a new virtual environment for `routerbench` and install the dependencies from `src/routerbench/requirements.txt`.\n\n```bash\npython -m venv .venv-routerbench\n.venv-routerbench\Scripts\activate\npip install -r src/routerbench/requirements.txt\n```\n\n### 2. Pre-train Predictors\n\nFor fast evaluation, it's crucial to pre-train the `CalibratedPredictor` models to avoid lengthy fitting times during each run.\n\nRun the `pretrain_predictors.py` script using the `routerbench` virtual environment:\n\n```bash\nset PYTHONPATH=C:\Users\paulc\projects\compitum\src && .\.venv-routerbench\Scripts\python.exe -m scripts.pretrain_predictors\n```\n\nThis will save the pre-trained predictors to `data/pretrain_predictors/predictors_all-MiniLM-L12-v2_0.1.joblib`.\n\n### 3. Run the RouterBench Evaluation\n\nExecute the `evaluate_routers.py` script as a module within the `routerbench` package. This command will run the full evaluation and generate the results.\n\n```bash\nset PYTHONPATH=C:\Users\paulc\projects\compitum\src && .\.venv-routerbench\Scripts\python.exe -m routerbench.evaluate_routers --config data\routerbench\evaluate_routers.yaml --local --data-path routerbench_5shot.pkl\n```\n\nThis command will generate CSV and PKL files in the `data/eval_results` directory, containing the evaluation metrics for various router models.\n\n## Cleaning the repo
\nUse the cleaner to remove caches/artifacts and optionally reports and eval outputs:\n\n```bat\nscripts\clean_repo.bat --artifacts --reports\n```\n\nOptions:\n- `--artifacts`: __pycache__, .pytest_cache, .ruff_cache, .mypy_cache, .hypothesis, .tox, .nox, coverage, build/dist\n- `--reports`: removes generated files under `reports/` (html/json/md)\n- `--eval-outputs`: removes eval CSV/PKL under `data/*/eval_results/`\n- `--venvs`: removes local venvs like `.venv-routerbench` (dangerous)\n- `--all`: all of the above\n- `--dry-run`: show what would be removed\n\nArchiving older eval outputs (keep the N most recent files per directory) and writing a manifest:\n\n```bat\npython tools\archive_eval_results.py --keep 4\npython tools\generate_artifact_manifest.py --out reports\artifact_manifest.json
```

## Pre-commit Checks

Install hooks and run fast gates locally before committing:

```bat
pip install pre-commit
pre-commit install
pre-commit run --all-files
```

Or run the Windows batch helper (ruff + mypy + pytest):

```bat
scripts\precommit_check.bat
```

## CLI Trace

Use `--trace` to emit mechanistic process signals in the routing certificate (constraints, boundary, drift, utility components).

```bash
compitum route --prompt "..." --trace
```

## LLM Context Pack

Build a compact snapshot (includes `.paleaeignore`, core code, and key docs):

```bash
python tools/build_llm_context_pack.py --out compitum_context.jsonl --target-size 1MB
```

Tune `--target-size` for your context window (e.g., `800KB`).

## Brand

- Logo mark (SVG): `assets/compitum-mark.svg`
- Social card (SVG): `assets/compitum-social-card.svg`
- Colors: background `#0a0b0d`, accent gradient `#00D4FF → #9A00FF`, text `#f3f5f7`

See `BRAND.md` for usage and integration details.

## Community

- Please read `CODE_OF_CONDUCT.md` and `ACCESSIBILITY.md` when engaging.
- Open issues/PRs in this repo. Feature ideas welcome.
- Wiki (design notes, tutorials, roadmaps): https://github.com/PaulTiffany/compitum/wiki
```

## Control of Error (Design Overview)

Compitum adopts a Control-of-Error design: detect, correct, and certify.

- Detect
  - Boundary ambiguity via gap, entropy, and uncertainty thresholds; suggests deferral when close calls arise (src/compitum/boundary.py:19).
  - Constraint feasibility and approximate local shadow prices (finite-difference viability) reported for auditing (src/compitum/constraints.py:36).
  - Utility uncertainty from component quantiles and distance variance (src/compitum/energy.py:33).
- Correct
  - Lyapunov-inspired trust-region controller caps effective update step sizes using EMA and integral signals (src/compitum/control.py:15).
  - SPD metric updates with positive-definite guarantee via L L^T + δI and Cholesky recovery (src/compitum/metric.py:23, src/compitum/metric.py:39, src/compitum/metric.py:106).
- Certify
  - Every decision emits a routing certificate exposing utility components, feasibility, boundary diagnostics, and drift state (src/compitum/router.py:25, src/compitum/router.py:80).

See `docs/PEER_REVIEW.md` for the decision rule and guarantees/approximations, `docs/Control-of-Error.md` for the pedagogy and CEI metrics, and `docs/Mathematical-Bridge.md` for term mapping to standard ML.

Engineering note: We build with the same Control‑of‑Error ethos. Invariants + Hypothesis, mutation testing, strict docs gates (Sphinx), and audience‑specific analyses (CEI, reliability, decision/control KPIs) provide immediate, judge‑free feedback for the codebase. During release prep we briefly edited and promptly reverted one source file; all results and reports were generated from the tagged code with fixed seeds and a recorded environment.

## Mathematical Guarantees and Approximations (Brief)

- Guaranteed by construction
  - Selection among feasible models only (capabilities + AxB ≤ b); then argmax utility (src/compitum/constraints.py:36, src/compitum/router.py:80).
  - Positive definiteness of the metric (M = L L^T + δI) with defensive Cholesky (src/compitum/metric.py:23, src/compitum/metric.py:39).
  - Bounded update magnitude via trust-region controller (src/compitum/control.py:15).
- Documented heuristics
  - Shadow prices are approximate local diagnostics (finite-difference viability); report-only, not used for selection (src/compitum/constraints.py:36).
  - Coherence is a bounded KDE log-density prior in whitened space with small weight; results are robust to modest β_s sweeps (src/compitum/coherence.py:41).
  - Batch metric update may be order-dependent; acceptable throughput trade-off (src/compitum/router.py:147).

Linux/macOS: Use Makefile targets (make test, make lint, make mypy, make docs, make peer-review) and run "bash scripts/run_peer_review.sh" for the reviewer pipeline.


# Roadmap

Short-term (pre-0.1)
- Harden CLI `--trace` signals and add a scalar process reward aggregator (configurable)
- Seed a micro-league evaluator for near-boundary prompts and summarize deltas across versions
- Polish docs landing with a brief “What is Compitum?” summary and links to traces
- Seed GitHub wiki (mirror of key docs + design notes) in separate repo

Mid-term
- Exploration schedule under constraints (epsilon/Thompson) with guardrails
- Add plotting helper enforcing accessible palettes/markers for reports
- Extend batch APIs and streaming outputs (JSONL traces)

Long-term
- Multi-agent curricula for routers (self-play style task generation)
- Formal verification of key invariants with tighter bounds


---
title: Artifact README
description: Reproducibility checklist, environment, commands, and outputs for artifact evaluation.
---

# Artifact README (Reproducibility)

Purpose

- Provide a reviewer-first, offline, one-shot path to reproduce the key results and artifacts.
- Summarize environment, commands, expected outputs, and integrity checks.

Environment

- Python: 3.11+
- Platform: Windows (primary). POSIX equivalents included in {doc}`PEER_REVIEW`.
- No network access required; inputs are locally cached and licensed (RouterBench). We do not redistribute proprietary datasets.

One-Shot (Windows)

```bat
make peer-review
python tools\generate_eval_tables.py
.\.venv\Scripts\python -m sphinx -b html docs docs\_build\html
```

POSIX Equivalents

```bash
python3 -m venv .venv && . .venv/bin/activate
pip install -e .[dev]
pytest -q && ruff check . && mypy src/compitum && bandit -q -r src/compitum -x src/routerbench
python tools/generate_eval_tables.py
python -m sphinx -b html docs docs/_build/html
```

Primary Outputs

- Reports (local):
  - `reports/report_release.html` (frontier plots, tables)
  - `reports/fixed_wtp_summary.{json,md}` (WTP = 0.1, 1.0 with 95% CIs)
  - `reports/mutation_summary.json` (Cosmic Ray summary)
  - `reports/artifact_manifest.json` (paths + SHA-256)

- Docs pages (local):
  - `docs/Per-Baseline-WinRate.md`
  - `docs/Frontier-Gap.md` (with 95% bootstrap CIs)
  - `docs/Results-By-Task.md`
  - `docs/Panel-Summary.md`

Integrity & Determinism

- Seeds are fixed for synthetic demo/predictors and evaluation scripts.
- A manifest with SHA-256 checksums is generated for key artifacts.
- Sphinx builds without warnings; evidence scripts are warning-free.

Runtime Notes (bounded panel)

- Quality gates (pytest/ruff/mypy/bandit): typically minutes on a workstation.
- Evidence generation and docs build: minutes; bounded panel keeps runtime modest.
- Full RouterBench sweeps are optional and excluded from default gates.

Badge Checklist (typical AE criteria)

- [x] No network access required for reproduction path
- [x] Exact environment and versions documented
- [x] One-shot scripts and explicit commands
- [x] Checksums/manifest for generated artifacts
- [x] Deterministic seeds and offline evaluation

For any questions or additional formats (e.g., SBOM, license summary), please open an issue. We aim to help without changing the scientific pipelines.


 #   c o m p i t u m   A P I

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . r o u t e r
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . m e t r i c
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . c o n s t r a i n t s
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . c o h e r e n c e
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . b o u n d a r y
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . c o n t r o l
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . e n e r g y
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . m o d e l s
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . p r e d i c t o r s
 : m e m b e r s :
 ` ` `

 ` ` ` { a u t o m o d u l e }   c o m p i t u m . c l i
 : m e m b e r s :
 ` ` `


import os
import sys
from datetime import datetime

project = "Compitum"
author = "Compitum authors"
copyright = f"{datetime.now():%Y}, {author}"

extensions = [
    "myst_parser",
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx.ext.autosectionlabel",
    "sphinx_sitemap",
    "sphinxext.opengraph",
]

autosectionlabel_prefix_document = True
templates_path = ["_templates"]
exclude_patterns = ["_build", "legacy/**"]

html_theme = "furo"
html_static_path = ["_static"]
html_extra_path = ["_extra"]

# SEO / sitemap
html_baseurl = "https://paultiffany.github.io/compitum/"

# Open Graph
ogp_site_url = html_baseurl
ogp_site_name = project
ogp_image = html_baseurl + "assets/compitum-social-card.svg"

# Branding
html_logo = "_static/compitum-mark.svg"
html_favicon = "_static/compitum-mark.svg"

# Allow autodoc to find sources
sys.path.insert(0, os.path.abspath("../src"))

napoleon_google_docstring = True
napoleon_numpy_docstring = True

myst_enable_extensions = [
    "colon_fence",
]


 T h i s   d i r e c t o r y   i s   i n t e n d e d   f o r   l e g a c y   o r   s c a f f o l d   d o c u m e n t s   k e p t   f o r   h i s t o r i c a l   c o n t e x t .

 C u r r e n t ,   r e v i e w e r - f o c u s e d   u s a g e   a n d   i n s t r u c t i o n s :
 -   S e e   ` R E A D M E . m d `   f o r   i n s t a l l ,   q u i c k   d e m o ,   t e s t s .
 -   S e e   ` d o c s / P E E R _ R E V I E W . m d `   f o r   t h e   p e e r   r e v i e w   p a c k a g e   a n d   a r t i f a c t   g e n e r a t i o n .
 -   R e v i e w e r   a r t i f a c t s   a r e   w r i t t e n   u n d e r   ` r e p o r t s / ` .

 L e g a c y   s c a f f o l d :   t h e   t o p - l e v e l   ` c o m p i t u m . m d `   c o n t a i n s   a n   o l d e r ,   g e n e r a t o r - o r i e n t e d   s c a f f o l d .   P r e f e r   t h e   c u r r e n t   R E A D M E   a n d   s c r i p t s .


[project]
name = "compitum"
version = "0.1.0"
description = "compitum: A Production-Ready, Geometrically-Aware AI Router"
authors = [{ name="compitum authors", email="dev@compitum.org" }]
license = "MIT"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
 "numpy>=1.24",
 "scipy>=1.10",
 "scikit-learn>=1.3",
 "pydantic>=2.7",
 "pyyaml>=6.0.1"
]

[project.optional-dependencies]
dev = [
 "pytest>=8.0",
 "hypothesis>=6.98",
 "pytest-cov>=5.0",
 "ruff>=0.5.0",
 "mypy>=1.10",
 "lightgbm>=4.3 ; python_version < '3.13'",
 "types-PyYAML",
 "cosmic-ray",
 "bandit",
"pytest-benchmark"
]

[project.urls]
Homepage = "https://github.com/PaulTiffany/compitum"
Documentation = "https://paultiffany.github.io/compitum/"
Repository = "https://github.com/PaulTiffany/compitum"
Issues = "https://github.com/PaulTiffany/compitum/issues"

[project.scripts]
compitum = "compitum.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
compitum = ["py.typed"]

# ---------------------------
# Ruff
# ---------------------------
[tool.ruff]
line-length = 100
target-version = "py39"
extend-exclude = [
  "src/routerbench",
  "docs",
  "data",
  "tools",
  "benchmarks",
  "scripts",
  ".venv*",
]

[tool.ruff.lint]
select = ["E", "F", "W", "I"]

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["I001", "E501"]
"tools/**" = ["E501"]

# ---------------------------
# MyPy
# ---------------------------

[tool.mypy]
mypy_path = "src"
files = [
  "src/compitum"
]
exclude = [
  "^src/routerbench/"
]

[[tool.mypy.overrides]]
module = ["numpy.*", "scipy.*", "sklearn.*", "yaml.*", "lightgbm.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["routerbench.*"]
ignore_errors = true

# ---------------------------
# PyTest
# ---------------------------
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
addopts = [
  "-ra",
  "--cov=compitum",
  "--cov-branch",
  "--cov-report=term-missing",
  "-q",
  "-m",
  "not routerbench"
]
markers = [
  "invariants: tests for core system invariants",
  "routerbench: RouterBench integration tests (optional)"
]

# ---------------------------
# Coverage
# ---------------------------
[tool.coverage.run]
branch = true
source = ["src/compitum", "src/routerbench"]
omit = ["tests/*"]

[tool.coverage.report]
show_missing = true
skip_covered = false
fail_under = 100


Metadata-Version: 2.4
Name: compitum
Version: 0.1.0
Summary: compitum: A Production-Ready, Geometrically-Aware AI Router
Author-email: compitum authors <dev@compitum.org>
License-Expression: MIT
Project-URL: Homepage, https://github.com/PaulTiffany/compitum
Project-URL: Documentation, https://paultiffany.github.io/compitum/
Project-URL: Repository, https://github.com/PaulTiffany/compitum
Project-URL: Issues, https://github.com/PaulTiffany/compitum/issues
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: scikit-learn>=1.3
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: hypothesis>=6.98; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.5.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: lightgbm>=4.3; python_version < "3.13" and extra == "dev"
Requires-Dist: types-PyYAML; extra == "dev"
Requires-Dist: cosmic-ray; extra == "dev"
Requires-Dist: bandit; extra == "dev"
Requires-Dist: pytest-benchmark; extra == "dev"
Dynamic: license-file

# compitum

\n\nA production-ready, geometrically-aware AI router with SPD metric learning, constraint-aware
selection (approximate shadow prices), metric-aware KDE coherence, and Lyapunov-inspired trust-region updates.

Note on media: `tess.mp4` at the repo root is an optional, illustrative video (generated with Sora 2) that conveys the cost–quality–constraints intuition for broader audiences. It is not required for any experiments or results and is excluded from distributions.

## Project Links

- Website/Docs: `index.html` (root) and `docs/` (in-repo)
- Quick links: `docs/PEER_REVIEW.md`, `docs/Math-Brief.md`, `docs/Control-of-Error.md`, `docs/Mathematical-Bridge.md`, `docs/Media.md`
 - For stat.ML reviewers: `docs/Statistical-Notes.md`
 - For cs.SY reviewers: `docs/Control-Perspective.md`
 - For cs.LG reviewers: `docs/Learning-Perspective.md`
 - Trust framing: `docs/Trust-From-Regret.md`
- Community: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `ACCESSIBILITY.md`
- Philosophy: `PHILOSOPHY.md`
- Security: `SECURITY.md`
- Changes: `CHANGELOG.md`
- Citation: `CITATION.cff`

## Install
```bash\npython -m venv .venv && source .venv/bin/activate\npip install -e ".[dev]"\n```\n\n## Quick demo\n\n```bash\ncompitum route --prompt "Prove the binomial identity using generating functions."\n```\n\n## Run tests\n\n```bash\npytest\n```\n\nSee `configs/` and `examples/` for constraints and a synthetic benchmark.\n\n## Reviewer Artifacts\n\n- Consolidated HTML report: `reports/report_release.html`\n- RouterBench markdown summary: `reports/routerbench_report.md`\n- Mutation testing dump + summary: `reports/cr_report.json`, `reports/mutation_summary.json`\n\n## Peer Review Package\n\nFor top‑tier peer review, see `docs/PEER_REVIEW.md`.\n\nOne‑shot pipeline to reproduce tests, bounded baselines, Compitum evaluation, fixed‑WTP bootstrap CIs, consolidated HTML, and an artifact manifest:\n\n```bat\nscripts\run_peer_review.bat\n```\n\nThis writes:\n\n- `reports\report_release.html` (consolidated report)\n- `reports\fixed_wtp_summary.{json,md}` (WTP=0.1 and 1.0 with 95% CIs)\n- `reports\routerbench_report.md` (baseline summary)\n- `reports\artifact_manifest.json` (artifact list with SHA256)\n\n## Testing Strategy\n\nThe project maintains a rigorous, deterministic testing program.\n\n*   **CI Profile (default):** `pytest` runs with `HYPOTHESIS_PROFILE=ci`. This uses a fixed random seed and a moderate number of examples (`max_examples=100`) for fast, repeatable builds.\n*   **Mutation Profile:** For mutation testing with `cosmic-ray`, a dedicated `HYPOTHESIS_PROFILE=mutation` is used via a wrapper script. This allows for a different number of examples to balance thoroughness and speed.\n*   **Invariants Suite:** A dedicated property-based test suite in `tests/invariants/` validates the core mathematical and operational invariants of the system. These tests are marked with `@pytest.mark.invariants`.\n\nTo run the full verification suite, including mutation testing:\n```bat\nset HYPOTHESIS_PROFILE=ci && ruff check . && mypy src\\compitum && pytest --cov=compitum --cov-branch && CALL .\\.venv-routerbench\\Scripts\\activate.bat && python -m pytest --cov=compitum --cov-branch --cov-append src/routerbench && coverage report -m && del /q session.sqlite 2>nul && cosmic-ray init --force cosmic-ray.toml session.sqlite && cosmic-ray exec cosmic-ray.toml session.sqlite && cr-report session.sqlite\n```\n**Note:** The `mypy` check has been temporarily removed from this command due to a path resolution issue. See `plan3.txt` for details.\n\n## Export Control\n\nThis project is open-source research code (MIT). Use is subject to U.S. export laws and sanctions compliance. Do not use if you are a sanctioned person/region.\n\n## Benchmark Profiles\n\nThree run profiles are provided for benchmarking. They are configured via environment variables.\n\n*   **SMOKE:** A very fast run to ensure the benchmarks execute without error.\n    ```bat\n    set COMPITUM_SKIP_PERF_ASSERTIONS=1 && set COMPITUM_STEPS=400 && set COMPITUM_REFIT_POLICY=never && set COMPITUM_UPDATE_BATCH_SIZE=100000 && pytest -q -k "test_energy_drift or test_constraint_violation_rate or test_spd_det_and_trust_radius_bounds" --benchmark-min-time=0.01\n    ```\n\n*   **DEV:** A standard development run for quick performance checks.\n    ```bat\n    set COMPITUM_STEPS=800 && set COMPITUM_REFIT_POLICY=adaptive && set COMPITUM_UPDATE_BATCH_SIZE=4000 && pytest -q -k "test_energy_drift or test_constraint_violation_rate or test_spd_det_and_trust_radius_bounds or test_iso_utility_savings_vs_fixed_best or test_router_throughput_and_latency" --benchmark-autosave --benchmark-save=DEV\n    ```\n\n*   **FULL:** The complete diagnostic run, including the heavy benchmarks.\n    ```bat\n    set COMPITUM_STEPS=4000 && set COMPITUM_REFIT_POLICY=periodic && set COMPITUM_UPDATE_BATCH_SIZE=2000 && pytest -q -m heavy_bench --benchmark-autosave --benchmark-save=FULL\n    ```\n\n### A Note on Threading\n\nFor performance-critical benchmark runs, it is advisable to set the following environment variables to prevent thread over-subscription from libraries like NumPy, which can interfere with measurements:\n\n```bat\nset OMP_NUM_THREADS=1\nset MKL_NUM_THREADS=1\nset OPENBLAS_NUM_THREADS=1\nset NUMEXPR_NUM_THREADS=1\n```\n\n## Running RouterBench Evaluation\n\nTo run the full `routerbench` evaluation, follow these steps:\n\n### 1. Setup RouterBench Environment\n\nCreate a new virtual environment for `routerbench` and install the dependencies from `src/routerbench/requirements.txt`.\n\n```bash\npython -m venv .venv-routerbench\n.venv-routerbench\Scripts\activate\npip install -r src/routerbench/requirements.txt\n```\n\n### 2. Pre-train Predictors\n\nFor fast evaluation, it's crucial to pre-train the `CalibratedPredictor` models to avoid lengthy fitting times during each run.\n\nRun the `pretrain_predictors.py` script using the `routerbench` virtual environment:\n\n```bash\nset PYTHONPATH=C:\Users\paulc\projects\compitum\src && .\.venv-routerbench\Scripts\python.exe -m scripts.pretrain_predictors\n```\n\nThis will save the pre-trained predictors to `data/pretrain_predictors/predictors_all-MiniLM-L12-v2_0.1.joblib`.\n\n### 3. Run the RouterBench Evaluation\n\nExecute the `evaluate_routers.py` script as a module within the `routerbench` package. This command will run the full evaluation and generate the results.\n\n```bash\nset PYTHONPATH=C:\Users\paulc\projects\compitum\src && .\.venv-routerbench\Scripts\python.exe -m routerbench.evaluate_routers --config data\routerbench\evaluate_routers.yaml --local --data-path routerbench_5shot.pkl\n```\n\nThis command will generate CSV and PKL files in the `data/eval_results` directory, containing the evaluation metrics for various router models.\n\n## Cleaning the repo
\nUse the cleaner to remove caches/artifacts and optionally reports and eval outputs:\n\n```bat\nscripts\clean_repo.bat --artifacts --reports\n```\n\nOptions:\n- `--artifacts`: __pycache__, .pytest_cache, .ruff_cache, .mypy_cache, .hypothesis, .tox, .nox, coverage, build/dist\n- `--reports`: removes generated files under `reports/` (html/json/md)\n- `--eval-outputs`: removes eval CSV/PKL under `data/*/eval_results/`\n- `--venvs`: removes local venvs like `.venv-routerbench` (dangerous)\n- `--all`: all of the above\n- `--dry-run`: show what would be removed\n\nArchiving older eval outputs (keep the N most recent files per directory) and writing a manifest:\n\n```bat\npython tools\archive_eval_results.py --keep 4\npython tools\generate_artifact_manifest.py --out reports\artifact_manifest.json
```

## Pre-commit Checks

Install hooks and run fast gates locally before committing:

```bat
pip install pre-commit
pre-commit install
pre-commit run --all-files
```

Or run the Windows batch helper (ruff + mypy + pytest):

```bat
scripts\precommit_check.bat
```

## CLI Trace

Use `--trace` to emit mechanistic process signals in the routing certificate (constraints, boundary, drift, utility components).

```bash
compitum route --prompt "..." --trace
```

## LLM Context Pack

Build a compact snapshot (includes `.paleaeignore`, core code, and key docs):

```bash
python tools/build_llm_context_pack.py --out compitum_context.jsonl --target-size 1MB
```

Tune `--target-size` for your context window (e.g., `800KB`).

## Brand

- Logo mark (SVG): `assets/compitum-mark.svg`
- Social card (SVG): `assets/compitum-social-card.svg`
- Colors: background `#0a0b0d`, accent gradient `#00D4FF → #9A00FF`, text `#f3f5f7`

See `BRAND.md` for usage and integration details.

## Community

- Please read `CODE_OF_CONDUCT.md` and `ACCESSIBILITY.md` when engaging.
- Open issues/PRs in this repo. Feature ideas welcome.
- Wiki (design notes, tutorials, roadmaps): https://github.com/PaulTiffany/compitum/wiki
```

## Control of Error (Design Overview)

Compitum adopts a Control-of-Error design: detect, correct, and certify.

- Detect
  - Boundary ambiguity via gap, entropy, and uncertainty thresholds; suggests deferral when close calls arise (src/compitum/boundary.py:19).
  - Constraint feasibility and approximate local shadow prices (finite-difference viability) reported for auditing (src/compitum/constraints.py:36).
  - Utility uncertainty from component quantiles and distance variance (src/compitum/energy.py:33).
- Correct
  - Lyapunov-inspired trust-region controller caps effective update step sizes using EMA and integral signals (src/compitum/control.py:15).
  - SPD metric updates with positive-definite guarantee via L L^T + δI and Cholesky recovery (src/compitum/metric.py:23, src/compitum/metric.py:39, src/compitum/metric.py:106).
- Certify
  - Every decision emits a routing certificate exposing utility components, feasibility, boundary diagnostics, and drift state (src/compitum/router.py:25, src/compitum/router.py:80).

See `docs/PEER_REVIEW.md` for the decision rule and guarantees/approximations, `docs/Control-of-Error.md` for the pedagogy and CEI metrics, and `docs/Mathematical-Bridge.md` for term mapping to standard ML.

Engineering note: We build with the same Control‑of‑Error ethos. Invariants + Hypothesis, mutation testing, strict docs gates (Sphinx), and audience‑specific analyses (CEI, reliability, decision/control KPIs) provide immediate, judge‑free feedback for the codebase. During release prep we briefly edited and promptly reverted one source file; all results and reports were generated from the tagged code with fixed seeds and a recorded environment.

## Mathematical Guarantees and Approximations (Brief)

- Guaranteed by construction
  - Selection among feasible models only (capabilities + AxB ≤ b); then argmax utility (src/compitum/constraints.py:36, src/compitum/router.py:80).
  - Positive definiteness of the metric (M = L L^T + δI) with defensive Cholesky (src/compitum/metric.py:23, src/compitum/metric.py:39).
  - Bounded update magnitude via trust-region controller (src/compitum/control.py:15).
- Documented heuristics
  - Shadow prices are approximate local diagnostics (finite-difference viability); report-only, not used for selection (src/compitum/constraints.py:36).
  - Coherence is a bounded KDE log-density prior in whitened space with small weight; results are robust to modest β_s sweeps (src/compitum/coherence.py:41).
  - Batch metric update may be order-dependent; acceptable throughput trade-off (src/compitum/router.py:147).

Linux/macOS: Use Makefile targets (make test, make lint, make mypy, make docs, make peer-review) and run "bash scripts/run_peer_review.sh" for the reviewer pipeline.


__all__ = ["router", "metric", "constraints", "coherence", "boundary", "control", "energy"]

__version__ = "0.1.0"


from __future__ import annotations

from typing import Any, Dict

import numpy as np

class BoundaryAnalyzer:
    def __init__(
        self,
        gap_threshold: float = 0.05,
        entropy_threshold: float = 0.65,
        sigma_threshold: float = 0.12,
    ) -> None:
        self.gap_threshold = gap_threshold
        self.entropy_threshold = entropy_threshold
        self.sigma_threshold = sigma_threshold

    def analyze(self, utilities: Dict[str, float], u_sigma: Dict[str, float]) -> Dict[str, Any]:
        if len(utilities) < 2:
            return {"is_boundary": False, "reason": "insufficient_models"}
        items = sorted(utilities.items(), key=lambda kv: kv[1], reverse=True)
        (m1, u1), (m2, u2) = items[0], items[1]
        gap = u1 - u2
        arr = np.array([u for _, u in items])
        probs = np.exp(arr - u1)
        probs /= probs.sum()
        entropy = -float(np.sum(probs * np.log(probs + 1e-12)))
        sigma = float(u_sigma.get(m1, 0.0))
        # BRIDGEBLOCK_START def:boundary_condition
        is_boundary = (
            (gap < self.gap_threshold or entropy > self.entropy_threshold)
            and (sigma > self.sigma_threshold)
        )
        # BRIDGEBLOCK_END def:boundary_condition
        return {
            "winner": m1,
            "runner_up": m2,
            "utility_gap": float(gap),
            "entropy": float(entropy),
            "uncertainty": sigma,
            "is_boundary": bool(is_boundary),
        }


from dataclasses import dataclass
from typing import Any, Dict, Set

@dataclass
class Capabilities:
    regions: Set[str]
    tools_allowed: Set[str]
    deterministic: bool = False

    def supports(self, pgd_vector: Any, context: Dict[str, Any] | None = None) -> bool:
        if context and "region" in context:
            return context["region"] in self.regions
        return True


from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
from typing import List, Tuple

import numpy as np
import yaml

from . import __version__ as COMPITUM_VERSION
from .boundary import BoundaryAnalyzer
from .capabilities import Capabilities
from .coherence import CoherenceFunctional
from .constraints import ReflectiveConstraintSolver
from .control import LyapunovController
from .energy import SymbolicFreeEnergy
from .metric import SymbolicManifoldMetric
from .models import Model
from .pgd import RegexPromptExtractor
from .predictors import CalibratedPredictor
from .router import CompitumRouter
from .security import (
    AuditRecord,
    file_sha256,
    git_commit_short,
    is_offline,
    redact_text,
    write_audit_record,
)
from .utils import pgd_hash

def _load_constraints(path: Path) -> Tuple[np.ndarray, np.ndarray]:
    cfg = yaml.safe_load(path.read_text())
    return np.array(cfg["A"], float), np.array(cfg["b"], float)

def _toy_models(D: int) -> List[Model]:
    rng = np.random.default_rng(7)
    centers = {
        "fast": rng.normal(0.0, 0.4, size=D),
        "thinking": rng.normal(0.0, 1.0, size=D),
        "auto": rng.normal(0.1, 0.7, size=D),
    }
    costs = {"fast": 0.1, "thinking": 0.5, "auto": 0.2}
    caps = Capabilities(regions={"US", "CA", "EU"}, tools_allowed={"none"})
    return [Model(name=k, center=v, capabilities=caps, cost=costs[k]) for k, v in centers.items()]

def route_command(args: argparse.Namespace) -> None:
    # Honor offline flag early for any optional integrations that may be added later
    if getattr(args, "offline", False):
        os.environ["COMPITUM_OFFLINE"] = "1"

    dcfg = yaml.safe_load(args.defaults.read_text())
    D = int(dcfg["metric"]["D"])
    rank = int(dcfg["metric"]["rank"])
    delta = float(dcfg["metric"]["delta"])

    models = _toy_models(D)
    predictors = {
        m.name: {
            "quality": CalibratedPredictor(),
            "latency": CalibratedPredictor(),
            "cost": CalibratedPredictor(),
        }
        for m in models
    }
    # quick synthetic fit for demo (deterministic)
    seed = int(getattr(args, "seed", 12345))
    # Global seed to ensure deterministic metric initializations and any
    # other global RNG usage aligns with demo reproducibility.
    np.random.seed(seed)
    rng = np.random.default_rng(seed)
    X_demo = rng.standard_normal((512, D))
    for m in models:
        yq = 0.6 + 0.1 * np.tanh(X_demo @ (m.center / np.linalg.norm(m.center) + 1e-8))
        yt = 0.5 + 0.5 * np.abs(X_demo @ np.ones(D) / np.sqrt(D))
        yc = 0.2 + 0.4 * np.abs(X_demo @ (np.arange(D) / D))
        predictors[m.name]["quality"].fit(X_demo, yq)
        predictors[m.name]["latency"].fit(X_demo, yt)
        predictors[m.name]["cost"].fit(X_demo, yc)

    metrics = {m.name: SymbolicManifoldMetric(D, rank, delta) for m in models}
    coherence = CoherenceFunctional(k=500)
    A, b = _load_constraints(args.constraints)
    solver = ReflectiveConstraintSolver(A, b)
    # Optional boundary thresholds from config
    bcfg = dcfg.get("boundary", {}) if isinstance(dcfg, dict) else {}
    boundary = BoundaryAnalyzer(
        gap_threshold=float(bcfg.get("gap_threshold", 0.05)),
        entropy_threshold=float(bcfg.get("entropy_threshold", 0.65)),
        sigma_threshold=float(bcfg.get("sigma_threshold", 0.12)),
    )
    lyapunov_controller = LyapunovController()
    energy = SymbolicFreeEnergy(
        dcfg["alpha"], dcfg["beta_t"], dcfg["beta_c"], dcfg["beta_d"], dcfg["beta_s"]
    )
    pgd = RegexPromptExtractor()

    router = CompitumRouter(
        models,
        predictors,
        solver,
        coherence,
        boundary,
        lyapunov_controller,
        pgd,
        metrics,
        energy,
        update_stride=int(dcfg["update_stride"]),
        enable_metric_update=not bool(getattr(args, "no_metric_update", False)),
        enable_controller=not bool(getattr(args, "no_controller", False)),
    )
    cert = router.route(args.prompt)
    if getattr(args, "trace", False) or getattr(args, "verbose", False):
        print(cert.to_json())
    else:
        print(json.dumps({"model": cert.model, "U": cert.utility}, indent=2))

    # Optional redacted audit record for reproducibility without storing plaintext prompts
    if getattr(args, "audit", False):
        constraints_sha = file_sha256(args.constraints)
        defaults_sha = file_sha256(args.defaults)
        prompt_redacted = redact_text(args.prompt)
        record = AuditRecord(
            version=COMPITUM_VERSION,
            offline=is_offline(),
            seed=int(getattr(args, "seed", 12345)),
            prompt={
                "hash": pgd_hash(args.prompt),
                "redaction": prompt_redacted,
            },
            config={
                "constraints_path": str(args.constraints),
                "constraints_sha256": constraints_sha,
                "defaults_path": str(args.defaults),
                "defaults_sha256": defaults_sha,
            },
            certificate=json.loads(cert.to_json()),
            commit=git_commit_short(),
        )
        out_dir = Path(getattr(args, "audit_dir", Path("artifacts/runs")))
        write_audit_record(record, out_dir)

def main() -> None:
    parser = argparse.ArgumentParser(description="compitum CLI")
    subparsers = parser.add_subparsers(dest="command")
    subparsers.required = True

    # route command
    route_parser = subparsers.add_parser("route", help="Route a prompt to the best model.")
    route_parser.add_argument("--prompt", required=True, help="The prompt to route.")
    route_parser.add_argument(
        "--constraints",
        type=Path,
        default=Path("configs/constraints_us_default.yaml"),
        help="Path to constraints config file.",
    )
    route_parser.add_argument(
        "--defaults",
        type=Path,
        default=Path("configs/router_defaults.yaml"),
        help="Path to router defaults config file.",
    )
    route_parser.add_argument("--verbose", action="store_true", help="Enable verbose output.")
    route_parser.add_argument(
        "--trace",
        action="store_true",
        help="Emit process signals (constraints, boundary, drift, components).",
    )
    route_parser.add_argument(
        "--no-controller",
        action="store_true",
        help="Disable drift controller updates (ablations only).",
    )
    route_parser.add_argument(
        "--no-metric-update",
        action="store_true",
        help="Disable SPD metric updates (ablations only).",
    )
    route_parser.add_argument(
        "--offline",
        action="store_true",
        help="Force offline mode (COMPITUM_OFFLINE=1).",
    )
    route_parser.add_argument(
        "--audit",
        action="store_true",
        help="Write a redacted audit record to artifacts/runs (no plaintext prompts).",
    )
    route_parser.add_argument(
        "--audit-dir",
        type=Path,
        default=Path("artifacts/runs"),
        help="Directory for audit records (defaults to artifacts/runs).",
    )
    route_parser.add_argument(
        "--seed",
        type=int,
        default=12345,
        help="Deterministic seed for synthetic demo fit.",
    )
    route_parser.set_defaults(func=route_command)

    parser.add_argument("--version", action="version", version=f"compitum {COMPITUM_VERSION}")

    args = parser.parse_args()
    args.func(args)

if __name__ == "__main__":
    main()


from __future__ import annotations

from collections import defaultdict
from typing import List, Optional, Tuple

import numpy as np
from sklearn.neighbors import KernelDensity

class WeightedReservoir:
    def __init__(self, k: int = 1000, rng: Optional[np.random.Generator] = None) -> None:
        self.k = k
        self.buf: List[Tuple[np.ndarray, float]] = []
        self.tot_w = 0.0
        self.rng = rng or np.random.default_rng()

    def add(self, x: np.ndarray, w: float) -> None:
        w = max(float(w), 1e-6)
        self.tot_w += w
        if len(self.buf) < self.k:
            self.buf.append((x.copy(), w))
        else:
            j = int(self.rng.integers(0, int(self.tot_w)))
            if j < self.k:
                self.buf[j] = (x.copy(), w)

class CoherenceFunctional:
    def __init__(self, k: int = 1000) -> None:
        self.res: defaultdict[str, WeightedReservoir] = defaultdict(lambda: WeightedReservoir(k))
        self.kde_cache: dict[str, KernelDensity] = {}

    def update(self, model_name: str, xw: np.ndarray, success: float) -> None:
        self.res[model_name].add(xw, success)
        self.kde_cache.pop(model_name, None)

    def _fit(self, model_name: str) -> KernelDensity | None:
        buf = self.res[model_name].buf
        if len(buf) < 10:
            return None
        X = np.stack([x for x, _ in buf], axis=0)
        w = np.array([wt for _, wt in buf], float)
        # Scott rule on whitened coords
        n, d = X.shape
        bw = n ** (-1.0 / (d + 4))
        kde = KernelDensity(kernel="gaussian", bandwidth=bw).fit(X, sample_weight=w / w.sum())
        self.kde_cache[model_name] = kde
        return kde

    # BRIDGEBLOCK_START def:coherence_log_evidence
    def log_evidence(self, model_name: str, xw: np.ndarray) -> float:
        kde = self.kde_cache.get(model_name) or self._fit(model_name)
        if kde is None:
            return 0.0
        val = float(kde.score_samples([xw])[0])
        return float(np.clip(val, -10.0, 10.0))

    def batch_log_evidence(self, model_name: str, xw_batch: np.ndarray) -> np.ndarray:
        kde = self.kde_cache.get(model_name) or self._fit(model_name)
        if kde is None:
            return np.zeros(xw_batch.shape[0])  # Return array of zeros for batch
        val_batch = kde.score_samples(xw_batch)
        return np.clip(val_batch, -10.0, 10.0)

    # BRIDGEBLOCK_END def:coherence_log_evidence


from __future__ import annotations

from typing import Any, Dict, List, Optional, Tuple, TypedDict

import numpy as np

from .models import Model

class InfoDict(TypedDict):
    status: str
    violations: List[str]
    feasible: bool
    shadow_prices: Dict[str, float]

class ReflectiveConstraintSolver:
    def __init__(self, A: np.ndarray, b: np.ndarray) -> None:
        self.A, self.b = A, b
        self.last_viable_models: List[Any] = []

    def _is_feasible(
        self, model: "Model", xB: np.ndarray, context: Optional[Dict[str, Any]] = None
    ) -> bool:
        """Checks if a model is feasible given the PGD vector and optional context."""
        if context is None:
            ok = model.capabilities.supports(xB)
        else:
            ok = model.capabilities.supports(xB, context=context)
        if not ok:
            return False
        if not np.all(self.A @ xB <= self.b + 1e-10):
            return False
        return True

    def select(
        self,
        xB: np.ndarray,
        models: List[Model],
        utilities: Dict[str, float],
        context: Optional[Dict[str, Any]] = None,
    ) -> Tuple[Model, InfoDict]:
        sorted_models = sorted(models, key=lambda m: utilities.get(m.name, -np.inf), reverse=True)

        viable = []
        for model in sorted_models:
            if self._is_feasible(model, xB, context=context):
                viable.append(model)

        info: InfoDict
        if not viable:
            info = {
                "status": "infeasible_fallback",
                "violations": ["all_models_violate_constraints"],
                "feasible": False,
                "shadow_prices": {},
            }
            for i in range(len(self.b)):
                info["shadow_prices"][f"lambda_{i}"] = 0.0
            return sorted_models[0], info

        m_star = viable[0]
        info = {
            "status": "optimal",
            "violations": [],
            "feasible": True,
            "shadow_prices": {},
        }
        # Shadow price calculation
        for i in range(len(self.b)):
            b_relaxed = self.b.copy()
            b_relaxed[i] += 1e-5

            shadow_price = 0.0
            for competitor in sorted_models:
                if competitor == m_star:
                    continue

                # Check if competitor is viable under relaxed constraints
                is_competitor_viable_relaxed = True
                if context is None:
                    ok_cap = competitor.capabilities.supports(xB)
                else:
                    ok_cap = competitor.capabilities.supports(xB, context=context)
                if not ok_cap:
                    is_competitor_viable_relaxed = False

                # Create a temporary solver with the relaxed constraints
                temp_solver = ReflectiveConstraintSolver(self.A, b_relaxed)
                if not temp_solver._is_feasible(competitor, xB, context=context):
                    is_competitor_viable_relaxed = False

                if is_competitor_viable_relaxed:
                    utility_competitor = utilities.get(competitor.name, -np.inf)
                    utility_m_star = utilities.get(m_star.name, -np.inf)
                    if utility_competitor > utility_m_star:
                        shadow_price = (utility_competitor - utility_m_star) / 1e-5
                        break
            info["shadow_prices"][f"lambda_{i}"] = shadow_price

        return m_star, info


from __future__ import annotations

from typing import Dict, List, Tuple

import numpy as np

class LyapunovController:
    """Lyapunov-based trust-region controller.

    This class supersedes the prior "SRMFController". In our framework, the
    SRMF functional acts as a Lyapunov candidate under bounded-observer
    assumptions; this controller implements a concrete, numerically stable
    update that decreases a positive-definite Lyapunov function and clips the
    trust region to maintain boundedness.
    """

    def __init__(self, kappa: float = 0.1, r0: float = 1.0, integral_gain: float = 0.005):
        self.kappa = kappa
        self.trust_radius = r0
        self.drift_ema = 0.0
        self.drift_integral = 0.0
        self.integral_gain = integral_gain

    def lyapunov_function(self) -> float:
        """Positive-definite Lyapunov candidate (squared integral error)."""
        return self.drift_integral**2

    # BRIDGEBLOCK_START alg:srmf_update
    def update(self, d_star: float, grad_norm: float) -> Tuple[float, Dict[str, float]]:
        """
        Updates the controller state based on the observed error `d_star` and gradient norm.

        This controller is inspired by Lyapunov stability theory. The `drift_integral` term
        acts as a simple Lyapunov function candidate. By driving this term towards zero, we
        ensure that the system remains in a stable state.
        """
        self.drift_ema = 0.9 * self.drift_ema + 0.1 * float(d_star)
        # Accumulate the error (contradiction signal)
        self.drift_integral += float(d_star)

        eta_cap = self.kappa / (float(grad_norm) + 1e-6)

        # Existing EMA-based adjustment
        if self.drift_ema > 1.5 * self.trust_radius:
            self.trust_radius *= 0.8
        elif self.drift_ema < 0.7 * self.trust_radius:
            self.trust_radius *= 1.1

        # Add a direct adjustment from the integral term for persistent drift
        self.trust_radius -= self.integral_gain * self.drift_integral

        # Decay the integral term to prevent wind-up and allow for recovery
        self.drift_integral *= 0.95

        self.trust_radius = float(np.clip(self.trust_radius, 0.2, 5.0))
        # Return the integral term in the status for observability
        return float(eta_cap), {
            "trust_radius": self.trust_radius,
            "drift_ema": self.drift_ema,
            "drift_integral": self.drift_integral,
            "lyapunov_function": self.lyapunov_function(),
        }

    # BRIDGEBLOCK_END alg:srmf_update

    def batch_update(
        self, d_star_batch: np.ndarray, grad_norm_batch: np.ndarray
    ) -> Tuple[List[float], List[Dict[str, float]]]:
        eta_caps = []
        drift_statuses = []
        for d_star, grad_norm in zip(d_star_batch, grad_norm_batch):
            eta_cap, drift_status = self.update(d_star, grad_norm)
            eta_caps.append(eta_cap)
            drift_statuses.append(drift_status)
        return eta_caps, drift_statuses

class SRMFController(LyapunovController):
    """Deprecated alias for backward compatibility.

    The Self-Regulating Mapping Function (SRMF) serves as a Lyapunov
    functional in our setting; this alias preserves older imports while the
    canonical implementation is provided by ``LyapunovController``.
    """
    pass


from typing import Dict, Tuple

def solve_effort_1d(
    q0: float,
    q1: float,
    t0: float,
    t1: float,
    c0: float,
    c1: float,
    beta: Tuple[float, float, float],
) -> Tuple[float, Dict[str, float]]:
    """
    Linearized effort e∈[0,1] around e0. Returns e_star and box multipliers.
    U(e) = α(q0+q1 e) - βt(t0+t1 e) - βc(c0+c1 e) + const
    """
    alpha, bt, bc = beta
    grad = alpha * q1 - bt * t1 - bc * c1
    e_star = 1.0 if grad > 0 else 0.0
    lam_low = max(0.0, -grad) if e_star == 0.0 else 0.0
    lam_high = max(0.0, grad) if e_star == 1.0 else 0.0
    return float(e_star), {"lambda_low": lam_low, "lambda_high": lam_high}


from __future__ import annotations

from typing import Dict

import numpy as np

class EmbeddingPGDExtractor:
    """
    A PGD extractor that works with pre-computed embeddings.
    """

    def __init__(self) -> None:
        pass

    def extract_features(self, embedding: np.ndarray) -> Dict[str, np.ndarray]:
        """
        Returns the embedding as a dictionary of features.
        """
        return {"embedding": embedding}


from __future__ import annotations

import time
from typing import Dict, List, Tuple

import numpy as np

from .coherence import CoherenceFunctional
from .metric import SymbolicManifoldMetric
from .models import Model
from .predictors import CalibratedPredictor

class SymbolicFreeEnergy:
    def __init__(
        self, alpha: float, beta_t: float, beta_c: float, beta_d: float, beta_s: float
    ) -> None:
        self.alpha = alpha
        self.beta_t = beta_t
        self.beta_c = beta_c
        self._beta_d = beta_d
        self.beta_s = beta_s
        self._step = 0

    @property
    def beta_d(self) -> float:
        return self._beta_d

    @beta_d.setter
    def beta_d(self, v: float) -> None:
        self._beta_d = v

    def compute(
        self,
        xR: np.ndarray,
        model: Model,
        predictors: Dict[str, CalibratedPredictor],
        coherence: CoherenceFunctional,
        metric: SymbolicManifoldMetric,
    ) -> Tuple[float, float, Dict[str, float]]:
        start_time = time.time()
        d, d_std = metric.distance(xR, model.center)
        q, q_lo, q_hi = predictors["quality"].predict(np.array([xR]))
        t, t_lo, t_hi = predictors["latency"].predict(np.array([xR]))
        c, c_lo, c_hi = predictors["cost"].predict(np.array([xR]))

        # evidence in whitened space
        W = metric.W if metric.W is not None else metric._update_cholesky()
        xw = W @ (xR - model.center)
        log_e = coherence.log_evidence(model.name, xw)

        # BRIDGEBLOCK_START def:symbolic_free_energy_computation
        # Debugging: Print individual components of utility calculation
        import os as _os
        if (
            self._step % 100 == 0
            and _os.environ.get("COMPITUM_DEBUG_ENERGY") == "1"
        ):
            print(
                (
                    f"DEBUG: Model: {model.name}, q0: {q[0]}, t0: {t[0]}, c0: {c[0]}, "
                    f"cost: {model.cost}, d: {d}, log_e: {log_e}"
                ),
                flush=True,
            )
            print(
                (
                    f"DEBUG: alpha: {self.alpha}, beta_t: {self.beta_t}, beta_c: {self.beta_c}, "
                    f"beta_d: {self.beta_d}, beta_s: {self.beta_s}"
                ),
                flush=True,
            )

        U = (
            self.alpha * q[0]
            - self.beta_t * t[0]
            - self.beta_c * (c[0] + model.cost)
            - self.beta_d * d
            + self.beta_s * log_e
        )
        # BRIDGEBLOCK_END def:symbolic_free_energy_computation
        U_var = (
            (self.alpha * (q_hi[0] - q_lo[0]) / 3.92) ** 2
            + (self.beta_t * (t_hi[0] - t_lo[0]) / 3.92) ** 2
            + (self.beta_c * (c_hi[0] - c_lo[0]) / 3.92) ** 2
            + (self.beta_d * d_std) ** 2
        )
        comps = {
            "quality": float(q[0]),
            "latency": float(-t[0]),
            "cost": float(-(c[0] + model.cost)),
            "distance": float(-d),
            "evidence": float(log_e),
            "uncertainty": float(np.sqrt(U_var)),
        }
        self._step += 1
        if (
            self._step % 100 == 0
            and _os.environ.get("COMPITUM_DEBUG_ENERGY") == "1"
        ):
            print(
                f"SymbolicFreeEnergy.compute took {time.time() - start_time:.4f} seconds"
            )
        return float(U), float(np.sqrt(U_var)), comps

    def batch_compute(
        self,
        xR_batch: np.ndarray,
        model: Model,
        predictors: Dict[str, CalibratedPredictor],
        coherence: CoherenceFunctional,
        metric: SymbolicManifoldMetric,
    ) -> Tuple[np.ndarray, np.ndarray, List[Dict[str, float]]]:
        start_time = time.time()
        num_samples = xR_batch.shape[0]

        # Batch distance computation
        d_batch, d_std_batch = metric.batch_distance(xR_batch, model.center)

        # Batch predictor calls
        q_batch, q_lo_batch, q_hi_batch = predictors["quality"].predict(xR_batch)
        t_batch, t_lo_batch, t_hi_batch = predictors["latency"].predict(xR_batch)
        c_batch, c_lo_batch, c_hi_batch = predictors["cost"].predict(xR_batch)

        # Batch evidence computation
        W = metric.W if metric.W is not None else metric._update_cholesky()
        xw_batch = (W @ (xR_batch - model.center).T).T  # Apply W to each sample in batch
        log_e_batch = coherence.batch_log_evidence(model.name, xw_batch)

        # Vectorized free energy computation
        U_batch = (
            self.alpha * q_batch
            - self.beta_t * t_batch
            - self.beta_c * (c_batch + model.cost)
            - self.beta_d * d_batch
            + self.beta_s * log_e_batch
        )

        U_var_batch = (
            (self.alpha * (q_hi_batch - q_lo_batch) / 3.92) ** 2
            + (self.beta_t * (t_hi_batch - t_lo_batch) / 3.92) ** 2
            + (self.beta_c * (c_hi_batch - c_lo_batch) / 3.92) ** 2
            + (self.beta_d * d_std_batch) ** 2
        )

        comps_list: List[Dict[str, float]] = []
        for i in range(num_samples):
            comps_list.append(
                {
                    "quality": float(q_batch[i]),
                    "latency": float(-t_batch[i]),
                    "cost": float(-(c_batch[i] + model.cost)),
                    "distance": float(-d_batch[i]),
                    "evidence": float(log_e_batch[i]),
                    "uncertainty": float(np.sqrt(U_var_batch[i])),
                }
            )

        self._step += num_samples  # Increment step by number of samples processed
        if self._step % 100 == 0:
            print(
                f"SymbolicFreeEnergy.batch_compute took {time.time() - start_time:.4f} "
                f"seconds for {num_samples} samples"
            )
        return U_batch, np.sqrt(U_var_batch), comps_list


from __future__ import annotations

import os
from typing import Optional, Tuple

import numpy as np
from scipy import linalg
from sklearn.covariance import LedoitWolf

from .control import LyapunovController

class SymbolicManifoldMetric:
    def __init__(self, D: int, rank: int, delta: float = 1e-3) -> None:
        # BRIDGEBLOCK_START alg:init_low_rank_factor
        self.D, self.rank, self.delta = D, rank, delta
        self.L = np.random.randn(D, rank) * 0.01
        self.W: Optional[np.ndarray] = None
        self.shrink = LedoitWolf()
        self.whitened_residuals: list[np.ndarray] = []
        # BRIDGEBLOCK_END alg:init_low_rank_factor

    def metric_matrix(self) -> np.ndarray:
        # BRIDGEBLOCK_START eq:spd_metric_matrix
        from .symbolic import SymbolicMatrix, SymbolicScalar

        L = SymbolicMatrix(name="L", value=self.L)
        delta = SymbolicScalar(name=r"\delta", value=self.delta)
        identity_matrix = SymbolicMatrix(name="I", value=np.eye(self.D))

        M_expression = (L @ L.T) + (delta * identity_matrix)

        # The M_expression object now contains both the computation and its own
        # LaTeX representation, which can be accessed via M_expression.to_latex()
        return M_expression.evaluate()
        # BRIDGEBLOCK_END eq:spd_metric_matrix

    # BRIDGEBLOCK_START alg:cholesky_decomposition
    def _update_cholesky(self) -> np.ndarray:
        try:
            self.W = linalg.cholesky(self.metric_matrix(), lower=False)
        except linalg.LinAlgError:
            print(f"Caught LinAlgError. Old delta: {self.delta}")
            self.delta = min(max(self.delta + 1e-3, 1e-5), 1e-1)
            print(f"New delta: {self.delta}")
            print(f"New metric_matrix: {self.metric_matrix()}")
            self.W = linalg.cholesky(self.metric_matrix(), lower=False)
        except np.linalg.LinAlgError:  # pragma: no cover - symmetric to LinAlgError above
            print(f"Caught LinAlgError. Old delta: {self.delta}")
            self.delta = min(max(self.delta + 1e-3, 1e-5), 1e-1)
            print(f"New delta: {self.delta}")
            print(f"New metric_matrix: {self.metric_matrix()}")
            self.W = linalg.cholesky(self.metric_matrix(), lower=False)
        return self.W

    # BRIDGEBLOCK_END alg:cholesky_decomposition

    # BRIDGEBLOCK_START eq:whitened_distance
    def distance(self, x: np.ndarray, mu: np.ndarray) -> Tuple[float, float]:
        if os.environ.get("COMPITUM_DEBUG_METRIC") == "1":
            print("!!! DISTANCE METHOD CALLED !!!")
        if self.W is None:
            self._update_cholesky()
        z = x - mu
        wz = self.W @ z
        d = float(np.linalg.norm(wz))
        if len(self.whitened_residuals) > self.rank:
            cov = self.shrink.fit(np.array(self.whitened_residuals)).covariance_
            sigma = float(np.sqrt(max(wz.T @ cov @ wz, 0.0)))
        else:
            sigma = 0.1
        return d, sigma

    # BRIDGEBLOCK_END eq:whitened_distance

    def batch_distance(self, x_batch: np.ndarray, mu: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
        if self.W is None:
            self._update_cholesky()
        if self.W is None:  # Check again after update
            raise ValueError("self.W should not be None after _update_cholesky()")
        W_matrix = self.W  # Explicitly assign to a local variable to help mypy infer type

        z_batch = x_batch - mu  # Broadcasting handles this
        wz_batch = z_batch @ W_matrix.T  # W is upper triangular, so W @ z is z @ W.T

        d_batch = np.linalg.norm(wz_batch, axis=1)  # Norm along each row

        # Sigma calculation is complex due to LedoitWolf. For batching, simplify for now.
        # This part might need further optimization or a different approach for true batching.
        if len(self.whitened_residuals) > self.rank:
            # Compute covariance once from historical residuals
            cov = self.shrink.fit(np.array(self.whitened_residuals)).covariance_

            # Vectorize quadratic form: wz.T @ cov @ wz for each wz in wz_batch
            # np.einsum('ij,jk,ik->i', wz_batch, cov, wz_batch) computes
            # sum_j sum_k wz_ij * cov_jk * wz_ik
            # This is equivalent to np.diag(wz_batch @ cov @ wz_batch.T)
            sigma_squared_batch = np.einsum("ij,jk,ik->i", wz_batch, cov, wz_batch)
            sigma_batch = np.sqrt(np.maximum(sigma_squared_batch, 0.0))
        else:
            sigma_batch = np.full_like(d_batch, 0.1)  # Default sigma for all samples

        return d_batch, sigma_batch

    # BRIDGEBLOCK_START alg:metric_update_step
    def update_spd(
        self,
        x: np.ndarray,
        mu: np.ndarray,
        beta_d: float,
        d: float,
        eta: float,
        srmf_controller: LyapunovController,
    ) -> float:
        return self.batch_update_spd(
            x_batch=np.array([x]),
            mu_batch=np.array([mu]),
            beta_d=beta_d,
            d_batch=np.array([d]),
            eta=eta,
            srmf_controller=srmf_controller,
        )

    # BRIDGEBLOCK_END alg:metric_update_step

    def batch_update_spd(
        self,
        x_batch: np.ndarray,
        mu_batch: np.ndarray,
        beta_d: float,
        d_batch: np.ndarray,
        eta: float,
        srmf_controller: LyapunovController,
    ) -> float:
        batch_size = x_batch.shape[0]
        if batch_size == 0:
            return 1.0

        z_batch = x_batch - mu_batch

        d_batch_safe = np.maximum(d_batch, 1e-8)
        # Vectorized computation of A_batch and grad_L_batch
        A_batch = (
            beta_d
            / (2 * d_batch_safe[:, np.newaxis, np.newaxis])
        ) * np.einsum("bi,bj->bij", z_batch, z_batch)

        # Sum gradients over the batch
        grad_L = 2 * np.sum(np.einsum("bij,jk->bik", A_batch, self.L), axis=0)

        grad_norm = float(np.linalg.norm(grad_L, 2))

        # For the SRMF controller, we can either pass the average d_star or the last one.
        # Let's use the average for a more stable update.
        avg_d_star = np.mean(d_batch)
        eta_cap, _ = srmf_controller.update(d_star=avg_d_star, grad_norm=grad_norm)

        # Stability cap using average z_norm2
        z_norm2_batch = np.sum(z_batch * z_batch, axis=1)
        avg_z_norm2 = np.mean(z_norm2_batch)
        lipschitz = max(beta_d * avg_z_norm2, 1e-8)
        eta_stab = 1.0 / lipschitz
        eta_eff = min(eta, eta_cap, eta_stab)

        def surrogate_energy(L: np.ndarray) -> float:
            v_batch = np.einsum("jd,bd->bj", L.T, z_batch)
            return 0.5 * beta_d * np.sum(np.sum(v_batch * v_batch, axis=1))

        e0 = surrogate_energy(self.L)
        new_L = self.L - eta_eff * grad_L
        e1 = surrogate_energy(new_L)

        if e1 > e0:
            bt = 0
            while bt < 8 and e1 > e0:
                eta_eff *= 0.5
                new_L = self.L - eta_eff * grad_L
                e1 = surrogate_energy(new_L)
                bt += 1
            if e1 > e0:  # pragma: no cover - defensive fallback
                eta_eff = 0.0  # pragma: no cover
                new_L = self.L  # pragma: no cover

        self.L = new_L
        fnorm = np.linalg.norm(self.L, "fro")
        if fnorm > 10.0:
            self.L *= 10.0 / fnorm

        W = self._update_cholesky()

        # Update whitened_residuals with the batch
        wz_batch = np.einsum('ij,bj->bi', W, z_batch)
        self.whitened_residuals.extend(wz_batch)
        while len(self.whitened_residuals) > 100:
            self.whitened_residuals.pop(0)

        return grad_norm


from dataclasses import dataclass

import numpy as np

from .capabilities import Capabilities

@dataclass
class Model:
    name: str
    center: np.ndarray  # center in Riemannian feature space
    capabilities: Capabilities
    cost: float


from __future__ import annotations

import re
from typing import Dict

import numpy as np

class RegexPromptExtractor:
    """
    Fast, regex-first extractor (spaCy optional). Returns a stable 35D Riemannian vector
    plus a small Banach vector attached separately by the caller if desired.
    """

    def __init__(self) -> None:
        self._r_keys = (
            [f"syn_{i}" for i in range(6)]
            + [f"math_{i}" for i in range(8)]
            + [f"code_{i}" for i in range(7)]
            + [f"sem_{i}" for i in range(6)]
            + [f"aux_{i}" for i in range(8)]
        )  # pad to 35 if some groups are light

    # Last modified: 2025-10-07 01:00:01
    def extract_features(self, prompt: str) -> np.ndarray:
        # Ensure prompt is a string, handling bytes-like objects (e.g., numpy.bytes_)
        if hasattr(prompt, "decode"):
            prompt = prompt.decode("utf-8")
        feats: Dict[str, float] = {}
        # syntactic (cheap proxies)
        sents = [s for s in re.split(r"[.!?]\s+", prompt) if s]
        feats["syn_0"] = float(np.mean([len(s.split()) for s in sents])) if sents else 0.0
        feats["syn_1"] = float(np.std([len(s.split()) for s in sents])) if sents else 0.0
        feats["syn_2"] = float(len(sents))
        feats["syn_3"] = float(prompt.count(","))
        feats["syn_4"] = float(prompt.count(";"))
        feats["syn_5"] = float(min(len(prompt), 4096))  # proxy for length

        # math
        math_ops = len(re.findall(r"[∑∏∫∂∇≤≥≠≈]|\\(sum|int|prod|frac|cdot)", prompt))
        latex = len(re.findall(r"\$[^$]+\$|\\begin{equation}", prompt))
        feats |= {
            "math_0": math_ops,
            "math_1": latex,
            "math_2": float(len(re.findall(r"\bprove|derive|compute|solve\b", prompt, re.I))),
            "math_3": float(len(re.findall(r"[0-9]+(\\.[0-9]+)?", prompt))),
            "math_4": float(prompt.count("^") + prompt.count("_")),
            "math_5": float("theorem" in prompt.lower()),
            "math_6": float("lemma" in prompt.lower()),
            "math_7": float("proof" in prompt.lower()),
        }

        # code
        code_blocks = len(re.findall(r"```[\s\S]*?```", prompt))
        lang_hits = len(re.findall(r"\b(python|sql|javascript|cpp|java|rust|go)\b", prompt, re.I))
        feats |= {
            "code_0": float(code_blocks),
            "code_1": float(lang_hits),
            "code_2": float(
                len(re.findall(r"\bfor|while|if|else|try|catch|except\b", prompt, re.I))
            ),
            "code_3": float(len(re.findall(r"[{}();]", prompt))),
            "code_4": float("class " in prompt or "def " in prompt),
            "code_5": float("SELECT " in prompt.upper()),
            "code_6": float("import " in prompt),
        }

        # semantic proxies
        tokens = prompt.split()
        diffs = (
            [abs(len(tokens[i + 1]) - len(tokens[i])) for i in range(len(tokens) - 1)]
            if len(tokens) > 1
            else []
        )
        feats |= {
            "sem_0": float(np.sum(diffs)) if diffs else 0.0,
            "sem_1": float(np.mean(diffs)) if diffs else 0.0,
            "sem_2": float(np.std(diffs)) if diffs else 0.0,
            "sem_3": float(len(set([t.lower() for t in tokens]))),
            "sem_4": float(len(tokens)),
            "sem_5": float(len(set(w for w in tokens if len(w) > 6))),
        }

        # aux padding (zeros)
        for i in range(8):
            feats[f"aux_{i}"] = feats.get(f"aux_{i}", 0.0)

        # minimal Banach (pragmatic) features for demo
        feats["prag_latency_class"] = 1.0
        feats["prag_cost_class"] = 1.0
        feats["prag_pii_level"] = 0.0
        feats["prag_region_eu_only"] = 0.0

        # Ensure all Riemannian keys are present
        for k in self._r_keys:
            if k not in feats:
                feats[k] = 0.0

        # Order the Riemannian features
        riemannian_feats = [feats[k] for k in self._r_keys]

        # Banach features
        banach_feats = [
            feats.get("prag_latency_class", 1.0),
            feats.get("prag_cost_class", 1.0),
            feats.get("prag_pii_level", 0.0),
            feats.get("prag_region_eu_only", 0.0),
        ]

        return np.array(riemannian_feats + banach_feats, dtype=np.float32)


from __future__ import annotations

from typing import Tuple

import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.isotonic import IsotonicRegression

class CalibratedPredictor:
    """
    Calibrated regressor with quantile bounds (p5,p95).
    For latency/cost: consider enabling monotonic constraints via LightGBM when available.
    """

    def __init__(self) -> None:
        self.base = GradientBoostingRegressor(n_estimators=5, random_state=42)
        self.iso = IsotonicRegression(out_of_bounds="clip")
        self.q05 = GradientBoostingRegressor(
            n_estimators=5, loss="quantile", alpha=0.05, random_state=41
        )
        self.q95 = GradientBoostingRegressor(
            n_estimators=5, loss="quantile", alpha=0.95, random_state=43
        )
        self.fitted = False

    def fit(self, X: np.ndarray, y: np.ndarray) -> None:
        self.base.fit(X, y)
        raw = self.base.predict(X)
        self.iso.fit(raw, y)
        self.q05.fit(X, y)
        self.q95.fit(X, y)
        self.fitted = True

    def predict(self, X: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        raw = self.base.predict(X)
        y = self.iso.transform(raw)
        lo = self.q05.predict(X)
        hi = self.q95.predict(X)
        return y, lo, hi


from __future__ import annotations

# Last modified: 2025-10-07 01:00:00
import json
import os
import time
from dataclasses import dataclass
from typing import Any, Dict, List

import numpy as np

from . import __version__ as COMPITUM_VERSION
from .boundary import BoundaryAnalyzer
from .coherence import CoherenceFunctional
from .constraints import InfoDict, ReflectiveConstraintSolver
from .control import LyapunovController
from .energy import SymbolicFreeEnergy
from .metric import SymbolicManifoldMetric
from .models import Model
from .pgd import RegexPromptExtractor
from .predictors import CalibratedPredictor
from .utils import pgd_hash, split_features

@dataclass
class SwitchCertificate:
    model: str
    utility: float
    utility_components: Dict[str, float]
    constraints: InfoDict
    boundary_analysis: Dict[str, Any]
    drift_status: Dict[str, float]
    pgd_signature: str
    timestamp: float
    router_version: str = COMPITUM_VERSION

    def to_json(self) -> str:
        return json.dumps(
            {
                "model": self.model,
                "utility": round(self.utility, 6),
                "utility_components": {k: float(v) for k, v in self.utility_components.items()},
                "constraints": self.constraints,
                "boundary": self.boundary_analysis,
                "drift": self.drift_status,
                "pgd_signature": self.pgd_signature[:16],
                "timestamp": self.timestamp,
                "router_version": self.router_version,
            },
            indent=2,
        )

class CompitumRouter:
    """Routes prompts/embeddings across models and emits a certificate.

    Orchestration order:
    - feature extraction (PGD)
    - per-model utility + diagnostics (energy, uncertainty)
    - constraint-feasible selection
    - boundary analysis
    - optional metric update (two-timescale) with bounded step size
    - controller update (Lyapunov-based trust region)
    """

    def __init__(
        self,
        models: List[Model],
        predictors: Dict[str, Dict[str, CalibratedPredictor]],
        solver: ReflectiveConstraintSolver,
        coherence: CoherenceFunctional,
        boundary: BoundaryAnalyzer,
        srmf: LyapunovController,
        pgd_extractor: RegexPromptExtractor,
        metric_map: Dict[str, SymbolicManifoldMetric],
        energy: SymbolicFreeEnergy,
        update_stride: int = 8,
        enable_metric_update: bool = True,
        enable_controller: bool = True,
    ) -> None:
        self.models = {m.name: m for m in models}
        self.predictors = predictors
        self.solver = solver
        self.coherence = coherence
        self.boundary = boundary
        ctrl = srmf
        # Preferred name
        self.controller = ctrl
        # Legacy alias retained for compatibility
        self.srmf = self.controller
        self.pgd = pgd_extractor
        self.metric_map = metric_map
        self.energy = energy
        self._step = 0
        self._stride = max(int(update_stride), 1)
        self.enable_metric_update = bool(enable_metric_update)
        self.enable_controller = bool(enable_controller)

    # BRIDGEBLOCK_START alg:router_orchestration
    def route(
        self,
        prompt: str,
        context: Dict[str, Any] | None = None,
        embedding: np.ndarray | None = None,
    ) -> SwitchCertificate:
        start_time = time.time()
        context = context or {}
        if embedding is not None:
            xR_all = embedding
            xB = np.zeros(4)
        else:
            feats = self.pgd.extract_features(prompt)
            xR_all, xB = split_features(feats)
        utilities: Dict[str, float] = {}
        comps: Dict[str, Dict[str, float]] = {}
        u_sigmas: Dict[str, float] = {}

        for name, model in self.models.items():
            met = self.metric_map[name]
            U, sig, uc = self.energy.compute(
                xR_all, model, self.predictors[name], self.coherence, met
            )
            utilities[name] = float(U)
            comps[name] = uc
            u_sigmas[name] = float(sig)

        m_star, cinfo = self.solver.select(
            xB, list(self.models.values()), utilities, context=context
        )
        binfo = self.boundary.analyze(utilities, u_sigmas)

        # Adapt metric periodically (two-timescale)
        self._step += 1
        grad_norm = 1.0
        if self.enable_metric_update and (self._step % self._stride == 0):
            met = self.metric_map[m_star.name]
            d_best = abs(-comps[m_star.name]["distance"])
            grad_norm = met.update_spd(
                xR_all,
                self.models[m_star.name].center,
                self.energy.beta_d,
                d_best,
                eta=1e-2,
                srmf_controller=self.controller,
            )

        if self.enable_controller:
            _, drift = self.controller.update(
                d_star=abs(-comps[m_star.name]["distance"]), grad_norm=grad_norm
            )
        else:
            drift = {
                "trust_radius": self.controller.trust_radius,
                "drift_ema": self.controller.drift_ema,
                "drift_integral": self.controller.drift_integral,
                "lyapunov_function": self.controller.lyapunov_function(),
            }

        cert = SwitchCertificate(
            model=m_star.name,
            utility=utilities[m_star.name],
            utility_components=comps[m_star.name],
            constraints=cinfo,
            boundary_analysis=binfo,
            drift_status=drift,
            pgd_signature=pgd_hash(prompt),
            timestamp=time.time(),
        )
        if (
            self._step % 100 == 0
            and os.environ.get("COMPITUM_DEBUG_ROUTER") == "1"
        ):  # pragma: no cover - debug output only
            print(f"CompitumRouter.route took {time.time() - start_time:.4f} seconds")
        return cert

    def batch_route(
        self,
        embeddings: np.ndarray,
        prompts: List[str] | None = None,
        xB_batch: np.ndarray | None = None,
    ) -> List[SwitchCertificate]:
        start_time = time.time()
        num_samples = embeddings.shape[0]
        prompts = prompts if prompts is not None else ["" for _ in range(num_samples)]
        pgd_signatures = [pgd_hash(p) for p in prompts]  # Compute all hashes at once

        if xB_batch is None:
            xB_batch = np.zeros((num_samples, 4))

        all_utilities: Dict[str, np.ndarray] = {name: np.zeros(num_samples) for name in self.models}
        all_comps: Dict[str, List[Dict[str, float]]] = {
            name: [{} for _ in range(num_samples)] for name in self.models
        }
        all_u_sigmas: Dict[str, np.ndarray] = {name: np.zeros(num_samples) for name in self.models}

        for name, model in self.models.items():
            met = self.metric_map[name]
            # Assuming energy.batch_compute exists and returns arrays
            U_batch, sig_batch, uc_batch = self.energy.batch_compute(
                embeddings, model, self.predictors[name], self.coherence, met
            )
            all_utilities[name] = U_batch
            all_comps[name] = uc_batch
            all_u_sigmas[name] = sig_batch

        certificates: List[SwitchCertificate] = []
        update_data: Dict[str, List] = {name: [] for name in self.models}
        d_star_drift_batch: List[float] = []
        grad_norm_drift_batch: List[float] = []

        for i in range(num_samples):
            xB = xB_batch[i]
            current_utilities = {name: all_utilities[name][i] for name in self.models}
            current_comps = {name: all_comps[name][i] for name in self.models}
            current_u_sigmas = {name: all_u_sigmas[name][i] for name in self.models}

            m_star, cinfo = self.solver.select(
                xB, list(self.models.values()), current_utilities, context=None
            )
            binfo = self.boundary.analyze(current_utilities, current_u_sigmas)

            self._step += 1
            d_best = abs(-current_comps[m_star.name]["distance"])

            if self.enable_metric_update and (self._step % self._stride == 0):
                update_data[m_star.name].append(
                    (
                        embeddings[i],
                        self.models[m_star.name].center,
                        d_best,
                    )
                )

            # For drift, we always update to maintain the controller's state
            d_star_drift_batch.append(d_best)
            # We will compute grad_norm after the metric update, so use a placeholder for now
            grad_norm_drift_batch.append(1.0)

            cert = SwitchCertificate(
                model=m_star.name,
                utility=current_utilities[m_star.name],
                utility_components=current_comps[m_star.name],
                constraints=cinfo,
                boundary_analysis=binfo,
                drift_status={},
                pgd_signature=pgd_signatures[i],
                timestamp=time.time(),
            )
            certificates.append(cert)

        # Perform batch metric updates
        if self.enable_metric_update and (self._step >= self._stride):
            for model_name, data in update_data.items():
                if not data:
                    continue

                x_batch, mu_batch, d_batch = zip(*data)
                met = self.metric_map[model_name]
                grad_norm = met.batch_update_spd(
                    np.array(x_batch),
                    np.array(mu_batch),
                    self.energy.beta_d,
                    np.array(d_batch),
                    eta=1e-2,
                    srmf_controller=self.controller,
                )
                # Update the grad_norm placeholder for the drift update
                for i in range(len(certificates)):
                    if certificates[i].model == model_name:
                        grad_norm_drift_batch[i] = grad_norm

        # Perform batch drift update
        if self.enable_controller:
            _, drift_statuses = self.controller.batch_update(
                np.array(d_star_drift_batch), np.array(grad_norm_drift_batch)
            )
        else:
            # Use current state without updating controller
            drift_statuses = [
                {
                    "trust_radius": self.controller.trust_radius,
                    "drift_ema": self.controller.drift_ema,
                    "drift_integral": self.controller.drift_integral,
                    "lyapunov_function": self.controller.lyapunov_function(),
                }
                for _ in range(num_samples)
            ]

        # Update drift status in certificates
        for i in range(num_samples):
            certificates[i].drift_status = drift_statuses[i]

        if self._step % 100 == 0:  # pragma: no cover - debug output only
            print(
                f"CompitumRouter.batch_route took {time.time() - start_time:.4f} "
                f"seconds for {num_samples} samples"
            )
        return certificates

    # BRIDGEBLOCK_END alg:router_orchestration


from __future__ import annotations

import hashlib
import json
import os
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any, Dict, Optional

def is_offline() -> bool:
    """Return True if offline mode is enabled via environment variable.

    Offline mode is enabled when COMPITUM_OFFLINE == "1".
    """
    return os.environ.get("COMPITUM_OFFLINE", "0") == "1"

def redaction_enabled() -> bool:
    """Return True if explicit redaction is requested via environment.

    Redaction mode is enabled when COMPITUM_REDACT == "1".
    """
    return os.environ.get("COMPITUM_REDACT", "0") == "1"

def redact_text(s: str) -> Dict[str, Any]:
    """Return a redacted representation of text without revealing content.

    Provides SHA-256 digest and length. Does not return the original text.
    """
    h = hashlib.sha256(s.encode()).hexdigest()
    return {"sha256": h, "len": len(s)}

def file_sha256(path: Path) -> Optional[str]:
    """Compute SHA-256 of a file, returning None if the path does not exist."""
    try:
        data = path.read_bytes()
    except FileNotFoundError:
        return None
    return hashlib.sha256(data).hexdigest()

@dataclass
class AuditRecord:
    version: str
    offline: bool
    seed: int
    prompt: Dict[str, Any]
    config: Dict[str, Any]
    certificate: Dict[str, Any]
    commit: Optional[str] = None

def write_audit_record(record: AuditRecord, out_dir: Path) -> Path:
    """Write an audit record JSON to the given directory.

    The filename is monotonic using UNIX epoch milliseconds to simplify ordering.
    Returns the full path written.
    """
    out_dir.mkdir(parents=True, exist_ok=True)
    # Monotonic filename: run_<ms>.json
    import time

    ts_ms = int(time.time() * 1000)
    out_path = out_dir / f"run_{ts_ms}.json"
    out_path.write_text(json.dumps(asdict(record), indent=2))
    return out_path

def git_commit_short(repo_root: Optional[Path] = None) -> Optional[str]:
    """Return the short git commit hash if available, else None.

    By default, this inspects the repository containing this file by reading
    from the local ``.git`` directory (no subprocess calls). For tests or
    tooling, an explicit ``repo_root`` may be provided to point to a directory
    that contains a ``.git`` folder.
    """
    try:
        if repo_root is None:
            # Resolve repository root (assumes this file lives under src/compitum)
            here = Path(__file__).resolve()
            # repo_root = .../compitum (one up from src)
            repo_root = here.parents[2]
        git_dir = repo_root / ".git"
        head_file = git_dir / "HEAD"
        if not head_file.exists():
            return None
        head = head_file.read_text(encoding="utf-8", errors="ignore").strip()
        if head.startswith("ref:"):
            ref = head.split(":", 1)[1].strip()
            ref_path = git_dir / ref
            if ref_path.exists():
                commit = ref_path.read_text(encoding="utf-8", errors="ignore").strip()
                return commit[:7] if commit else None
            return None
        # Detached HEAD: HEAD contains the commit hash
        return head[:7] if head else None
    except Exception:
        return None


from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any

class SymbolicValue(ABC):
    """
    Abstract base class for a value that has both a symbolic (LaTeX)
    and a concrete (numerical) representation.
    """

    def __init__(self, name: str, value: Any):
        if not isinstance(name, str):
            raise TypeError("Symbolic name must be a string.")
        self.name = name
        self.value = value

    @abstractmethod
    def to_latex(self) -> str:
        """Return the LaTeX string representation of the value."""
        ...

    def evaluate(self) -> Any:
        """Return the concrete numerical value."""
        return self.value

    def __repr__(self) -> str:
        return f"SymbolicValue(name='{self.name}', value={self.value})"

    # --- Operator Overloading ---
    def __add__(self, other: SymbolicValue) -> SymbolicExpression:
        return SymbolicExpression(self, other, operator="+")

    def __mul__(self, other: SymbolicValue) -> SymbolicExpression:
        return SymbolicExpression(self, other, operator="*", latex_op=r" \cdot ")

    def __matmul__(self, other: SymbolicValue) -> SymbolicExpression:
        return SymbolicExpression(self, other, operator="@", latex_op="")

class SymbolicScalar(SymbolicValue):
    """Represents a scalar value."""

    def to_latex(self) -> str:
        """Return the LaTeX string representation of the scalar."""
        return self.name

class SymbolicMatrix(SymbolicValue):
    """Represents a matrix value."""

    def to_latex(self) -> str:
        """Return the LaTeX string representation of the matrix."""
        return self.name

    @property
    def T(self) -> SymbolicMatrix:
        """Returns a new SymbolicMatrix representing the transpose."""
        # This is a symbolic operation; the actual transpose happens during evaluation.
        return SymbolicMatrix(name=f"{self.name}^T", value=self.value.T)

class SymbolicExpression(SymbolicValue):
    """Represents a combination of two SymbolicValues via an operator."""

    def __init__(
        self, left: SymbolicValue, right: SymbolicValue, operator: str, latex_op: str | None = None
    ):
        self.left = left
        self.right = right
        self.operator = operator
        self.latex_op = latex_op if latex_op is not None else operator
        # The value of an expression is not known at creation, it must be evaluated.
        super().__init__(name=self.to_latex(), value=None)

    def to_latex(self) -> str:
        """Creates the LaTeX string for the expression."""
        # Basic formatting, can be improved for complex cases
        return f"({self.left.to_latex()} {self.latex_op} {self.right.to_latex()})"

    def evaluate(self) -> Any:
        """
        Evaluates the expression by applying the operator to the concrete
        values of the operands.
        """
        left_val = self.left.evaluate()
        right_val = self.right.evaluate()
        if self.operator == "+":
            return left_val + right_val
        elif self.operator == "-":
            return left_val - right_val
        elif self.operator == "*":
            return left_val * right_val
        elif self.operator == "/":
            return left_val / right_val
        elif self.operator == "@":
            return left_val @ right_val
        else:
            raise ValueError(f"Unknown operator: {self.operator}")


from __future__ import annotations

import hashlib
from typing import Dict, Tuple, Union

import numpy as np

def split_features(x: Union[Dict[str, float], np.ndarray]) -> Tuple[np.ndarray, np.ndarray]:
    if isinstance(x, dict):
        # Riemannian: everything except prag_*, Banach: prag_* only
        xR = [v for k, v in x.items() if not k.startswith("prag_")]
        xB = [v for k, v in x.items() if k.startswith("prag_")]
        return np.array(xR, float), np.array(xB, float)
    else:
        # Riemannian: everything except the last 4, Banach: last 4 only
        return x[:-4], x[-4:]

def pgd_hash(prompt: str) -> str:
    return hashlib.sha256(prompt.encode()).hexdigest()


# pytest cache directory #

This directory contains data from the pytest's cache plugin,
which provides the `--lf` and `--ff` options, as well as the `cache` fixture.

**Do not** commit this to version control.

See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.


# RouterBench

### [Paper](https://arxiv.org/abs/2403.12031) | [Dataset](https://huggingface.co/datasets/withmartian/routerbench)

The code for the paper ROUTERBENCH: A Benchmark for Multi-LLM Routing System

## Setup process
1. Create .env file in the root directory. With the following variables:
```
CONNECTION_STRING='your mongodb connection string'
```
if you want to use MongoDB as an embedding cache.

We use Martian as it provides a unified gateway to access all the models we use. Please visit withmartian.com to create a new account and get started.

2. In root directory, run `pip install -e .` to install the packages.

## Running the pipeilne

The pipeline relies on various command line arguments to specify the configuration. Alternatively, you can specify the
configuration in a yaml file and pass it to the command line. Example configurations are in the `configs/` directory.

First, if desired, make sure there is a MongoDB instance running that you can connect to. If there is not one, ensure that `local_cache: true` to ensure that
the code only uses local files for caching.

Second, run `convert_data.py --config=configs/convert_data.yaml` to process the different data formats into a common format.
    This script can take raw format from `martian-evals` repo, as well as other relevant input formats.

Third, run `evaluate_routers.py --config=configs/evaluate_routers.yaml` to use the processed data to evaluate different routers. It generates a csv file (long format) with the results of the evaluation, and creates an EvaluationCollection containing the results.

Fourth, run `visualize_results.py --config=configs/visualize.yaml` uses the EvaluationCollection to visualize the results in a performance-vs-cost plot.

For these configurations, the paths to the data files will need to be updated to use your local paths. Example files to recreate results from the paper are available on [Hugging Face](https://huggingface.co/datasets/withmartian/routerbench).

## Contribution Guide

The code is designed to be easily extended. To add a new router, or convertor for a different input data format, simply look
at the abstract classes `AbstractRouter` and `AbstractConvertor` in `routers/` and `convertors/` respectively.

- For each PR, please run flake8, black, isort
```bash
flake8 $(git ls-files '*.py')
black $(git ls-files '*.py')
isort $(git ls-files '*.py')
```
`$(git ls-files '*.py')` is for running only the files tracked by git, so exclude virtual env files or data files.
You may need to run `pip install flake8 black isort` if you don't have them installed.

## Modal update Guide
To deploy the updated modal app, run the following commands:
```bash
modal deploy modal_router.py
```

## Citation
If you use this code, please cite the following paper:
```bibtex
@article{hu2024routerbench,
  title   = {ROUTERBENCH: A Benchmark for Multi-LLM Routing System},
  author  = {Qitian Jason Hu and Jacob Bieker and Xiuyu Li and Nan Jiang and Benjamin Keigwin and Gaurav Ranganath and Kurt Keutzer and Shriyash Kaustubh Upadhyay},
  year    = {2024},
  journal = {arXiv preprint arXiv: 2403.12031}
}
```


[pytest]
testpaths = tests