-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculator.py
195 lines (162 loc) · 7.28 KB
/
calculator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import sympy
import str_parser as parser
from typing import List, Tuple, Union
MAX_NUMBER_THRESHOLD = sympy.Number(1e-10) # The max size of numbers to be considered zero.
x, y = sympy.symbols("x y") # Variables for the function, y is dependent on x.
dbg_print = False
dbg = print if dbg_print else lambda *a, **k: None
class Line:
"""
Represents information about a line.
Attributes:
slope (sympy.Number): The slope of the line.
x_value (sympy.Number): The x value of the tangent point.
y_value (sympy.Number): The y value of the tangent point.
equation (sympy.Expr): The right-hand side of the equation of the line, where y is the left-hand side.
"""
def __init__(self, slope: sympy.Number, x_value: sympy.Number, y_value: sympy.Number, equation: sympy.Expr):
self.slope = slope
self.x_value = x_value
self.y_value = y_value
self.equation = equation
@property
def __dict__(self) -> dict:
return {
"slope": str(self.slope),
"x_value": str(self.x_value),
"y_value": str(self.y_value),
"equation": str(self.equation)
}
def build_line_equation(slope: sympy.Number, x_value: sympy.Number, y_value: sympy.Number, exact: bool) -> sympy.Expr:
"""
Build the equation of a line given the slope, x value, and y value.
Args:
slope (sympy.Number): The slope of the line.
x_value (sympy.Number): The x value of the line.
y_value (sympy.Number): The y value of the line.
exact (bool): Whether to use exact values.
Returns:
sympy.Expr: The right-hand side of the line equation.
"""
equation = None
if slope == sympy.zoo: # Vertical line
equation = sympy.simplify(x - x_value)
else:
equation = sympy.simplify(slope * x - y - slope * x_value + y_value)
if exact:
return equation
else:
return equation.evalf()
def on_function_line(function: sympy.Expr, dy_dx: sympy.Expr, x_input: sympy.Number, y_input: sympy.Number, exact: bool) -> Line:
"""
Find the equation of a tangent line when the point is on the function.
Args:
function (sympy.Expr): The function.
dy_dx (sympy.Expr): The derivative of the function.
x_input (sympy.Number): The x value of the point.
y_input (sympy.Number): The y value of the point.
exact (bool): Whether to use exact values.
Returns:
Line: The tangent line.
"""
slope = dy_dx.subs(x, x_input).subs(y, y_input) # Determine the slope of the tangent.
rhs_equation = build_line_equation(slope, x_input, y_input, exact)
return Line(slope, x_input, y_input, rhs_equation)
def exterior_function_line(function: sympy.Expr, dy_dx: sympy.Expr, x_input: sympy.Number, y_input: sympy.Number, exact: bool) -> List[Line]:
"""
Find the equation of a tangent line when the point is exterior to the function.
Args:
function (sympy.Expr): The function.
dy_dx (sympy.Expr): The derivative of the function.
x_input (sympy.Number): The x value of the point.
y_input (sympy.Number): The y value of the point.
exact (bool): Whether to use exact values.
Returns:
List[Line]: The tangent lines.
"""
# Since the point is not on the curve, we need to solve for the point of tangency.
# Let the point of tangency be (a, f(a)), where f is the function.
a = sympy.Symbol("a") # Define the variable a.
f_a_list = sympy.solve(function.subs(x, a), y) # Solve for f(a).
# The slope of the tangent is dy/dx, so dy/dx = (f(a) - y) / (a - x).
lhs_list = [dy_dx.subs(x, a).subs(y, f_a) for f_a in f_a_list]
rhs_list = [(f_a - y_input) / (a - x_input) for f_a in f_a_list]
# Solve for a.
x_values = set()
for i in range(len(f_a_list)):
lhs = lhs_list[i]
rhs = rhs_list[i]
for x_value in sympy.solve(lhs - rhs, a, check=False): # Avoid checking to keep vertical lines
real, imag = x_value.as_real_imag()
if imag < MAX_NUMBER_THRESHOLD: # If the imaginary part is small enough, it is considered zero.
x_values.add(real)
points = []
for x_i in x_values:
y_values = list(sympy.solve(function.subs(x, x_i), y))
if exact:
for y_i in y_values:
points.append([x_i, y_i])
else:
for y_i in y_values:
points.append([x_i.evalf(), y_i.evalf()])
dbg("Point(s) of tangency (may contain extraneous points):")
for point in points:
dbg(f"({point[0]}, {point[1]})")
lines = []
for [x_i, y_i] in points:
slope_1 = dy_dx.subs(x, x_i).subs(y, y_i)
slope_2 = (y_i - y_input) / (x_i - x_input)
# Check if the slope of the tangent and two points are equal.
try:
if sympy.Abs(slope_1 - slope_2) > MAX_NUMBER_THRESHOLD:
continue
except TypeError as exception:
# Edge case: The line is vertical.
if not (slope_1 == sympy.zoo and slope_2 == sympy.zoo):
continue
rhs_equation = build_line_equation(slope_1, x_i, y_i, exact)
lines.append(Line(slope_1, x_i, y_i, rhs_equation))
return lines
def calculate(function: sympy.Expr, x_input: sympy.Number, y_input: sympy.Number, exact: bool) -> Tuple[sympy.Expr, Union[List[Line], Exception]]:
"""
Calculate the equation of the tangent line.
Args:
function (sympy.Expr): The function.
x_input (sympy.Number): The x value of the point.
y_input (sympy.Number): The y value of the point.
exact (bool): Whether to use exact values.
Returns:
sympy.Expr: dy/dx of the function.
Union[List[Line], Exception]: The tangent lines or an exception if one occurs when solving for the point of tangency
"""
dy_dx = sympy.idiff(function, y, x) # Find the derivative of y with respect to x.
lines = []
try:
# Determine if the point is on the function.
if function.subs(x, x_input).subs(y, y_input) == 0:
lines.append(on_function_line(function, dy_dx, x_input, y_input, exact))
else:
lines.extend(exterior_function_line(function, dy_dx, x_input, y_input, exact))
except Exception as exception:
return dy_dx, exception
return dy_dx, lines
if __name__ == "__main__":
print("Tangent Line Equation Finder Using Derivatives")
function_str = input("Enter a function in terms of x and y: 0 = ")
if "y" not in function_str:
function_str = f"y - ({function_str})"
function = parser.parse(function_str)
x_input = parser.parse(input("Enter the x value of the point: "))
y_input = parser.parse(input("Enter the y value of the point: "))
exact = input("Type y to use exact values: ") == "y"
dy_dx, lines = calculate(function, x_input, y_input, exact)
print(f"The derivative of y with respect to x is {dy_dx}.")
if isinstance(lines, Exception):
print(f"An error occurred when solving for the point of tangency: {lines}")
else:
print("The tangent point(s) and line(s) is/are:")
for line in lines:
print(f"Tangent point: ({line.x_value}, {line.y_value})")
print(f"{line.equation} = 0")
if len(lines) == 0:
print("No tangent line exists.")