Repository: scipy/scipy
Pull request: BUG: fix linprog revised simplex docstring problem failure
Fixes #10124
`linprog` with `method='revised simplex'` failed the `linprog` docstring problem. The cause was that option `'disp'=True` executes `_postsolve`, which changes the solution vector. Simple fix was to copy the solution vector before passing it into `_postsolve`.

`linprog` with `method='interior-point'` was also reporting an error with the `linprog` docstring problem, also because of `'disp'=True`. A variable expected to be a `float` was occasionally an `int`, and a format specifier string caused issues. Solution: cast to float.

Problem statement:
linprog(method='revised simplex') doctest bug
`linprog` `method='revised simplex'` (in the 1.3 branch) doesn't solve the example problem in the docstring. The example was supposed to be a doctest, but all the outputs were marked "# may vary", so the problem wasn't caught. 

I think a small bug in the phase 1 problem formulation was introduced at some point. Strangely, the issue didn't surface in any of the other tests (or the much more difficult benchmark problems). I can fix it before 1.3 and add the example as a regular test before 1.3 is released. @tylerjereddy Do I submit a PR to the "maintenance/1.3.x" branch?

### Reproducing code example:
```
c = [-1, 4]
A = [[-3, 1], [1, 2]]
b = [6, 4]
x0_bounds = (None, None)
x1_bounds = (-3, None)
from scipy.optimize import linprog
res = linprog(c, A_ub=A, b_ub=b, bounds=(x0_bounds, x1_bounds), method="revised simplex",
              options={"disp": True})
print(res)
```
The status is 4 (algorithm reported success, but solution was infeasible).

Hints from issue discussion:
We are at RC phase to catch these post branch issues. If needed, which seems so, Tyler would backport it
If you open a PR also mark it backport candidate and remove it here maybe? 
Will try to do this weekend. Still scheduled to release 5/20?

Commit message: BUG: optimize: fix linprog docstring failure for interior-point

Patch to review:
diff --git a/scipy/optimize/_linprog_ip.py b/scipy/optimize/_linprog_ip.py
index 781e61ae93d7..a8b4ec6f8d7b 100644
--- a/scipy/optimize/_linprog_ip.py
+++ b/scipy/optimize/_linprog_ip.py
@@ -794,7 +794,8 @@ def eta(g=gamma):
         go = rho_p > tol or rho_d > tol or rho_A > tol
 
         if disp:
-            _display_iter(rho_p, rho_d, rho_g, alpha, float(rho_mu), obj)
+            _display_iter(rho_p, rho_d, rho_g, float(alpha),
+                          float(rho_mu), obj)
         if callback is not None:
             x_o, fun, slack, con, _, _ = _postsolve(x/tau, *_T_o,
                                                     tol=tol)
diff --git a/scipy/optimize/_linprog_rs.py b/scipy/optimize/_linprog_rs.py
index 8cc98d6d7c09..7c2d5dbfe595 100644
--- a/scipy/optimize/_linprog_rs.py
+++ b/scipy/optimize/_linprog_rs.py
@@ -93,7 +93,6 @@ def _phase_one(A, b, x0, maxiter, tol, maxupdate, mast, pivot, callback=None,
     basis = basis[keep_rows]
     x = x[:n]
     m = A.shape[0]
-
     return x, basis, A, b, residual, status, iter_k
 
 
@@ -341,7 +340,7 @@ def _phase_two(c, A, x, b, maxiter, tol, maxupdate, mast, pivot, iteration=0,
             else:
                 phase = 2
                 x_postsolve = x
-            x_o, fun, slack, con, _, _ = _postsolve(x_postsolve, *_T_o,
+            x_o, fun, slack, con, _, _ = _postsolve(x_postsolve.copy(), *_T_o,
                                                     tol=tol)
 
             if callback is not None:
diff --git a/scipy/optimize/tests/test_linprog.py b/scipy/optimize/tests/test_linprog.py
index b3d7f1bb5663..01faa554eb77 100644
--- a/scipy/optimize/tests/test_linprog.py
+++ b/scipy/optimize/tests/test_linprog.py
@@ -1301,6 +1301,25 @@ def test_bug_8973_2(self):
                       method=self.method, options=self.options)
         _assert_success(res, desired_x=[-2], desired_fun=0)
 
+    def test_bug_10124(self):
+        """
+        Test for linprog docstring problem
+        'disp'=True caused revised simplex failure
+        """
+        c = np.zeros(1)
+        A_ub = np.array([[1]])
+        b_ub = np.array([-2])
+        bounds = (None, None)
+        c = [-1, 4]
+        A_ub = [[-3, 1], [1, 2]]
+        b_ub = [6, 4]
+        bounds = [(None, None), (-3, None)]
+        o = {"disp": True}
+        o.update(self.options)
+        res = linprog(c, A_ub, b_ub, A_eq, b_eq, bounds,
+                      method=self.method, options=self.options)
+        _assert_success(res, desired_x=[10, -3], desired_fun=-22)
+
 #########################
 # Method-specific Tests #
 #########################


Code review comments:
diff_hunk: @@ -794,7 +794,8 @@ def eta(g=gamma):
         go = rho_p > tol or rho_d > tol or rho_A > tol
 
         if disp:
-            _display_iter(rho_p, rho_d, rho_g, alpha, float(rho_mu), obj)
+            _display_iter(rho_p, rho_d, rho_g, float(alpha),
path: scipy/optimize/_linprog_ip.py
text: @user1:
Minor style nitpick, I think it would be more readable to convert this inside `` _display_iter ``. Casting to a float is conceptually a part of ``_display_iter`` (otherwise it would not be needed). Further, someone who is reading _linprog_ip won't have to think about **why** these are being cast - instead only seeing it when it becomes relevant.

@author:
Fair. And I should probably cast them all. 

@author:
Done.