@@ -4,7 +4,7 @@ jupytext:
44extension: .md
55format_name: myst
66format_version: 0.13
7-jupytext_version: 1.14.4
7+jupytext_version: 1.17.1
88kernelspec:
99display_name: Python 3 (ipykernel)
1010language: python
@@ -330,12 +330,12 @@ Let's write Python code that sets up the problem and solves it.
330330m = 3
331331n = 5
332332333-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])
335335336-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]])
339339340340# Vectorize matrix C
341341C_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).)
373373374-**Interpreting the warning:**
374+**Interpreting the solver behavior:**
375375376-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+```
377381378382This 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
389393390394One equality constraint here is redundant.
391395392-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.
393399394400After 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
475481We can also solve optimal transportation problems using a powerful tool from
476482QuantEcon, namely, `quantecon.optimize.linprog_simplex`.
477483478-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
480486compiler shipped in the `numba` library.
481487482-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.
483489484490```{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`.
502508res_qe = linprog_simplex(-c, A_eq=A_eq, b_eq=b_eq)
503509```
504510505-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+```
506518507519```{code-cell} ipython3
508520res_qe.x.reshape((m, n), order='C')
@@ -530,7 +542,6 @@ As you can see, the `quantecon.optimize.linprog_simplex` is much faster.
530542QuantEcon version, having been tested more extensively over a longer period of
531543time.)
532544533-534545## The Dual Problem
535546536547Let $u, v$ denotes vectors of dual decision variables with entries $(u_i), (v_j)$.
@@ -584,48 +595,23 @@ print("u:", res_dual.x[:m])
584595print("v:", res_dual.x[-n:])
585596```
586597587-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.
604599605-```{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:
608601609602```{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```
612608613-`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:
618610619611```{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```
630616631617### Interpretation of dual problem