Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/changelog.d/974-utility-control-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Selection utility numeric trust boundary

## Fixed

- Hardened classical selection-utility and Taylor-Russell scalar controls so booleans, non-real objects, and non-finite values fail with package-owned validation before compiled Rust discovery.
- Prevented arbitrary caller-defined `__float__` callbacks from executing during public control marshalling while preserving genuine Python/NumPy real scalar compatibility and keeping all BCG, Naylor-Shine, and Taylor-Russell arithmetic Rust-owned.
- Normalized exact built-in integers outside the representable float range to the same package-owned validation error instead of leaking `OverflowError`.
56 changes: 53 additions & 3 deletions python/fast_mlsirm/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,29 @@

from __future__ import annotations

import math
from dataclasses import dataclass

import numpy as np


_TRUSTED_REAL_SCALAR_TYPES = (
int,
float,
np.int8,
np.int16,
np.int32,
np.int64,
np.uint8,
np.uint16,
np.uint32,
np.uint64,
np.float16,
np.float32,
np.float64,
np.longdouble,
)


@dataclass
class SelectionUtilityResult:
Expand All @@ -46,7 +67,7 @@ class SelectionUtilityResult:
``xc`` is the standard-normal predictor cutoff ``Phi^-1(1 - sr)``;
``ux`` the selection intensity ``phi(xc)/sr`` (mean standardized
predictor of those selected); ``pux = rxy * ux`` the Naylor-Shine mean
standardized criterion of those selected; ``utility_gain`` the BCG
standardized criterion of those selected); ``utility_gain`` the BCG
Comment thread
github-actions[bot] marked this conversation as resolved.
Resolved
gain ``n * period * sdy * pux - cost_total``."""

xc: float
Expand All @@ -67,6 +88,19 @@ class TaylorRussellResult:
q_joint: float


def _coerce_finite_real(value: object, *, name: str) -> float:
"""Return a trusted finite real scalar without invoking caller protocols."""
if type(value) not in _TRUSTED_REAL_SCALAR_TYPES:
raise ValueError(f"{name} must be a finite real number")
Comment thread
seonghobae marked this conversation as resolved.
Resolved
try:
marshaled = float(value)
except OverflowError:
raise ValueError(f"{name} must be a finite real number") from None
if not math.isfinite(marshaled):
raise ValueError(f"{name} must be a finite real number")
return marshaled


def selection_utility(
n: float,
sdy: float,
Expand All @@ -84,10 +118,22 @@ def selection_utility(
applicant" but never multiplies by ``n``; we document the actual
semantics), ``period`` expected tenure (>= 1).
"""
marshaled_n = _coerce_finite_real(n, name="n")
marshaled_sdy = _coerce_finite_real(sdy, name="sdy")
marshaled_rxy = _coerce_finite_real(rxy, name="rxy")
marshaled_sr = _coerce_finite_real(sr, name="sr")
marshaled_cost_total = _coerce_finite_real(cost_total, name="cost_total")
marshaled_period = _coerce_finite_real(period, name="period")

from . import _core

r = _core.selection_utility(
float(n), float(sdy), float(rxy), float(sr), float(cost_total), float(period)
marshaled_n,
marshaled_sdy,
marshaled_rxy,
marshaled_sr,
marshaled_cost_total,
marshaled_period,
)
return SelectionUtilityResult(
xc=r["xc"], ux=r["ux"], pux=r["pux"], utility_gain=r["utility_gain"]
Expand All @@ -101,9 +147,13 @@ def taylor_russell(rxy: float, sr: float, br: float) -> TaylorRussellResult:
base rate of success in (0, 1). At ``rxy = 0`` the success ratio
equals ``br`` (no selection information).
"""
marshaled_rxy = _coerce_finite_real(rxy, name="rxy")
marshaled_sr = _coerce_finite_real(sr, name="sr")
marshaled_br = _coerce_finite_real(br, name="br")

from . import _core

r = _core.taylor_russell(float(rxy), float(sr), float(br))
r = _core.taylor_russell(marshaled_rxy, marshaled_sr, marshaled_br)
return TaylorRussellResult(
success_ratio=r["success_ratio"],
base_rate=r["base_rate"],
Expand Down
100 changes: 100 additions & 0 deletions tests/test_utility_control_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Trust-boundary regressions for classical selection utility controls."""

from __future__ import annotations

import math

import numpy as np
import pytest

from fast_mlsirm.utility import selection_utility, taylor_russell


class _HostileFloat:
"""Arbitrary float-protocol object whose callback must stay unreachable."""

calls = 0

@classmethod
def reset(cls) -> None:
"""Reset the callback counter."""
cls.calls = 0

def __float__(self) -> float:
type(self).calls += 1
raise AssertionError("caller __float__ callback must not execute")


class _HostileFloatSubclass(float):
"""Float subclass whose conversion callback must stay unreachable."""

calls = 0

@classmethod
def reset(cls) -> None:
"""Reset the callback counter."""
cls.calls = 0

def __float__(self) -> float:
type(self).calls += 1
raise AssertionError("caller float-subclass __float__ callback must not execute")


_UTILITY_CONTROL_CALLS = (
lambda value: selection_utility(value, 1.0, 0.5, 0.5),
lambda value: selection_utility(1.0, value, 0.5, 0.5),
lambda value: selection_utility(1.0, 1.0, value, 0.5),
lambda value: selection_utility(1.0, 1.0, 0.5, value),
lambda value: selection_utility(1.0, 1.0, 0.5, 0.5, value, 1.0),
lambda value: selection_utility(1.0, 1.0, 0.5, 0.5, 0.0, value),
lambda value: taylor_russell(value, 0.5, 0.5),
lambda value: taylor_russell(0.4, value, 0.5),
lambda value: taylor_russell(0.4, 0.5, value),
)


@pytest.mark.parametrize("call", _UTILITY_CONTROL_CALLS)
@pytest.mark.parametrize("hostile_type", (_HostileFloat, _HostileFloatSubclass))
def test_utility_rejects_float_protocol_objects_without_callbacks(
call, hostile_type
) -> None:
"""Every scalar position rejects hostile conversion without callback execution."""
hostile_type.reset()

with pytest.raises(ValueError, match="must be a finite real number"):
call(hostile_type())

assert hostile_type.calls == 0


@pytest.mark.parametrize("call", _UTILITY_CONTROL_CALLS)
@pytest.mark.parametrize("value", (True, False, math.inf, -math.inf, math.nan))
def test_utility_rejects_boolean_and_nonfinite_controls(call, value) -> None:
"""Every scalar position rejects boolean and non-finite control values."""
with pytest.raises(ValueError, match="must be a finite real number"):
call(value)


@pytest.mark.parametrize("call", _UTILITY_CONTROL_CALLS)
def test_utility_rejects_unrepresentable_builtin_integers(call) -> None:
"""Exact huge integers must become stable package-owned validation errors."""
with pytest.raises(ValueError, match="must be a finite real number"):
call(10**400)


def test_numpy_real_scalars_preserve_native_results() -> None:
"""Trusted NumPy real scalars produce the same Rust-owned results as floats."""
py_utility = selection_utility(10.0, 2.0, 0.4, 0.5, 1.0, 2.0)
np_utility = selection_utility(
np.float64(10.0),
np.float64(2.0),
np.float64(0.4),
np.float64(0.5),
np.float64(1.0),
np.float64(2.0),
)
assert np_utility == py_utility

py_tr = taylor_russell(0.4, 0.5, 0.6)
np_tr = taylor_russell(np.float64(0.4), np.float64(0.5), np.float64(0.6))
assert np_tr == py_tr
Loading