aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano.tronto@gmail.com>2021-05-26 11:26:53 +0200
committerSebastiano Tronto <sebastiano.tronto@gmail.com>2021-05-26 11:26:53 +0200
commit843d9afdb48f1344bd1ab1473ee39be0cccb23ca (patch)
tree5d5712f7a9f12a3750f23fb8c0e4382e1d228b0d
parentb776f967e46827d2936be82466ce7cdfd8cfc0d1 (diff)
downloadarithmeticbilliard-843d9afdb48f1344bd1ab1473ee39be0cccb23ca.tar.gz
arithmeticbilliard-843d9afdb48f1344bd1ab1473ee39be0cccb23ca.zip
First commit
-rw-r--r--README.md4
-rw-r--r--billiard3d.py197
2 files changed, 201 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..11ad7d8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
1See [Wikipedia: Arithmetic Billiards](https://en.wikipedia.org/wiki/Arithmetic_billiards).
2
3This Python script uses matplotlib to draw pictures for a 3d arithmetic
4billiard, either as a 2d projection or as an axonometry.
diff --git a/billiard3d.py b/billiard3d.py
new file mode 100644
index 0000000..d77fc1a
--- /dev/null
+++ b/billiard3d.py
@@ -0,0 +1,197 @@
1import matplotlib.pyplot as plt
2from mpl_toolkits.mplot3d import Axes3D
3from math import pi, sin, cos, degrees, gcd #, lcm (python3.9+ only)
4
5# Python 3.8 or lower
6def lcm(a, b):
7 return a*b // gcd(a,b)
8
9###############################################################################
10# This part is generic for any size
11###############################################################################
12def is_corner(n, sizes, point):
13 for i in range(n):
14 if point[i] % sizes[i] != 0:
15 return False
16 return True
17
18def is_bouncing(n, sizes, point):
19 for i in range(n):
20 if point[i] % sizes[i] == 0:
21 return True
22 return False
23
24def lcm_list(L):
25 ret = 1
26 for e in L:
27 ret = lcm(ret, e)
28 return ret
29
30# Generic n-dimensional path.
31def get_path(n, sizes, r):
32 if n <= 1 or len(sizes) != n or len(r) != n:
33 print("Invalid sizes or starting point:", n, sizes, r)
34 return
35
36 path = [r]
37 p = r
38 d = [1] * n
39 steps = 0
40 while steps < 2*lcm_list(sizes):
41 p = [p[i] + d[i] for i in range(n)]
42 path.append(p)
43 steps = steps+1
44 d = [d[i] if p[i] % sizes[i] != 0 else -d[i] for i in range(n)]
45 return path
46
47###############################################################################
48# This part is specific for 2d drawings
49###############################################################################
50class Transformation:
51 def __init__(self, angle=0.0, mirror=False, shift=(0,0)):
52 self.angle = angle
53 self.mirror = mirror
54 self.shift = shift
55
56 def rotate(self, point):
57 c, s = cos(self.angle), sin(self.angle)
58 return [c*point[0] - s*point[1], s*point[0] + c*point[1]]
59
60 def reflect(self, point):
61 return [point[0], -point[1] if self.mirror else point[1]]
62
63 def translate(self, point):
64 return [point[i] + self.shift[i] for i in range(2)]
65
66 def apply_to(self, point):
67 return self.translate(self.rotate(self.reflect(point)))
68
69def draw_line_2d(p1, p2, c="black", w=1):
70 plt.plot([p1[0], p2[0]], [p1[1], p2[1]], color=c, linewidth=w)
71
72def draw_grid_and_rectangle(sizes, t):
73 for i in range(0, sizes[0]+1):
74 p1 = t.apply_to([i,0])
75 p2 = t.apply_to([i,sizes[1]])
76 color, width = ("red", 1) if i % sizes[0] == 0 else ("grey", 0.5)
77 draw_line_2d(p1, p2, c=color, w=width)
78 for i in range(0, sizes[1]+1):
79 p1 = t.apply_to([0,i])
80 p2 = t.apply_to([sizes[0],i])
81 color, width = ("red", 1) if i % sizes[1] == 0 else ("grey", 0.5)
82 draw_line_2d(p1, p2, c=color, w=width)
83
84def draw_path_2d(sizes, r, transformation):
85 if len(sizes) != 2:
86 print("Cannot draw non-2d path")
87 return
88
89 plt.axis("off")
90 plt.axis("equal")
91 draw_grid_and_rectangle(sizes, transformation)
92
93 path = [transformation.apply_to(p) for p in get_path(2, sizes, r)]
94 plt.plot([p[0] for p in path], [p[1] for p in path], color="blue")
95
96###############################################################################
97# This part is specific for drawing the 2d projections of 3d billiards
98###############################################################################
99class Face:
100 def __init__(self, fixed_coordinate, value, transformation):
101 self.f = fixed_coordinate
102 self.v = value
103 self.t = transformation
104
105 def contains(self, point):
106 return point[self.f] == self.v
107
108 def proj(self, point):
109 return [point[i] for i in range(3) if i != self.f]
110
111def draw_bouncing_points_3d2d(sizes, r, face):
112 path_3d = get_path(3, sizes, r)
113 bp = [face.t.apply_to(face.proj(p))
114 for p in path_3d if is_bouncing(3, sizes, p) and face.contains(p)]
115
116 plt.scatter([p[0] for p in bp], [p[1] for p in bp], color="green")
117
118def draw_3d_projections(sizes, r):
119 a, b, c = sizes
120
121 bottom = Face(2, 0, Transformation())
122 top = Face(2, c, Transformation(shift=(a+c, -(b+c))))
123 front = Face(1, 0, Transformation(mirror=True))
124 back = Face(1, b, Transformation(shift=(0,b)))
125 left = Face(0, 0, Transformation(angle=pi/2))
126 right = Face(0, a, Transformation(angle=pi/2, mirror=True, shift=(a,0)))
127
128 for face in [bottom, top, front, back, left, right]:
129 draw_path_2d(face.proj(sizes), face.proj(r), face.t)
130 draw_bouncing_points_3d2d(sizes, r, face)
131 plt.show()
132
133###############################################################################
134# This part is for drawing 3d pictures
135###############################################################################
136def draw_line_3d(ax, p1, p2, c="black", w=1):
137 ax.plot([p1[0], p2[0]], [p1[1], p2[1]], [p1[2], p2[2]], color=c, lw=w)
138
139def draw_box_3d(ax, s):
140 color, width = ("red", 1)
141
142 draw_line_3d(ax, ( 0, 0, 0), (s[0], 0, 0), color, width)
143 draw_line_3d(ax, ( 0, s[1], 0), (s[0], s[1], 0), color, width)
144 draw_line_3d(ax, ( 0, 0, s[2]), (s[0], 0, s[2]), color, width)
145 draw_line_3d(ax, ( 0, s[1], s[2]), (s[0], s[1], s[2]), color, width)
146
147 draw_line_3d(ax, ( 0, 0, 0), ( 0, s[1], 0), color, width)
148 draw_line_3d(ax, (s[0], 0, 0), (s[0], s[1], 0), color, width)
149 draw_line_3d(ax, ( 0, 0, s[2]), ( 0, s[1], s[2]), color, width)
150 draw_line_3d(ax, (s[0], 0, s[2]), (s[0], s[1], s[2]), color, width)
151
152 draw_line_3d(ax, ( 0, 0, 0), ( 0, 0, s[2]), color, width)
153 draw_line_3d(ax, (s[0], 0, 0), (s[0], 0, s[2]), color, width)
154 draw_line_3d(ax, ( 0, s[1], 0), ( 0, s[1], s[2]), color, width)
155 draw_line_3d(ax, (s[0], s[1], 0), (s[0], s[1], s[2]), color, width)
156
157def draw_3d_picture(sizes, r):
158 ax = plt.figure().add_subplot(111, projection="3d")
159
160 plt.axis("off")
161 ax.set_xlim(0, max(sizes))
162 ax.set_ylim(0, max(sizes))
163 ax.set_zlim(0, max(sizes))
164
165 draw_box_3d(ax, sizes)
166
167 path = get_path(3, sizes, r)
168 plt.plot([p[0] for p in path], [p[1] for p in path], [p[2] for p in path],
169 color="blue")
170
171 plt.show()
172
173###############################################################################
174# Billiard data / user input
175###############################################################################
176def user_input():
177 a = int(input("Value for a: "))
178 b = int(input("Value for b: "))
179 c = int(input("Value for c: "))
180 ra = int(input("a-coordinate of r: "))
181 rb = int(input("b-coordinate of r: "))
182 rc = int(input("c-coordinate of r: "))
183 pic = input("Choose p for projections or 3 for 3d (empty = both): ")
184 return [a,b,c], [ra,rb,rc], pic
185
186# You can choose to write your input here or to get it interactively
187#sizes, r, pic = [15, 9, 7], [2, 0, 0], "p"
188#sizes, r, pic = [2, 3, 4], [0, 0, 0], "3"
189sizes, r, pic = user_input()
190
191if pic == "p":
192 draw_3d_projections(sizes, r)
193elif pic == "3":
194 draw_3d_picture(sizes, r)
195else:
196 draw_3d_projections(sizes, r)
197 draw_3d_picture(sizes, r)

Generated with cgit - Back to sebastiano.tronto.net