# Connection between BFGS and ROOT.Minuit. Stopping criteria

**URL:** <https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729>\
**Category:** Optimization (Mathematical)\
**Tags:** optimization, hep\
**Created:** [October 21, 2020, 8:19am UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729 "2020-10-21T08:19:55Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![misha\_mikhasenko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/misha_mikhasenko/32/5060_2.png) [@misha\_mikhasenko](https://discourse.julialang.org/u/misha_mikhasenko)\
**Post date:** [October 21, 2020, 8:19am UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/1 "2020-10-21T08:19:55Z")

</div>

Dear experts on optimization problems,

I am trying to establish a connection between the common tools in High Energy Physics (HEP) and Julia ecosystem.

In HEP, the main tool for optimization is [Minuit library](http://seal.web.cern.ch/seal/snapshot/work-packages/mathlibs/minuit/). Nowadays, the original Fortran code is literally converted to C++ and used entirely for all problems just because it is so reliable. (here are [Yggdrasil binaries](https://github.com/JuliaPackaging/Yggdrasil/tree/master/M/Minuit2) thanks to @jstrube, @giordano)

On the non-HEP side, one of the most reliable and widely used optimizers seems to be [BFGS](https://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm), particularly in Julia in [NLopt](https://github.com/JuliaOpt/NLopt.jl) or [Optim](https://julianlsolvers.github.io/Optim.jl/stable/#algo/lbfgs/).

Actually, it might turn that the two libraries are implementing the same algorithm with different stopping conditions.

Here is a quote from [the original Minuit paper] on the implemented algorithm ([https://www.sciencedirect.com/science/article/pii/0010465575900399](https://www.sciencedirect.com/science/article/pii/0010465575900399)).  
 ![image](https://global.discourse-cdn.com/julialang/original/3X/6/6/66f4dcaa031d281c964b6c5195b1ef8865f9207d.png)

and the stopping criteria

 ![image](https://global.discourse-cdn.com/julialang/original/3X/1/a/1add0c14cb3d8ac0a35bbe4c86b42b212350b581.png)

So,

- Is Minuit doing BFGS or its ancestor?
- From my experience, EDM is a good indicator of convergence. Is there anything similar in the current implementation of optimizers?
- Is there a way to compute EDM with Julia, e.g. in Optim?

Thanks

(pin @pkofod, @andreasnoack, @anriseth from `Optim/bfgs.jl`)

---

<div class="post-metadata">

**Author:** ![pkofod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pkofod/32/2179_2.png) [@pkofod](https://discourse.julialang.org/u/pkofod)\
**Post date:** [October 21, 2020, 10:57am UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/2 "2020-10-21T10:57:21Z")

</div>

It’s doing something very similar to BFGS, look up variable metric or quasi-Newton methods. The stopping criterion is a test of a “hypothesis” that the gradient is zero that only makes sense for statistical problems. No this is not implemented in Optim or NLopt but you could do it with a callback I think. Many of these methods (DFP, BFGS, …) come from the econometrics/statistics literature so you will sometimes find such stopping criterions in the original papers, but they’re rarely used in general purpose software.

---

<div class="post-metadata">

**Author:** ![misha\_mikhasenko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/misha_mikhasenko/32/5060_2.png) [@misha\_mikhasenko](https://discourse.julialang.org/u/misha_mikhasenko)\
**Post date:** [October 21, 2020, 6:54pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/3 "2020-10-21T18:54:13Z")

</div>

Many thanks for the reply.

> [@pkofod](#):
>
> look up variable metric or quasi-Newton methods

What should I look for? Could you expand, please?

> [@pkofod](#):
>
> The stopping criterion is a test of a “hypothesis” that the gradient is zero that only makes sense for statistical problems. No this is not implemented in Optim or NLopt but you could do it with a callback I think.

Yes, I would like to try something in this spirit. What do you have in mind for a callback?  
EDM requires hessian that is very expensive unless it is a by-product of minimization.

> [@pkofod](#):
>
> but they’re rarely used in general-purpose software.

the objective stopping condition looks like a great thing to have. With a numerical threshold `g_tol` I have the impression that sometimes it takes ages to reach 1e-8. However, it is not clear how to adjust it, i.e. if it is safe to do

---

<div class="post-metadata">

**Author:** ![Yuan-Ru-Lin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuan-ru-lin/32/46068_2.png) [@Yuan-Ru-Lin](https://discourse.julialang.org/u/Yuan-Ru-Lin)\
**Post date:** [January 21, 2023, 6:26pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/4 "2023-01-21T18:26:35Z")

</div>

The stopping criteria can be implemented as follows.

```julia
"""
See also MIGrad in Chapter 4: Minuit Commands, https://root.cern.ch/download/minuit.pdf
"""
function miniutestop(state)
    mt = state.metadata
    edm = mt["g(x)"]' * mt["~inv(H)"] * mt["g(x)"] / 2
    edm < 1e-3 * 0.1 * 1.0
end

```

Here is a minimal working example:

```julia
using Distributions, Optim

"""
See also MIGrad in Chapter 4: Minuit Commands, https://root.cern.ch/download/minuit.pdf
""" 
function miunitstop(state)
    mt = state.metadata
    edm = mt["g(x)"]' * mt["~inv(H)"] * mt["g(x)"] / 2
    edm < 1e-3 * 0.1 * 1.0
end

data = randn(1000)
res = optimize([-Inf, 0.0], [Inf, Inf], [0.0, 1.0], Fminbox(BFGS()), Optim.Options(extended_trace=true, callback=miunitstop)) do pars
    -loglikelihood(Normal(pars...), data)
end

```

Please note that the callback has to be added in `Optim.Options` along with `extended_trace=true` for those metadata to be accessible.

Note also that the inverted hessian used in the calculation of EDM is just an approximate one that happens to be used in BFGS. So, after the minimum is found, you may want to calculate an exact one with `Zygote.hessian(pars -> -loglikelihood(Normal(pars...), data), res.minimizer)` (especially when you need to invert the hessian matrix to get the covariance matrix).

* * *

## Demo

The `miunitstop` can reduce the number of calls by ~100x, with effectively the same results:

```julia
julia> res = optimize([-Inf, 0.0], [Inf, Inf], [0.0, 1.0], Fminbox(BFGS()), Optim.Options(extended_trace=true, callback=miunitstop)) do pars
           -loglikelihood(Normal(pars...), data)
       end
 * Status: failure

 * Candidate solution
    Final objective value: 1.421423e+03

 * Found with
    Algorithm: Fminbox with BFGS

 * Convergence measures
    |x - x'| = 1.34e-02 ≰ 0.0e+00
    |x - x'|/|x'| = 1.34e-02 ≰ 0.0e+00
    |f(x) - f(x')| = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 0.00e+00 ≤ 0.0e+00
    |g(x)| = 2.06e-02 ≰ 1.0e-08

 * Work counters
    Seconds run: 0 (vs limit Inf)
    Iterations: 1
    f(x) calls: 9
    ∇f(x) calls: 9

julia> res2 = optimize([-Inf, 0.0], [Inf, Inf], [0.0, 1.0], Fminbox(BFGS())) do pars
           -loglikelihood(Normal(pars...), data)
       end
 * Status: success (objective increased between iterations)

 * Candidate solution
    Final objective value: 1.421423e+03

 * Found with
    Algorithm: Fminbox with BFGS

 * Convergence measures
    |x - x'| = 9.17e-11 ≰ 0.0e+00
    |x - x'|/|x'| = 9.15e-11 ≰ 0.0e+00
    |f(x) - f(x')| = 0.00e+00 ≤ 0.0e+00
    |f(x) - f(x')|/|f(x')| = 0.00e+00 ≤ 0.0e+00
    |g(x)| = 3.75e-08 ≰ 1.0e-08

 * Work counters
    Seconds run: 0 (vs limit Inf)
    Iterations: 4
    f(x) calls: 713
    ∇f(x) calls: 713

julia> res.minimizer
2-element Vector{Float64}:
 0.013204458490624406
 1.002498042266284

julia> res2.minimizer
2-element Vector{Float64}:
 0.013199480602918739
 1.002487701716321

```

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [January 30, 2023, 7:48pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/5 "2023-01-30T19:48:13Z")

</div>

that’s pretty amazing, for the record, in my proof of concept package [GitHub - JuliaHEP/LiteHF.jl: Light-weight HistFactory in pure Julia, attempts to be compatible with `pyhf` json format](https://github.com/JuliaHEP/LiteHF.jl)  
(this is suppose to be `pyhf` or `HistFactory` in Julia), I use something like  
[LiteHF.jl/teststatistics.jl at 69439e0e2ac0669e6c38a79ebafc4046cb749b9d · JuliaHEP/LiteHF.jl · GitHub](https://github.com/JuliaHEP/LiteHF.jl/blob/69439e0e2ac0669e6c38a79ebafc4046cb749b9d/src/teststatistics.jl#L1-L6)

which diesn’t reproduce BFGS or ROOT Minuit result numerically, but gives very close final result. It’s potentially useful to include your implementation as a legacy/sanity check option since it uses Optim.jl which is a dependency already

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [April 1, 2025, 7:34pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/6 "2025-04-01T19:34:03Z")

</div>

idk why but in the current project I work on, I find Minuit often agrees more with NelderMead, esp a box-ed version of it. Do you have any insight there?

I guess Minuit.Migrad and NelderMead both do not require gradient… but otherwise I can’t think of why they’re similar, do they both use Simplex?

---

<div class="post-metadata">

**Author:** ![Yuan-Ru-Lin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuan-ru-lin/32/46068_2.png) [@Yuan-Ru-Lin](https://discourse.julialang.org/u/Yuan-Ru-Lin)\
**Post date:** [April 3, 2025, 11:17pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/7 "2025-04-03T23:17:44Z")

</div>

> [@jling](#):
>
> I guess Minuit.Migrad and NelderMead both do not require gradient… but otherwise I can’t think of why they’re similar, do they both use Simplex?

`MIGRAD` does require gradient. In [the document of Minuit](https://root.cern.ch/download/minuit.pdf), Sec. 5.1.1 states

> [MIGRAD’s] main weakness is that it depends heavily on knowledge of the first derivatives, and fails miserably if they are very inaccurate.

> [@jling](#):
>
> idk why but in the current project I work on, I find Minuit often agrees more with NelderMead, esp a box-ed version of it. Do you have any insight there?

I need more context 🙂 I saw your messages on the #hep Slack channel. Let me take a closer look at them and we can continue this discussion there.

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [April 3, 2025, 11:32pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/8 "2025-04-03T23:32:57Z")

</div>

> [@Yuan-Ru-Lin](#):
>
> `MIGRAD` does require gradient. In [the document of Minuit](https://root.cern.ch/download/minuit.pdf), Sec. 5.1.1 states

Right, but by default it would try to use the numerical approximation of it if user does not provide a gradient?

At one point I was calling IMinuit via PythonCall so it must not have access to auto-diff gradient. (If you’re reading this after 2025, you should use Minuit2.jl instead of calling python wrapper

---

<div class="post-metadata">

**Author:** ![misha\_mikhasenko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/misha_mikhasenko/32/5060_2.png) [@misha\_mikhasenko](https://discourse.julialang.org/u/misha_mikhasenko)\
**Post date:** [April 12, 2025, 3:25pm UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/9 "2025-04-12T15:25:26Z")

</div>

With [Minuit2 being easy to access](https://github.com/JuliaHEP/Minuit2.jl), I’d love to see a comparison of `Minuit2` and `BFGS`. I remember that the algorithms are very similar, but Minuit2 has some settings magic:

- parameter ranges (addressed)
- stopping criterion (addressed)
- initial steps - i have a vague memory that it determines initial steps. I use it sometimes for `initial_invH`

```julia
BFGS(; initial_invH = x -> initial_invH)

```

Would be cool to set up the initial state between `Minuit2` and `BFGS` the same and see which arrives first

---

<div class="post-metadata">

**Author:** ![misha\_mikhasenko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/misha_mikhasenko/32/5060_2.png) [@misha\_mikhasenko](https://discourse.julialang.org/u/misha_mikhasenko)\
**Post date:** [August 11, 2026, 8:00am UTC](https://discourse.julialang.org/t/connection-between-bfgs-and-root-minuit-stopping-criteria/48729/10 "2026-08-11T08:00:01Z")

</div>

A detailed comparison of MIGRAD and `Optim.BFGS()` turned out to be one prompt away. I found the result interesting and instructive, so I am posting it as a follow-up of our discussion.

> **How this note was made**
>
> **Model:** Codex Sol 5.6, reasoning level High  
> **Project context:** `NativeMinuit.jl` + `Optim.jl`
> 
> **Prompt:**
> 
> > I have been fascinated by the numerical algorithm of Minuit2 and stability it has. `Optim.BFGS()` should use a similar algorithm, but it seems not to be well tuned for the problems I solve. I would like a compact explanation of how the algorithms work, how similar they are, and where they diverge. Study the implementations and check the existing setting mappings in FitterHEP.jl.
> 
> The analysis used the ROOT Minuit2, Optim.jl, NativeMinuit.jl, and FitterHEP.jl sources. The matrix-update identities were also checked numerically. The principal local versions were Optim 2.2.1 and NativeMinuit 0.6.2.

## Common quasi-Newton iteration

Both algorithms maintain an approximation M\_k to the inverse Hessian,

M\_k \approx [\nabla^2 f(x\_k)]^{-1},

and form the search direction

p\_k=-M\_k g\_k,\qquad g\_k=\nabla f(x\_k).

A line search selects \alpha\_k, after which

x\_{k+1}=x\_k+\alpha\_kp\_k.

The curvature update uses

s=x\_{k+1}-x\_k,\qquad y=g\_{k+1}-g\_k,

and attempts to satisfy the secant equation

M\_{k+1}y=s.

This is the common algorithmic structure of MIGRAD and Optim’s BFGS implementation.

## BFGS and MIGRAD matrix updates

[Optim’s BFGS implementation](https://github.com/JuliaNLSolvers/Optim.jl/blob/9b0fe99c665503ff706f0defcb90af2a0e35e78f/src/multivariate/solvers/first_order/bfgs.jl) uses the inverse-BFGS update

M\_{k+1} = M\_k+ \frac{\delta+\gamma}{\delta^2}ss^\mathsf T - \frac{M\_ky\,s^\mathsf T+s\,y^\mathsf TM\_k}{\delta},

where

\delta=s^\mathsf Ty,\qquad \gamma=y^\mathsf TM\_ky.

Optim applies this update only when \delta\>0; otherwise it retains the previous matrix.

Default MIGRAD first constructs the DFP update

M\_{\mathrm{DFP}} = M\_k+\frac{ss^\mathsf T}{\delta} -\frac{(M\_ky)(M\_ky)^\mathsf T}{\gamma}.

When \delta\>\gamma, it additionally applies

\gamma \left( \frac{s}{\delta}-\frac{M\_ky}{\gamma} \right) \left( \frac{s}{\delta}-\frac{M\_ky}{\gamma} \right)^\mathsf T.

Expanding this term gives exactly the inverse-BFGS formula. Therefore, default MIGRAD uses:

- the BFGS update when s^\mathsf Ty\>y^\mathsf TM\_ky;
- the DFP update otherwise.

This behaviour is explicit in ROOT’s [`DavidonErrorUpdator.cxx`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/DavidonErrorUpdator.cxx). ROOT also provides a separate pure-BFGS updater, but the standard `MnMigrad` constructor uses the Davidon updater.

I checked the identity numerically using non-collinear random secant pairs. For \delta/\gamma=3, the MIGRAD and BFGS matrices agreed to 1.9\times10^{-15}. For \delta/\gamma=0.5, the MIGRAD update agreed with DFP and differed from BFGS.

Thus, some MIGRAD iterations use exactly the BFGS matrix update. The standard MIGRAD algorithm as a whole is a DFP/BFGS hybrid.

## Initial scaling

Optim’s default initial inverse-Hessian approximation is the identity matrix. It can be replaced through `initial_invH` or scaled through `initial_stepnorm`, as shown in the [`BFGS` constructor and initialization code](https://github.com/JuliaNLSolvers/Optim.jl/blob/9b0fe99c665503ff706f0defcb90af2a0e35e78f/src/multivariate/solvers/first_order/bfgs.jl).

Minuit derives its initial metric from the user-supplied parameter steps. For an unbounded parameter with initial step e\_i, Minuit estimates the diagonal curvature as

g\_{2,i}=\frac{2\,\mathrm{Up}}{e\_i^2},

where `Up` is `errordef`. The corresponding diagonal inverse-Hessian estimate is

(M\_0)\_{ii}\approx\frac{e\_i^2}{2\,\mathrm{Up}}.

This construction appears in ROOT’s [`InitialGradientCalculator.cxx`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/InitialGradientCalculator.cxx).

Minuit’s initial parameter errors therefore have two roles: they influence numerical differentiation and define the initial optimization metric. This matters when parameters have different units or characteristic scales.

An equivalent Optim initialization is

```julia
initial_invH =
    _ -> Diagonal(step_sizes.^2 ./ (2errordef))

```

For a negative-log-likelihood objective with `errordef = 0.5`, this reduces to

```julia
initial_invH = _ -> Diagonal(step_sizes.^2)

```

For a \chi^2 objective with `errordef = 1`, the diagonal is instead `step_sizes.^2 / 2`.

## Line search and numerical derivatives

Optim’s default BFGS method uses a Hager–Zhang line search. It tests sufficient-decrease and curvature conditions and may evaluate both the objective and directional derivative at several trial points.

MIGRAD uses a parabolic line search. It first evaluates the nominal quasi-Newton step and then uses two- and three-point quadratic interpolation. ROOT’s [`MnLineSearch.cxx`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/MnLineSearch.cxx) limits this procedure to 12 line-search iterations.

This difference affects the cost of numerical gradients. A directional-derivative evaluation in Optim may require a complete numerical gradient. MIGRAD primarily evaluates the objective during its line search and recalculates the gradient after choosing a new point.

Minuit’s numerical gradient calculation also refines the finite-difference step separately for every parameter. The number of refinement cycles and their tolerances depend on the strategy level; the corresponding settings are defined in [`MnStrategy.cxx`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/MnStrategy.cxx).

With accurate analytic or automatic gradients, the Hager–Zhang search provides stronger general-purpose line-search conditions. With an expensive objective and numerical derivatives, the Minuit procedure may require fewer objective evaluations per line search.

## Stopping criteria

Optim normally tests the infinity norm of the gradient,

\lVert g\rVert\_\infty \leq g\_{\mathrm{abstol}},

with a default threshold of 10^{-8}. Changes in the parameters or objective can also terminate minimization. These conditions are implemented in [Optim’s convergence assessment](https://github.com/JuliaNLSolvers/Optim.jl/blob/9b0fe99c665503ff706f0defcb90af2a0e35e78f/src/utilities/assess_convergence.jl).

MIGRAD instead uses the Expected Distance to Minimum,

\mathrm{EDM}=\frac12g^\mathsf TMg.

Under the local quadratic model, EDM estimates the remaining decrease in the objective.

The effective MIGRAD target is

\mathrm{EDM}\_{\mathrm{goal}} = 0.002\, \texttt{tolerance}\, \mathrm{errordef},

subject to a machine-precision floor. During the variable-metric iteration, Minuit corrects EDM using its covariance-change estimate `Dcovar`,

\mathrm{EDM}\_{\mathrm{corrected}} = \mathrm{EDM}\,(1+3\,\mathrm{Dcovar}).

Depending on the strategy and `Dcovar`, MIGRAD may recompute the Hessian and continue minimization from the refined state. This control flow is implemented in ROOT’s [`VariableMetricBuilder.cxx`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/VariableMetricBuilder.cxx).

The EDM callback proposed earlier in this thread,

```julia
edm = dot(g, invH * g) / 2

```

therefore reproduces the principal Minuit convergence quantity when `invH` is Optim’s current BFGS approximation. It does not reproduce the `Dcovar` correction, Hessian recomputation, or subsequent MIGRAD restart.

There is no parameter-independent conversion from Minuit’s `tolerance` to Optim’s `g_tol`. A raw gradient threshold depends on parameter units and scaling, whereas EDM includes the local inverse-Hessian metric.

## Matrix validation and recovery

Optim preserves positive definiteness by applying its BFGS update only when s^\mathsf Ty\>0. If the search direction is not a descent direction, the inverse-Hessian approximation can be reset.

MIGRAD additionally:

- checks whether -Mg is a descent direction;
- repairs a non-positive-definite metric by shifting its eigenvalues;
- tracks the estimated covariance change through `Dcovar`;
- can run HESSE when the variable-metric covariance remains uncertain;
- can resume the variable-metric iteration after HESSE.

These operations belong to the [`VariableMetricBuilder`](https://github.com/root-project/root/blob/8284b7e4c199c603721c4cbb790fddb439ce38e2/math/minuit2/src/VariableMetricBuilder.cxx) control flow rather than to the Davidon update itself.

## Bounds

Minuit transforms bounded external parameters into unconstrained internal coordinates and performs MIGRAD in the internal space.

Optim normally handles box constraints through `Fminbox`, which adds a logarithmic barrier around an unconstrained inner optimizer. FitterHEP can alternatively use logistic and softplus transformations.

These methods enforce the same physical limits but do not define the same optimization problem in the working coordinates. A comparison involving bounds therefore includes differences in the transformation or barrier method, in addition to differences between MIGRAD and BFGS.

## FitterHEP setting mappings

The [FitterHEP Optim backend](https://github.com/RUB-EP1/FitterHEP.jl/blob/f2dabf6f422556c8a73d28b23758dd3cf13eb2a4/src/backends/optim.jl) initializes Optim’s BFGS metric using

```julia
Diagonal(step_sizes .^ 2)

```

and supplies the corresponding diagonal Hessian preconditioner to LBFGS.

This agrees with Minuit for `errordef = 0.5`. The general mapping should include `errordef`:

```julia
initial_invH =
    Diagonal(step_sizes.^2 ./ (2errordef))

```

and, for the LBFGS Hessian preconditioner,

```julia
P = Diagonal((2errordef) ./ step_sizes.^2)

```

For example, with steps `[2, 3]`:

- `errordef = 0.5` gives `[4, 9]`;
- `errordef = 1` gives `[2, 4.5]`;
- `errordef = 2` gives `[1, 2.25]`.

Fixed parameters are treated consistently in intent: FitterHEP removes them from Optim’s active vector, while Minuit marks them as fixed.

The stopping options are not equivalent mappings. `g_tol`, `x_tol`, and `f_tol` retain their Optim meanings and do not correspond directly to Minuit’s EDM tolerance.

The current benchmarks also do not initialize the backends identically. In the [mass-fit benchmark](https://github.com/RUB-EP1/FitterHEP.jl/blob/f2dabf6f422556c8a73d28b23758dd3cf13eb2a4/benchmark/massfit_toys.jl), Minuit receives the explicit steps

```julia
[0.01, 0.05, 0.05, 0.1]

```

while Optim receives generic default steps.

In addition, `MinuitBackend` enables its SIMPLEX fallback by default, whereas the Optim BFGS backend has no equivalent fallback. These are reasonable backend defaults, but they should be equalized when comparing the underlying minimizers.

## Summary

The comparison gives the following conclusions:

1. MIGRAD and `Optim.BFGS()` use the same quasi-Newton iteration structure.
2. Default MIGRAD switches between DFP and BFGS matrix updates.
3. EDM can be calculated directly from Optim’s BFGS inverse-Hessian approximation.
4. Minuit differs from plain BFGS through its initialization, numerical derivatives, line search, EDM correction, covariance monitoring, matrix repair, Hessian recomputation, and bound transformations.
5. Minuit’s initial steps map to
(M\_0)\_{ii}=\frac{e\_i^2}{2\,\mathrm{errordef}},
rather than to e\_i^2 for every objective convention.
6. A controlled comparison should use the same initial metric, derivative information, working coordinates, stopping rule, fallback policy, and objective-call budget.
