Skip to content

Constraints refactor 3 typed resolution - #688

Merged
janosg merged 6 commits into
mainfrom
constraints-refactor-3-typed-resolution
Jul 3, 2026
Merged

Constraints refactor 3 typed resolution#688
janosg merged 6 commits into
mainfrom
constraints-refactor-3-typed-resolution

Conversation

@janosg

@janosg janosg commented Jul 3, 2026

Copy link
Copy Markdown
Member

Part 3 of the constraints refactoring stack (follows #686 and #687).

This PR creates the optimagic.parameters.constraints subpackage that will host
the refactored constraints machinery, and replaces the first pipeline stage —
selector processing — with typed resolution.

Create the parameters/constraints subpackage that will host the refactored
constraints machinery, starting with the first pipeline stage:

- types.py: ConstraintSource provenance (user constraint + position) and one
  frozen Resolved* dataclass per constraint kind, carrying flat parameter
  positions as read-only arrays. Linear weights are aligned and broadcast at
  resolution time; absent bounds/values are inf/nan sentinels instead of
  missing dict keys.
- resolution.py: resolve_constraints evaluates selectors on the positional
  helper tree and returns typed resolved constraints. Error messages now cite
  the originating user constraint. The loc/query branches are gone from this
  stage (the deprecation adapter owns them since the previous commit).
- process_selectors.py is deleted; conversion.py calls resolve_constraints and
  converts the result to legacy dicts right before the (still dict-based)
  process_constraints via the temporary to_legacy_dicts seam.

The new modules are fully typed and not exempted from strict mypy checking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@janosg
janosg force-pushed the constraints-refactor-3-typed-resolution branch from d0d54aa to c561968 Compare July 3, 2026 15:57
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.88136% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/optimagic/constraints.py 98.59% 2 Missing ⚠️
src/optimagic/parameters/constraints/resolution.py 97.56% 2 Missing ⚠️
src/optimagic/deprecations.py 83.33% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/optimagic/parameters/constraints/__init__.py 100.00% <100.00%> (ø)
src/optimagic/parameters/conversion.py 96.84% <100.00%> (+0.03%) ⬆️
src/optimagic/deprecations.py 95.63% <83.33%> (-0.25%) ⬇️
src/optimagic/constraints.py 91.90% <98.59%> (+6.48%) ⬆️
src/optimagic/parameters/constraints/resolution.py 97.56% <97.56%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

janosg and others added 4 commits July 3, 2026 18:22
- ResolvedConstraint is now an ABC that all Resolved* dataclasses subclass,
  mirroring the Constraint base class, instead of a union type alias.
- The index and weights arrays are no longer made read-only; the frozen
  dataclasses provide enough immutability. __post_init__ still normalizes
  inputs to properly typed arrays.
- to_legacy_dicts dispatches on ResolvedLinear explicitly because the ABC,
  unlike the union, does not let mypy narrow the final else branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Constraint gets an abstract _resolve method that each constraint class
implements by constructing its Resolved* counterpart, replacing the isinstance
dispatch chains in resolution.py. A frozen ResolutionContext (helper tree,
registry, param names, provenance source) bundles everything the methods need
and owns the selector evaluation and duplicate check.

The Resolved* dataclasses, ConstraintSource, and the array aliases move from
parameters/constraints/types.py into optimagic.constraints, each Resolved*
class directly after its user-facing counterpart. This keeps all import edges
pointing in the existing direction (pipeline modules import
optimagic.constraints) and avoids a circular import; types.py is deleted.

Cleanups that fall out: the type-ignore on constraint.selector and the
getattr value hack for FixedValueConstraint are gone (the deprecated subclass
overrides _resolve); linear weight alignment lives on LinearConstraint;
NonlinearConstraint._resolve raises the same InvalidConstraintError as the
old fallback branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ResolvedFixed -> ResolvedFixedConstraint etc.; in particular the covariance
and sdcorr classes now mirror the Flat* user classes
(ResolvedFlatCovConstraint, ResolvedFlatSDCorrConstraint).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nonlinear constraints are passed directly to optimizers that support them and
are split off before resolution, so reaching NonlinearConstraint._resolve is
an internal error, not an invalid user constraint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@janosg
janosg force-pushed the constraints-refactor-3-typed-resolution branch from c3a905c to ce090ad Compare July 3, 2026 19:09
Each constraint class's _resolve is now tested directly against a small
ResolutionContext: typed result with positions and provenance, empty
selections returning None, regularization carry-over, linear weight
broadcasting/alignment and bound sentinels, pairwise length validation, and
the NotImplementedError for nonlinear constraints. test_resolution.py keeps
only the general end-to-end behavior of resolve_constraints (tree selectors,
provenance positions, dropped empty selections, duplicate and failing
selectors) plus the temporary to_legacy_dicts seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@janosg
janosg merged commit b1b0a75 into main Jul 3, 2026
34 of 36 checks passed
@janosg
janosg deleted the constraints-refactor-3-typed-resolution branch July 3, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant