Skip to content

Use pydantic for algo option validation (instead of our own validation logict). - #696

Merged
janosg merged 7 commits into
mainfrom
pydantic-for-algo-options
Aug 4, 2026
Merged

janosg merged 7 commits into
mainfrom
pydantic-for-algo-options

Conversation

@janosg

@janosg janosg commented Aug 2, 2026 •

Copy link
Copy Markdown
Member

Current situation

Algorithm options are converted and validated by a hand-written converter table (TYPE_CONVERTERS in type_conversion.py) that Algorithm.post_init looks up per field, keyed by type objects for eight supported types. AlgoInfo and InternalOptimizeResult carry ~50 lines of manual isinstance checks each.

Two problems are hard to solve within this design:

  • In modules with from future import annotations — which the how-to-document-optimizers guide requires — field.type is an annotation string, so the table lookup silently fails and options are neither coerced nor validated. This already affects most optimizer modules.
  • Anything beyond the eight exact types is skipped: PositiveInt | None, Literals, unions, nested option types. Supporting them would mean reimplementing generic type introspection and coercion — exactly what pydantic provides. Notably, our type aliases are already built on annotated-types (Annotated[int, Gt(0)]), which is pydantic's native constraint vocabulary.

Proposed Implementation

Replace our validation logic with pydantic.

Backwards compatibility

  • Algorithm classes (including user-defined ones) are still written as plain frozen dataclasses; validation is added by mark.minimizer, so no algorithm definition changes.
  • All public exception types and message prefixes are preserved (InvalidAlgoOptionError, InvalidAlgoInfoError, TypeError for InternalOptimizeResult), including for unknown constructor kwargs (extra="forbid").
  • The internal classes use strict mode, mirroring the old isinstance semantics.

Remaining breaking changes:

  • Fractional floats for int options raise instead of being silently truncated (stopping_maxiter=5.5 was 5; integral floats like 1e6 still coerce). Numeric strings in float notation ("1e6") are rejected for int options; plain int strings still work.
  • Fields that were previously unvalidated (Literals, unions, str, arrays, ...) are now enforced, so invalid values that used to pass silently now raise.

Alternatives

PR #691 patches the string-annotation symptom by adding a second, string-keyed converter table (TYPE_CONVERTERS_BY_NAME) and branching on the annotation kind in post_init. That fixes the silently-skipped coercion but keeps everything else: optional and union types remain unvalidated, coverage stays limited to eight hand-written converters, and two parallel tables must be kept in sync with typing.py forever. This PR solves the whole class of problems, deletes code instead of adding it, and extends naturally to the other validation boilerplate in the codebase (#694, #695). If this is merged, #691 should be closed and #690 rebased onto main afterwards.

@janosg
janosg requested a review from timmens August 2, 2026 17:55
@codecov

codecov Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/optimagic/mark.py 91.07% <100.00%> (+1.07%) ⬆️
src/optimagic/optimization/algorithm.py 94.53% <100.00%> (-0.45%) ⬇️
src/optimagic/optimizers/bayesian_optimizer.py 91.66% <100.00%> (+0.07%) ⬆️
src/optimagic/optimizers/fides.py 96.05% <100.00%> (+0.16%) ⬆️
src/optimagic/optimizers/gfo_optimizers.py 100.00% <100.00%> (ø)
src/optimagic/optimizers/pygmo_optimizers.py 94.98% <100.00%> (+0.01%) ⬆️
src/optimagic/optimizers/tranquilo.py 99.21% <100.00%> (+0.06%) ⬆️
src/optimagic/typing.py 91.15% <100.00%> (+2.38%) ⬆️

... and 2 files with indirect coverage changes

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

@timmens timmens left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice; I couldn't find anything to flag.

@janosg
janosg merged commit 185ac8c into main Aug 4, 2026
21 checks passed
@janosg
janosg deleted the pydantic-for-algo-options branch August 4, 2026 14:58
janosg added a commit that referenced this pull request Sep 29, 2026
Brings in #696, which replaces the hand-written option converter table with
pydantic validation. PR #691, whose fix for the same problem was the first
commit on this branch, was closed in favor of #696, so its changes are dropped
here rather than merged:

- src/optimagic/type_conversion.py: deleted (superseded by validated_dataclass
  in optimagic/typing.py), including the TYPE_CONVERTERS_BY_NAME lookup #691
  added.
- Algorithm.__post_init__: removed, main validates via mark.minimizer instead.
- tests/.../test_algorithm_future_annotations.py: deleted. Its coverage of the
  postponed-annotations path is provided by main's
  test_algo_option_validation_stringified.py, and its registry-wide int
  coercion test by test_all_algorithms_can_be_instantiated_with_defaults.

Docs conflicts resolved by keeping this branch's docstrings on top of main's
widened annotations:

- Fides.hessian_update_strategy is now HessianUpdateStrategy, not a bare
  Literal, so its docstring no longer claims that HessianApproximation
  instances are unavailable; it explains that Broyden must be passed as an
  instance because phi has no default.
- PygmoMbh.inner_algorithm uses the PygmoAlgorithm alias.

Also documents the new constraint that field annotations must be resolvable at
runtime in how_to_document_optimizers.md, since pydantic now resolves them and
a TYPE_CHECKING-only name in a field type raises on instantiation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants