GitHub

@@ -29,18 +29,21 @@ premature optimization is the root of all evil." -- Donald Knuth

29293030

Python is extremely popular for scientific computing, due to such factors as

313132-

* the accessible and flexible nature of the language itself,

33-

* the huge range of high quality scientific libraries now available,

32+

* the accessible and expressive nature of the language itself,

33+

* its vast range of high quality scientific libraries,

3434

* the fact that the language and libraries are open source,

35-

* the popular Anaconda Python distribution, which simplifies installation and

36-

management of those libraries, and

37-

* the recent surge of interest in using Python for machine learning and

38-

artificial intelligence.

35+

* the popular [Anaconda Python distribution](https://www.anaconda.com/download), which simplifies installation and management of scientific libraries, and

36+

* the key role that Python plays in data science, machine learning and artificial intelligence.

393740-

In this lecture we give a short overview of scientific computing in Python,

41-

addressing the following questions:

38+

In previous lectures, we looked at some scientific Python libaries such as NumPy and Matplotlib.

423943-

* What are the relative strengths and weaknesses of Python for these tasks?

40+

However, our main focus was the core Python language, rather than the libraries.

41+42+

Now we turn to the scientific libraries and give them our full attention.

43+44+

We'll also discuss the following topics:

45+46+

* What are the relative strengths and weaknesses of Python for scientific work?

4447

* What are the main elements of the scientific Python ecosystem?

4548

* How is the situation changing over time?

4649

@@ -53,62 +56,67 @@ tags: [hide-output]

5356

!pip install quantecon

5457

```

555859+60+5661

## Scientific Libraries

576258-

Let's briefly review Python's scientific libraries, starting with why we need

59-

them.

63+

Let's briefly review Python's scientific libraries, starting with why we need them.

60646165

### The Role of Scientific Libraries

626663-

One obvious reason we use scientific libraries is because they implement

64-

routines we want to use.

67+

One reason we use scientific libraries is because they implement routines we want to use.

68+69+

* numerical integration, interpolation, linear algebra, root finding, etc.

657066-

For example, it's almost always better to use an existing routine for root

67-

finding than to write a new one from scratch.

71+

For example, it's almost always better to use an existing routine for root finding than to write a new one from scratch.

687269-

(For standard algorithms, efficiency is maximized if the community can coordinate on a

70-

common set of implementations, written by experts and tuned by users to be as fast and robust as possible.)

73+

(For standard algorithms, efficiency is maximized if the community can coordinate on a common set of implementations, written by experts and tuned by users to be as fast and robust as possible.)

71747275

But this is not the only reason that we use Python's scientific libraries.

73767477

Another is that pure Python, while flexible and elegant, is not fast.

75787679

So we need libraries that are designed to accelerate execution of Python code.

778078-

As we'll see below, there are now Python libraries that can do this extremely well.

81+

They do this using two strategies:

798280-

### Python's Scientific Ecosystem

83+

1. using compilers that convert Python-like statements into fast machine code for individual threads of logic and

84+

2. parallelizing tasks across multiple "workers" (e.g., CPUs, individual threads inside GPUs).

818582-

In terms of popularity, the big four in the world of scientific Python

83-

libraries are

86+

There are several Python libraries that can do this extremely well.

848785-

* NumPy

86-

* SciPy

87-

* Matplotlib

88-

* Pandas

898890-

For us, there's another (relatively new) library that will also be essential for

91-

numerical computing:

89+

### Python's Scientific Ecosystem

929093-

* Numba

91+

At QuantEcon, the scientific libraries we use most often are

949295-

Over the next few lectures we'll see how to use these libraries.

93+

* [NumPy](https://numpy.org/)

94+

* [SciPy](https://scipy.org/)

95+

* [Matplotlib](https://matplotlib.org/)

96+

* [Pandas](https://pandas.pydata.org/)

97+

* [Numba](https://numba.pydata.org/) and

98+

* [JAX](https://github.com/jax-ml/jax)

969997-

But first, let's quickly review how they fit together.

100+

Here's how they fit together:

9810199-

* NumPy forms the foundations by providing a basic array data type (think of

102+

* NumPy forms foundations by providing a basic array data type (think of

100103

vectors and matrices) and functions for acting on these arrays (e.g., matrix

101104

multiplication).

102-

* SciPy builds on NumPy by adding the kinds of numerical methods that are

103-

routinely used in science (interpolation, optimization, root finding, etc.).

105+

* SciPy builds on NumPy by adding numerical methods routinely used in science (interpolation, optimization, root finding, etc.).

104106

* Matplotlib is used to generate figures, with a focus on plotting data stored in NumPy arrays.

105-

* Pandas provides types and functions for empirical work (e.g., manipulating data).

106-

* Numba accelerates execution via JIT compilation --- we'll learn about this

107-

soon.

107+

* Pandas provides types and functions for manipulating data.

108+

* Numba provides a just-in-time compiler that integrates well with NumPy and

109+

helps accelerate Python code.

110+

* JAX includes array processing operations similar to NumPy, automatic

111+

differentiation, a parallelization-centric just-in-time compiler, and automated integration with hardware accelerators such as

112+

GPUs.

113+114+115+108116109117

## The Need for Speed

110118111-

Now let's discuss execution speed.

119+

Let's discuss execution speed and how scientific libraries can help us accelerate code.

112120113121

Higher-level languages like Python are optimized for humans.

114122

@@ -117,35 +125,38 @@ This means that the programmer can leave many details to the runtime environment

117125

* specifying variable types

118126

* memory allocation/deallocation, etc.

119127120-

The upside is that, compared to low-level languages, Python is typically faster to write, less error-prone and easier to debug.

128+

On one hand, compared to low-level languages, high-level languages are typically faster to write, less error-prone and easier to debug.

121129122-

The downside is that Python is harder to optimize --- that is, turn into fast machine code --- than languages like C or Fortran.

130+

On the other hand, high-level languages are harder to optimize --- that is, to turn into fast machine code --- than languages like C or Fortran.

123131124132

Indeed, the standard implementation of Python (called CPython) cannot match the speed of compiled languages such as C or Fortran.

125133126134

Does that mean that we should just switch to C or Fortran for everything?

127135128-

The answer is: No, no and one hundred times no!

136+

The answer is: No, no, and one hundred times no!

129137130-

(This is what you should say to the senior professor insisting that the model

131-

needs to be rewritten in Fortran or C++.)

138+

(This is what you should say to your professor when they insist that your model needs to be rewritten in Fortran or C++.)

132139133140

There are two reasons why:

134141135-

First, for any given program, relatively few lines are ever going to

136-

be time-critical.

142+

First, for any given program, relatively few lines are ever going to be time-critical.

137143138144

Hence it is far more efficient to write most of our code in a high productivity language like Python.

139145140146

Second, even for those lines of code that *are* time-critical, we can now achieve the same speed as C or Fortran using Python's scientific libraries.

141147148+

In fact we can often do better, because some scientific libraries are so

149+

effective at accelerating and parallelizing our code.

150+151+142152

### Where are the Bottlenecks?

143153144-

Before we learn how to do this, let's try to understand why plain vanilla

145-

Python is slower than C or Fortran.

154+

Before we learn how to do this, let's try to understand why plain vanilla Python is slower than C or Fortran.

146155147156

This will, in turn, help us figure out how to speed things up.

148157158+

In reading the following, remember that the Python interpreter executes code line-by-line.

159+149160

#### Dynamic Typing

150161151162

```{index} single: Dynamic Typing

@@ -180,10 +191,11 @@ a + b

180191

(We say that the operator `+` is *overloaded* --- its action depends on the

181192

type of the objects on which it acts)

182193183-

As a result, Python must check the type of the objects and then call the correct operation.

194+

As a result, when executing `a + b`, Python must first check the type of the objects and then call the correct operation.

184195185196

This involves substantial overheads.

186197198+187199

#### Static Types

188200189201

```{index} single: Static Types

@@ -255,6 +267,9 @@ In fact, it's generally true that memory traffic is a major culprit when it come

255267256268

Let's look at some ways around these problems.

257269270+271+272+258273

## {index}`Vectorization <single: Vectorization>`

259274260275

```{index} single: Python; Vectorization

@@ -272,173 +287,12 @@ For example, when working in a high level language, the operation of inverting a

272287273288

This clever idea dates back to MATLAB, which uses vectorization extensively.

274289275-

Vectorization can greatly accelerate many numerical computations (but not all,

276-

as we shall see).

277-278-

Let's see how vectorization works in Python, using NumPy.

279-280-

### Operations on Arrays

281-282-

```{index} single: Vectorization; Operations on Arrays

283-

```

284-285-

First, let's run some imports

286-287-

```{code-cell} python3

288-

import random

289-

import numpy as np

290-

import quantecon as qe

291-

```

292-293-

Next let's try some non-vectorized code, which uses a native Python loop to generate,

294-

square and then sum a large number of random variables:

295-296-

```{code-cell} python3

297-

n = 1_000_000

298-

```

299-300-

```{code-cell} python3

301-

%%time

302-303-

y = 0 # Will accumulate and store sum

304-

for i in range(n):

305-

x = random.uniform(0, 1)

306-

y += x**2

307-

```

308-309-

The following vectorized code achieves the same thing.

310-311-

```{code-cell} ipython

312-

%%time

313-314-

x = np.random.uniform(0, 1, n)

315-

y = np.sum(x**2)

316-

```

317-318-

As you can see, the second code block runs much faster. Why?

319-320-

The second code block breaks the loop down into three basic operations

321-322-

1. draw `n` uniforms

323-

1. square them

324-

1. sum them

325-326-

These are sent as batch operators to optimized machine code.

327290328-

Apart from minor overheads associated with sending data back and forth, the result is C or Fortran-like speed.

329-330-

When we run batch operations on arrays like this, we say that the code is *vectorized*.

331-332-

Vectorized code is typically fast and efficient.

333-334-

It is also surprisingly flexible, in the sense that many operations can be vectorized.

335-336-

The next section illustrates this point.

337-338-

(ufuncs)=

339-

### Universal Functions

340-341-

```{index} single: NumPy; Universal Functions

342-

```

343-344-

Many functions provided by NumPy are so-called *universal functions* --- also called [ufuncs](https://docs.scipy.org/doc/numpy/reference/ufuncs.html).

345-346-

This means that they

347-348-

* map scalars into scalars, as expected

349-

* map arrays into arrays, acting element-wise

350-351-

For example, `np.cos` is a ufunc:

352-353-

```{code-cell} python3

354-

np.cos(1.0)

355-

```

356-357-

```{code-cell} python3

358-

np.cos(np.linspace(0, 1, 3))

291+

```{figure} /_static/lecture_specific/need_for_speed/matlab.png

359292

```

360293361-

By exploiting ufuncs, many operations can be vectorized.

362-363-

For example, consider the problem of maximizing a function $f$ of two

364-

variables $(x,y)$ over the square $[-a, a] \times [-a, a]$.

365-366-

For $f$ and $a$ let's choose

367-368-

$$

369-

f(x,y) = \frac{\cos(x^2 + y^2)}{1 + x^2 + y^2}

370-

\quad \text{and} \quad

371-

a = 3

372-

$$

373-374-

Here's a plot of $f$

375-376-

```{code-cell} ipython

377-

import matplotlib.pyplot as plt

378-

from mpl_toolkits.mplot3d.axes3d import Axes3D

379-

from matplotlib import cm

380-381-

def f(x, y):

382-

return np.cos(x**2 + y**2) / (1 + x**2 + y**2)

383-384-

xgrid = np.linspace(-3, 3, 50)

385-

ygrid = xgrid

386-

x, y = np.meshgrid(xgrid, ygrid)

387-388-

fig = plt.figure(figsize=(10, 8))

389-

ax = fig.add_subplot(111, projection='3d')

390-

ax.plot_surface(x,

391-

y,

392-

f(x, y),

393-

rstride=2, cstride=2,

394-

cmap=cm.jet,

395-

alpha=0.7,

396-

linewidth=0.25)

397-

ax.set_zlim(-0.5, 1.0)

398-

ax.set_xlabel('$x$', fontsize=14)

399-

ax.set_ylabel('$y$', fontsize=14)

400-

plt.show()

401-

```

402-403-

To maximize it, we're going to use a naive grid search:

404-405-

1. Evaluate $f$ for all $(x,y)$ in a grid on the square.

406-

1. Return the maximum of observed values.

407-408-

The grid will be

409-410-

```{code-cell} python3

411-

grid = np.linspace(-3, 3, 1000)

412-

```

413-414-

Here's a non-vectorized version that uses Python loops.

415-416-

```{code-cell} python3

417-

%%time

418-419-

m = -np.inf

420-421-

for x in grid:

422-

for y in grid:

423-

z = f(x, y)

424-

if z > m:

425-

m = z

426-

```

427-428-

And here's a vectorized version

429-430-

```{code-cell} python3

431-

%%time

432-433-

x, y = np.meshgrid(grid, grid)

434-

np.max(f(x, y))

435-

```

436-437-

In the vectorized version, all the looping takes place in compiled code.

438-439-

As you can see, the second version is **much** faster.

440-441-

(We'll make it even faster again later on, using more scientific programming tricks.)

294+

Vectorization can greatly accelerate many numerical computations, as we will see

295+

in later lectures.

442296443297

(numba-p_c_vectorization)=

444298

## Beyond Vectorization

@@ -462,11 +316,11 @@ In these kinds of settings, we need to go back to loops.

462316

Fortunately, there are alternative ways to speed up Python loops that work in

463317

almost any setting.

464318465-

For example, in the last few years, a new Python library called [Numba](http://numba.pydata.org/) has appeared that solves the main problems

466-

with vectorization listed above.

319+

For example, [Numba](http://numba.pydata.org/) solves the main problems with

320+

vectorization listed above.

467321468322

It does so through something called **just in time (JIT) compilation**,

469323

which can generate extremely fast and efficient code.

470324471-

We'll learn how to use Numba {doc}`soon <numba>`.

325+

{doc}`Later <numba>` we'll learn how to use Numba to accelerate Python code.

472326

Read the original on github.com ↗