Calculus 1, Lab 1: Limits and Python!#

By Raheem Mir

Welcome!#

We are going to be using some Python to explore and work through calculus concepts. Now don’t worry, you don’t need to be a seasoned programmer or have any previous experience. We’ll just be writing some simple code and make use of powerful libraries that do the heavy lifting for us!

For a review of basic Python syntax, see the introduction.

Using SymPy and NumPy#

Python has many powerful librairies that add convenience and functionality. Two great libraries that we’ll be using for doing calculus with Python, are SymPy and NumPy.

SymPy is a feature rich library designed for symbolic mathematics. We can use it to do symbolic calculations and manipulations, meaning, it deals with math like we do on paper. It can simplify algebraic expressions, solve equations, perform calculus tasks like evaluating limits or computing integrals and a plethora of other mathematical operations. SymPy gives us exact symbolic answers instead of numerical approximations, making it a great tool for intuitively exploring calculus concepts.

To begin using SymPy we start by “importing” the library:

# Make sure to run this cell! (shift + enter)
import sympy as sy
sy.init_printing() 

The line: import sympy as sy loads in the SymPy library for us to use. We give it the shorthand name sy, so every time we call something from SymPy, we prefix it with sy.. This is a good way to keep things organized when we are working with multiple libraries.

The next line: sy.init_printing() calls SymPy’s init_printing() function, which gets the output to be displayed as nicely formatted mathematics.

A good next step is telling SymPy the variables we would like to use with the symbols() function.

x, y, z = sy.symbols('x y z')    # this makes x, y, and z symbols

SymPy is great for defining (and working with) symbolic expressions:

f = x**2-5*x+6
f, f.factor()

We will provide you with the tools you need for this lab assignment, but an overview of SymPy is available if you’d like to take a look at what’s available.

While SymPy is great for symbolic manipulation, when it comes numerical computation, NumPy is the library of choice. Think of it as a high powered calculator with a large toolset. It implements many common mathematical functions, like trigonometric functions, logarithms and more.

To start using NumPy, like we did with SymPy, we have to import it:

# Make sure to run this cell! (shift + enter)
import numpy as np

Just as before, the import as syntax loads in a particular Python library and creates a shorthand name for it to call functions with. In this case, when we want to use a function from NumPy, we prefix it with np..

Now to walkthrough some examples:

Let’s call the function \(\sin(x)\) using np.sin() and evaluate \(\sin(π)\):

np.sin(np.pi) # should output a value very close to zero

Notice how we used np.pi, to represent the constant \(π\) in NumPy.

Here’s \(\tan(π/4)\) using np.tan():

np.tan(np.pi/4) 

NumPy is also a bit different than SymPy when it comes to handling functions. In SymPy, we simply define symbolic expressions to represent functions:

In SymPy we would write something like: f = sy.sqrt(x**3 + sy.sin(x)) (after defining ‘x’ as a symbol)

and then recieve a nicely formatted ouput when displaying f: \(\sqrt{x^3 + \sin^2(x)}\).

In NumPy however, we use Python’s def syntax to define functions. This gives us the ability to evaluate functions at specific values, and have the convenience of instant numerical outputs.

Here’s how we would define the same function in NumPy:

def g(x):
    return np.sqrt(x**3 + np.sin(x)**2)

We can’t get Python to display g(x) as a formula like the one above, but we can ask it to evaluate our function at some points:

g(0), g(1), g(np.pi)

Let’s look at Limits!#

The concept of a limit#

The limit concept is a difficult one, but it’s essential to understanding calculus. When we write

\[\lim_{x\to c}f(x) = L,\]

we are saying that the values of \(f(x)\) can be made as close to \(L\) as we want, as long as we use an \(x\) value that’s close enough to \(c\).

Crucially, we do not allow \(x\) to equal \(c\) in the definition. It can get close, but never equal.

Run the next cell to display an interactive graph of a function. Notice how the \(y\) values change as we drag \(x\). At what points does it appear as though our function does not have a limit?

%%html
<iframe scrolling="no" title="Function demo for Jupyter" src="https://www.geogebra.org/material/iframe/id/j7yaaxr9/width/599/height/349/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/true/rc/false/ld/false/sdz/true/ctl/false" width="599px" height="349px" style="border:0px;"> </iframe>

An algebraic limit#

Consider the following example:

\[\lim_{x\to 2}\frac{x^2-5x+6}{x^2-4}=-\frac14.\]

Why is this true?

First, look at the graph:

