Tutorial
Tutorial#
We will here write some code to create and manipulate quadratic expressions.
With sympy
this is not necessary as all functionality required is available
within sympy
however this will be a good exercise in understanding how to
build such functionality.
Problem
Consider the following quadratics:
Without using sympy
, obtain the roots for all the quadratics.
We will start by defining an object to represent a quadratic. This is called a class.
import math
class QuadraticExpression:
"""A class for a quadratic expression"""
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
self.discriminant = self.b ** 2 - 4 * self.a * self.c
def get_roots(self):
"""
Return the real valued roots of the quadratic expression
Returns
-------
array
The roots of the quadratic
"""
if self.discriminant >= 0:
x1 = -(self.b + math.sqrt(self.discriminant)) / (2 * self.a)
x2 = -(self.b - math.sqrt(self.discriminant)) / (2 * self.a)
return x1, x2
return ()
def __add__(self, other):
"""A magic method: let's us have addition between expressions"""
return QuadraticExpression(self.a + other.a, self.b + other.b, self.c + other.c)
def __repr__(self):
"""A magic method: changes the default way an instance is displayed"""
return f"Quadratic expression: {self.a} x ^ 2 + {self.b} x + {self.c}"
Tip
Four functions were created with this class:
__init__
: as this is surrounded by__
(two underscores) this is a magic function that is run when we create an instance of our class.get_roots
: this returns the two real valued roots if the discriminant is positive.__add__
: another magic function that is run when the+
operator is used.__repr__
: another magic function that gives the string representation of the instance.
Let us now use this class to solve the specified problem. First we create
instances the class that correspond to \(f\) and \(g\). This is using the __init__
function in the background.
f = QuadraticExpression(a=5, b=2, c=-7)
g = QuadraticExpression(a=-4, b=-3, c=12)
We can now take a look at both of these instances. This is using the __repr__
function in the background:
f
Quadratic expression: 5 x ^ 2 + 2 x + -7
g
Quadratic expression: -4 x ^ 2 + -3 x + 12
Now we are going to create \(h(x) = f(x) + g(x)\). This is using the __add__
function in the background:
h = f + g
h
Quadratic expression: 1 x ^ 2 + -1 x + 5
We can now iterate over our quadratics and find the roots. This is using the
get_roots
function in the background:
roots = [quadratic.get_roots() for quadratic in (f, g, h)]
roots
[(-1.4, 1.0), (1.3971808598447282, -2.1471808598447284), ()]
We see that \(f\) and \(g\) have real valued roots but \(h\) does not. We can check the value of the discriminant of \(h\):
h.discriminant
-19
We are going to now create a new class from QuadraticExpression
where we
replace the get_roots
function with a new one that can handle imaginary roots
and update the __add__
function to make sure we return an instance of the new
class.
class QuadraticExpressionWithAllRoots(QuadraticExpression):
"""
A class for a quadratic expression that can return imaginary roots
The `get_roots` function returns two tuples of the form (re, im) where re is
the real part and im is the imaginary part.
"""
def get_roots(self):
"""
Return the real valued roots of the quadratic expression
Returns
-------
array
The roots of the quadratic
"""
if self.discriminant >= 0:
x1 = -(self.b + math.sqrt(self.discriminant)) / (2 * self.a)
x2 = -(self.b - math.sqrt(self.discriminant)) / (2 * self.a)
return (x1, 0), (x2, 0)
real_part = self.b / (2 * self.a)
im1 = math.sqrt(-self.discriminant) / (2 * self.a)
im2 = -math.sqrt(-self.discriminant) / (2 * self.a)
return ((real_part, im1), (real_part, im2))
def __add__(self, other):
"""A special method: let's us have addition between expressions"""
return QuadraticExpressionWithAllRoots(
self.a + other.a, self.b + other.b, self.c + other.c
)
Now let us define our quadratics once again but using this new class:
f = QuadraticExpressionWithAllRoots(a=5, b=2, c=-7)
g = QuadraticExpressionWithAllRoots(a=-4, b=-3, c=12)
h = f + g
f
Quadratic expression: 5 x ^ 2 + 2 x + -7
g
Quadratic expression: -4 x ^ 2 + -3 x + 12
h
Quadratic expression: 1 x ^ 2 + -1 x + 5
Attention
We have not needed to redefine __init__
, or __repr__
as the new
class inherits these from QuadraticExpression
due to this statement:
class QuadraticExpressionWithAllRoots(QuadraticExpression):
We can now get all the roots for both quadratics:
roots = [quadratic.get_roots() for quadratic in (f, g, h)]
roots
[((-1.4, 0), (1.0, 0)),
((1.3971808598447282, 0), (-2.1471808598447284, 0)),
((-0.5, 2.179449471770337), (-0.5, -2.179449471770337))]