Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download
Project: Peter's Files
Views: 3893
Visibility: Unlisted (only visible to those who know the link)
Image: ubuntu1804
1
# ___ _____ ____ _____ _ _ ____ _
2
# |_ _| ___/ ___| | ___| __ __ _ ___| |_ __ _| | / ___| ___ _ __ ___ _ __ __ _| |_ ___ _ __
3
# | || |_ \___ \ | |_ | '__/ _` |/ __| __/ _` | | | | _ / _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
4
# | || _| ___) | | _|| | | (_| | (__| || (_| | | | |_| | __/ | | | __/ | | (_| | || (_) | |
5
# |___|_| |____/___|_| |_| \__,_|\___|\__\__,_|_|___\____|\___|_| |_|\___|_| \__,_|\__\___/|_|
6
#
7
8
9
# Load Etxternal packages
10
import numpy as np # for wicked fast arrays MORE INFORMATION: https://docs.scipy.org/doc/
11
from numpy import * # for ease of math function use
12
from numpy.linalg import * # for ease of linalg function use
13
from numba import njit # for compiling functions into C, so they run faster. MORE INFORMATION: http://numba.pydata.org/
14
from numba import jit
15
import matplotlib.pyplot as plt # for simple plotting
16
from matplotlib import collections as mc # for simple plotting
17
import random # for random numbers
18
import time # for timing
19
from termcolor import colored # for colored print statements
20
import os # for deep file manipulation
21
import imageio # for gif making
22
from matplotlib.ticker import FormatStrFormatter # for FormatStrFormatter
23
from typing import List # for specification of types
24
from PIL import Image # for faster images
25
import math # for math
26
from scipy.stats import linregress # for linear regressions
27
28
29
30
31
32
## Math Ops
33
@njit
34
def opNorm(A):
35
G = A[:2].T[:2].T
36
return np.sqrt(np.max(np.linalg.eig(G @ G.T)[0]))
37
def check_transformations(transformations):
38
failed = []
39
for i in np.arange(len(transformations)):
40
if opNorm(transformations[i]) >= 1:
41
failed = failed + [i+1]
42
if len(failed) == 0:
43
print(colored('The opNorm of every transformation is less than 1 so all of the transformations are contraction mappings.','green'))
44
elif len(failed) == 1:
45
print(colored(f'The opNorm of transformation {failed[0]} is greater than or equal to 1 so is not a contraction mapping.','red'))
46
elif len(failed) > 1:
47
print(colored(f'The opNorm of transformations {failed} are greater than or equal to 1 so are not contraction mappings.','red'))
48
@njit
49
def choose_random_index(weights):
50
r = random.uniform(0, 1)
51
t = 0
52
sum = weights[0]
53
while r > sum:
54
t += 1
55
sum = sum + weights[t]
56
return min(t, len(weights)-1)
57
58
59
60
61
62
63
64
65
66
67
68
## Built-In Transformations
69
def Scale(s):
70
return np.array([[s, 0, 0],[0, s, 0],[0, 0, 1]], dtype=np.float64)
71
def Translate(a, b):
72
return np.array([[1, 0, a],[0, 1, b],[0, 0, 1]], dtype=np.float64)
73
def Rotate(theta):
74
return np.array([[np.cos(theta), -np.sin(theta), 0],[np.sin(theta), np.cos(theta), 0],[0, 0, 1]], dtype=np.float64)
75
def ShearX(t):
76
return np.array([[1, t, 0],[0,1, 0],[0, 0, 1]], dtype=np.float64)
77
def ShearY(t):
78
return np.array([[1, 0, 0],[t,1, 0],[0, 0, 1]], dtype=np.float64)
79
def ScaleX(s):
80
return np.array([[s, 0, 0],[0, 1, 0],[0, 0, 1]], dtype=np.float64)
81
def ScaleY(s):
82
return np.array([[1, 0, 0],[0, s, 0],[0, 0, 1]], dtype=np.float64)
83
def ScaleXY(s, t):
84
return np.array([[s, 0, 0],[0,t, 0],[0, 0, 1]], dtype=np.float64)
85
86
87
## Built-In Figures
88
Box = np.array([ [0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.], [0., 0., 1.], [1/8, 1/8, 1.], [1/8-1/16, 1/8+1/16, 1.] ]).T
89
def rect(n):
90
return ScaleY(1/n) @ Box
91
Line = np.array([ [0., 0., 1.], [1., 0., 1.] ]).T
92
93
XBox = np.array([ [0., 0., 1.], [1., 0., 1.], [1., 1., 1.], [0., 1., 1.], [0., 0., 1.], [0.5, 0., 1.], [0.5, 1., 1.], [1., 1., 1.], [1., 0.5, 1.], [0., 0.5, 1.], [0., 0., 1.], [1/8, 1/8, 1.], [1/8-1/16, 1/8+1/16, 1.]]).T
94
95
96
97
98
99
100
101
102
## for Iterating Figures
103
def transform(figures, transformations): # takes a list of 3xW numpy arrays and a list of transformations (2D 3x3 numpy arrays)
104
newFigures = [np.array([[1.,0.,1.],[0.,1.,1.]]).T]
105
for M in figures:
106
for T in transformations:
107
newFigures = newFigures + [T @ M]
108
return newFigures[1:] # returns the list containing each original 3xW numpy array multiplied by each transformation
109
def generate_figures(n, figures, transformations): # takes a natural number n, a list of 3xW numpy arrays and a list of transformations (2D 3x3 numpy arrays)
110
if n == 0:
111
return transform(figures, [np.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])])
112
else:
113
outputFigures = transform(figures, transformations)
114
for i in range(1,n):
115
outputFigures = transform(outputFigures, transformations)
116
return outputFigures # returns the nth iteration of the transform(), a liste
117
118
119
120
121
122
123
124
125
## the Fractal class
126
class Fractal(object):
127
def __init__(self, transformations, weights=np.array([0.]), size=5, color=(0,0,255)):
128
self.transformations = transformations
129
self.color = color
130
if all(weights == np.array([0.])):
131
self.weights = np.array([1/len(transformations)]*len(transformations))
132
else:
133
self.weights = weights
134
135
self.size = size
136
self.xmin, self.xmax, self.ymin, self.ymax = find_bounds(self.transformations,self.weights)
137
self.bounds = (self.xmin, self.xmax, self.ymin, self.ymax)
138
if self.xmax-self.xmin==0 or self.ymax-self.ymin==0:
139
raise ValueError('Fractal converges to point.')
140
self.width = math.ceil((self.xmax-self.xmin)*36*self.size)
141
self.height = math.ceil((self.ymax-self.ymin)*36*self.size)
142
self.isZoomed = False
143
144
self.point = np.array([0,0,1])
145
self.developement = 0
146
self.pic = Image.new('RGB', (self.width, self.height), (255, 255, 255))
147
self.pixels = self.pic.load()
148
149
def _scale(self, point):
150
h = self.width * (point[0]-self.xmin)/(self.xmax-self.xmin) # (x + 2.182)*(self.width - 1)/4.8378 Why take an input?
151
k = self.height * (self.ymax-point[1])/(self.ymax-self.ymin) # (9.9983 - y)*(self.height - 1)/9.9983
152
return h, k
153
154
def _iterate(self):
155
r = random.uniform(0, 1)
156
t = 0
157
sum = self.weights[0]
158
while r > sum:
159
t += 1
160
sum += self.weights[t]
161
self.point = self.transformations[min(t, len(self.weights)-1)] @ self.point
162
163
def add_points(self, n):
164
for _ in range(100):
165
self._iterate()
166
for _ in range(n):
167
self._iterate()
168
self.pixels[self._scale(self.point)] = self.color
169
self.developement += n
170
171
def set_zoom(self, isZoomed=True, xmin=None, xmax=None, ymin=None, ymax=None):
172
self.isZoomed = isZoomed
173
if xmin is not None:
174
self.xmin = xmin
175
if xmax is not None:
176
self.xmax = xmax
177
if ymin is not None:
178
self.ymin = ymin
179
if ymax is not None:
180
self.ymax = ymax
181
182
def add_points_zoom(self, n, x, y):
183
self.point = np.array([x,y,1])
184
for _ in range(100):
185
self._iterate()
186
while (self.xmin>self.point[0]) or (self.xmax<self.point[0]) or (self.ymin>self.point[1]) or (self.ymax<self.point[1]):
187
self._iterate()
188
for _ in range(n):
189
self._iterate()
190
while (self.xmin>self.point[0]) or (self.xmax<self.point[0]) or (self.ymin>self.point[1]) or (self.ymax<self.point[1]):
191
self._iterate()
192
self.pixels[self._scale(self.point)] = self.color
193
194
def make_gif(self, name='GIF', n=100_000, zoom=2, frames=7, zoomPoint=None):
195
start = time.time()
196
print(f'Generating {frames} Zoomed images: ', end='')
197
if zoomPoint is None:
198
x, y = (self.xmax + self.xmin)/2, (self.ymax + self.ymin)/2
199
else:
200
x, y = zoomPoint
201
for frame in range(frames+1):
202
step = Fractal(self.transformations, self.weights, self.size, self.color)
203
scale = 1 - ((zoom-1)/zoom)*sqrt(frame/frames)
204
print(f'{frame}({round(1/scale, 2)}) ', end='')
205
xminZ = x - (self.xmax - self.xmin) * scale / 2
206
xmaxZ = x + (self.xmax - self.xmin) * scale / 2
207
yminZ = y - (self.ymax - self.ymin) * scale / 2
208
ymaxZ = y + (self.ymax - self.ymin) * scale / 2
209
step.set_zoom(True, xminZ, xmaxZ, yminZ, ymaxZ)
210
step.add_points_zoom(n, x, y)
211
step.save_pic(f'Saved Fractals/For Zooms/{name}_{frame}.png')
212
images = [imageio.imread(f'Saved Fractals/For Zooms/{name}_0.png')]*frames
213
for frame in range(frames+1):
214
images.append(imageio.imread(f'Saved Fractals/For Zooms/{name}_{frame}.png'))
215
os.remove(f'Saved Fractals/For Zooms/{name}_{frame}.png')
216
imageio.mimsave(f'Saved Fractals/{name}.gif', images)
217
end = time.time()-start
218
print(f'\nCompleted in {int(end//60)} minutes and {round(end%60)} seconds.')
219
220
def load_in_points(self, externalTup, frame=None):
221
n = len(externalTup[0])
222
externalArray = np.array([*externalTup,[1.]*n]).T
223
if frame is None:
224
for row in externalArray:
225
self.pixels[self._scale(row)] = self.color
226
self.developement += n
227
else:
228
for row in externalArray:
229
if (frame[0]<=row[0]) and (frame[1]>=row[0]) and (frame[2]<=row[1]) and (frame[3]>=row[1]):
230
self.pixels[self._scale(row)] = self.color
231
self.developement += n
232
233
def save_pic(self, path='Trash.png'):
234
self.pic.save(path)
235
236
def display_pic(self):
237
# https://stackoverflow.com/a/26649884
238
plt.imshow(np.asarray(self.pic))
239
240
def dark_pixels(self):
241
return np.count_nonzero(255 - np.asarray(self.pic)) // 3
242
243
def dimension(self, n=3_000_000, startSize=2, endSize=100, samples=None):
244
start = time.time()
245
test = Fractal(self.transformations, weights=self.weights, size = endSize, color=(0,0,0))
246
G = _generate_points_full(n, self.transformations, weights=self.weights)
247
test.load_in_points(G)
248
tag = str(time.time())
249
tag = tag[:10] + '_' + tag[11:]
250
test.pic.save(f'DIMENSION_CALCULATION_MAX_SIZE_CHECK_{tag}.png')
251
print(f'Verify that the image of the fractal with {n} points is \'filled in\' at size {endSize}. (Image file is in parent directory).')
252
elapsed = time.time() - start
253
go = input("Press Enter to continue or type stop.")
254
middle = time.time()
255
if (go == 'stop') or (go == 'STOP') or (go == 'Stop'):
256
print('Dimension calculation terminated.')
257
os.remove(f'DIMENSION_CALCULATION_MAX_SIZE_CHECK_{tag}.png')
258
return None
259
os.remove(f'DIMENSION_CALCULATION_MAX_SIZE_CHECK_{tag}.png')
260
print('Computing fractal dimension ', end='')
261
# find samples
262
if samples is None:
263
samples = endSize - startSize
264
for j in range(endSize - startSize):
265
samples -= j
266
potentialArray = np.floor(np.geomspace(startSize, endSize, num=samples))
267
if potentialArray[0] != potentialArray[1]:
268
break
269
print(f'with {samples} samples...')
270
numberOfPixelsDark = np.array([0.]*samples)
271
numberOfPixelsDark[samples - 1] = test.dark_pixels()
272
# https://docs.scipy.org/doc/numpy/reference/generated/numpy.geomspace.html#numpy.geomspace
273
scalingFactor = np.floor(np.geomspace(startSize, endSize, num=samples))
274
for i in range(samples-1):
275
if scalingFactor[i] == scalingFactor[i-1]:
276
numberOfPixelsDark[i] = numberOfPixelsDark[i-1]
277
continue
278
test = Fractal(self.transformations, weights=self.weights, size=scalingFactor[i], colour=(0,0,0))
279
test.load_in_points(G)
280
numberOfPixelsDark[i] = test.dark_pixels()
281
if i == 0:
282
print(f'Finished samples with sizes: {int(np.floor(startSize))}', end='')
283
elif i == samples - 2:
284
print(f', {int(scalingFactor[i])}, {int(scalingFactor[i+1])}.')
285
else:
286
print(f', {int(scalingFactor[i])}', end='')
287
print(f'Completed in {np.round((elapsed + time.time() - middle)/60, decimals = 1)} minutes.')
288
return np.round(linregress(np.log(scalingFactor), np.log(numberOfPixelsDark))[0], decimals = 2)
289
290
291
292
293
## for iterating points (and saving the array of points)
294
def generate_points(n, transformations, weights=np.array([0.]), startingPoint=np.array([0.,0.,1.]), frame=np.array([0.])):
295
start = time.time()
296
print(f'Generating {n} points...', end='')
297
if all(frame == np.array([0.])):
298
G = _generate_points_full(n, transformations, weights, startingPoint)
299
else:
300
startingPoint = np.array([(frame[1]-frame[0])/2,(frame[3]-frame[2])/2,1.])
301
G = _generate_points_zoom(n, transformations, weights, startingPoint, frame)
302
print(f' Finished in {time.time()-start} seconds.')
303
return G
304
305
@njit
306
def _generate_points_full(n, transformations, weights=np.array([0.]), startingPoint=np.array([0.,0.,1.])):
307
if all(weights == np.array([0.])):
308
return _generate_points_full_simple(n, transformations, startingPoint)
309
output = np.array([[startingPoint[0],startingPoint[1],1.]]*n)
310
for i in range(100):
311
output[0] = transformations[choose_random_index(weights)] @ output[0]
312
for i in range(1,n):
313
output[i] = transformations[choose_random_index(weights)] @ output[i-1]
314
return (output[:,0],output[:,1])
315
316
@njit
317
def _generate_points_full_simple(n, transformations, startingPoint=np.array([0.,0.,1.])):
318
output = np.array([[startingPoint[0],startingPoint[1],1.]]*n)
319
for _ in np.arange(100):
320
output[0] = transformations[random.randint(0,len(transformations)-1)] @ output[0]
321
for i in np.arange(1,n):
322
output[i] = transformations[random.randint(0,len(transformations)-1)] @ output[i-1]
323
return (output[:,0], output[:,1])
324
325
@njit
326
def _generate_points_zoom(n, transformations, weights=np.array([0.]), startingPoint=np.array([0.,0.,1.]), frame=np.array([0.])):
327
if all(weights == np.array([0.])):
328
return generate_points_zoom_simple(n, transformations, startingPoint, frame)
329
if all(frame == np.array([0.])): # this is to keep the order of function inputs consistant
330
print('THERE WAS AN ERROR: _generate_points_zoom was not given a zoom frame.')
331
output = np.array([[startingPoint[0],startingPoint[1],1]]*n, dtype=np.float64)
332
for _ in range(20):
333
potentialPoint = transformations[choose_random_index(weights)] @ outputFigures[i-1]
334
while (frame[0]>potentialPoint[0]) or (frame[1]<potentialPoint[0]) or (frame[2]>potentialPoint[1]) or (frame[3]<potentialPoint[1]):
335
potentialPoint = transformations[choose_random_index(weights)] @ potentialPoint
336
output[0] = potentialPoint
337
for i in np.arange(1,n):
338
potentialPoint = transformations[choose_random_index(weights)] @ outputFigures[i-1]
339
while (frame[0]>potentialPoint[0]) or (frame[1]<potentialPoint[0]) or (frame[2]>potentialPoint[1]) or (frame[3]<potentialPoint[1]):
340
potentialPoint = transformations[choose_random_index(weights)] @ potentialPoint
341
output[i] = potentialPoint
342
return (output[:,0], output[:,1])
343
344
@njit
345
def _generate_points_zoom_simple(n, transformations, startingPoint, frame):
346
outputFigures = np.array([[startingPoint[0],startingPoint[1],1.]]*n)
347
for _ in range(20):
348
potentialPoint = transformations[random.randint(0,len(transformations)-1)] @ potentialPoint
349
while (frame[0]>potentialPoint[0]) or (frame[1]<potentialPoint[0]) or (frame[2]>potentialPoint[1]) or (frame[3]<potentialPoint[1]):
350
potentialPoint = transformations[random.randint(0,len(transformations)-1)] @ potentialPoint
351
output[0] = potentialPoint
352
for i in np.arange(1,n):
353
potentialPoint = transformations[random.randint(0,len(transformations)-1)] @ potentialPoint
354
while (frame[0]>potentialPoint[0]) or (frame[1]<potentialPoint[0]) or (frame[2]>potentialPoint[1]) or (frame[3]<potentialPoint[1]):
355
potentialPoint = transformations[random.randint(0,len(transformations)-1)] @ potentialPoint
356
output[i] = potentialPoint
357
return (output[:,0], output[:,1])
358
359
360
@njit
361
def find_bounds(transformations, weights=np.array([0.])):
362
G = _generate_points_full(100000, transformations=transformations, weights=weights)
363
xmin, xmax, ymin, ymax = np.min(G[0]), np.max(G[0]), np.min(G[1]), np.max(G[1])
364
errX, errY = (xmax-xmin)/10, (ymax-ymin)/10
365
return np.array([xmin-errX, xmax+errX, ymin-errY, ymax+errY], dtype=np.float64)
366
367
368
## Simple plots and saves
369
def plot_figures(figuresToPlot:List[np.ndarray], size:float=5, width:float=1 , colour:str='blue', path:str="TRASH.png"):
370
lines = [ [(1.1, 1.1), (1.1, 1.1)] ]
371
for figure in figuresToPlot:
372
lines = lines + [[ (figure[0][i], figure[1][i]), (figure[0][i+1], figure[1][i+1]) ] for i in range(len(figure.T)-1)]
373
lc = mc.LineCollection(lines[1:], linewidths=width, color=colour)
374
fig, ax = plt.subplots(figsize=(size,size))
375
ax.add_collection(lc)
376
ax.set_aspect('equal')
377
ax.autoscale()
378
plt.savefig(path, bbox_inches='tight', dpi=144, format='png')
379
plt.show()
380
def plot_points(points, size=5, colour="blue", path='TRASH.png'):
381
fig, ax = plt.subplots(figsize=(size,size))
382
ax.set_aspect('equal')
383
ax.plot(*points, ',', color=colour)
384
plt.axis('off')
385
plt.savefig(path, bbox_inches='tight', dpi=144, format='png')
386
plt.axis('on')
387
# num, KCnum, (X, Y) = pixel_data(path)
388
plt.show()
389
# print(f'{num} pixels are dark ({num*100/KCnum} percent)')
390
def save_points(points, size=5, colour="blue", path='TRASH.png'):
391
if path == 'TRASH.png':
392
print(colored('No Path Specified!','red'))
393
return None
394
fig, ax = plt.subplots(figsize=(size,size))
395
ax.set_aspect('equal')
396
ax.plot(*points, ',', color=colour)
397
plt.axis('off')
398
ax.yaxis.set_major_formatter(FormatStrFormatter('%.3f')) # https://stackoverflow.com/a/29188910/6504760
399
ax.xaxis.set_major_formatter(FormatStrFormatter('%.3f'))
400
plt.savefig(path, bbox_inches='tight', dpi=144, format='png');
401
fig.clear()
402
plt.close(fig)
403
404
405
406
407
408
409
410
411
412
413
414
415
416
## Word Fractals
417
418
def word_fractal(string):
419
n = len(string)
420
gap = 1/(n**2)
421
width = (1 - (n-1)*gap)/n
422
macros = [eval('_'+letter)(n) for letter in string.upper()]
423
transformations = []
424
for i in range(n):
425
letter = macros[i]
426
for t in range(len(letter)):
427
transformations = transformations + [Translate(i*(width + gap),0) @ ScaleXY(width, 1/n) @ letter[t]]
428
return transformations
429
430
def _(n):
431
return []
432
def _a(n):
433
theta = pi/2.3
434
phi = pi/2 - theta
435
h = (n-cos(theta))/(n*sin(theta))
436
ell = (h*(n+1)*cos(theta))/(2*n-2*cos(theta))
437
b = sin(theta)/n + ell
438
X1 = h*cos(theta) + sin(theta)/n - cos(theta)/n
439
Y1 = 1 - sin(theta)/n - cos(theta)/n
440
c = (1 - 1/n - cos(theta)/(2*n) -X1)/sin(theta)
441
return [Translate(sin(theta)/n,0) @ Rotate(theta) @ ScaleX(h),
442
Translate(b,1/2-1/(2*n)) @ ScaleX(1-b-1/n),
443
Translate(X1,Y1) @ Rotate(-phi) @ ScaleX(c),
444
Translate(1-1/n,Y1-c*cos(theta)+sin(theta)/(2*n)) @ Rotate(-pi/2) @ ScaleX(Y1-c*cos(theta)+sin(theta)/(2*n))]
445
def _A(n):
446
return [ShearY((1-1/n)*2) @ ScaleX(1/2),
447
Translate(1/2, 1-1/n) @ ShearY((1/n-1)*2) @ ScaleX(1/2),
448
Translate(n/(4*(n-1)),1/2-1/n) @ ScaleX(1 - n/(2*(n-1)))]
449
def _B(n):
450
return [Translate(0,1) @ Rotate(-pi/2),
451
Translate(1/n, 1-1/(2*n)) @ ScaleXY(1/2 - 1/(2*n),1/2),
452
Translate(1/2 + 1/(2*n), 1/2+1/(4*n)) @ Rotate(pi/2) @ ScaleXY(1/2 - 3/(4*n),1/2),
453
Translate(1/n,1/2 - 1/(4*n)) @ ScaleXY(1-2/n, 1/2),
454
Translate(1-1/n, 1/2+1/(4*n)) @ Rotate(-pi/2) @ ScaleX(1/2 - 3/(4*n)),
455
Translate(1/n,0) @ ScaleX(1 - 1/n)]
456
def _C(n):
457
return [Translate(1/n,0) @ ScaleX(1-1/n),
458
Translate(1/n,0) @ Rotate(pi/2),
459
Translate((1/n),1-1/n) @ ScaleX(1-1/n)]
460
def _D(n):
461
return [Translate(0,1) @ Rotate(-pi/2),
462
Translate(1/n,1-1/n) @ ShearY(-1/2) @ ScaleX(1-2/n),
463
Translate(1-1/n, 1/2+1/n) @ Rotate(-pi/2) @ ScaleX(1/2+1/n),
464
Translate(1/n,0) @ ScaleX(1-2/n)]
465
def _E(n):
466
return [Translate(0,1) @ Rotate(-pi/2),
467
Translate(1/n,1-1/n) @ ScaleX(1-1/n),
468
Translate(1/n,1/2-1/(4*n)) @ ScaleXY(1-2/n,1/2),
469
Translate(1/n,0) @ ScaleX(1-1/n)]
470
def _F(n):
471
return [Translate(0,1) @ Rotate(-pi/2),
472
Translate(1/n,1-1/n) @ ScaleX(1-1/n),
473
Translate(1/n,1/2-1/(4*n)) @ ScaleXY(1-2/n,1/2)]
474
def _G(n):
475
return [Translate(1/n,0) @ ScaleX(1-1/n),
476
Translate(1/n,0) @ Rotate(pi/2),
477
Translate((1/n),1-1/n) @ ScaleX(1-1/n),
478
Translate(1-1/n,1/2) @ Rotate(-pi/2) @ ScaleX(1/2 - 1/n),
479
Translate(1-1/n-1/4,1/2 - 1/(2*n)) @ ScaleXY(1/4,1/2)]
480
def _H(n):
481
return [Translate(1/n,0) @ Rotate(pi/2),
482
Translate(1,0) @ Rotate(pi/2),
483
Translate(1/n,1/2-1/(2*n)) @ ScaleX(1-2/n)]
484
def _I(n):
485
return [Translate(0,1-1/n),
486
Translate(1/2 - 1/(2*n),1-1/n) @ Rotate(-pi/2) @ ScaleX(1-2/n),
487
Scale(1)]
488
def _J(n):
489
return [Translate(0,1-1/n),
490
Translate(1/2 - 1/(2*n),1-1/n) @ Rotate(-pi/2) @ ScaleX(1-2/n),
491
ScaleX(1/2 + 1/(2*n))]
492
def _K(n):
493
return [Translate(0,1) @ Rotate(-pi/2),
494
Translate(1/n,1/2) @ ShearY((n/2 - 1)/(n-1)) @ ScaleX(1-1/n),
495
Translate(1/n,1/2-1/n) @ ShearY((1 - n/2)/(n-1)) @ ScaleX(1-1/n)]
496
def _L(n):
497
return [Translate(0,1) @ Rotate(-pi/2),
498
Translate(1/n,0) @ ScaleX(1-1/n)]
499
def _M(n):
500
return [Translate(0,1) @ Rotate(-pi/2),
501
Translate(1/n,1-1/n) @ ShearY(-1) @ ScaleX(1/2 - 1/n),
502
Translate(1/2,1/2) @ ShearY(1) @ ScaleX(1/2 - 1/n),
503
Translate(1-1/n,1) @ Rotate(-pi/2)]
504
def _N(n):
505
return [Translate(0,1) @ Rotate(-pi/2),
506
Translate(1/n,1-1/n) @ ShearY((1/n-1)/(1-2/n)) @ ScaleX(1 - 2/n),
507
Translate(1-1/n,1) @ Rotate(-pi/2)]
508
def _O(n):
509
return [Translate(1/n,0) @ ScaleX(1-2/n),
510
Translate(1,0) @ Rotate(pi/2),
511
Translate(1/n,1-1/n) @ ScaleX(1-2/n),
512
Translate(1/n,0) @ Rotate(pi/2)]
513
def _P(n):
514
return [Translate(0,1) @ Rotate(-pi/2),
515
Translate(1/n, 1-3/(4*n)) @ ScaleXY(1-2/n,3/4),
516
Translate(1-1/n, 1) @ Rotate(-pi/2) @ ScaleX(1/2),
517
Translate(1/n, 1/2) @ ScaleXY(1-2/n,3/4)]
518
def _Q(n):
519
theta = arctan(1 - 2/n)
520
z = (1/2 - 1/n - sin(theta)/n)*cos(theta)
521
x = 1/2 - z*sin(theta) - cos(theta)/n
522
y = x*tan(theta)
523
return [Translate(0,1/2) @ ShearY(1-2/n) @ ScaleX(1/2),
524
Translate(0,1/2-1/n) @ ShearY(2/n-1) @ ScaleX(1/2),
525
Translate(1/2,0) @ ShearY(1-2/n) @ ScaleX(1/2),
526
Translate(1/2,1-1/n) @ ShearY(2/n-1) @ ScaleX(1/2),
527
Translate(1/2 + x,y) @ Rotate(theta-pi/2) @ ScaleXY(z,1/2)]
528
def _R(n):
529
return [Translate(0,1) @ Rotate(-pi/2),
530
Translate(1/n, 1-3/(4*n)) @ ScaleXY(1-2/n,3/4),
531
Translate(1-1/n, 1) @ Rotate(-pi/2) @ ScaleX(1/2),
532
Translate(1/n, 1/2) @ ScaleXY(1-2/n,3/4),
533
Translate(1/n,1/2-1/n) @ ShearY((1/n-1/2)/(1-1/n)) @ ScaleX(1-1/n)]
534
def _S(n):
535
return [ScaleX(1-1/n),
536
Translate(1,0) @ Rotate(pi/2) @ ScaleX(1/2-1/(2*n)),
537
Translate(1/n,1/2-1/(2*n)) @ ScaleX(1-1/n),
538
Translate(1/n,1/2-1/(2*n)) @ ScaleY(1/2-1/(2*n)) @ Rotate(pi/2),
539
Translate(0,1-1/n)]
540
def _T(n):
541
return [Translate(0,1-1/n),
542
Translate(1/2 - 1/(2*n),1-1/n) @ Rotate(-pi/2) @ ScaleX(1-1/n)]
543
def _U(n):
544
return [Scale(1),
545
Translate(0,1) @ Rotate(-pi/2) @ ScaleX(1-1/n),
546
Translate(1-1/n,1) @ Rotate(-pi/2) @ ScaleX(1-1/n)]
547
def _V(n):
548
return [Translate(0,1-1/n) @ ShearY(2/n-2) @ ScaleX(1/2),
549
Translate(1/2,0) @ ShearY(2-2/n) @ ScaleX(1/2)]
550
def _W(n):
551
return [Translate(0,1) @ Rotate(-pi/2),
552
Translate(1/n,0) @ ShearY(1) @ ScaleX(1/2 - 1/n),
553
Translate(1/2,1/2-1/n) @ ShearY(-1) @ ScaleX(1/2 - 1/n),
554
Translate(1-1/n,1) @ Rotate(-pi/2)]
555
def _X(n):
556
return [Translate(cos(pi/4)/n,0) @ Rotate(pi/4) @ ScaleX(sqrt(2)-1/n),
557
Translate(0,1-cos(pi/4)/n) @ Rotate(-pi/4) @ ScaleX(cos(pi/4) - 1/n),
558
Translate(1/2,1/2-cos(pi/4)/n) @ Rotate(-pi/4) @ ScaleX(cos(pi/4) - 1/n)]
559
def _Y(n):
560
return [Translate(1/2-1/(2*n),1/2) @ Rotate(-pi/2) @ ScaleX(1/2),
561
Translate(0,1-1/n) @ ShearY((1/2)/(1/(2*n) - 1/2)) @ ScaleX(1/2 - 1/(2*n)),
562
Translate(1/2 + 1/(2*n),1/2 - 1/n) @ ShearY((1/2)/(1/2 - 1/(2*n))) @ ScaleX(1/2 - 1/(2*n))]
563
def _Z(n):
564
return [Translate(0,1-1/n),
565
Translate(1/n,1/n) @ Rotate(pi/2) @ ShearY(1/(1/n-1)) @ ScaleX(1-2/n),
566
Scale(1)]
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
######### Pre load njit functions ?
584
585
586
587
588
589
590
591