tangled
alpha
login
or
join now
ponder.ooo
/
snakepyt
0
fork
atom
๐๐๐
0
fork
atom
overview
issues
pulls
pipelines
laplacian growth
autumn
11 months ago
1fb9e12a
bb155247
+193
-18
4 changed files
expand all
collapse all
unified
split
lib
log.py
main.py
sketch
laplace.py
plume
plume_c.py
+6
-2
lib/log.py
···
63
63
if file == "<string>" and source is not None:
64
64
file = inspect.getfile(source)
65
65
lines, first_line = inspect.getsourcelines(source)
66
66
-
line = lines[line_number-1].strip()
67
67
-
line_number = first_line + line_number - 1
66
66
+
if line_number < len(lines):
67
67
+
line = lines[line_number-1].strip()
68
68
+
line_number = first_line + line_number - 1
69
69
+
else:
70
70
+
# TODO fix this!!
71
71
+
_log(f"SNAKEPYT", "TRACING ERROR", mode="error", indent=self._indent+8)
68
72
file_end = file.split("/")[-1]
69
73
_log(f"in {file_end} {line_number}", line, mode="error", indent=self._indent+4)
70
74
return self
+11
main.py
···
6
6
import sys
7
7
import os
8
8
import time
9
9
+
import shutil
9
10
from pathlib import Path
10
11
from argparse import ArgumentParser as ArgParser
11
12
from importlib import import_module, reload
···
19
20
#parser.add_argument("sketch", help="the sketch to run", type=str)
20
21
args = parser.parse_args()
21
22
23
23
+
snakepyt_version = (0, 0)
22
24
23
25
def establish_scheduler():
24
26
schedule = []
···
144
146
persistent_state = {}
145
147
persistent_hashes = {}
146
148
pyt_print("state flushed")
149
149
+
if command in ["exit", "quit", ":q", ",q"]:
150
150
+
pyt_print.blank().log("goodbye <3").blank()
151
151
+
repl_continue = False
152
152
+
continue
147
153
if command == "run":
148
154
sketch_name, remainder = lsnap(remainder)
149
155
···
182
188
run_dir = time.strftime(f"%d.%m.%Y/{sketch_name}_t%H.%M.%S")
183
189
sketch.__dict__["run_dir"] = run_dir
184
190
Path("out/" + run_dir).mkdir(parents=True, exist_ok=True)
191
191
+
192
192
+
shutil.copy(sketch.__file__, f"out/{run_dir}/{sketch_name}.py")
193
193
+
194
194
+
with open(f"out/{run_dir}/.snakepyt", "w") as metadata:
195
195
+
metadata.write(f"snakepyt version {snakepyt_version[0]}.{snakepyt_version[1]}\n")
185
196
186
197
if hasattr(sketch, "init"):
187
198
if hasattr(sketch, "final"):
+142
sketch/laplace.py
···
1
1
+
# laplacian growth
2
2
+
# see "Fast Simulation of Laplacian Growth" by Theodore Kim, Jason Sewall, Avneesh Sud and Ming C. Lin
3
3
+
4
4
+
import itertools
5
5
+
from math import tau, log
6
6
+
7
7
+
import torch
8
8
+
9
9
+
from lib.util import load_image_tensor, save, msave
10
10
+
11
11
+
12
12
+
13
13
+
def init():
14
14
+
torch.set_default_device("cuda")
15
15
+
16
16
+
dims = 2
17
17
+
#scale = 2 ** 10
18
18
+
#h = scale
19
19
+
#w = scale
20
20
+
margin = 2**4
21
21
+
22
22
+
physics_scale = 0.5
23
23
+
eta = 10.7 # fractal dimension or something
24
24
+
25
25
+
max_points = 2 ** 13
26
26
+
27
27
+
t_real = torch.half
28
28
+
29
29
+
point_positions = torch.zeros([max_points, dims], dtype=t_real)
30
30
+
point_values = torch.zeros([max_points], dtype=t_real)
31
31
+
is_charge = torch.zeros([max_points], dtype=torch.bool)
32
32
+
do_neighbors = torch.zeros([max_points], dtype=torch.bool)
33
33
+
is_candidate = torch.zeros_like(is_charge)
34
34
+
is_free = torch.ones_like(is_charge)
35
35
+
36
36
+
neighbors = torch.tensor([v for v in itertools.product([-1,0,1], repeat=dims) if any(v)], dtype=t_real)
37
37
+
38
38
+
inner_radius = physics_scale / 2
39
39
+
40
40
+
def insert_charge(position, value, was_candidate=True, no_neighbors=False):
41
41
+
if was_candidate: # index could probably be provided by the caller for efficiency
42
42
+
match_coords = point_positions == position.expand(point_positions.shape)
43
43
+
match_pos = match_coords.prod(dim=1)
44
44
+
index = torch.nonzero(is_candidate & match_pos)[0]
45
45
+
is_candidate[index] = False
46
46
+
is_charge[index] = True
47
47
+
do_neighbors[index] = not no_neighbors
48
48
+
point_values[index] = value
49
49
+
else:
50
50
+
# create the charge
51
51
+
free_index = torch.nonzero(is_free)[0] # maybe not ideal
52
52
+
is_charge[free_index] = True
53
53
+
is_free[free_index] = False
54
54
+
do_neighbors[free_index] = not no_neighbors
55
55
+
point_positions[free_index] = position
56
56
+
point_values[free_index] = value
57
57
+
58
58
+
# update existing candidate potentials (eqn 11)
59
59
+
candidate_pos = point_positions[is_candidate]
60
60
+
dist = (candidate_pos - position.expand(candidate_pos.shape)).norm(p=2, dim=1)
61
61
+
point_values[is_candidate] += 1 - inner_radius/dist
62
62
+
63
63
+
if no_neighbors:
64
64
+
return
65
65
+
66
66
+
# add new candidates
67
67
+
charge_pos = point_positions[is_charge & do_neighbors]
68
68
+
for charge_index in range(charge_pos.shape[0]):
69
69
+
neighborhood = neighbors + charge_pos[charge_index].expand(neighbors.shape)
70
70
+
for neighbor_index in range(neighborhood.shape[0]):
71
71
+
maybe_insert_candidate(neighborhood[neighbor_index])
72
72
+
73
73
+
def maybe_insert_candidate(position):
74
74
+
if torch.any((point_positions == position.expand(point_positions.shape)).prod(dim=1)):
75
75
+
return
76
76
+
77
77
+
free_index = torch.nonzero(is_free)[0] # maybe not ideal
78
78
+
is_candidate[free_index] = True
79
79
+
is_free[free_index] = False
80
80
+
point_positions[free_index] = position
81
81
+
charge_pos = point_positions[is_charge]
82
82
+
charge_val = point_values[is_charge]
83
83
+
dist = (charge_pos - position.expand(charge_pos.shape)).norm(p=2, dim=1)
84
84
+
point_values[free_index] = (1 - inner_radius/dist).sum() # eqn 10
85
85
+
86
86
+
def select_candidate(scale):
87
87
+
try:
88
88
+
candidate_val = point_values[is_candidate]
89
89
+
candidate_pos = point_positions[is_candidate]
90
90
+
max_val = candidate_val.max()
91
91
+
min_val = candidate_val.min()
92
92
+
normalized_val = (candidate_val - min_val) / (max_val - min_val) # eqn 13
93
93
+
val_pow = normalized_val ** eta
94
94
+
selection_prob = val_pow / val_pow.sum()
95
95
+
96
96
+
prob_sum = selection_prob.cumsum(dim=0)
97
97
+
98
98
+
choices = []
99
99
+
for _ in range(scale):
100
100
+
r = torch.rand(1, dtype=t_real)
101
101
+
choice = torch.nonzero(prob_sum > r)[0]
102
102
+
if choice not in choices:
103
103
+
insert_charge(candidate_pos[choice], 1)
104
104
+
choices.append(choice)
105
105
+
except KeyboardInterrupt:
106
106
+
raise
107
107
+
except:
108
108
+
print(f"selection failed")
109
109
+
110
110
+
111
111
+
schedule(run, None)
112
112
+
113
113
+
114
114
+
def run():
115
115
+
116
116
+
# set initial conditions
117
117
+
insert_charge(torch.tensor([0]*dims, dtype=t_real), 1, was_candidate=False)
118
118
+
for n in range(-40,40):
119
119
+
insert_charge(torch.tensor([2, n], dtype=t_real), 1, was_candidate=False, no_neighbors=True)
120
120
+
121
121
+
schedule(main_loop, None)
122
122
+
123
123
+
def main_loop():
124
124
+
for iteration in range(10000):
125
125
+
select_candidate(int(log(iteration * 100 + 10)))
126
126
+
if iteration % 10 == 0:
127
127
+
final_charges = point_positions[is_charge].clone()
128
128
+
129
129
+
for d in range(dims):
130
130
+
final_charges[:,d] -= final_charges[:,d].min()
131
131
+
final_charges[:,d] += margin
132
132
+
133
133
+
h = int(final_charges[:,0].max().item() + margin)
134
134
+
w = int(final_charges[:,1].max().item() + margin)
135
135
+
136
136
+
indices = final_charges.long()
137
137
+
138
138
+
canvas = torch.ones([h,w], dtype=t_real)
139
139
+
canvas[(indices[:,0], indices[:,1])] = 0
140
140
+
141
141
+
msave(canvas, f"{run_dir}/{iteration:06d}")
142
142
+
+34
-16
sketch/plume/plume_c.py
···
22
22
random_range = tau #/ 50
23
23
24
24
# 2 ** 9 = 512; 10 => 1024; 11 => 2048
25
25
-
scale_power = 12
25
25
+
scale_power = 13
26
26
scale = 2 ** scale_power
27
27
28
28
···
39
39
out[:,dim] *= scale
40
40
return out
41
41
42
42
+
def bad_proj(u, v):
43
43
+
dot = (u * v).sum(dim=1)
44
44
+
scale = (dot / v.norm(p=2, dim=1))
45
45
+
out = v.clone()
46
46
+
for dim in range(out.shape[1]):
47
47
+
out[:,dim] *= scale
48
48
+
return out
49
49
+
42
50
def proj_shift(a, b):
43
51
return a + proj(b, a)
52
52
+
53
53
+
def bad_proj_shift(a, b):
54
54
+
return a + bad_proj(b, a)
44
55
45
56
def get_transformation(direction, flow, ebb, rescaling):
46
57
def transformation(p):
58
58
+
d = direction
59
59
+
d[:,0] += torch.rand_like(direction[:,0]) * 0.0001
47
60
if flow == 0:
48
61
result = p - direction * ebb
49
62
else:
50
50
-
result = proj_shift(p, direction * flow) - direction * ebb
63
63
+
result = proj_shift(p, d * flow)
64
64
+
result -= direction * ebb #proj_shift(result, direction * ebb)
51
65
if rescaling:
52
66
result /= result.norm(p=2,dim=1).max()
53
67
return result
···
61
75
return math.pow(b/a, t)*a
62
76
63
77
def per_t(t):
64
64
-
origin = 0, -1
78
78
+
origin = 0.0625, -0.15
65
79
66
80
#s = eerp(1000, 0.001, t)
67
67
-
s = 2
68
68
-
x_range = 1.2 * s
69
69
-
y_range = 1.2 * s
81
81
+
s = 0.25
82
82
+
x_range = 0.5 * s
83
83
+
y_range = 1 * s
70
84
71
85
span = x_range, y_range
72
86
mapping = map_space(origin, span, zooms, stretch, scale)
···
82
96
direction = torch.ones_like(p_positions)
83
97
direction[:,0] = 0
84
98
85
85
-
iterations = 100
99
99
+
iterations = 1301
86
100
#show_frame = lambda i: i == iterations - 1
87
87
-
show_frame = lambda i: i % 10 == 0#True
101
101
+
show_frame = lambda i: i % 100 == 0#True
88
102
#show_frame = lambda i: True
89
103
90
90
-
ebb = 0.804#0.9000469530469531 - 0.001 + 0.002 * t
104
104
+
#ebb = 0.804#0.9000469530469531 - 0.001 + 0.002 * t
105
105
+
#ebb = 1 + 0.4 * t
106
106
+
#ebb = 0.4
107
107
+
ebb = 0.83314# + (0.001 * t)
91
108
#ebb = (0.5 + 0.5 * t)#0.804#482 / 600
92
92
-
#ebb =0.9000469530469531 #0.9000595404595405
109
109
+
#ebb = 0.0 + 0.01 * t #0.9000469530469531 #+ 0.00001 * t #0.9000595404595405
93
110
flow = 1
94
111
rescaling = False
95
112
···
122
139
#diff /= diff.max()
123
140
124
141
if show_frame(iteration):
142
142
+
#diff_avg = diff
125
143
frame_index[0] += 1
126
144
print(f"{frame_index[0]}: ebb={ebb}")
127
145
128
146
#pos_reshape = p_positions.reshape((h,w,2))
129
129
-
#scratch[:,:,0] = pos_reshape[:,:,0]
130
130
-
#scratch[:,:,2] = pos_reshape[:,:,1]
147
147
+
#scratch[:,:,0] = pos_reshape[:,:,0] + 0.5
148
148
+
#scratch[:,:,1] = pos_reshape[:,:,1] + 0.5
131
149
132
150
#scratch += 0.5
133
151
#scratch *= 2
134
134
-
scratch[:,:,0] = diff_avg.nan_to_num().reshape((h,w))
152
152
+
scratch[:,:,2] = diff_avg.nan_to_num().reshape((h,w))
135
153
#scratch[:,:,2] = diff_avg.nan_to_num().reshape((h,w))
136
154
137
155
scratch[scratch < 0] = 0
···
139
157
#scratch[:,:,0] = scratch[:,:,0].pow(2)
140
158
#scratch[:,:,2] = scratch[:,:,2].pow(0.5)
141
159
142
142
-
scratch[:,:,0] -= scratch[:,:,0].mean()
143
143
-
scratch[:,:,0] /= scratch[:,:,0].std() * 6
160
160
+
scratch[:,:,2] -= scratch[:,:,2].mean()
161
161
+
scratch[:,:,2] /= scratch[:,:,2].std() * 6
144
162
#scratch[:,:,2] /= scratch[:,:,2].max()
145
163
146
146
-
scratch[:,:,0] += 0.5
164
164
+
scratch[:,:,2] += 0.5
147
165
148
166
#scratch[:,:,1] = diff.reshape((h,w)) / diff.max()
149
167