## Contour Maps in Python

This activity requires that the following software is installed on your system:

- Python 2.5 or higher.
- Numpy 1.1 or higher.
- Matplotlib 0.98.3.
- And a few other Python packages.

The simplest way to get a Python environment with all the packages needed for a successful scientific environment is to download (free for academics) the Enthought Python Distribution, available for Windows, Mac OS X, and Linux. Simply visit the following URL:

http://enthought.com/products/epddownload.php

and download the version compatible with your operating system. Installation is a breeze; it's double-click and accept the defaults. Some caveats:

- The Windows installation will put a "PyLab" icon on the desktop. Double-clicking this icon will start Ipython with the "pylab" interface. You are then good-to-go.
- On Mac and Linux, open a terminal window and type
**ipython --pylab**at the prompt. Ipython will open with the "PyLab" interface. Again, you are good-to-go.

Once you have the EPD distribution installed, you can continue with this level curve activity. One of the packages included in the EPD distribution is Matplotlib, which allows the user to make 2D plots with commands that are almost identical to those used in Matlab.

### Level Curves

In this activity we will introduce Matplotlib's **contour** command, which is used to plot the level curves of a multivariable function. Let's begin with a short discussion of the level curve concept.

Hikers and backpackers are likely to take along a copy of a topographical map when verturing into the wilderness (see Figure 1).

If you walk along one of the contours shown in Figure 1, you will neither gain nor lose elevation. You're walking along a curve of constant elevation. If you walk directly perpendicular to a contour, then you are either walking directly downhill or uphill. When the contours are far apart, the gain or loss in elevation is gradual. When the contours are close together, the gain or loss in elevation is quite rapid.

The level curves of a multivariate function are analogous to the contours in the topographical map. They are curves of constant elevation. Let's look at an example.

Sketch several level curves of the function `f(x,y)=x^2+y^2`.

**Solution: ** We are interested in finding points of constant elevation, that is, solutions of the equation

`f(x,y)=c`,

where `c` is a constant. Equivalently, we wish to sketch solutions of

`x^2+y^2=c`,

