Skip to content

Commit 200429f

Browse files
ChrisO345github-actionspre-commit-ci[bot]
authored
Dual Number Automatic Differentiation (TheAlgorithms#8760)
* Added dual_number_automatic_differentiation.py * updating DIRECTORY.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update maths/dual_number_automatic_differentiation.py --------- Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent df88771 commit 200429f

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

Diff for: DIRECTORY.md

+1
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@
549549
* [Dodecahedron](maths/dodecahedron.py)
550550
* [Double Factorial Iterative](maths/double_factorial_iterative.py)
551551
* [Double Factorial Recursive](maths/double_factorial_recursive.py)
552+
* [Dual Number Automatic Differentiation](maths/dual_number_automatic_differentiation.py)
552553
* [Entropy](maths/entropy.py)
553554
* [Euclidean Distance](maths/euclidean_distance.py)
554555
* [Euclidean Gcd](maths/euclidean_gcd.py)

Diff for: maths/dual_number_automatic_differentiation.py

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
from math import factorial
2+
3+
"""
4+
https://en.wikipedia.org/wiki/Automatic_differentiation#Automatic_differentiation_using_dual_numbers
5+
https://blog.jliszka.org/2013/10/24/exact-numeric-nth-derivatives.html
6+
7+
Note this only works for basic functions, f(x) where the power of x is positive.
8+
"""
9+
10+
11+
class Dual:
12+
def __init__(self, real, rank):
13+
self.real = real
14+
if isinstance(rank, int):
15+
self.duals = [1] * rank
16+
else:
17+
self.duals = rank
18+
19+
def __repr__(self):
20+
return (
21+
f"{self.real}+"
22+
f"{'+'.join(str(dual)+'E'+str(n+1)for n,dual in enumerate(self.duals))}"
23+
)
24+
25+
def reduce(self):
26+
cur = self.duals.copy()
27+
while cur[-1] == 0:
28+
cur.pop(-1)
29+
return Dual(self.real, cur)
30+
31+
def __add__(self, other):
32+
if not isinstance(other, Dual):
33+
return Dual(self.real + other, self.duals)
34+
s_dual = self.duals.copy()
35+
o_dual = other.duals.copy()
36+
if len(s_dual) > len(o_dual):
37+
o_dual.extend([1] * (len(s_dual) - len(o_dual)))
38+
elif len(s_dual) < len(o_dual):
39+
s_dual.extend([1] * (len(o_dual) - len(s_dual)))
40+
new_duals = []
41+
for i in range(len(s_dual)):
42+
new_duals.append(s_dual[i] + o_dual[i])
43+
return Dual(self.real + other.real, new_duals)
44+
45+
__radd__ = __add__
46+
47+
def __sub__(self, other):
48+
return self + other * -1
49+
50+
def __mul__(self, other):
51+
if not isinstance(other, Dual):
52+
new_duals = []
53+
for i in self.duals:
54+
new_duals.append(i * other)
55+
return Dual(self.real * other, new_duals)
56+
new_duals = [0] * (len(self.duals) + len(other.duals) + 1)
57+
for i, item in enumerate(self.duals):
58+
for j, jtem in enumerate(other.duals):
59+
new_duals[i + j + 1] += item * jtem
60+
for k in range(len(self.duals)):
61+
new_duals[k] += self.duals[k] * other.real
62+
for index in range(len(other.duals)):
63+
new_duals[index] += other.duals[index] * self.real
64+
return Dual(self.real * other.real, new_duals)
65+
66+
__rmul__ = __mul__
67+
68+
def __truediv__(self, other):
69+
if not isinstance(other, Dual):
70+
new_duals = []
71+
for i in self.duals:
72+
new_duals.append(i / other)
73+
return Dual(self.real / other, new_duals)
74+
raise ValueError()
75+
76+
def __floordiv__(self, other):
77+
if not isinstance(other, Dual):
78+
new_duals = []
79+
for i in self.duals:
80+
new_duals.append(i // other)
81+
return Dual(self.real // other, new_duals)
82+
raise ValueError()
83+
84+
def __pow__(self, n):
85+
if n < 0 or isinstance(n, float):
86+
raise ValueError("power must be a positive integer")
87+
if n == 0:
88+
return 1
89+
if n == 1:
90+
return self
91+
x = self
92+
for _ in range(n - 1):
93+
x *= self
94+
return x
95+
96+
97+
def differentiate(func, position, order):
98+
"""
99+
>>> differentiate(lambda x: x**2, 2, 2)
100+
2
101+
>>> differentiate(lambda x: x**2 * x**4, 9, 2)
102+
196830
103+
>>> differentiate(lambda y: 0.5 * (y + 3) ** 6, 3.5, 4)
104+
7605.0
105+
>>> differentiate(lambda y: y ** 2, 4, 3)
106+
0
107+
>>> differentiate(8, 8, 8)
108+
Traceback (most recent call last):
109+
...
110+
ValueError: differentiate() requires a function as input for func
111+
>>> differentiate(lambda x: x **2, "", 1)
112+
Traceback (most recent call last):
113+
...
114+
ValueError: differentiate() requires a float as input for position
115+
>>> differentiate(lambda x: x**2, 3, "")
116+
Traceback (most recent call last):
117+
...
118+
ValueError: differentiate() requires an int as input for order
119+
"""
120+
if not callable(func):
121+
raise ValueError("differentiate() requires a function as input for func")
122+
if not isinstance(position, (float, int)):
123+
raise ValueError("differentiate() requires a float as input for position")
124+
if not isinstance(order, int):
125+
raise ValueError("differentiate() requires an int as input for order")
126+
d = Dual(position, 1)
127+
result = func(d)
128+
if order == 0:
129+
return result.real
130+
return result.duals[order - 1] * factorial(order)
131+
132+
133+
if __name__ == "__main__":
134+
import doctest
135+
136+
doctest.testmod()
137+
138+
def f(y):
139+
return y**2 * y**4
140+
141+
print(differentiate(f, 9, 2))

0 commit comments

Comments
 (0)