geometryai 0.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geometryai-0.0.5/PKG-INFO +11 -0
- geometryai-0.0.5/README.md +3 -0
- geometryai-0.0.5/geometryai/__init__.py +3 -0
- geometryai-0.0.5/geometryai/core.py +367 -0
- geometryai-0.0.5/geometryai.egg-info/PKG-INFO +11 -0
- geometryai-0.0.5/geometryai.egg-info/SOURCES.txt +8 -0
- geometryai-0.0.5/geometryai.egg-info/dependency_links.txt +1 -0
- geometryai-0.0.5/geometryai.egg-info/top_level.txt +1 -0
- geometryai-0.0.5/setup.cfg +4 -0
- geometryai-0.0.5/setup.py +11 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
4
|
+
class Graph:
|
|
5
|
+
def __init__(self, space):
|
|
6
|
+
self.n = len(space.point_location)
|
|
7
|
+
self.adj = {i: set() for i in range(self.n)}
|
|
8
|
+
self._build(space.give_connect())
|
|
9
|
+
def _build(self, edges):
|
|
10
|
+
for u, v in edges:
|
|
11
|
+
self.adj[u].add(v)
|
|
12
|
+
self.adj[v].add(u)
|
|
13
|
+
def all_cycles(self):
|
|
14
|
+
cycles = []
|
|
15
|
+
visited = [False] * self.n
|
|
16
|
+
def dfs(start, current, parent, path):
|
|
17
|
+
visited[current] = True
|
|
18
|
+
path.append(current)
|
|
19
|
+
for nxt in self.adj[current]:
|
|
20
|
+
if nxt == parent:
|
|
21
|
+
continue
|
|
22
|
+
if nxt == start and len(path) > 2:
|
|
23
|
+
cycles.append(path.copy())
|
|
24
|
+
elif not visited[nxt]:
|
|
25
|
+
dfs(start, nxt, current, path)
|
|
26
|
+
path.pop()
|
|
27
|
+
visited[current] = False
|
|
28
|
+
for v in range(self.n):
|
|
29
|
+
dfs(v, v, -1, [])
|
|
30
|
+
return cycles
|
|
31
|
+
def _canonical_cycle(self, cycle):
|
|
32
|
+
cycle = cycle[:-1] if cycle[0] == cycle[-1] else cycle
|
|
33
|
+
n = len(cycle)
|
|
34
|
+
rotations = []
|
|
35
|
+
for i in range(n):
|
|
36
|
+
r = cycle[i:] + cycle[:i]
|
|
37
|
+
rotations.append(tuple(r))
|
|
38
|
+
rotations.append(tuple(reversed(r)))
|
|
39
|
+
return min(rotations)
|
|
40
|
+
def simple_cycles(self):
|
|
41
|
+
raw = self.all_cycles()
|
|
42
|
+
unique = set()
|
|
43
|
+
for cycle in raw:
|
|
44
|
+
if len(set(cycle)) != len(cycle):
|
|
45
|
+
continue
|
|
46
|
+
unique.add(self._canonical_cycle(cycle))
|
|
47
|
+
return [list(c) for c in unique]
|
|
48
|
+
def consecutive_triplets_at(self):
|
|
49
|
+
triplets = []
|
|
50
|
+
for v in self.adj.keys():
|
|
51
|
+
nbrs = list(self.adj[v])
|
|
52
|
+
if len(nbrs) >=2:
|
|
53
|
+
for i in range(len(nbrs)):
|
|
54
|
+
for j in range(len(nbrs)):
|
|
55
|
+
if i != j:
|
|
56
|
+
triplets.append((nbrs[i], v, nbrs[j]))
|
|
57
|
+
return triplets
|
|
58
|
+
def draw_geometry(points, edges, size=600, margin=40):
|
|
59
|
+
pts = [(float(x), float(y)) for x, y in points]
|
|
60
|
+
xs = [x for x, y in pts]
|
|
61
|
+
ys = [y for x, y in pts]
|
|
62
|
+
min_x, max_x = min(xs), max(xs)
|
|
63
|
+
min_y, max_y = min(ys), max(ys)
|
|
64
|
+
def transform(x, y):
|
|
65
|
+
sx = (x - min_x) / (max_x - min_x or 1)
|
|
66
|
+
sy = (y - min_y) / (max_y - min_y or 1)
|
|
67
|
+
px = margin + sx * (size - 2 * margin)
|
|
68
|
+
py = size - (margin + sy * (size - 2 * margin))
|
|
69
|
+
return px, py
|
|
70
|
+
img = Image.new("RGB", (size, size), "white")
|
|
71
|
+
draw = ImageDraw.Draw(img)
|
|
72
|
+
for i, j in edges:
|
|
73
|
+
p1 = transform(*pts[i])
|
|
74
|
+
p2 = transform(*pts[j])
|
|
75
|
+
draw.line([p1, p2], fill="black", width=2)
|
|
76
|
+
try:
|
|
77
|
+
font = ImageFont.truetype("arial.ttf", 22)
|
|
78
|
+
except:
|
|
79
|
+
font = ImageFont.load_default()
|
|
80
|
+
r = 4
|
|
81
|
+
label_offset = (6, -6)
|
|
82
|
+
for idx, (x, y) in enumerate(pts):
|
|
83
|
+
px, py = transform(x, y)
|
|
84
|
+
draw.ellipse((px-r, py-r, px+r, py+r), fill="red")
|
|
85
|
+
label = chr(ord("A") + idx)
|
|
86
|
+
draw.text(
|
|
87
|
+
(px + label_offset[0], py + label_offset[1]),
|
|
88
|
+
label,
|
|
89
|
+
fill="blue",
|
|
90
|
+
font=font
|
|
91
|
+
)
|
|
92
|
+
return img
|
|
93
|
+
|
|
94
|
+
def merge_category(cat, mergefx):
|
|
95
|
+
n = len(cat)
|
|
96
|
+
used = [False] * n
|
|
97
|
+
out = []
|
|
98
|
+
for i in range(n):
|
|
99
|
+
if used[i]:
|
|
100
|
+
continue
|
|
101
|
+
merged = []
|
|
102
|
+
for j in range(i, n):
|
|
103
|
+
if not used[j] and mergefx(cat[i], cat[j]):
|
|
104
|
+
merged += cat[j]
|
|
105
|
+
used[j] = True
|
|
106
|
+
out.append(merged)
|
|
107
|
+
return [list(set(item)) for item in out]
|
|
108
|
+
|
|
109
|
+
def polygon_area(points):
|
|
110
|
+
n = len(points)
|
|
111
|
+
area = 0
|
|
112
|
+
for i in range(n - 1):
|
|
113
|
+
area += points[i][0] * points[i + 1][1] - points[i][1] * points[i + 1][0]
|
|
114
|
+
area += points[-1][0] * points[0][1] - points[-1][1] * points[0][0]
|
|
115
|
+
return abs(area) / 2
|
|
116
|
+
def intersection(p1, p2, p3, p4):
|
|
117
|
+
x1, y1 = p1
|
|
118
|
+
x2, y2 = p2
|
|
119
|
+
x3, y3 = p3
|
|
120
|
+
x4, y4 = p4
|
|
121
|
+
A1 = y2 - y1
|
|
122
|
+
B1 = x1 - x2
|
|
123
|
+
C1 = A1 * x1 + B1 * y1
|
|
124
|
+
A2 = y4 - y3
|
|
125
|
+
B2 = x3 - x4
|
|
126
|
+
C2 = A2 * x3 + B2 * y3
|
|
127
|
+
det = A1 * B2 - A2 * B1
|
|
128
|
+
if det == 0:
|
|
129
|
+
return None
|
|
130
|
+
x = (C1 * B2 - C2 * B1) / det
|
|
131
|
+
y = (A1 * C2 - A2 * C1) / det
|
|
132
|
+
return (x, y)
|
|
133
|
+
|
|
134
|
+
def line_sort(line):
|
|
135
|
+
if isinstance(line, str):
|
|
136
|
+
return tuple(sorted([ord(item)-ord("A") for item in line]))
|
|
137
|
+
return tuple(sorted(list(line)))
|
|
138
|
+
class Space:
|
|
139
|
+
def __init__(self):
|
|
140
|
+
self.point_location = []
|
|
141
|
+
self.line_info = []
|
|
142
|
+
self.angle_list = {}
|
|
143
|
+
self.command = []
|
|
144
|
+
self.line = []
|
|
145
|
+
self.ray = []
|
|
146
|
+
self.graph = None
|
|
147
|
+
self.line_eq = []
|
|
148
|
+
self.angle_eq = []
|
|
149
|
+
self.tri_eq = []
|
|
150
|
+
def standard_angle(self, angle):
|
|
151
|
+
|
|
152
|
+
if isinstance(angle, str):
|
|
153
|
+
angle = tuple([ord(item)-ord("A") for item in angle])
|
|
154
|
+
if angle[0] > angle[2]:
|
|
155
|
+
angle = (angle[2],angle[1],angle[0])
|
|
156
|
+
for key in self.angle_list.keys():
|
|
157
|
+
if key == angle or angle in self.angle_list[key]:
|
|
158
|
+
return key
|
|
159
|
+
return None
|
|
160
|
+
def straight_line(self, point_list):
|
|
161
|
+
return polygon_area([self.point_location[x] for x in point_list]) == 0
|
|
162
|
+
def sort_collinear(self, point_list):
|
|
163
|
+
p = min([self.point_location[x][1] for x in point_list])
|
|
164
|
+
p2 = [x for x in point_list if self.point_location[x][1] == p]
|
|
165
|
+
p3 = list(sorted(p2, key=lambda x: self.point_location[x][0]))[0]
|
|
166
|
+
m, n = self.point_location[p3]
|
|
167
|
+
return list(sorted(point_list, key=lambda x: (self.point_location[x][0]-m)**2 + (self.point_location[x][1]-n)**2))
|
|
168
|
+
def calc_angle_list(self):
|
|
169
|
+
lst = self.graph.consecutive_triplets_at()
|
|
170
|
+
lst = list(set([item if item[0]<item[2] else (item[2],item[1],item[0]) for item in lst]))
|
|
171
|
+
lst = [item for item in lst if not self.straight_line(list(item))]
|
|
172
|
+
for item2 in lst:
|
|
173
|
+
a = list(item2[:2])
|
|
174
|
+
b = list(item2[1:])
|
|
175
|
+
self.angle_list[item2] = []
|
|
176
|
+
for item in itertools.permutations(list(set(range(len(self.point_location))) - {item2[1]}),2):
|
|
177
|
+
if (self.straight_line(a+[item[0]]) and self.straight_line(b+[item[1]])) or\
|
|
178
|
+
(self.straight_line(b+[item[0]]) and self.straight_line(a+[item[1]])):
|
|
179
|
+
x = (item[0], item2[1], item[1])
|
|
180
|
+
y = (item[1], item2[1], item[0])
|
|
181
|
+
self.angle_list[item2]+= [x,y]
|
|
182
|
+
def give_connect(self):
|
|
183
|
+
out = []
|
|
184
|
+
for item in self.line_info:
|
|
185
|
+
for i in range(len(item)-1):
|
|
186
|
+
out.append(item[i:i+2])
|
|
187
|
+
return out
|
|
188
|
+
def line_eq_fx(self, line1, line2):
|
|
189
|
+
line1 = line_sort(line1)
|
|
190
|
+
line2 = line_sort(line2)
|
|
191
|
+
for item in self.line_info:
|
|
192
|
+
if line1[0] in item and line1[1] in item and line2[0] in item and line2[1] in item:
|
|
193
|
+
return True
|
|
194
|
+
for item in self.line_eq:
|
|
195
|
+
if line1 in item and line2 in item:
|
|
196
|
+
return True
|
|
197
|
+
return False
|
|
198
|
+
def angle_eq_fx(self, angle1, angle2):
|
|
199
|
+
angle1 = self.standard_angle(angle1)
|
|
200
|
+
angle2 = self.standard_angle(angle2)
|
|
201
|
+
for item in self.angle_eq:
|
|
202
|
+
if angle1 in item and angle2 in item:
|
|
203
|
+
return True
|
|
204
|
+
return False
|
|
205
|
+
def valid_line(self, line):
|
|
206
|
+
line = line_sort(line)
|
|
207
|
+
return any((line[0] in item and line[1] in item) for item in self.line_info)
|
|
208
|
+
def show_diagram(self):
|
|
209
|
+
draw_geometry(self.point_location, self.give_connect()).show()
|
|
210
|
+
def calc_line_info(self):
|
|
211
|
+
line = [self.line_info[x] for x in self.line]
|
|
212
|
+
ray = [(self.line_info[x[0]], x[1]) for x in self.ray]
|
|
213
|
+
self.line = []
|
|
214
|
+
self.ray = []
|
|
215
|
+
cat = []
|
|
216
|
+
for index in range(2):
|
|
217
|
+
for item in itertools.combinations(list(range(len(self.point_location))), 3):
|
|
218
|
+
if self.straight_line(list(item)):
|
|
219
|
+
cat.append(list(item))
|
|
220
|
+
for item in self.line_info:
|
|
221
|
+
if len(item) == 2 and all(item[0] not in item2 or item[1] not in item2 for item2 in cat):
|
|
222
|
+
cat.append(list(item))
|
|
223
|
+
def mergefx(a, b):
|
|
224
|
+
return self.straight_line(list(set(a+b)))
|
|
225
|
+
|
|
226
|
+
cat = merge_category(cat, mergefx)
|
|
227
|
+
p = []
|
|
228
|
+
|
|
229
|
+
for item in itertools.combinations(cat, 2):
|
|
230
|
+
p2 = intersection(*[self.point_location[item2] for item2 in item[0][:2]+item[1][:2]])
|
|
231
|
+
p.append(p2)
|
|
232
|
+
p = list(set(p))
|
|
233
|
+
if self.command != [] and index == 0:
|
|
234
|
+
for i in range(len(self.command)-1,-1,-1):
|
|
235
|
+
for item in p:
|
|
236
|
+
if any(item2[0]==item[0] and item2[1]==item[1] for item2 in self.point_location):
|
|
237
|
+
continue
|
|
238
|
+
self.point_location.append(item)
|
|
239
|
+
lst = [self.command[i][0], len(self.point_location)-1, self.command[i][1]]
|
|
240
|
+
if self.straight_line(lst) and (any(lst[0] in item2 and lst[1] in item2 for item2 in self.line) or self.sort_collinear(lst)[1] == lst[1]):
|
|
241
|
+
pass
|
|
242
|
+
else:
|
|
243
|
+
self.point_location.pop(-1)
|
|
244
|
+
#self.command.pop(-1)
|
|
245
|
+
cat = []
|
|
246
|
+
else:
|
|
247
|
+
break
|
|
248
|
+
self.line_info = [self.sort_collinear(item) for item in cat]
|
|
249
|
+
for item in line:
|
|
250
|
+
for i in range(len(self.line_info)):
|
|
251
|
+
if self.straight_line(list(self.line_info[i])+item):
|
|
252
|
+
self.line.append(i)
|
|
253
|
+
for item in ray:
|
|
254
|
+
for i in range(len(self.line_info)):
|
|
255
|
+
if self.straight_line(list(self.line_info[i])+item[0]):
|
|
256
|
+
self.ray.append((i, item[1]))
|
|
257
|
+
self.graph = Graph(self)
|
|
258
|
+
def default_merge(a, b):
|
|
259
|
+
return (set(a)&set(b)) != {}
|
|
260
|
+
space = Space()
|
|
261
|
+
def draw_triangle():
|
|
262
|
+
global space
|
|
263
|
+
space.point_location = [
|
|
264
|
+
(Fraction(0), Fraction(0)), # A
|
|
265
|
+
(Fraction(4), Fraction(1)), # B
|
|
266
|
+
(Fraction(1), Fraction(3)), # C
|
|
267
|
+
]
|
|
268
|
+
space.line_info = [[0,1],[1,2],[2,0]]
|
|
269
|
+
def given_equal_line(line1, line2):
|
|
270
|
+
global space
|
|
271
|
+
line1 = line_sort(line1)
|
|
272
|
+
line2 = line_sort(line2)
|
|
273
|
+
space.line_eq.append([line1, line2])
|
|
274
|
+
space.line_eq = merge_category(space.line_eq, default_merge)
|
|
275
|
+
def cpct():
|
|
276
|
+
global space
|
|
277
|
+
for item in space.tri_eq:
|
|
278
|
+
for item2 in itertools.combinations(item, 2):
|
|
279
|
+
m2 = list(zip(*item2))
|
|
280
|
+
for item3 in itertools.permutations(m2):
|
|
281
|
+
angle1, angle2 = (item3[0][0], item3[1][0], item3[2][0]), (item3[0][1], item3[1][1], item3[2][1])
|
|
282
|
+
angle1, angle2 = space.standard_angle(angle1), space.standard_angle(angle2)
|
|
283
|
+
|
|
284
|
+
if angle1 is None or angle2 is None or angle1 == angle2:
|
|
285
|
+
continue
|
|
286
|
+
space.angle_eq.append([angle1, angle2])
|
|
287
|
+
for item3 in itertools.combinations(m2, 2):
|
|
288
|
+
line1, line2 = item3
|
|
289
|
+
line1, line2 = line_sort(line1), line_sort(line2)
|
|
290
|
+
if not space.valid_line(line1) or not space.valid_line(line2) or line1 == line2:
|
|
291
|
+
continue
|
|
292
|
+
space.line_eq.append([line1, line2])
|
|
293
|
+
space.line_eq = merge_category(space.line_eq, default_merge)
|
|
294
|
+
space.angle_eq = merge_category(space.angle_eq, default_merge)
|
|
295
|
+
def sss_rule(a1, a2, a3, b1, b2, b3):
|
|
296
|
+
global space
|
|
297
|
+
a1, a2, a3, b1, b2, b3 = [[item] for item in [a1, a2, a3, b1, b2, b3]]
|
|
298
|
+
line = [
|
|
299
|
+
line_sort(a1 + a2),
|
|
300
|
+
line_sort(b1 + b2),
|
|
301
|
+
line_sort(a2 + a3),
|
|
302
|
+
line_sort(b2 + b3),
|
|
303
|
+
line_sort(a1 + a3),
|
|
304
|
+
line_sort(b1 + b3),
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
for item in line:
|
|
308
|
+
if not space.valid_line(item):
|
|
309
|
+
return False
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
space.line_eq_fx(line[0], line[1])
|
|
313
|
+
and space.line_eq_fx(line[2], line[3])
|
|
314
|
+
and space.line_eq_fx(line[4], line[5])
|
|
315
|
+
)
|
|
316
|
+
def tri_sort(tri):
|
|
317
|
+
return tuple([ord(item)-ord("A") for item in tri])
|
|
318
|
+
def check_equal_angle(a, b):
|
|
319
|
+
global space
|
|
320
|
+
return space.angle_eq_fx(a, b)
|
|
321
|
+
def check_equal_line(a, b):
|
|
322
|
+
global space
|
|
323
|
+
return space.line_eq_fx(a, b)
|
|
324
|
+
def prove_congruent_triangle(tri1, tri2=None):
|
|
325
|
+
global space
|
|
326
|
+
if tri2 is None:
|
|
327
|
+
tri2 = tri1
|
|
328
|
+
list1 = list(itertools.permutations(list(tri_sort(tri1))))
|
|
329
|
+
list2 = list(itertools.permutations(list(tri_sort(tri2))))
|
|
330
|
+
for x in list1:
|
|
331
|
+
for y in list2:
|
|
332
|
+
a = list(x)
|
|
333
|
+
b = list(y)
|
|
334
|
+
for item in [a+b,b+a]:
|
|
335
|
+
for rule in [sss_rule]:
|
|
336
|
+
out = rule(*item)
|
|
337
|
+
if out:
|
|
338
|
+
space.tri_eq.append([x, y])
|
|
339
|
+
space.tri_eq = merge_category(space.tri_eq, default_merge)
|
|
340
|
+
def process():
|
|
341
|
+
global space
|
|
342
|
+
space.calc_line_info()
|
|
343
|
+
space.calc_angle_list()
|
|
344
|
+
def split_line(line):
|
|
345
|
+
global space
|
|
346
|
+
line = line_sort(line)
|
|
347
|
+
a, b = space.point_location[line[0]], space.point_location[line[1]]
|
|
348
|
+
px = (a[0]+b[0])/2
|
|
349
|
+
py = (a[1]+b[1])/2
|
|
350
|
+
space.point_location.append((px, py))
|
|
351
|
+
for i in range(len(space.line_info)-1,-1,-1):
|
|
352
|
+
if line[0] in space.line_info[i] and line[1] in space.line_info[i]:
|
|
353
|
+
space.line_info[i] = space.line_info[:min(line)]+[len(space.point_location)-1]+space.line_info[max(line):]
|
|
354
|
+
def extended_line(line):
|
|
355
|
+
global space
|
|
356
|
+
line = line_sort(line)
|
|
357
|
+
for i in range(len(space.line_info)):
|
|
358
|
+
if line[0] in space.line_info[i] and line[1] in space.line_info[i]:
|
|
359
|
+
self.line.append(i)
|
|
360
|
+
def join(line):
|
|
361
|
+
global space
|
|
362
|
+
line = line_sort(line)
|
|
363
|
+
space.line_info.append(line)
|
|
364
|
+
space.command.append(line)
|
|
365
|
+
def show():
|
|
366
|
+
global space
|
|
367
|
+
space.show_diagram()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
geometryai
|