Skip to content

added type hints and doctests to arithmetic_analysis/newton_method.py #2259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 1, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions arithmetic_analysis/newton_method.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
"""Newton's Method."""

# Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method


# function is the f(x) and function1 is the f'(x)
def newton(function, function1, startingInt):
x_n = startingInt
from typing import Callable


# function is the f(x) and derivative is the f'(x)
def newton(
function: Callable[[float], float],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to defined a type for Callable[[float], float] and then use that type to simplify the call parameters?

Copy link
Contributor Author

@spamegg1 spamegg1 Aug 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I think there is. I could try defining a type hint alias as described here:
https://docs.python.org/3/library/typing.html#type-aliases
Let me try and see if it passes mypy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you know? It does work!

derivative: Callable[[float], float],
starting_int: int,
) -> float:
"""
>>> newton(lambda x: x ** 3 - 2 * x - 5, lambda x: 3 * x ** 2 - 2, 3)
2.0945514815423474
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -2)
1.0
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -4)
1.0000000000000102
>>> import math
>>> newton(math.sin, math.cos, 1)
0.0
>>> newton(math.sin, math.cos, 2)
3.141592653589793
>>> newton(math.cos, lambda x: -math.sin(x), 2)
1.5707963267948966
>>> newton(math.cos, lambda x: -math.sin(x), 0)
Traceback (most recent call last):
...
ZeroDivisionError: float division by zero, could not find root
"""
prev_guess: float = starting_int
while True:
x_n1 = x_n - function(x_n) / function1(x_n)
if abs(x_n - x_n1) < 10 ** -5:
return x_n1
x_n = x_n1
if derivative(prev_guess) == 0:
raise ZeroDivisionError("float division by zero, could not find root")

next_guess: float = prev_guess - function(prev_guess) / derivative(prev_guess)
Copy link
Member

@cclauss cclauss Aug 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if derivative(prev_guess) == 0:
raise ZeroDivisionError("float division by zero, could not find root")
next_guess: float = prev_guess - function(prev_guess) / derivative(prev_guess)
try:
next_guess: float = prev_guess - function(prev_guess) / derivative(prev_guess)
except ZeroDivisionError:
raise ZeroDivisionError("Could not find root")

Avoid calculating the derivative twice. The error type already says that we have a division by zero -- we do not need to repeat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, got it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @spamegg1, from OSSU here. I was just going through this issue and looking at the changes made and this will create a problem where if there is a ZeroDivisionError then two exceptions will be raised. This PR is merged so it's better for @cclauss to make this small change.

>>> try:
...     a = 1/0
... except ZeroDivisionError:
...     raise ZeroDivisionError("Not possible")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
ZeroDivisionError: Not possible

Why? the except block is used to handle the exceptions, if you want to raise an exception inside it you need to use the from clause like so:

>>> try:
...     a = 1/0
... except ZeroDivisionError:
...     raise ZeroDivisionError("Not possible") from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
ZeroDivisionError: Not possible

You can also use from in other ways which I won't go into detail much here but just wanted to point this out. 😁

if abs(prev_guess - next_guess) < 10 ** -5:
return next_guess
prev_guess = next_guess


def f(x):
def f(x: float) -> float:
return (x ** 3) - (2 * x) - 5


def f1(x):
def f1(x: float) -> float:
return 3 * (x ** 2) - 2


Expand Down