Simpler examples in 2D:
– rule WM148 : [(x, y)] -> [(x, y), (y, z)]
x, y, z = 0, 1, 2
arr = [(x, y)]
steps = 4
for step in xrange(steps):
temp = [] #temporary array to store new pairs of nodes
for i, (x, y) in enumerate(arr):
new_pair = (y, z) #create a new pair connecting 'y' to 'z'
idx = 2*i+1 #compute its future index in array (next generation)
temp.append((new_pair, idx)) #store it with its index
z += 1
[arr.insert(i, p) for p, i in temp] #intertwine previous array with temporary array -> ([x, y], [y, z], [x, y], [y, z],...)
or in a more pythonic way:
for step in xrange(steps):
temp = [(y, z+i+len(arr)-1) for i, (x,y) in enumerate(arr)]
arr = sum(zip(arr, temp),())
Then attaching the pairs of nodes with springs using Toxiclibs:
Full sketch
### A basic implementation of rule WM148 in Python mode ###
### doc --> https://www.wolframphysics.org/universes/wm148/ ###
### explanation --> https://www.wolframphysics.org/technical-introduction/basic-form-of-models/first-example-of-a-rule/index.html ###
from collections import defaultdict
add_library('toxiclibs')
W, H = 1000, 600 #dimensions of canvas
x, y, z = 0, 1, 2
a = [(x, y)] #starting axiom
sw = []
def setup():
size(1000, 600)
global physics, a, sw
# Instantiate Verlet Physics + set drag
physics = VerletPhysics2D()
physics.setDrag(.2)
# RULE: [(x, y)] --> [(x, y), (y, z)]
for step in xrange(11):
temp = [(y, z+i+len(a)-1) for i, (x,y) in enumerate(a)]
a = sum(zip(a, temp),())
d = defaultdict(set)
for n1, n2 in a:
d[n1].add(n2)
d[n2].add(n1)
neighbors_count = sorted(len(d[k]) for k in d)
minl, maxl = neighbors_count[0], neighbors_count[-1]
# Get id of each node
uniques = set(sum(a, ())) # [0, 1, 2, ..., n]
# Add particle for each node
for id in uniques:
p = VerletParticle2D(Vec2D.randomVector().scale(W).add(Vec2D(W>>1, H>>1)))
physics.addParticle(p)
physics.addBehavior(AttractionBehavior2D(p, 40, -.5))
# Create spring between each pair of nodes
for n1, n2 in a:
p1 = physics.particles.get(n1)
p2 = physics.particles.get(n2)
l = (len(d[n1]) + len(d[n2])) * .5
f = map(l, minl, maxl, 1, 0.1)
s = VerletSpring2D(p1, p2, l*l*f, .3)
physics.addSpring(s)
w = l*l*.015
sw.append(w)
def draw():
background('#FFFFFF')
physics.update() #update physics
#Draw springs + nodes
for i, s in enumerate(physics.springs):
strokeWeight(sw[i])
line(s.a.x(), s.a.y(), s.b.x(), s.b.y())
pushStyle()
strokeWeight(2)
for p in physics.particles:
point(p.x(), p.y())
popStyle()
– rule WM686 : [(x, y)] --> [(y, z), (z, x)]
This rule can be used as a simple circle growth algorithm if we update only one pair per iteration (instead of the whole stack)
Full sketch
### How rule WM686 can be used as a circle growth algorithm ###
### doc --> https://www.wolframphysics.org/universes/wm686/ ###
add_library('toxiclibs')
W, H = 1000, 600 #dimensions of canvas
a = [(0, 1), (0, 2), (1, 2)] #starting axiom
z = max(sum(a,()))+1 #total number of edges AND the id of the next node to add
def setup():
size(W, H)
global physics
# Instantiate Verlet Physics + set drag
physics = VerletPhysics2D()
physics.setDrag(.2)
# Get id of each node
uniques = set(sum(a, ())) # [0, 1, 2]
# Add particle for each node
for id in uniques:
p = VerletParticle2D(Vec2D.randomVector().add(Vec2D(W>>1, H>>1)))
physics.addParticle(p)
physics.addBehavior(AttractionBehavior2D(p, 10, -.5))
# Create spring between each pair of nodes
for n1, n2 in a:
p1 = physics.particles.get(n1)
p2 = physics.particles.get(n2)
s = VerletSpring2D(p1, p2, 4, .5)
physics.addSpring(s)
def draw():
background('#FFFFFF')
global z
physics.update() #update physics
# Process rule: [(x, y)] --> [(y, z), (z, x)]
id = int(random(z)) #pick an edge at random
x, y = a[id] #coordinates of selected edge
del a[id] #remove edge
a.extend([(y, z), (z, x)]) #add 2 new edges according to replacement rule
# Manage physics accordingly
px = physics.particles.get(x) #coordinate of node x
py = physics.particles.get(y) #coordinate of node y
pz = VerletParticle2D(px.add(py).scale(.5)) #create a new particle in between
s = physics.getSpring(px, py) #find spring between the deleted edge
physics.removeSpring(s) #remove that spring
physics.addParticle(pz) #add particle
physics.addBehavior(AttractionBehavior2D(pz, 10, -.5)) #attach a repulsion behavior to it
s1 = VerletSpring2D(py, pz, 4, .5) #create spring between 1st new edge
s2 = VerletSpring2D(pz, px, 4, .5) #create spring between 2nd new edge
physics.addSpring(s1) #add them to physics
physics.addSpring(s2)
z += 1 #increment 'z'
#Draw springs
for s in physics.springs:
line(s.a.x(), s.a.y(), s.b.x(), s.b.y())