%%html
<iframe scrolling="no" title="Limit material for Jupyter 2" src="https://www.geogebra.org/material/iframe/id/sqwrhfup/width/598/height/400/border/888888/sfsb/true/smb/false/stb/true/stbh/false/ai/false/asb/false/sri/true/rc/false/ld/false/sdz/true/ctl/false" width="598px" height="400px" style="border:0px;"> </iframe>

We can also enter this function into the computer, and analyze it.

f = (x**2-5*x+6)/(x**2-4)
f

We can ask SymPy to evaluate the limit. The syntax is sy.limit(function, variable, value). Let’s try it!

sy.limit(f,x,2)

All signs seem to point to a limit of \(-\frac14\). How do we see this for ourselves?

Note that a limit is necessary here: \(f(2)\) is undefined.

If we try to put \(x=2\), we get the nonsensical result of \(\frac00\).

But this is a rational function: the top and bottom are both polynomials.

For any polynomial \(p(x)\), if \(p(a)=0\), then \((x-a)\) is a factor!

So we should be able to factor out \((x-2)\) from both top and bottom:

\[f(x) = \frac{x^2-5x+6}{x^2-4} = \frac{(x-2)(x-3)}{(x-2)(x+2)}\]

Next, you want to cancel the \(x-2\) factors and proceed.

But it would be false to write \(f(x)=\dfrac{x-3}{x+2}\), because we lose the exception \(x\neq 2\).

Here’s where limits come to the rescue!

Remember: in \(\lim\limits_{x\to 2}f(x)\), we consider values of \(x\) close to 2, but not equal to 2.

Since we don’t let \(x=2\), we’re allowed to cancel, and after that, the properties of limits take over:

\[\begin{split}\begin{aligned}\lim_{x\to 2}f(x) &= \lim_{x\to 2}\frac{x^2-5x+6}{x^2-4}\\ &= \lim_{x\to 2}\frac{(x-2)(x-3)}{(x-2)(x+2)}\\ &= \lim_{x\to 2}\frac{x-3}{x+2}\\ &= \frac{\lim_{x\to 2}(x-3)}{\lim_{x\to 2}(x+2)}\\ &= \frac{2-3}{2+2} = -\frac14,\end{aligned}\end{split}\]

just as we expected.

A trigonometric limit#

Next, we’re going to explore the limit

\[\lim_{x\to 0}\dfrac{\sin(x)}{x}.\]

Like the last example, trying to evaluate directly results in \(\frac00\).

Unlike the last example, we cannot simply factor an \(x\) out from the top to cancel it.

To get an idea of what we’re dealing with, we’ll first explore numerically and graphically.

Let’s start by defining our function. Note that we need to specify that the sin function comes from the SymPy library with the np. prefix, because we are going to start by investigating numerically.

def g(x):
  if x == 0:
        return "not defined" # because sin(0)/0 is indeterminate
  return np.sin(x)/x

First, we define our function:

Next we choose some \(x\) values to test our function near \(x = 0\):

x_values = [-0.1, -0.01, -0.001, -0.0001, 0, 0.0001, 0.001, 0.01, 0.1] # using a list

Now we compute the corresponding \(f(x)\) values for the chosen \(x\) values and store them:

results = [] # storing in a list

# using a loop to compute values and set up table's columns
for value in x_values:
    results.append([value, g(value)]) 

Let’s display our results in a table:

print("x", " "*6, "| g(x)") # column headings
print("-"*29) 
for result in results:
    print (result[0], " "*(7 - len(str(result[0]))), "|", result[1]) # formatting the table

The table helps to illustrate the function’s behaviour near \(x = 0\), and given this behaviour, it appears that

\[\lim_{x\to 0}\dfrac{\sin(x)}{x} = 1.\]

We can also see this by looking at the graph. First, we need to load a library to handle plotting:

import matplotlib.pyplot as plt

Next, we need to store some \(x\) values to use as inputs for the plot. The linspace command from NumPy produces a set of evenly-spaced points over an interval. The plotting function also doesn’t like the logic we used to define \(g(x)\) when \(x=0\), so we will also redefine our function.

x1 = np.linspace(-2,2,100) #use 100 points between -2 and 2
def g(x):
    return np.sin(x)/x

Now we can use the plot command from matplotlib, which we call as plt.plot:

plt.plot(x1,g(x1))

If you prefer, we can also plot the function using the SymPy library that we loaded earlier. The (x,-2,2) part of the command tells SymPy to use the domain \([-2,2]\).

