Skip to content

Releases: wlazlod/treecf

treecf 0.3.2

Choose a tag to compare

@wlazlod wlazlod released this 06 Sep 10:44
1e87701

The exact backend now searches coarse-to-fine by default, which certifies far more of the
measured matrix in the same budget; a budget that ran out no longer costs seconds more for
its own warning; certificates of exact solves always name the engine that ran; and a packaged
credit demo makes every example in the documentation and the README run as written. The
documentation is restructured so that each page has one job, and its claims are sized to
what the benchmarks measure.

Full record: the changelog; the measured comparison against DiCE and NICE is on the benchmarks page.

treecf 0.3.1

Choose a tag to compare

@wlazlod wlazlod released this 06 Sep 00:05
a7272ce

The exact backend gains an opt-in coarse-to-fine search (search="refine") that
certifies far larger models in the same time, a maximal region mode that proves where a
certified box ends, a certification trace you can plot, and a search profile that sizes a
solve before it runs. Auditors get a one-page portfolio report; analysts get certified
recourse menus over lever sets and lever-diverse plans. Two correctness fixes reach every
user: XGBoost and CatBoost models now route float64 inputs exactly as the native model
does, and a value policy no longer withdraws an exact certificate up front. Regions stop at
the observed data range instead of running to infinity, and region phrases never overstate
the box. Everything else is byte-identical to 0.3.0.

Full record: the changelog and the benchmarks.

treecf 0.3.0

Choose a tag to compare

@wlazlod wlazlod released this 31 Aug 06:12

Added

  • Native categorical splits. Models trained with native categorical support now parse
    exactly into set-membership IR nodes: LightGBM (categorical_feature), XGBoost
    (enable_categorical), scikit-learn HistGradientBoosting (categorical_features,
    including string categories via categories=), and CatBoost (cat_features; one-hot and
    single-feature-statistic splits, hashing reproduced bit-exactly). CatBoost models built with
    categorical feature combinations raise ParserError naming the max_ctr_complexity=1
    retraining recipe; the new ParserError type covers "recognized but unparseable as given".
  • Explainer(categories=...). Display names (and, where useful, declared cardinalities
    beyond training) for categorical features; required for CatBoost with native categorical
    features and for HistGradientBoosting trained on string categories, optional elsewhere.
  • Category blocks. Every backend searches categorical features over routing-equivalence
    classes of codes, so search cost scales with how finely the ensemble partitions the feature,
    not its cardinality. Categorical distance is flat: any change of code costs one weighted
    unit.
  • AllowedCategories constraint. Whitelist a categorical feature's codes by display name
    or raw code; order- and arithmetic-shaped constraints (Range, Monotone, Linear,
    Equals, Implies, OneHot) are rejected on categorical features at construction.
  • Categorical exact search and regions. backend="exact" proves optimality and certified
    infeasibility over the block grid; region=True certifies category sets per categorical
    feature (RecourseRegion.feature_categories / .category_names / .cat_sets), stored in
    certificates as schema version 2. Schema version 1 certificates still verify, pinned by a
    committed golden file.
  • Presolve. The exact backend filters each feature's candidate states by reachable score
    and plausibility brackets before branching; solver_stats gains presolve_removed and
    presolve_certified, and an emptied domain certifies infeasibility with zero nodes
    expanded. Results are bit-identical with presolve on; only node counts drop.
  • Visualization. plot_region (the certified box, with per-bound cap markers and
    categorical tiles) and plot_recourse_burden / recourse_burden_table (feasible share and
    cost distribution by segment, kept side by side).
  • Docs. Reader-oriented navigation (workflow guides, grouped concepts, split API pages,
    benchmarks and changelog pages); every fenced snippet executed in CI against a committed
    docs model; a structure test pins that no published URL disappears and every plot function
    ships a committed figure.
  • SECURITY.md, CONTRIBUTING.md, scripts/bump_version.py (with a version-consistency
    test), and #![forbid(unsafe_code)] in the Rust core.

