Skip to content

Constraints

Constraints are declared once, validated against the model's feature space, and compiled by a single visitor into every backend — the abstract constraint form and the genetic engines' vectorized check/repair pair share one source of truth.

from treecf import Freeze, Monotone, Range, Linear, Implies, Equals, OneHot, constraint

Freeze("age_of_bureau_file")                    # immutable
Monotone("age", "increase")                     # directional
Range("utilization", 0.0, 1.5)                  # hard bounds
constraint("max_dpd_30d <= max_dpd_12m")        # string sugar -> Linear
constraint("2*a - b <= c + 5")                  # any linear expression
Implies(Equals("has_mortgage", 0), Equals("mortgage_balance", 0))
OneHot(("channel_web", "channel_app", "channel_branch"))

Only linear expressions are expressible as strings by design; richer constraints are objects. Parse errors carry a caret marking the offending token.

Mining candidates from data

suggest_constraints scans a background sample for invariants — pairwise orders, equalities, binary implications, one-hot groups, missingness links, integer-valuedness — and returns them for human review; nothing is ever auto-applied:

import treecf

result = treecf.suggest_constraints(X_train, feature_names=names)
for s in result[:20]:
    print(s.as_code())     # constraint("n_active_loans <= n_loans_total")  # support=1.0000, n=48211

accepted = [s.constraint for s in result if s.kind == "order"]

Hierarchical time-window features (dpd_7d <= dpd_30d <= dpd_90d) are reduced to a minimal generating set (transitive reduction), and suggestions are ranked by support and shared name tokens — never filtered.

Near-invariants (support ≥ 99.9% but below 1.0) are returned separately as DataQualityFinding records with the violating rows: a handful of violations of an otherwise universal rule usually signals an ETL defect, not a domain exception.