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.