main.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from dataclasses import dataclass
  2. from math import pi, tan, atan2, sqrt
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. @dataclass
  6. class Line:
  7. x: float
  8. y: float
  9. angle: float
  10. def __repr__(self):
  11. return f"Line(x={self.x}, y={self.y}, angle={self.angle*180/pi})"
  12. def bounce_wall(line):
  13. if line.angle > pi:
  14. raise ValueError
  15. intercept_x = line.x - line.y / tan(line.angle)
  16. angle = -line.angle + 2*pi
  17. return Line(x=intercept_x, y=0, angle=angle)
  18. def bounce_circle(line):
  19. x0 = line.x
  20. t = tan(line.angle)
  21. t2 = t*t
  22. a = 1 + t2
  23. b = 4*t - 2*t2*x0
  24. c = t2*x0*x0 - 4*x0*t + 3
  25. rad = b*b - 4*a*c
  26. if rad < 0:
  27. raise ValueError
  28. x1 = (-b + sqrt(rad)) / (2*a)
  29. y1 = (x1 - x0)*t
  30. x2 = (-b - sqrt(rad)) / (2*a)
  31. y2 = (x2 - x0)*t
  32. x, y = (x1, y1) if y1 > y2 else (x2, y2)
  33. t_tan = (atan2(y+2, x) - pi/2) % (2*pi)
  34. angle = (line.angle + 2*(t_tan - line.angle)) % (2*pi)
  35. return Line(x=x, y=y, angle=angle)
  36. def do_bounces(angle_deg, x=None):
  37. if x is not None:
  38. angle_deg = np.arctan2(2, x + 2) * 180 / pi
  39. lines = [Line(-2, -2, angle_deg*pi/180)]
  40. try:
  41. while True:
  42. lines.append(bounce_wall(lines[-1]))
  43. lines.append(bounce_circle(lines[-1]))
  44. except ValueError:
  45. pass
  46. return len(lines)-1, lines
  47. def draw_bounces(lines):
  48. xs = [line.x for line in lines]
  49. ys = [line.y for line in lines]
  50. plt.plot([-4, 4], [0, 0])
  51. plt.plot(xs, ys)
  52. plt.gca().add_patch(plt.Circle((0, -2), radius=1))
  53. plt.xlim((-1, 1))
  54. plt.ylim((-1.8, 0.2))
  55. plt.show()
  56. def count_range(start, end, count=100):
  57. xs = np.linspace(start, end, count)
  58. angles = np.arctan2(2, xs + 2) * 180 / pi
  59. bounces = []
  60. for angle in angles:
  61. count, _ = do_bounces(angle)
  62. bounces.append(count)
  63. plt.plot(xs, bounces)
  64. plt.show()
  65. return xs, np.array(bounces)
  66. def iterative_zoom(start, end, n_iter):
  67. maxes = []
  68. for _ in range(n_iter):
  69. xs, bounces = count_range(start, end, 1000)
  70. max_x = xs[np.argmax(bounces)]
  71. maxes.append(max_x)
  72. range_ = end - start
  73. start = max_x - 0.05*range_
  74. end = max_x + 0.05*range_
  75. print(maxes)
  76. plt.plot(maxes)
  77. plt.show()
  78. def main():
  79. # count_range(-2, 0, 10000)
  80. # count_range(-0.824, -0.822, 10000)
  81. # count_range(-0.8225, -0.8223, 10000)
  82. # count_range(58, 62)
  83. # count_range(59, 60)
  84. # count_range(59.51, 59.515)
  85. # draw_bounces(do_bounces(None, x=-0.8224863249433897)[1])
  86. iterative_zoom(-2, 0, 20)
  87. if __name__ == '__main__':
  88. main()