Skip to content

Commit c5c0174

Browse files
authored
[opt_transport] Update Description on outputs of LP solvers (#465)
* update opt_transport * minor changes * fix space
1 parent 9c9bdd0 commit c5c0174

File tree

1 file changed

+35
-49
lines changed

1 file changed

+35
-49
lines changed

lectures/opt_transport.md

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ jupytext:
44
extension: .md
55
format_name: myst
66
format_version: 0.13
7-
jupytext_version: 1.14.4
7+
jupytext_version: 1.17.1
88
kernelspec:
99
display_name: Python 3 (ipykernel)
1010
language: python
@@ -330,12 +330,12 @@ Let's write Python code that sets up the problem and solves it.
330330
m = 3
331331
n = 5
332332
333-
p = np.array([50, 100, 150])
334-
q = np.array([25, 115, 60, 30, 70])
333+
p = np.array([50.0, 100.0, 150.0])
334+
q = np.array([25.0, 115.0, 60.0, 30.0, 70.0])
335335
336-
C = np.array([[10, 15, 20, 20, 40],
337-
[20, 40, 15, 30, 30],
338-
[30, 35, 40, 55, 25]])
336+
C = np.array([[10.0, 15.0, 20.0, 20.0, 40.0],
337+
[20.0, 40.0, 15.0, 30.0, 30.0],
338+
[30.0, 35.0, 40.0, 55.0, 25.0]])
339339
340340
# Vectorize matrix C
341341
C_vec = C.reshape((m*n, 1), order='F')
@@ -371,9 +371,13 @@ Here `'F'` stands for "Fortran", and we are using Fortran style column-major ord
371371
lecture by Alfred
372372
Galichon](https://www.math-econ-code.org/dynamic-programming).)
373373
374-
**Interpreting the warning:**
374+
**Interpreting the solver behavior:**
375375
376-
The above warning message from SciPy points out that A is not full rank.
376+
Looking at matrix $A$, we can see that it is rank deficient.
377+
378+
```{code-cell} ipython3
379+
np.linalg.matrix_rank(A) < min(A.shape)
380+
```
377381
378382
This indicates that the linear program has been set up to include one or more redundant constraints.
379383
@@ -389,7 +393,9 @@ The singularity of $A$ reflects that the first three constraints and the last f
389393
390394
One equality constraint here is redundant.
391395
392-
Below we drop one of the equality constraints, and use only 7 of them.
396+
Fortunately, SciPy's `linprog` function handles the redundant constraints automatically without explicitly warning about rank deficiency.
397+
398+
But we can drop one of the equality constraints, and use only 7 of them.
393399
394400
After doing this, we attain the same minimized cost.
395401
@@ -475,11 +481,11 @@ The minimized cost from the optimal transport plan is given by the $fun$ variabl
475481
We can also solve optimal transportation problems using a powerful tool from
476482
QuantEcon, namely, `quantecon.optimize.linprog_simplex`.
477483
478-
While this routine uses the same simplex algorithm as
479-
`scipy.optimize.linprog`, the code is accelerated by using a just-in-time
484+
While `scipy.optimize.linprog` uses the HiGHS solver by default,
485+
`quantecon.optimize.linprog_simplex` implements the simplex algorithm accelerated by using a just-in-time
480486
compiler shipped in the `numba` library.
481487
482-
As you will see very soon, by using `scipy.optimize.linprog` the time required to solve an optimal transportation problem can be reduced significantly.
488+
As you will see very soon, by using `quantecon.optimize.linprog_simplex` the time required to solve an optimal transportation problem can be reduced significantly.
483489
484490
```{code-cell} ipython3
485491
# construct matrices/vectors for linprog_simplex
@@ -502,7 +508,13 @@ minimization, we need to put a negative sign before vector `c`.
502508
res_qe = linprog_simplex(-c, A_eq=A_eq, b_eq=b_eq)
503509
```
504510
505-
Since the two LP solvers use the same simplex algorithm, we expect to get exactly the same solutions
511+
While the two LP solvers use different algorithms (HiGHS vs. simplex), both should find optimal solutions.
512+
513+
The solutions differs since there are multiple optimal solutions, but the objective values are the same
514+
515+
```{code-cell} ipython3
516+
np.allclose(-res_qe.fun, res.fun)
517+
```
506518
507519
```{code-cell} ipython3
508520
res_qe.x.reshape((m, n), order='C')
@@ -530,7 +542,6 @@ As you can see, the `quantecon.optimize.linprog_simplex` is much faster.
530542
QuantEcon version, having been tested more extensively over a longer period of
531543
time.)
532544
533-
534545
## The Dual Problem
535546
536547
Let $u, v$ denotes vectors of dual decision variables with entries $(u_i), (v_j)$.
@@ -584,48 +595,23 @@ print("u:", res_dual.x[:m])
584595
print("v:", res_dual.x[-n:])
585596
```
586597
587-
We can also solve the dual problem using [quantecon.optimize.linprog_simplex](https://quanteconpy.readthedocs.io/en/latest/optimize/linprog_simplex.html).
588-
589-
```{code-cell} ipython3
590-
res_dual_qe = linprog_simplex(b_eq, A_ub=A_eq.T, b_ub=c)
591-
```
592-
593-
And the shadow prices computed by the two programs are identical.
594-
595-
```{code-cell} ipython3
596-
res_dual_qe.x
597-
```
598-
599-
```{code-cell} ipython3
600-
res_dual.x
601-
```
602-
603-
We can compare computational times from using our two tools.
598+
`quantecon.optimize.linprog_simplex` computes and returns the dual variables alongside the primal solution.
604599
605-
```{code-cell} ipython3
606-
%time linprog(-b, A_ub=A.T, b_ub=C_vec, bounds=[(None, None)]*(m+n))
607-
```
600+
The dual variables (shadow prices) can be extracted directly from the primal solution:
608601
609602
```{code-cell} ipython3
610-
%time linprog_simplex(b_eq, A_ub=A_eq.T, b_ub=c)
603+
# The dual variables are returned by linprog_simplex
604+
print("Dual variables from linprog_simplex:")
605+
print("u:", -res_qe.lambd[:m])
606+
print("v:", -res_qe.lambd[m:])
611607
```
612608
613-
`quantecon.optimize.linprog_simplex` solves the dual problem 10 times faster.
614-
615-
Just for completeness, let's solve the dual problems with nonsingular $A$ matrices that we create by dropping a redundant equality constraint.
616-
617-
Try first leaving out the first constraint:
609+
We can verify these match the dual solution from SciPy:
618610
619611
```{code-cell} ipython3
620-
linprog(-b[1:], A_ub=A[1:].T, b_ub=C_vec,
621-
bounds=[(None, None)]*(m+n-1))
622-
```
623-
624-
Not let's instead leave out the last constraint:
625-
626-
```{code-cell} ipython3
627-
linprog(-b[:-1], A_ub=A[:-1].T, b_ub=C_vec,
628-
bounds=[(None, None)]*(m+n-1))
612+
print("Dual variables from SciPy linprog:")
613+
print("u:", res_dual.x[:m])
614+
print("v:", res_dual.x[-n:])
629615
```
630616
631617
### Interpretation of dual problem

0 commit comments

Comments
 (0)