You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a pluggable optimizer protocol to PyBioNetFit, with adapter wrappers
for scipy.optimize.minimize and scipy.optimize.least_squares. The
current optimizer suite is metaheuristic only (genetic algorithm,
particle swarm, simulated annealing, scatter search, etc.); gradient-based
methods are not currently available.
pybnf/algorithms.py contains hand-written implementations of the
metaheuristic optimizers. There is no shared optimizer protocol or
registry; each algorithm is its own class with its own conventions.
The PyBioNetFit configuration schema selects an algorithm by name via
the fit_type keyword; supported values are the existing metaheuristic
names.
Scope of this issue
Add three pieces to PyBioNetFit:
Optimizer protocol and registry under pybnf/optimizers/. Define
a common Optimizer base class and an OptimizationResult return
type; provide a name → class registry so new optimizers can be added
without modifying the dispatch code.
scipy.optimize.minimize wrapper supporting the scalar-objective
methods: L-BFGS-B, BFGS, trust-constr, Newton-CG, trust-ncg, SLSQP, COBYLA, Nelder-Mead, Powell, CG. A single wrapper
class parameterized by the method string.
scipy.optimize.least_squares wrapper supporting the residual-
shape methods: trf, dogbox, lm. A separate wrapper class
because the objective interface differs (residual vector rather than
scalar).
Out of scope
Third-party optimizer libraries (fides, cyipopt for IPOPT, nlopt, etc.). Each is a separate follow-up.
Algorithms with hand-rolled implementations specific to PyBioNetFit;
this issue covers wrappers around external libraries only.
Hyperparameter-tuning infrastructure for optimizer options.
Proposed interfaces
Core protocol
# pybnf/optimizers/base.py@dataclassclassOptimizationResult:
x: np.ndarray# best parameter vectorfun: float# best objective valuesuccess: booln_fev: int# objective evaluationsn_jev: int=0# gradient evaluations (0 for derivative-free)message: str=""history: list|None=None# optional per-iteration logclassOptimizer(ABC):
"""Common interface for local optimizers wrapped from external libraries."""@abstractmethoddeffit(self,
objective: Callable[[np.ndarray], float],
x0: np.ndarray,
bounds: Sequence[tuple[float, float]],
*,
gradient: Callable[[np.ndarray], np.ndarray] |None=None,
max_iter: int=1000,
tol: float=1e-6,
callback: Callable[[np.ndarray], None] |None=None,
) ->OptimizationResult:
...
For residual-shape optimizers (least_squares family), an alternative
signature is exposed as fit_residuals taking a residual callable
returning a vector, with an optional Jacobian callable returning a
matrix.
The existing fit_type keyword is extended to accept any name in the
optimizer registry. When fit_type resolves to a registered optimizer
class, the algorithm dispatch in algorithms.py constructs the
optimizer via get_optimizer(fit_type, method=optimizer_method, **optimizer_options) and drives it with the objective callable and the
gradient callable (when available from #385).
Edge cases and design considerations
No gradient available. If a scalar-objective method that benefits
from a gradient (L-BFGS-B, BFGS, trust-constr, Newton-CG,
etc.) is selected and no gradient is supplied, scipy falls back to
finite differences. This works but is expensive. Document the
behavior; do not silently ignore the cost.
Method does not support bounds. Some scipy methods (Newton-CG, CG, BFGS) do not accept bounds. Raise a clear error rather than
silently dropping bounds.
Method requires a Hessian.trust-ncg, trust-exact, Newton-CG accept a Hessian callable. The current scope does not
surface Hessians (see Gradient plumbing: surface objective gradients via the BNGsim backend #385's out-of-scope statement on second-order
sensitivities); these methods are still usable via scipy's
Hessian-by-finite-differences fallback. Document the cost.
scipy.optimize.least_squares Jacobian. The residual-shape
wrapper accepts a Jacobian callable returning the full Jacobian
matrix (n_residuals × n_params). The residual decomposition for the
combined SSR + qualitative-loss objective is a follow-up; the wrapper
itself just adapts the interface.
Multistart orchestration. Out of scope here. The existing
multistart logic in algorithms.py continues to drive starting
points; each restart calls the wrapped optimizer once.
Optimizer-level callbacks. The standardized callback(x)
interface is the lowest-common-denominator across scipy methods. Some
methods provide richer callback interfaces (e.g., trust-constr's OptimizeResult-style callback); these can be surfaced in later
refinements if needed.
pybnf/optimizers/registry.py: name → class registry with register decorator and get_optimizer lookup.
pybnf/optimizers/scipy_min.py: ScipyMinimizeOptimizer wrapping scipy.optimize.minimize with the 10 supported methods.
pybnf/optimizers/scipy_lsq.py: ScipyLeastSquaresOptimizer
wrapping scipy.optimize.least_squares with trf, dogbox, lm.
Integration in algorithms.py: fit_type dispatch consults the
optimizer registry when a registered name is supplied.
Configuration-schema support for optimizer_method and optimizer_options keywords.
Unit tests: each supported scipy method on a small smooth
objective with known minimum; verify convergence and that OptimizationResult fields are populated.
Documentation: a "Gradient-based optimizers" section in the user
guide listing the supported methods and configuration syntax.
No behavioral change for existing metaheuristic algorithms.
Backward compatibility
Purely additive. The existing fit_type values for hand-rolled
algorithms continue to resolve to the existing algorithm classes. New fit_type values for registered optimizers route through the new
adapter layer. No existing API method or configuration keyword changes
meaning.
Summary
Add a pluggable optimizer protocol to PyBioNetFit, with adapter wrappers
for
scipy.optimize.minimizeandscipy.optimize.least_squares. Thecurrent optimizer suite is metaheuristic only (genetic algorithm,
particle swarm, simulated annealing, scatter search, etc.); gradient-based
methods are not currently available.
Depends on #385.
Current state
pybnf/algorithms.pycontains hand-written implementations of themetaheuristic optimizers. There is no shared optimizer protocol or
registry; each algorithm is its own class with its own conventions.
surfaced by Gradient plumbing: surface objective gradients via the BNGsim backend #385.
the
fit_typekeyword; supported values are the existing metaheuristicnames.
Scope of this issue
Add three pieces to PyBioNetFit:
pybnf/optimizers/. Definea common
Optimizerbase class and anOptimizationResultreturntype; provide a name → class registry so new optimizers can be added
without modifying the dispatch code.
methods:
L-BFGS-B,BFGS,trust-constr,Newton-CG,trust-ncg,SLSQP,COBYLA,Nelder-Mead,Powell,CG. A single wrapperclass parameterized by the method string.
shape methods:
trf,dogbox,lm. A separate wrapper classbecause the objective interface differs (residual vector rather than
scalar).
Out of scope
fides,cyipoptfor IPOPT,nlopt, etc.). Each is a separate follow-up.this issue covers wrappers around external libraries only.
Proposed interfaces
Core protocol
For residual-shape optimizers (least_squares family), an alternative
signature is exposed as
fit_residualstaking a residual callablereturning a vector, with an optional Jacobian callable returning a
matrix.
Registry
scipy.optimize.minimize wrapper
scipy.optimize.least_squares wrapper
Configuration integration
In a
.conffile:The existing
fit_typekeyword is extended to accept any name in theoptimizer registry. When
fit_typeresolves to a registered optimizerclass, the algorithm dispatch in
algorithms.pyconstructs theoptimizer via
get_optimizer(fit_type, method=optimizer_method, **optimizer_options)and drives it with the objective callable and thegradient callable (when available from #385).
Edge cases and design considerations
from a gradient (
L-BFGS-B,BFGS,trust-constr,Newton-CG,etc.) is selected and no gradient is supplied, scipy falls back to
finite differences. This works but is expensive. Document the
behavior; do not silently ignore the cost.
Newton-CG,CG,BFGS) do not accept bounds. Raise a clear error rather thansilently dropping bounds.
trust-ncg,trust-exact,Newton-CGaccept a Hessian callable. The current scope does notsurface Hessians (see Gradient plumbing: surface objective gradients via the BNGsim backend #385's out-of-scope statement on second-order
sensitivities); these methods are still usable via scipy's
Hessian-by-finite-differences fallback. Document the cost.
scipy.optimize.least_squaresJacobian. The residual-shapewrapper accepts a Jacobian callable returning the full Jacobian
matrix (
n_residuals × n_params). The residual decomposition for thecombined SSR + qualitative-loss objective is a follow-up; the wrapper
itself just adapts the interface.
multistart logic in
algorithms.pycontinues to drive startingpoints; each restart calls the wrapped optimizer once.
callback(x)interface is the lowest-common-denominator across scipy methods. Some
methods provide richer callback interfaces (e.g.,
trust-constr'sOptimizeResult-style callback); these can be surfaced in laterrefinements if needed.
Deliverables
pybnf/optimizers/base.py:OptimizerABC +OptimizationResultdataclass.
pybnf/optimizers/registry.py: name → class registry withregisterdecorator andget_optimizerlookup.pybnf/optimizers/scipy_min.py:ScipyMinimizeOptimizerwrappingscipy.optimize.minimizewith the 10 supported methods.pybnf/optimizers/scipy_lsq.py:ScipyLeastSquaresOptimizerwrapping
scipy.optimize.least_squareswithtrf,dogbox,lm.algorithms.py:fit_typedispatch consults theoptimizer registry when a registered name is supplied.
optimizer_methodandoptimizer_optionskeywords.objective with known minimum; verify convergence and that
OptimizationResultfields are populated.guide listing the supported methods and configuration syntax.
Backward compatibility
Purely additive. The existing
fit_typevalues for hand-rolledalgorithms continue to resolve to the existing algorithm classes. New
fit_typevalues for registered optimizers route through the newadapter layer. No existing API method or configuration keyword changes
meaning.