plot=sy.plot(sy.sin(x)/x,(x,-2,2))

Again, it appears as though the limit of \(\sin(x)/x\) as \(x\to 0\) is 1.

Establishing this result as fact is actually quite a bit of work, involving a fair amount of trigonometry, and a result called the squeeze theorem. The squeeze theorem says that if

\[f(x)\leq g(x)\leq h(x)\]

on some interval containing a number \(c\), and \(f(x)\) and \(h(x)\) both have the same limit at \(c\), then \(g(x)\) must have that limit, too. The graph of \(g(x)\) gets “squeezed” between the other two graphs at \(c\):

For now, we’ll cheat and ask the computer to evaluate the limit, using SymPy.

Evaluating Limits Using SymPy#

To evaluate limits using SymPy, we can use the limit() function. The syntax is

sy.limit(expression,variable,value)

where expression is the function, the variable is usually (but not always) x, and the value is the point at which you want to take the limit.

Below are some examples:

Let’s start with the example we introduced above:

\[\lim_{x\to 0}\dfrac{\sin(x)}{x}.\]

Since Sympy has already been imported, and \(x\) has been set as symbol, we can get straight into representing the function and evaluating:

f = sy.sin(x)/x
f
sy.limit(f, x, 0) 

From our result it appears that:

\[\lim_{x\to 0}\dfrac{\sin(x)}{x} = 1.\]

But how do we know this? Let’s see how the squeeze theorem is used. Consider the following unit circle diagram. Try dragging the point on the circle.

Notice how the length \(\theta\) of the circular arc (in red) is always between the vertical line on the left (in blue, of length \(\sin(\theta)\)), and the vertical line on the right (in green, of length \(\tan(\theta)\)).

%%html
<iframe scrolling="no" title="Limit Material for Jupyter 1" src="https://www.geogebra.org/material/iframe/id/zdtuv9mq/width/510/height/353/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/true/rc/false/ld/false/sdz/true/ctl/false" width="510px" height="353px" style="border:0px;"> </iframe>

This gives the inequality \(\sin(\theta)\leq \theta\leq \tan(\theta)\).

If we take reciprocals and multiply everything by \(\sin(\theta)\), we get the inequality

\[\cos(\theta)\leq \frac{\sin(\theta)}{\theta}\leq 1,\]

when \(\theta>0\). A similar result holds when \(\theta<0\).

In the diagram below, you can see how, as \(\theta\to 0\), the value of \(\sin(\theta)/\theta\) is “squeezed” between \(\cos(\theta)\) and \(1\):

%%html
<iframe scrolling="no" title="Limit material for Jupyter 3" src="https://www.geogebra.org/material/iframe/id/gfa5pqeh/width/736/height/367/border/888888/sfsb/true/smb/false/stb/false/stbh/false/ai/false/asb/false/sri/true/rc/false/ld/false/sdz/true/ctl/false" width="736px" height="367px" style="border:0px;"> </iframe>

Some further limit practice#

Now for some more!

Let’s evaluate:

\(\displaystyle \lim_{x\to 1}{(x^2 + 2x + 2)}\)

f1 = x**2 + 2*x + 2
f1
sy.limit(f1, x, 1)

The above result makes sense according to the limit properties. In particular, we know that we can evaluate the limit of a polynomial function by simply plugging in the number.

In the next example, plugging in \(x=1\) would give us an answer of \(0/0\), which is undefined. Such expressions are called indeterminate forms. We saw this earlier with the expression \(\sin(x)/x\).

Despite the \(0/0\) result, we still get a limit!

Example 1: \(\displaystyle \lim_{x\to 1}\dfrac{x^2 -1}{x-1}\)

f2 = (x**2 - 1) / (x - 1)
f2
sy.limit(f2, x, 1)

The reason we can evaluate this limit can be seen if we factor the numerator:

sy.factor(x**2-1)

Both numerator and denominator have the factor \(x-1\). Since the limit involves values of \(x\) that are close, but not equal to 1, we can cancel this factor from top and bottom, and then evaluate.

Example 2: \(\displaystyle \lim_{x\to -3}\dfrac{x^2 +10x + 21}{x^2+5x+6}\)

f3 = (x**2 + 10*x + 21) / (x**2 + 5*x + 6)
f3
sy.limit(f3, x, -3)

SymPy can also handle limits at infinity. The infinity symbol is entered as oo (that’s the letter o, twice). But we need to specify that it comes from SymPy, so we type sy.oo.

sy.limit(f3,x,sy.oo)