-
Notifications
You must be signed in to change notification settings - Fork 54
added initial wrapper for migrad #568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
09b11f0
add initial wrapper for migrad
spline2hg 38ce9e4
add hess and minos
spline2hg fcd9443
add support for custom derivative passing to migrad
spline2hg 4292b63
add corrections
spline2hg 8ed506d
make process minuit result private
spline2hg 4bdd45b
add conditional import
spline2hg 9040028
fix iterate and add docs
spline2hg 9a6b954
Merge branch 'main' into iminuit_migrad
janosg 57c0cb8
Merge branch 'main' into iminuit_migrad
janosg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| from dataclasses import dataclass | ||
| from typing import Optional | ||
|
|
||
| import numpy as np | ||
| from numpy.typing import NDArray | ||
|
|
||
| from optimagic import mark | ||
| from optimagic.config import IS_IMINUIT_INSTALLED | ||
| from optimagic.optimization.algo_options import ( | ||
| STOPPING_MAXFUN, | ||
| STOPPING_MAXITER, | ||
| ) | ||
| from optimagic.optimization.algorithm import Algorithm, InternalOptimizeResult | ||
| from optimagic.optimization.internal_optimization_problem import ( | ||
| InternalOptimizationProblem, | ||
| ) | ||
| from optimagic.typing import AggregationLevel | ||
|
|
||
| if IS_IMINUIT_INSTALLED: | ||
| from iminuit import Minuit | ||
|
|
||
|
|
||
| @mark.minimizer( | ||
| name="iminuit_migrad", | ||
| solver_type=AggregationLevel.SCALAR, | ||
| is_available=IS_IMINUIT_INSTALLED, | ||
| is_global=False, | ||
| needs_jac=True, | ||
| needs_hess=False, | ||
| supports_parallelism=False, | ||
| supports_bounds=True, | ||
| supports_linear_constraints=False, | ||
| supports_nonlinear_constraints=False, | ||
| disable_history=False, | ||
| ) | ||
| @dataclass(frozen=True) | ||
| class IminuitMigrad(Algorithm): | ||
| stopping_maxfun: int = STOPPING_MAXFUN | ||
| stopping_maxiter: int = STOPPING_MAXITER | ||
|
|
||
| def _solve_internal_problem( | ||
| self, problem: InternalOptimizationProblem, params: NDArray[np.float64] | ||
| ) -> InternalOptimizeResult: | ||
| def wrapped_objective(x: NDArray[np.float64]) -> float: | ||
| return float(problem.fun(x)) | ||
|
|
||
| m = Minuit(wrapped_objective, params, grad=problem.jac) | ||
|
|
||
| bounds = _convert_bounds_to_minuit_limits( | ||
| problem.bounds.lower, problem.bounds.upper | ||
| ) | ||
| _set_minuit_limits(m, bounds) | ||
|
|
||
| m.migrad( | ||
| ncall=self.stopping_maxfun, | ||
| iterate=self.stopping_maxiter, # review | ||
| ) | ||
|
|
||
| res = _process_minuit_result(m) | ||
| return res | ||
|
|
||
|
|
||
| def _process_minuit_result(minuit_result: Minuit) -> InternalOptimizeResult: | ||
| """Convert iminuit result to Optimagic's internal result format.""" | ||
|
|
||
| res = InternalOptimizeResult( | ||
| x=np.array(minuit_result.values), | ||
| fun=minuit_result.fval, | ||
| success=minuit_result.valid, | ||
| message=repr(minuit_result.fmin), | ||
| n_fun_evals=minuit_result.nfcn, | ||
| n_jac_evals=minuit_result.ngrad, | ||
| n_hess_evals=None, | ||
| n_iterations=minuit_result.nfcn, | ||
| status=None, | ||
| jac=None, | ||
| hess=None, | ||
| hess_inv=np.array(minuit_result.covariance), | ||
| max_constraint_violation=None, | ||
| info=None, | ||
| history=None, | ||
| ) | ||
| return res | ||
|
|
||
|
|
||
| def _convert_bounds_to_minuit_limits( | ||
| lower_bounds: Optional[NDArray[np.float64]], | ||
| upper_bounds: Optional[NDArray[np.float64]], | ||
| ) -> list[tuple[Optional[float], Optional[float]]]: | ||
| """Convert optimization bounds to Minuit-compatible limit format. | ||
|
|
||
| Transforms numpy arrays of bounds into List of tuples as expected by iminuit. | ||
| Handles special values like np.inf, -np.inf, and np.nan by converting | ||
| them to None where appropriate, as required by Minuit's limits API. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| lower_bounds : Optional[NDArray[np.float64]] | ||
| Array of lower bounds for parameters. | ||
| upper_bounds : Optional[NDArray[np.float64]] | ||
| Array of upper bounds for parameters. | ||
|
|
||
| Returns | ||
| ------- | ||
| list[tuple[Optional[float], Optional[float]]] | ||
| List of (lower, upper) limit tuples in Minuit format, where: | ||
| - None indicates unbounded (equivalent to infinity) | ||
| - Float values represent actual bounds | ||
|
|
||
| Notes | ||
| ----- | ||
| Minuit expects bounds as tuples of (lower, upper) where: | ||
| - `None` indicates no bound (equivalent to -inf or +inf) | ||
| - A finite float value indicates a specific bound | ||
| - Bounds can be asymmetric (e.g., one side bounded, one side not) | ||
|
|
||
| """ | ||
| if lower_bounds is None or upper_bounds is None: | ||
| return [] | ||
|
|
||
| return [ | ||
| ( | ||
| None if np.isneginf(lower) or np.isnan(lower) else float(lower), | ||
| None if np.isposinf(upper) or np.isnan(upper) else float(upper), | ||
| ) | ||
| for lower, upper in zip(lower_bounds, upper_bounds, strict=True) | ||
| ] | ||
|
|
||
|
|
||
| def _set_minuit_limits( | ||
| m: Minuit, bounds: list[tuple[Optional[float], Optional[float]]] | ||
| ) -> None: | ||
| """Set parameter limits on a Minuit minimizer instance. | ||
|
|
||
| Applies the converted bounds to an iminuit.Minuit object. Minuit expects | ||
| parameter limits as tuples of (lower, upper) for each parameter, where | ||
| None indicates an unbounded direction. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| m : Minuit | ||
| The iminuit minimizer instance to configure. | ||
| bounds : list[tuple[Optional[float], Optional[float]]] | ||
| List of parameter bounds as (lower, upper) tuples in Minuit format. | ||
| For each tuple: | ||
| - (None, None): Fully unbounded parameter | ||
| - (value, None): Lower bound only | ||
| - (None, value): Upper bound only | ||
| - (min, max): Two-sided constraint | ||
|
|
||
| """ | ||
| for i, (lower, upper) in enumerate(bounds): | ||
| if lower is not None or upper is not None: | ||
| m.limits[i] = (lower, upper) | ||
|
spline2hg marked this conversation as resolved.
Outdated
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.