Changed

  • Exact-search performance. Presolve, a feature-to-trees index, and per-tree bracket
    caching in region growth. Measured before/after (same machine, same seeds, medians):

    Scenario (exact backend, warm start, 5 s / 2M-node budgets, 10 seeds, 4-core x86_64) 0.2.4 median 0.3.0 median
    30 trees / depth 4 / 8 features — every solve proved optimal 0.292 s 0.295 s
    60 trees / depth 5 / 12 features — budget-capped, best-found 5.014 s 5.012 s
    300 trees / depth 6 / 50 features — budget-capped, best-found 5.208 s 5.133 s

    No legacy case regresses (the largest change is +1.0% on the small case, within run
    noise); on the large budget-capped case the search now expands 308,822 nodes in the same
    budget where 0.2.4 expanded 205,755. New certification measurements (60 s budget,
    3 seeds): the 200-tree / depth-5 / 12-feature reference case does not certify within
    60 s on the 4-core benchmark machine; the native-categorical suite (4 numeric levers plus
    cardinality-3/8/15 categoricals, LightGBM) certifies in 0.022 s median even at 200 trees.
    Full tables: the docs benchmarks page, generated from the same measured JSON.

Invariants

  • Numeric-model results are byte-identical to the previous release: fingerprints, solves,
    regions, and stored fixtures are unchanged, pinned by a dedicated invariance suite.
  • No genetic or parity fixture was regenerated; exact fixtures were regenerated only under an
    equality guard asserting identical plans, distances, and proofs.
  • The Python and Rust engines remain byte-identical on every solve, domain, and region,
    including the new categorical paths.

treecf 0.2.4

Choose a tag to compare

@wlazlod wlazlod released this 23 Aug 18:51
50b085b

Added

  • Calibrator provenance in certificates. For calibrated-space targets the certificate's
    target.calibrator block is now structured — {embedded: false, fingerprint, type, buffer_logit} — with the fingerprint duck-read from the calibrator's own fingerprint()
    (null when absent; probcal calibrators provide one). check_certificate accepts an
    optional calibrator= keyword: when given, the report gains calibrator_match, true only
    if the fingerprints agree and re-inverting the stored calibrated bounds through the passed
    calibrator reproduces the stored raw interval.
  • Calibrator provenance in batch records. BatchRecord.calibrator_fingerprint repeats the
    target calibrator's fingerprint on every row, so each JSON line stays self-contained.
  • score_calibrated read-out. Counterfactual, BatchRecord, and the certificate's
    factual block now carry the calibrator's probability at the result (and at the factual)
    for calibrated targets whose calibrator exposes predict_proba; None otherwise.
    Presentational only: the engine still optimizes and verifies against the raw interval.
  • Plateau-aware exactness tests. Calibrated targets on and one float above step-calibrator
    plateau levels, cross-checked against brute-force enumeration in calibrated space and
    against real probcal isotonic/centered-isotonic fits.
  • probcal test matrix. New optional test extra (and probcal in the dev extra):
    7 fitted probcal calibrators x target ops x buffer levels on sklearn and LightGBM models,
    every plan re-verified through the model and calibrator; dedicated CI job with pinned
    probcal + lightgbm. src/ never imports probcal — the duck-typed protocol is unchanged.
  • Docs. concepts/calibration.md gains provenance, read-out, and worked-example
    sections, and pins the guarantee that explain_batch calls interval_inverse exactly
    once per call (once per band for ladders), backed by counting tests.

Compatibility

  • Strictly additive. All new dataclass fields default to None; 0.2.x batch JSON and
    certificates load with the new fields defaulted. check_certificate without calibrator=
    produces byte-identical reports to 0.2.3. Calibrators missing optional duck members
    (fingerprint, predict_proba) degrade to null/None, never an error.

treecf 0.2.3

Choose a tag to compare

@wlazlod wlazlod released this 23 Aug 14:24
36d5ed3

Fixed

  • sklearn tree_-based ensembles (RandomForest, GradientBoosting, IsolationForest) routed
    differently from sklearn itself at split boundaries
    , because sklearn casts inputs to
    float32 before comparing against the float64 threshold while the IR evaluates in float64.
    A counterfactual whose coordinate landed exactly on a split threshold — the natural optimum
    of a smallest-change search, since <= cells are closed on the left — could flip through
    many trees at once: in the reproducing case (GradientBoostingClassifier,
    subsample=0.8), the exact backend stamped proof="optimal" on an x_cf whose true
    decision_function margin was 3.09 raw-score units away from the reported score_raw,
    silently violating the target. Thresholds are now re-expressed at parse time as the exact
    float64 boundary of the float32 cast (largest float64 T with float32(T) <= t,
    round-half-to-even handled), so float64 IR routing reproduces sklearn bit-for-bit for
    every input — search, certificates, and score_raw included. Verified by a 138k-probe
    property sweep, new unquantized conformance tests (exact-threshold and float64-neighbour
    probes; the old harness quantized all probes to the float32 grid, which is exactly why
    this never surfaced), and probcal's joint recourse scenarios. HistGradientBoosting
    predicts on the float64 grid and is unchanged; XGBoost also casts features to float32
    natively and should get the same treatment once a reproducing case is confirmed
    (follow-up).

Internal

  • Restructured a late-initialized binding in the exact search's proof/lower-bound
    epilogue (behavior-identical) — clippy 1.98's needless_late_init began rejecting
    the old form under -D warnings on the freshly installed stable toolchain in CI.

v0.2.2 — batch proof fields and audit certificates

Choose a tag to compare

@wlazlod wlazlod released this 18 Aug 23:00

Added

  • Audit certificates: Explainer.certificate(x, result, target) turns any stored Counterfactual or Infeasible (the certified "no" included) into a strict-JSON-serializable audit record — a reproducibility record plus a fresh verification. It binds the claim to a model fingerprint, a constraint fingerprint, and the solve parameters, and re-verifies the returned plan (score, target membership, constraint check, plausibility, sampled region points) at issue time. It does not cryptographically prove that a search ran or that a proof="optimal" claim is true — re-running with the recorded seed/budgets on a fingerprint-matching model is how a validator checks that. Explainer.check_certificate(cert) recomputes both fingerprints against the current explainer, re-runs the verification block, and reports (model_match/constraints_match/verification_ok/mismatches) without ever raising on a mismatch. The new treecf.audit module exposes the underlying ir_fingerprint and constraints_fingerprint; a callable value_policy has no canonical encoding and marks the certificate "reproducible": false with a reason.
  • BatchRecord.proof and BatchRecord.solver_stats: every batch record now carries the claim and (for exact solves) the diagnostics of the single-instance result that produced it. BatchResult.to_frame gains a proof column; save/load round-trip both fields, and files from earlier versions load with feasibility-based defaults.

Fixed

  • The batch aggregate degraded-result warning pointed at "each result's own proof/solver_stats" while BatchRecord exposed neither field; the fields now exist, so the message is true as written.

Notes

  • No solver behavior changes; no fixtures touched; no Rust source changes (only the mirrored version in rust/Cargo.toml/Cargo.lock).

See CHANGELOG.md for details.

treecf 0.1.1

Choose a tag to compare

@wlazlod wlazlod released this 08 Aug 05:56
cad69b8

Added

  • TreecfWarning, emitted when a factual violates its constraints — once per
    explain call, and as a single per-constraint aggregate in explain_batch.
    The warning spells out that the returned plan includes changes made solely
    to satisfy the violated constraints.
  • Derived per-feature bounds for single-feature Linear constraints
    (constraint("income >= 100") now clips candidates like the equivalent
    Range); vacuous zero-coefficient linears are dropped, unsatisfiable ones
    rejected at compile time.
  • Declared Rust MSRV (rustc 1.86) in rust/Cargo.toml with an enforcing CI
    job; building from the sdist needs 1.86+, wheels need no toolchain.
  • Wheel smoke tests in the release workflow: every runnable wheel target is
    installed into a fresh venv (musllinux inside an Alpine container) and runs
    one explain per backend before upload.
  • CITATION.cff version is now checked against treecf.__version__ in the
    test suite.

Fixed

  • Satisfiable Linear constraints whose feasible set lies far from the
    factual no longer come back Infeasible: single-feature linears lower into
    bounds, and multi-feature linears get halfspace-projection repair.
  • apply_link no longer raises OverflowError for raw scores below ≈ −710;
    mid-range sigmoid outputs are bit-for-bit unchanged.
  • CITATION.cff and rust/Cargo.toml version drift (both said 0.0.1 while
    the released package was 0.1.0).

Changed

  • Repair for non-canonical linear constraints now runs a 3-round cyclic
    halfspace projection; seeded results from 0.1.0 that involve such
    constraints are not reproducible in 0.1.1
    . The canonical order-pair
    repair (a - b <= 0) is unchanged, and the existing parity fixtures
    regenerated byte-identical; a new 11-linear-projection fixture pins the
    projection behavior.

treecf 0.1.0

Choose a tag to compare

@wlazlod wlazlod released this 23 Jul 12:03

Added

  • Calibrated targets: Target.calibrated(calibrator, ...) expresses the
    target on the post-hoc calibrated probability scale and lazily inverts it
    through the calibrator's duck-typed generalized inverse
    (interval_inverse(lo, hi, *, space="logit", buffer_logit=...) +
    is_monotone_) — no calibration-library dependency. Target.bands accepts
    space="calibrated" with calibrator=/buffer_logit= for masterscales
    defined on calibrated PD. Target.probability now documents that it targets
    the uncalibrated model probability.

Fixed

  • Target.band_intervals field propagation: per-band targets were rebuilt
    from (space, lo, hi) only, silently dropping any other field — now all
    fields propagate (surfaced by the calibrated-bands work).

  • Competitor benchmark: scripts/bench_vs_competitors.py (PEP 723,
    self-contained via uv run) compares treecf with DiCE and NICE on two
    model scales; results published in Backends and proofs — 8–3400× faster
    than DiCE with far cheaper plans, cheapest plans overall, 157 rows/s batch
    production on the medium model; NICE's per-instance speed and treecf's own
    misses reported as-is.

  • Post-solve pruning: every returned plan now drops changes that
    verification proves unnecessary (cheapest first, each revert re-verified in
    float space). The search's revert-to-factual mutation is stochastic, so a
    stalled run could ship a residual micro-change that crossed no decision
    threshold — pure distance cost with zero score effect.

  • CITATION.cff.

Changed

  • Compiled extensions are no longer tracked in git (history rewritten to drop
    the committed .so; wheels come from CI, local builds via maturin).
  • Publish steps skip files already on the index, making tag-triggered
    re-releases idempotent; retroactive v0.0.1 tag and GitHub release created.
  • PyPI keywords no longer mention the removed CP-SAT backend; README/docs
    state the published version (0.0.1) consistently.

treecf 0.0.1

Choose a tag to compare

@wlazlod wlazlod released this 13 Jul 23:06

First published release — on PyPI.

Highlights (full details in CHANGELOG.md):

  • Constrained genetic counterfactual search on a bundled Rust core (44–58× the numpy reference); every result float-verified against the model IR.
  • Targets as intervals on the raw output; declarative constraints; NaN as a first-class value; optional isolation-forest plausibility; constraint mining.
  • Batch production with parallel solves, portable JSON storage, and batch-level visualizations; coalitions mode for grouped recourse.
  • Wheels for linux (x86_64, aarch64, musl), macOS (arm64, x86_64), and Windows; docs at https://wlazlod.github.io/treecf/.