Optimize action fails for certain optimize methods when tol is given
Please check the Issue Tracker if your bug is already listed before reporting.
Summary
finesse.analysis.actions.optimize fails for certain methods when a tolerance is given. The function calls scipy.optimize.minimize and passes tol as a list, but this should be a float (https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html).
See line 247 of finesse/analysis/actions/optimisation.py
Steps to reproduce
Minimal example:
model = finesse.Model()
model.parse(
"""
l l1 P=1
pd P l1.p1.o
"""
)
sol = model.run(
Minimize(model.P, model.l1.P, method=None, bounds=[-2, 2], tol=1e-8)
)
Bug behavior
Crashes for certain methods. In this case
What is the expected correct behavior?
Should not crash.
Relevant logs and/or screenshots
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[11], line 19
4 base = finesse.Model()
5 base.parse(
6 """
7 l l1 P=1
(...) 16 """
17 )
---> 19 base.run(Maximize(base.P, base.m2_z.DC, method=None, bounds=[-2, 2], tol=1e-10, verbose=True))
20 print(base.m2_z)
21 sol = base.run()
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/model.py:110, in locked_when_built.<locals>.wrapper(self, *args, **kwargs)
106 if self.is_built:
107 raise Exception(
108 f"Model has been built for a simulation, cannot use {func} here"
109 )
--> 110 return func(self, *args, **kwargs)
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/model.py:3322, in Model.run(self, analysis, return_state, progress_bar, simulation_type, simulation_options)
3319 else:
3320 raise ValueError(f"Cannot handle analysis input `{analysis}`")
-> 3322 rtn = _analysis._run(
3323 self,
3324 return_state=return_state,
3325 progress_bar=progress_bar,
3326 simulation_type=simulation_type,
3327 simulation_options=simulation_options,
3328 )
3330 self.analysis = curr_analysis
3332 if not return_state:
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/analysis/actions/base.py:239, in Action._run(self, model, return_state, progress_bar, simulation_type, simulation_options)
236 else:
237 action = self
--> 239 result = state.apply(action)
241 if type(result) is tuple:
242 sol = BaseSolution("root")
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/analysis/actions/base.py:105, in AnalysisState.apply(self, action)
103 def apply(self, action):
104 start = time.time_ns()
--> 105 sol = action._do(self)
106 if sol is not None:
107 if not isinstance(sol, BaseSolution):
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/analysis/actions/series.py:65, in Series._do(self, state)
63 for action in pbar:
64 pbar.set_description_str(self.name)
---> 65 next_sol = state.apply(action)
66 if self.flatten and next_sol is not None:
67 first.add(next_sol)
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/analysis/actions/base.py:105, in AnalysisState.apply(self, action)
103 def apply(self, action):
104 start = time.time_ns()
--> 105 sol = action._do(self)
106 if sol is not None:
107 if not isinstance(sol, BaseSolution):
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/finesse/analysis/actions/optimisation.py:243, in Optimize._do(self, state)
240 else:
241 opfunc = self.opfunc
--> 243 res = minimize(
244 opfunc,
245 np.array([_.value for _ in params]),
246 bounds=np.atleast_2d(self.bounds) if self.bounds else None,
247 tol=[self.tol] if self.tol else None,
248 options={"maxiter": self.max_iterations, **self.kwargs},
249 method=self.method,
250 args=(params, state, dws),
251 )
252 if res.nit == 1:
253 warnings.warn(
254 "Optimisation finished after 1 iteration, tolerances might be too high",
255 OptimizationWarning,
256 stacklevel=1,
257 )
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/scipy/optimize/_minimize.py:784, in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)
781 res = _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,
782 **options)
783 elif meth == 'l-bfgs-b':
--> 784 res = _minimize_lbfgsb(fun, x0, args, jac, bounds,
785 callback=callback, **options)
786 elif meth == 'tnc':
787 res = _minimize_tnc(fun, x0, args, jac, bounds, callback=callback,
788 **options)
File ~/miniconda3/envs/finessedoc/lib/python3.14/site-packages/scipy/optimize/_lbfgsb_py.py:461, in _minimize_lbfgsb(fun, x0, args, jac, bounds, disp, maxcor, ftol, gtol, eps, maxfun, maxiter, iprint, callback, maxls, finite_diff_rel_step, workers, **unknown_options)
459 g = g.astype(np.float64)
460 # x, f, g, wa, iwa, task, csave, lsave, isave, dsave = \
--> 461 _lbfgsb.setulb(m, x, low_bnd, upper_bnd, nbd, f, g, factr, pgtol, wa,
462 iwa, task, lsave, isave, dsave, maxls, ln_task)
464 if task[0] == 3:
465 # The minimization routine wants f and g at the current x.
466 # Note that interruptions due to maxfun are postponed
467 # until the completion of the current minimization iteration.
468 # Overwrite f and g:
469 f, g = func_and_grad(x)
TypeError: only 0-dimensional arrays can be converted to Python scalars
Possible Fixes
Do not put tol in a list.
Urgency
If the bug is preventing Finesse from being used, for example if it produces seemingly valid but incorrect results, add this label.