GitHub

@@ -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

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

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).)

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+

```

377381378382

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

389393390394

One 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.

393399394400

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`.

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

480486

compiler 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`.

502508

res_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

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.)

532544533-534545

## The Dual Problem

535546536547

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

```

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

Read the original on github.com ↗