Drawing directions fields

You can use this matplotlib code as a base. Modify it for your needs. I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).

It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.

import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np

fig = plt.figure()

def vf(x, t):
    dx = np.zeros(2)
    dx[0] = 1.0
    dx[1] = x[0] ** 2 - x[0] - 2.0
    return dx


# Solution curves
t0 = 0.0
tEnd = 10.0

# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")

t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
    y_initial = [y0, -10.0]
    y = odeint(vf, y_initial, t)
    plt.plot(y[:, 0], y[:, 1], "-")

plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")

The slope field


I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):

dy/dx=log(sin(x/y)+cos(y/x)

You can zoom in & out - here is zoomed in on the middle upper part here:

as above zoomed in

and also click on a point to graph the line going through that point:

and as it is such so also as such is it unto you

It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.

The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.

def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
    su = ScreenUpdater(50)
    lastskip = self.xscreensize
    quitit = False
    for squaresize in sqsizes:
        xsquaresize = squaresize
        ysquaresize = squaresize

        if squaresize == 1:
            self.screen.lock()
        y = 0
        while y <= self.yscreensize:
            x = 0
            skiprow = y%lastskip == 0
            while x <= self.xscreensize:
                if skiprow and x%lastskip==0:
                    x += squaresize
                    continue

                color = (255,255,255)
                try:
                    m = self.func.eval(*self.ct.untranscoord(x, y))
                    if m >= 0:
                        if m < 1:
                            c = 255 * m
                            color = (0, 0, c)
                        else:
                            #c = 255 - 255 * (1.0/m)
                            #color = (c, c, 255)
                            c = 255 - 255 * (1.0/m)
                            color = (c/2.0, c/2.0, 255)

                    else:
                        pm = -m
                        if pm < 1:
                            c = 255 * pm
                            color = (c, 0, 0)
                        else:
                            c = 255 - 255 * (1.0/pm)
                            color = (255, c/2.0, c/2.0)                        
                except:
                    color = (0, 0, 0)

                if squaresize > 1:
                    self.screen.fill(color, (x, y, squaresize, squaresize))
                else:
                    self.screen.set_at((x, y), color)

                if su.update():
                    quitit = True
                    break

                x += xsquaresize

            if quitit:
                break

            y += ysquaresize

        if squaresize == 1:
            self.screen.unlock()
        lastskip = squaresize
        if quitit:
            break

This is the code which graphs a line through a point:

def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
    x = sx
    y = sy
    i = 0

    pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
    while i < numsteps:
        lastx = x
        lasty = y

        try:
            m = self.func.eval(x, y)
        except:
            return

        x += stepsize            
        y = y + m * stepsize

        screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
        screenx2, screeny2 = self.ct.transcoord(x, y)

        #print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)

        try:
            pygame.draw.line(self.screen, color,
                             (screenx1, screeny1),
                             (screenx2, screeny2), 2)
        except:
            return

        i += 1

    stx, sty = self.ct.transcoord(sx, sy)
    pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)

And it runs backwards & forwards starting from that point:

def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
    """Graph the differential equation, given the starting point sx and sy, for length
    length using stepsize stepsize."""
    numstepsf = (self.xrange[1] - sx) / stepsize
    numstepsb = (sx - self.xrange[0]) / stepsize

    self._grapheqhelp(sx, sy,  stepsize, numstepsf, color)
    self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)

I never got around to drawing actual lines because the pixel approach looked too cool.

Tags:

Python

Math