Repository: scipy/scipy

Issue:
BUG: linprog failure - OptimizeResult.x returns NoneType
### Describe your issue.

NetworkX is seeing a regression in scipy-1.9.0rc1 in `scipy.optimize.linprog`. Comparing the `OptimizeResult.x` attribute to an integer threshold which worked in scipy 1.8 is now raising a `TypeError: '>=' not supported between instances of 'NoneType' and 'int'`.

See [this CI log](https://github.com/networkx/networkx/runs/7043690993?check_suite_focus=true) for details.

A quick look at `blame` suggests that the behavior change was likely introduced in the switch to `highs` as the default method #15512 .

### Reproducing Code Example

```python
Unfortunately the NX example is deeply embedded in a quite complicated test, so I don't have an MRE to hand. I will try to tease out a more minimal example than the above CI failure.
```


### Error message

```shell
Relevant snippet from above CI log::

            program_result = optimize.linprog(c, A_eq=a_eq, b_eq=b_eq)
>           bool_result = program_result.x >= 0
E           TypeError: '>=' not supported between instances of 'NoneType' and 'int'
```


### SciPy/NumPy/Python version information

1.9.0rc1 1.23.0 sys.version_info(major=3, minor=10, micro=4, releaselevel='final', serial=0)


Discussion:
Okay, after extracting a MRE, it seems like this is mostly a problem on NetworkX's side. The only thing that might be considered on scipy's end is the difference in return type: i.e. linprog with `"interior-point"` gives a 1D array for the `x` attribute on failure (consistent with the docstring Returns description) whereas `"highs"` gives `None`.

Here's the example:

```python
>>> import numpy as np
>>> import scipy as np
>>> A_eq = np.array([[ 0,  0],
...        [ 1,  2],
...        [ 0,  0],
...        [ 0, -1],
...        [-1, -1],
...        [ 0,  0],
...        [ 1,  1]])
>>> b_eq = np.array([0, 0, 0, 0, 0, 0, 1])
>>> c = np.array([-1, -1])
>>> res_sp1_9 = sp.optimize.linprog(c, A_eq=a_eq, b_eq=b_eq)
>>> res_sp1_9
           con: None
 crossover_nit: 0
         eqlin:  marginals: None
  residual: None
           fun: None
       ineqlin:  marginals: None
  residual: None
         lower:  marginals: None
  residual: None
       message: "The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is b'At lower/fixed bound')"
           nit: 0
         slack: None
        status: 2
       success: False
         upper:  marginals: None
  residual: None
             x: None
>>> res_sp1_8 = sp.optimize.linprog(c, A_eq=a_eq, b_eq=b_eq, method="interior-point")
>>> res_sp1_8
     con: array([0., 0., 0., 0., 0., 0., 1.])
     fun: 0.0
 message: 'There is a linear combination of rows of A_eq that results in zero, suggesting a redundant constraint. However the same linear combination of b_eq is nonzero, suggesting that the constraints conflict and the problem is infeasible.'
     nit: 0
   slack: array([], dtype=float64)
  status: 2
 success: False
       x: array([ 0., -0.])
```

Notice the difference in `res.x` for the two results. I suspect the best practices is always to check that `success == True` prior to doing any further analysis on the OptimizeResult, in which case feel free to close!
Yes, IIRC we considered this, and we decided that the change was better than for SciPy to continue returning an incorrect result for `x`. We should mention this in the release notes, though. 
@Kai-Striega @mckib2 thoughts here?
@tylerjereddy should I open a PR for making an addition to the 1.9.0 release notes?
> We should mention this in the release notes, though.

Yes, adding a note to the release notes would be helpful for broadcasting this change in behavior.  @rossbar , I suggest testing against the result `status` for an acceptable code (usually success) before accessing the result
> Yes, adding a note to the release notes would be helpful for broadcasting this change in behavior.

+1

> Yes, IIRC we considered this, and we decided that the change was better than for SciPy to continue returning an incorrect result for `x` 

I wasn't involved in the discussion when it occurred. When trying to find it I couldn't. However, I don't think there's much merit to changing the existing behaviour.
> Yes, adding a note to the release notes would be helpful for broadcasting this change in behavior

In addition to adding a release note, it might be worth also updating the `Returns` section of the docstring to note that the listed return types of the fields may change when the optimization is not successful.
> @tylerjereddy should I open a PR for making an addition to the 1.9.0 release notes?

@mdhaber sure, you can open the PR to the maintenance branch; maybe hold off on merging until I/someone pushes in the bump to `1.9.0rc2` version though. I'll probably do that in the next day or two. Currently stuck traveling--it is a bit crazy with flight cancellations in the US/Canada at the moment.
@rossbar I opened a PR about the release notes. Would you open a PR for the changes to the documentation you're suggesting? 
I merged Matt's backport to the `1.9.0` release notes. If you change the docstring(s) proper just put a backport label on the PR against `main` so that I see it as an option when collecting PRs to put in for the release branch.

Resolving patch:
diff --git a/scipy/optimize/_linprog.py b/scipy/optimize/_linprog.py
--- a/scipy/optimize/_linprog.py
+++ b/scipy/optimize/_linprog.py
@@ -357,7 +357,10 @@ def linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None,
     Returns
     -------
     res : OptimizeResult
-        A :class:`scipy.optimize.OptimizeResult` consisting of the fields:
+        A :class:`scipy.optimize.OptimizeResult` consisting of the fields
+        below. Note that the return types of the fields may depend on whether
+        the optimization was successful, therefore it is recommended to check
+        `OptimizeResult.status` before relying on the other fields:
 
         x : 1-D array
             The values of the decision variables that minimizes the


Test patch: