Skip to content

Monotonic

monotonic

Exact monotonicity constraints for the FlagGAM additive head.

This module is an ORIGINAL ADDITION and is not part of Zhao & Welsch (arXiv:2605.31189). Because each numerical feature contributes monotone step/ramp bases (tail flags, hinges, trend), sign constraints on their coefficients yield EXACT monotonicity of the additive contribution. Sign table and design notes in docs/DECISIONS.md entry 20.

MonotonicAdditiveHead

MonotonicAdditiveHead(task: str, bounds: list[tuple[float | None, float | None]], C: float = 1.0, alpha: float = 1.0)

L-BFGS-B box-constrained L2 logistic (binary) or ridge (regression) head.

Drop-in for AdditiveHead when monotonic_constraints is active. Unlike AdditiveHead, C/alpha are single floats only: CV tuning of the constrained head is out of scope (spec routes list-valued C/alpha to the spec default of 1.0 before construction, see estimator.py).

Source code in src/flaggam/monotonic.py
def __init__(
    self,
    task: str,
    bounds: list[tuple[float | None, float | None]],
    C: float = 1.0,
    alpha: float = 1.0,
) -> None:
    self.task = task
    self.bounds = bounds
    self.C = C
    self.alpha = alpha

bounds_for_bases

bounds_for_bases(bases: list, constraints: dict) -> list[tuple[float | None, float | None]]

Per-basis-column box bounds implementing spec §8.2 sign constraints.

Source code in src/flaggam/monotonic.py
def bounds_for_bases(bases: list, constraints: dict) -> list[tuple[float | None, float | None]]:
    """Per-basis-column box bounds implementing spec §8.2 sign constraints."""
    out: list[tuple[float | None, float | None]] = []
    for b in bases:
        direction = constraints.get(b.feature, 0)
        rule = _PLUS_ONE.get(b.kind)
        if direction == 0 or rule is None:  # unconstrained, categorical, or missing
            if direction != 0 and b.kind not in ("category", "missing_indicator"):
                logger.warning(
                    "basis kind %r on constrained feature %r is not in the "
                    "monotonicity sign table; leaving its coefficient unconstrained — "
                    "monotonicity is no longer guaranteed",
                    b.kind,
                    b.feature,
                )
            out.append(_FREE)
        elif direction == 1:
            out.append(rule)
        else:  # -1: mirror the +1 bound by swapping the tuple
            out.append((rule[1], rule[0]))
    return out