where `c` is a constant. Of course, these "level curves" are circles, centered at the origin, with radius `sqrt(c)`. These level curves are drawn in Figure 2 for constants `c=0`, 1, 2, 3, and 4`.

**Matplotlib: **It's a simple task to draw the level curves of Figure 2 using Matplotlib's **contour** command. First, use the **cd** command to change to the directory in which you want to work. For example,

In [100]: cd \math50c\activities\python\contours

will change directories to **\math50c\activities\python\contours**, provided this folder exists on this path. From this point onwards, any saved images will wind up in the folder **contours** on this path.

To sketch contours, we begin as if we were going to draw a surface, creating a grid of `(x,y)` pairs with the **meshgrid** command.

In [105]: x=linspace(-3,3,40) In [106]: y=linspace(-3,3,40) In [107]: x,y=meshgrid(x,y)

We then use the function `f(x,y)=x^2+y^2`, or equivalently, `z=x^2+y^2`, to calculate the `z`-values.

In [108]: z=x**2+y**2

Use the **contour** command to draw the level curves.

In [109]: contour(x,y,z)

Add a grid, then equalize the axes.

In [120]: grid(True) In [121]: axis('scaled')

Annotate the plot.

In [122]: xlabel('x-axis') In [123]: ylabel('y-axis') In [124]: title('Level curves of the function f(x,y) = x^2 + y^2.')

The above sequence of commands will produce the level curves shown in Figure 3.

By default, Matplotlib draws a few more level curves than the number shown in Figure 2.

**Adding Labels to the Contours: **It would be nice if we could label each contour with its height. As one might expect, Matplotlib has this capability. First, clear the figure window.

In [130]: clf()

Next, draw the set of contours again, capturing the output in a return label for later use with the **clabel** command.

In [131]: cs=contour(x,y,z)

Without getting too technical, information on the level curves is stored in the output variable **cs**. We then feed the output as input to Matlplotlib's **clabel** command.

In [132]: clabel(cs,inline=1,fontsize=10)

Using the same formatting as above (grid, scaling, and annotations), this produces the image shown in Figure 4.

### Forcing Contours

Sometimes you'd like to do one of two things:

- Force more contours than the default number provided by the
**contour**command. - Force contours at particular heights.

**Forcing More Contours: **You can force more contours by adding an additional argument to the contour command. First, close the current figure window with the following command.

In [141]: close()

To force 20 contours, execute the following command.

In [201]: cs=contour(x,y,z,20)

Adding the formatting commands (grid, scale, and annotate) produces the additional contours shown in Figure 6.

We wouldn't want to label all of these contours as it would be a bit crowded. It is possible to label specific contours. First, let's use routines from the **numpy** package (which is installed by the EPD distribution and used by Matplotlib) to find the maximum and minimum `z`-values.

In [206]: z.min() Out[206]: 0.011834319526627135 In [207]: z.max() Out[207]: 18.0

Create a vector with the levels you wish to label.

In [208]: levels=arange(2,18,2)

Numpy's **arange(start,stop,increment)** syntax creates a of value ranging from **start** through **stop** in increments of **increment**. Thus, it is no surprise that **levels** contains the following entries.

In [209]: levels Out[209]: array([ 2, 4, 6, 8, 10, 12, 14, 16])

Now, let's label level curves at the heights contained in **levels**.

In [211]: clabel(cs,levels,inline=1,fontsize=9)

The result is shown in Figure 6.

**Forcing Specific Contours: **You can also force contours at specific heights. To reproduce the level curves of Figure 1, at the heights `c=0`, 1, 2, 3, and 4, we pass the specific heights we wish to see in a vector to the **contour** command. First, clear the current figure window.

In [155]: clf()

Next, list the specific heights in a vector.

In [157]: levels=[0,1,2,3,4]

Pass the vector **levels** to the **contour** command as follows:

In [158]: cs=contour(x,y,z,levels)

Labeling the contours shows that our contours have the heights requested.

In [159]: clabel(cs,inline=1,fontsize=9)

These commands, plus the formatting commands (grid, scale, and annotate) produce the result shown in Figure 7.

Note the strong resemblance of Figure 7 to Figure 1

### Miscellaneous Extras

**Implicit Plotting: **Sometimes you want to draw a single contour. For example, suppose you wish to draw the graph of the implict relation `x^2+2xy+y2-2x=3`. One way to proceed would be to first define the function

`f(x,y)=x^2+2xy+y^2-2x`,

then plot the level curve `F(x,y)=3`. Start by closing the current figure window.

In [166]: close()

Create a grid of `(x,y)` pairs.

In [167]: x=linspace(-3,3,40) In [168]: y=linspace(-3,3,40) In [169]: x,y=meshgrid(x,y)

Calculate `z=f(x,y)=x^2+2xy+y^2-2x`.

In [170]: z=x**2-2*x*y+y**2-2*x

Now, we wish to draw the single contour `z=f(x,y)=3`. Create a vector with this height. Matplotlib's **contour** command has a different response, depending on whether a number of an array is entered in the fourth position. If we go with **contour(x,y,z,3)**, then three level curves will be drawn. Instead, we set the levels in a vector.

In [171]: levels=[3]

The following command will plot the single contour at height 3.

In [178]: contour(x,y,z,levels)

Add a grid, scale, and add annotations.

In [180]: grid(True) In [181]: axis('scaled')

Finally, add appropriate annotations.

In [182]: xlabel('x-axis') In [183]: ylabel('y-axis') In [189]: title('The implicit curve x^2 + 2xy + y^2 - 2x = 3.')

The result of the above sequence of commands is captured in Figure 8.

### Saving Files

Matplotlib allows the user to save files in a variety of formats (pdf, ps, eps, png, and svg). For example, to save the last image as a pdf (Portable Document Format) file, enter the following command.

In [224]: savefig('junk.pdf')

To save the file in svg (Scalable Vector Graphics) format, enter the following command.

In [227]: savefig('junk.svg')

### Python Files

The following file features Python code. We include it here for those interested in discovering how we generated the images for this activity. You can download the Python file at the following link. Download the file to a directory or folder on your system.

The file **levels.py** is designed to be run from a terminal window or DOS window. Open a terminal window or a DOS window, then use the **cd** command to change to the directory in which you downloaded the file **levels.m**. Type the command **python levels.py** and all of the files used in this activity will be generated. If you want to see a file before it is save, uncomment the **show()** command for the image you'd like to see. When you close the figure window, the file will be save and the rest of the code will be executed.

### Exercises

In each of the following exercises, obtain a printout of your result and the Python that produced the result. You are encouraged to write a small Python file to generate the result for each exercise. Follow the lead shown in the downloadable file **levels.py** shown in the section above.

- Use
**contour**to sketch default level curves for the function `f(x,y)=sqrt(1-x-y)`. Use the**clabel**command to automatically label the level curves. - Use
**contour**to sketch the level curves `f(x,y)=c` for `f(x,y)=x^2+4y^2` for the following values of `c`: 1,2,3,4, and 5. - Use the
**contour**command to force 20 level curves for the function `f(x,y)=2+3x-2y`. - Use the
**contour**to sketch the graph of the implicit equation `x^3+y^3=3xy`. This curve is known as the*Folium of Descartes*.