Duality gap computation in covariance.GraphicalLasso yields negative values.

### Describe the bug

The computation of the duality gap in `_dual_gap(emp_cov, precision_, alpha)` of `GraphicalLasso` uses the definition from `Duchi et al., 2012`. 
However, their duality gap is expressed given a _feasible_ dual variable. In the implementation, it is applied to a primal variable that doesn't necessarily satisfy the dual variable's feasibility constraints.
This results in potentially negative values for the duality gap.

The primal problem reads

$$P(\Theta) = \min_\Theta \varphi(\Theta) + \nu(\Theta)$$
with
$$\varphi(\Theta) =  -\log \det (\Theta)  + \langle S, \Theta \rangle \text{ and } \nu(\Theta) =  \Vert \Lambda \odot \Theta \Vert_1$$
The Fenchel-Rockafellar dual problem is then given by

$$D(W) = \min_{W}  \varphi^{\star} (W) + \nu^{\star} (-W)$$
$$= \min_{W} -\log \det(S - W) - p \quad \text{s.t. } \quad \vert W_{ij} \vert < \lambda_{ij}$$
We can then compute the duality gap as follows:
$$G(\Theta, W) = P(\Theta) + D(W)$$
Only for a *feasible* $W$ can we actually take $\theta = (S - W)^{-1}$ and thus simplify the duality gap expression and fall back on the implemented formula:
$$G(\Theta) = \langle S, \Theta \rangle + \Vert \Lambda \odot \Theta \Vert_1 - p$$ 
@mathurinm 

### Steps/Code to Reproduce

```python
import numpy as np
from sklearn.covariance import GraphicalLasso

np.random.seed(0)
X = np.random.randn(100, 50)
emp_cov = X.T @ X / len(X)

clf = GraphicalLasso(verbose=True, tol=1e-20).fit(emp_cov)
```

### Expected Results

The expected results would be positive values for the duality gap at all iterations.

### Actual Results

Here are the duality gaps for the first 4 iterations : 
```
[graphical_lasso] Iteration   0, cost  5.24e+01, dual gap 6.745e-01
[graphical_lasso] Iteration   1, cost  5.24e+01, dual gap -3.808e-05
[graphical_lasso] Iteration   2, cost  5.24e+01, dual gap -4.379e-09
[graphical_lasso] Iteration   3, cost  5.24e+01, dual gap 5.049e-09

```

### Versions

```shell
System:
    python: 3.10.6 (main, Oct 24 2022, 16:07:47) [GCC 11.2.0]
executable: /home/cpouliquen/anaconda3/envs/these-can/bin/python3.10
   machine: Linux-5.4.0-148-generic-x86_64-with-glibc2.27

Python dependencies:
          pip: 22.2.2
   setuptools: 64.0.2
      sklearn: 1.0.2
        numpy: 1.22.3
        scipy: 1.8.1
       Cython: 0.29.33
       pandas: 1.5.1
   matplotlib: 3.5.3
       joblib: 1.2.0
threadpoolctl: 3.1.0

Built with OpenMP: True
```
```

COMMENT:
Indeed, the solution suggested by @mathurinm is also what is proposed in `Rolfs 2012` to compute the duality gap, but it might be too expensive as he mentions (although it is what seems to be implemented in `skggm` for their `QUIC` solver ? ).

In the original `glasso` implementation in `CRAN`, they use a convergence criterion akin to the one proposed by @ogrisel  : Iterations stop when the average absolute parameter change is less than tol*mean(abs(offdiag($\Theta$))).

I think it would make sense to go for a stopping criterion of the sort, and spare the computation of two additional determinants and a $\Theta^{-1}$ at each iteration.

Labels: Bug, module:covariance