4. Improved Quadratic Equation Solver¶
In this notebook, we will improve the quadratic equation solver in the previous lab using conditional executions.
First of all, run the following to setup the environment.
%reset -f
from ipywidgets import interact
import math
4.1. Zero Discriminant¶
Recall that the quadratic equation is
where \(a\), \(b\), and \(c\) are real-valued coefficients, and \(x\) is the unknown variable. The roots are normally given by
The roots are the same (repeated) when the discriminant \(b^2-4ac\) is zero.
Exercise Assign to roots
only one root when the discriminant is zero. E.g., if \((a,b,c)=(1,-2,1)\), then roots
should be assigned the value 1.0
instead of 1.0, 1.0
. If there are two roots, give them in the order of the above formula.
Hint: Use the if
statement.
Hint: The following is a solution template with some missing code. You are NOT required to follow the template.
def get_roots(a, b, c):
d = b**2 - 4 * a * c # discriminant
if math.isclose(d, 0):
roots = __________ # repeated root
else:
d **= 0.5
roots = __________________________________
return roots
def get_roots(a, b, c):
d = b**2 - 4 * a * c # discriminant
if math.isclose(d, 0):
# YOUR CODE HERE
raise NotImplementedError()
return roots
# tests
def test_get_roots(roots, a, b, c):
roots_ = get_roots(a, b, c)
if roots is None:
correct = roots_ is None
elif isinstance(roots, float):
correct = isinstance(roots_, float) and math.isclose(roots, roots_)
else:
correct = isinstance(roots_, tuple) and len(roots_) == 2 and all([
math.isclose(root, roots_) for root, roots_ in zip(roots, roots_)
])
if not correct:
print(f'With (a, b, c)={a,b,c}, roots should be {roots} not {roots_}.')
assert correct
test_get_roots((-1.0, 0.0), 1, 1, 0)
test_get_roots(0.0, 1, 0, 0)
Exercise Why use math.isclose(d,0)
instead of d == 0
?
YOUR ANSWER HERE
4.2. Linear Equation¶
If \(a=0\), the earlier formula for the roots are invalid due to division by zero. Nevertheless, the equation remains valid:
Exercise Improve the function get_roots
to return the root \(-\frac{c}{b}\) if \(a=0\).
Hint: Solution template:
def get_roots(a, b, c):
d = b**2 - 4 * a * c # discriminant
if __________________:
roots = ______
elif math.isclose(d, 0):
roots = __________ # repeated root
else:
d **= 0.5
roots = __________________________________
return roots
def get_roots(a, b, c):
d = b**2 - 4 * a * c
# YOUR CODE HERE
raise NotImplementedError()
return roots
# tests
def test_get_roots(roots, a, b, c):
roots_ = get_roots(a, b, c)
if roots is None:
correct = roots_ is None
elif isinstance(roots, float):
correct = isinstance(roots_, float) and math.isclose(roots, roots_)
else:
correct = isinstance(roots_, tuple) and len(roots_) == 2 and all([
math.isclose(root, roots_) for root, roots_ in zip(roots, roots_)
])
if not correct:
print(f'With (a, b, c)={a,b,c}, roots should be {roots} not {roots_}.')
assert correct
test_get_roots((-1.0, -0.0), 1, 1, 0)
test_get_roots(0.0, 1, 0, 0)
test_get_roots(0.5, 0, -2, 1)
4.3. Degenerate Cases¶
What if \(a=b=0\)? In that case, the equation becomes
which is always satisfied if \(c=0\), but never satisfied if \(c\neq 0\).
Exercise Improve the function get_roots
to return root(s) under all cases:
If \(a=0\) and \(b\neq 0\), assign
roots
to the single root \(-\frac{c}{b}\).If \(a=b=0\) and \(c\neq 0\), assign
roots
toNone
.
Note thatNone
is an object, not a string.If \(a=b=c=0\), there are infinitely many roots. Assign to
roots
the tuple-float('inf'), float('inf')
.
Note thatfloat('inf')
converts the string'inf'
to a floating point value that represents \(\infty\).
Hint: Use nested if
statements such as the followings (with the blanks filled in properly):
def get_roots(a, b, c):
d = b**2 - 4 * a * c
if __________________:
if __________________:
if __________________:
roots = -float('inf'), float('inf')
else:
roots = None
else:
______________
elif math.isclose(d, 0):
roots = __________ # repeated root
else:
d **= 0.5
roots = __________________________________
return roots
def get_roots(a, b, c):
d = b**2 - 4 * a * c
# YOUR CODE HERE
raise NotImplementedError()
return roots
# tests
def test_get_roots(roots, a, b, c):
roots_ = get_roots(a, b, c)
if roots is None:
correct = roots_ is None
elif isinstance(roots, float):
correct = isinstance(roots_, float) and math.isclose(roots, roots_)
else:
correct = isinstance(roots_, tuple) and len(roots_) == 2 and all([
math.isclose(root, roots_) for root, roots_ in zip(roots, roots_)
])
if not correct:
print(f'With (a, b, c)={a,b,c}, roots should be {roots} not {roots_}.')
assert correct
test_get_roots((-1.0, 0.0), 1, 1, 0)
test_get_roots(0.0, 1, 0, 0)
test_get_roots((-float('inf'), float('inf')), 0, 0, 0)
test_get_roots(None, 0, 0, 1)
test_get_roots(0.5, 0, -2, 1)
test_get_roots(1.0, 1, -2, 1)
4.4. Run the calculator¶
After you have complete the exercises, you can run your robust solver below:
# quadratic equations solver
@interact(a=(-10,10,1),b=(-10,10,1),c=(-10,10,1))
def quadratic_equation_solver(a=1,b=2,c=1):
print('Root(s):',get_roots(a,b,c))