"Small Universes" : Pattern hunting with the Wolfram Physics Project

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())
10 Likes