euler/python/e144.py

130 lines
3.3 KiB
Python
Raw Normal View History

2023-11-04 02:45:40 +01:00
import math
from dataclasses import dataclass
import matplotlib.pyplot as plt
import numpy as np
@dataclass
class P:
x: float
y: float
@dataclass
class Line:
m: float
n: float
def plot(self, x_min: float, x_max: float, color='r'):
x = np.linspace(x_min, x_max, 10**5)
y = self.m * x + self.n
plt.plot(x, y, color, label='')
def line_from_two_points(p1: P, p2: P) -> Line:
m = (p2.y - p1.y) / (p2.x - p1.x)
n = ((p1.y * p2.x) - (p2.y * p1.x)) / (p2.x - p1.x)
return Line(m, n)
def line_from_one_point(p: P, m: float) -> Line:
n = -m * p.x + p.y
return Line(m, n)
def reflected_slope(m1, m2):
""" Math """
alpha1 = math.atan(m1)
alpha2 = -math.atan(1/m2)
reflected_angle = alpha1 - 2*alpha2
m3 = math.tan(reflected_angle)
return -m3
def get_next_point(start_point: P, line: Line) -> P:
# With y = m*x + n into 4x**2 + y**2 = 10 get
# quadratic equation x**2 + 2mn * x + (n**2 - 10) = 0.
# Solve quadratic equation
a = 4 + line.m ** 2
b = 2 * line.m * line.n
c = line.n**2 - 100
s = math.sqrt(b**2 - 4*a*c)
# Pick correct x
EPSILON = 10e-9
x1 = (-b + s) / (2*a)
x2 = (-b - s) / (2*a)
x = x2 if abs(x1 - start_point.x) < EPSILON else x1
# Calculate corresponding y and return point
y = x * line.m + line.n
return P(x, y)
def calc_slope_ellipse(p: P) -> float:
""" Given by Project Euler. """
return -4 * p.x / p.y
def plot_first_ten():
x = np.linspace(-10, 10, 10**5)
y_positive = np.sqrt(100 - 4*x**2)
y_negative = -np.sqrt(100 - 4*x**2)
plt.figure(figsize=(6,6))
plt.plot(x, y_positive, 'b', label='y=sqrt(10-4x^2)')
plt.plot(x, y_negative, 'b', label='y=-sqrt(10-4x^2)')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Plot of the ellipse 4x^2 + y^2 = 10')
plt.axis('equal')
current_point = P(0.0, 10.1)
next_point = P(1.4, -9.6)
line = line_from_two_points(current_point, next_point)
next_point_computed = get_next_point(current_point, line)
line.plot(0, 1.4)
assert(next_point == next_point_computed)
current_point = next_point
for _ in range(10):
m1 = line.m
m2 = calc_slope_ellipse(current_point)
m3 = reflected_slope(m1, m2)
line = line_from_one_point(current_point, m3)
next_point = get_next_point(current_point, line)
x_min = min(current_point.x, next_point.x)
x_max = max(current_point.x, next_point.x)
line.plot(x_min, x_max, 'g')
current_point = next_point
plt.legend()
plt.show()
def euler_144():
plot_first_ten()
current_point = P(0.0, 10.1)
next_point = P(1.4, -9.6)
line = line_from_two_points(current_point, next_point)
current_point = next_point
counter = 0
while True:
counter += 1
m1 = line.m
m2 = calc_slope_ellipse(current_point)
m3 = reflected_slope(m1, m2)
line = line_from_one_point(current_point, m3)
next_point = get_next_point(current_point, line)
current_point = next_point
if -0.01 <= current_point.x and current_point.x <= 0.01 and current_point.y > 0:
break
return counter
if __name__ == "__main__":
solution = euler_144()
print("e144.py: " + str(solution))
assert(solution == 354)