Rhizomatic programming

 

  • A rhizome ceaselessly establishes connections.
  • Any point of a rhizome can be connected to any other and must might be.
  • A multiplicity is, in the most basic sense, a complex structure that does not reference a prior unity.
  • If you break a rhizome it can start growing again on its old line or on a new line. Connections are constantly breaking and reforming.
  • The rhizome is like a map and not a tracing.

Principles for Rhizomatic Thinking, Jenny Mackness

 

The rhizomatic programming paradigm is introduced in this Note by examples written in Python*. Rhizomatic programming is defined here as a heterogeneous, multigrain parallel paradigm, and a variant of reactive, actor, and dataflow programming. It derives its conception and terminology from Grenander’s Geometries of knowledge, A Calculus of Ideas, and other pattern-theoretic works. [Rhizomatic programming is the culmination of the Calideas programming framework: See Notes 33,35,39.]

* All code is written and executed in Python version 3.5.

 

‘Term’inology

A configuration is heterogeneous collection of nodes, created by generators (or parametric node factories) connected by bonds. Each node can have multiple inputs and multiple outputs. Each input to a node flows from a bond, and each output from a node flows into a bond. A bond is connected to at most one input and at most one output. Data objects, or terms flow via bonds from node to node asynchronously. Each node processes its input data and bonds transfer node outputs to other nodes. A snapshot of a running configuration at a particular time step is an image. A stream of images is a cine. An equivalence class of images formed by a group of generator parameter settings is a pattern. A genre is an equivalence class of cines.

Terms (objects, or molecules) are transported via bonds though a configuration (a rhizomic structure), are processed by nodes which produce transformed and new terms. The bond values are effectively steams of terms which represent the state of the configuration.

 

Nodes and generators

A node function is a function with two arguments. The two arguments — conventionally In and Out — are lists representing input and output bond values: In represents the input buffers and Out represents the output buffers of the node.

Example. A node function with 2 inputs and 1 output:

def nodePlus(In , Out):
     Out[0] = In[0]+In[1]
     return True
 

A node is a dictionary, return by the function node:

def node(Function, NumberInputs, NumberOutputs, Type):
#   node execution type: ALL_IN_AUTO, ALL_IN_MANUAL, ONE_IN_AUTO, ONE_IN_MANUAL
   return {'function': Function, 'inputs': [None]*NumberInputs, 'outputs': [None]*NumberOutputs, 'type': Type}
 

There are (currently) four types of nodes that specify when a node can execute:

  • 'ALL_IN_AUTO': All input buffers are filled, and input buffers are cleared automatically when the node function returns.
  • 'ALL_IN_MANUAL': All input buffers are filled, but input buffers must be cleared (with the clear or clear_all function.
  • 'ONE_IN_AUTO': Same as ‘ALL_IN_AUTO’ but the node executes when at least one input buffer is filled.
  • 'ONE_IN_MANUAL': Same as ‘ALL_IN_MANUAL’ but the node executes when at least one input buffer is filled.

Example. A node with 2 input buffers and 1 output buffer:

adder = node(nodePlus, 2, 1, 'ALL_IN_AUTO')
 

Note: An empty buffer is represented by None.

A generator is a node factory: it takes arguments (called generator parameters) and produces a new node.

Example. A generator that takes a Scale parameter and produces a node that inputs 2 values representing a 2-D point x and outputs 2 values representing the end point of a 45-degree vector based at x and length Scale:

def gen45deg(Scale):
   def node45deg(In,Out):
      # sqrt(2)/2 = 0.70710678
      Out[0] = In[0]+0.70710678*Scale
      Out[1] = In[1]+0.70710678*Scale
      return True
   return node(node45deg, 2, 2, 'ALL_IN_AUTO')

Here’s the generator of nodes utilizing the nodePlus function:

def genPlus( ):
   def nodePlus(In, Out):
       Out[0] = In[0]+In[1]
       return True
   return node(nodePlus, 2, 1, 'ALL_IN_AUTO')

And another that performs the print function:

def genPrint( ):
   def nodePrint(In, Out):
      print(In[0])
      return True
   return node(nodePrint, 1, 0, 'ALL_IN_AUTO') 

Some node utilities:

def all_full(Buffers):
   if Buffers:
      for b in Buffers:
         if b == None:
            return False
   return True

def all_empty(Buffers):
   if Buffers:
      for b in Buffers:
          if b != None:
             return False
   return True

def not_all_empty(Buffers):
   if Buffers:
      for b in Buffers:
          if b != None:
             return True
   return False

def empty(Buffers):
   if Buffers:
      for i in range(0,len(Buffers)):
         Buffers[i] = None
   return True

def clear(Buffers, b):
   Buffers[b] = None
   return True

def clear_all(Buffers):
   return empty(Buffers)

def execute(Node):
   inputs = Node['inputs']
   outputs = Node['outputs']
   type = Node['type']
   if (type == 'ALL_IN_AUTO'):
      if all_full(inputs) and all_empty(outputs):
         Node['function'](inputs, outputs)
         empty(inputs)
         return True
   elif (type == 'ONE_IN_AUTO'):
       if not_all_empty(inputs) and all_empty(outputs):
         Node['function'](inputs, outputs)
         empty(inputs) 
         return True
   elif (type == 'ALL_IN_MANUAL'):
      if all_full(inputs) and all_empty(outputs):
         Node['function'](inputs, outputs) 
         return True
   elif (type == 'ONE_IN_MANUAL'):
      if not_all_empty(inputs) and all_empty(outputs):
         Node['function'](inputs, outputs)
         return True
   return False
 

Bonds

A bond is a dictionary, returned by the function bond:

def bond(From, To, Value):
   return {'from': From, 'to': To, 'value': Value}

def transfer(Bond):
    input = Bond['to']         #  (node,  index)
    output  = Bond['from']  # (node, index)
    input_value = None
    output_value = None
    if output != None:
          output_value = output[0]['outputs'][output[1]]
    if input != None:
           input_value = input[0]['inputs'][input[1]]
    if  output_value != None and Bond['value'] == None:
           Bond['value'] = output_value
           output[0]['outputs'][output[1]] = None
    if input != None and input_value == None and Bond['value'] != None:
            input[0]['inputs'][input[1]] = Bond['value']
            Bond['value'] = None
    return Bond['value']

The first two arguments of bond are pairs: From for the output node and index, and To for the input node and index. Value is the initial bond value (which could be None).

 

Configurations

A configuration of formed from generator created nodes and bonds:

def config(Nodes, Bonds):     # (Nodes, Topology) in future
    return { 'nodes': Nodes, 'bonds': Bonds }

def runfig(Config, N):
   nodes = Config['nodes']
   bonds = Config['bonds']
   for n in range(0,N):
      for g in nodes:
         execute(g)
      for b in bonds:
         transfer(b)
   return True
 

Diagrammatic representation

A diagrammatic representation of a configuration (rhizomatatc program) consists of bonds (represented by identifiers in brackets [ ]), generator nodes (represented by bold identifiers), and flows (represented by arrows labeled by an input or output identifier).

[ bond_1_0 ]
     ↓0
                    [ bond_1_1 ]   →1 adder1
     ↓0
[ bond_2_1 ]
     ↓1
                    [ bond_2_0 ]   →1 adder2
     ↓0
[ bond_3_0 ]
0
printer1
 

Example code

# examples

def gen45deg(Scale):
   def node45deg(In,Out):
      # sqrt(2)/2 = 0.70710678
      Out[0] = In[0]+0.70710678*Scale
      Out[1] = In[1]+0.70710678*Scale
      return True
   return node(node45deg, 2, 2, 'ALL_IN_AUTO')

def genPrint( ):
   def nodePrint(In, Out):
      print(In[0])
      return True
   return node(nodePrint, 1, 0, 'ALL_IN_AUTO') 

def genPlus( ):
   def nodePlus(In, Out):
       Out[0] = In[0]+In[1]
       return True
   return node(nodePlus, 2, 1, 'ALL_IN_AUTO')


# tests

vector = gen45deg(3.0)
printer0 = genPrint( )
printer1 = genPrint( )
b0 = bond(None, (vector,0), 0.0)
b1 = bond(None, (vector,1), 0.0)
b2 = bond((vector,0), (printer0,0), None)
b3 = bond((vector,1), (printer1,0), None)
test0 = config([vector, printer0, printer1], [b0, b1, b2, b3])
runfig(test0, 10)

adder1 = genPlus( )   
adder2 =  genPlus( )
printer1 = genPrint( )
bond_1_0 = bond(None, (adder1,0), 1)
bond_1_1 = bond(None, (adder1,1), 2)
bond_2_0 = bond(None, (adder2,0), 3)
bond_2_1 = bond((adder1,0), (adder2,1), None)
bond_3_0 = bond((adder2,0), (printer1,0), None)
twoAdders = config([adder1, adder2, printer1],  [bond_1_0, bond_1_1, bond_2_0, bond_2_1, bond_3_0])
runfig(twoAdders, 10)



# examples

def genPass( ):
   def nodePass(In, Out):
      Out[0] = In[0]
      return True
   return node(nodePass, 1, 1, 'ALL_IN_AUTO')


# tests

g_1 = genPass( )
g_2 = genPass( )
g_3 = genPass( )
g_4 = genPass( )
g_5 = genPrint( )
b_1 = bond(None,  (g_1,0), 'o')
b_2 = bond((g_1,0), (g_2,0), 'l')
b_3 = bond((g_2,0),  (g_3,0), 'l')
b_4 = bond((g_3,0) , (g_4,0), 'e')
b_5 = bond((g_4,0),  (g_5,0), 'H')
hello = config([g_1, g_2, g_3, g_4, g_5], [b_1, b_2, b_3, b_4, b_5])
runfig(hello, 10)


# examples

def genDup( ):
   def nodeDup(In, Out):
      Out[0] = In[0]
      Out[1] = In[0]
      return True
   return node(nodeDup, 1, 2, 'ALL_IN_AUTO')

def genPlusPass( ):
   def nodePlusPass(In, Out):
       Out[0] = In[1]
       Out[1] = In[0]+In[1]
       return True
   return node(nodePlusPass, 2, 2, 'ALL_IN_AUTO')

# tests

adder1 = genPlusPass( )
printer1 = genPrint( )
duper1 = genDup( )
b1 = bond((adder1,0), (duper1,0), 1)
b2 = bond((adder1,1), (adder1,1), 1)
b3 = bond((duper1,0), (adder1,0), None)
b4 = bond((duper1,1), (printer1,0), None)
fib = config([adder1, duper1, printer1], [b1, b2, b3, b4])
runfig(fib, 25)



# examples

def genConst(Value):
   def nodeConst(In, Out):
      Out[0] = Value
      return True
   return node(nodeConst, 0, 1, 'ALL_IN_AUTO')


# tests

g1 = genConst('Hey')
p1 = genPrint( )
b1 = bond((g1,0), (p1,0), None)
c1 = config([g1,p1], [b1])
runfig(c1, 10)


#examples

# nodeMerge has 3 inputs and 2 outputs.
# The first two inputs are streams to be merged, and the third is an item that is given priority.
# If there are two items to be merged, one is output and the second is output on Out[1], which will appear in In[2].
def genMerge( ):
   def nodeMerge(In, Out):
      if (In[2] != None):
         Out[0] = In[2]
         clear(In, 2)
         return True
      elif (In[0] != None):
         Out[0] = In[0]
         if (In[1] != None):
            Out[1] = In[1]
            clear(In,1)
         clear(In, 0)
         return True
      elif (In[1] != None):
         Out[0] = In[1]
         clear(In, 1)
         return True
      return True
   return node(nodeMerge, 3, 2, 'ONE_IN_MANUAL')

# tests

m1 = genMerge( )
c1 = genConst('a')
c2 = genConst('b')
p1 = genPrint( )
b1 = bond((c1,0), (m1,0), None)
b2 = bond((c2,0), (m1,1), None)
b3 = bond((m1,0), (p1,0), None)
b4 = bond((m1,1), (m1,2), None)
merge = config([m1,c1,c2,p1], [b1,b2,b3,b4])
runfig(merge,20)

m1 = genMerge( )
x1 = genPass( )
c1 = genConst('a')
c2 = genConst('b')
p1 = genPrint( )
b0 = bond((c1,0), (x1,0), None)
b1 = bond((x1,0), (m1,0), None)
b2 = bond((c2,0), (m1,1), None)
b3 = bond((m1,0), (p1,0), None)
b4 = bond((m1,1), (m1,2), None)
merge = config([m1,x1,c1,c2,p1], [b0,b1,b2,b3,b4])
runfig(merge,20)

m1 = genMerge( )
x1 = genPass( )
x2 = genPass( )
c1 = genConst('a')
c2 = genConst('b')
p1 = genPrint( )
b = bond((c1,0), (x1,0), None)
b0 = bond((x1,0), (x2,0), None)
b1 = bond((x2,0), (m1,0), None)
b2 = bond((c2,0), (m1,1), None)
b3 = bond((m1,0), (p1,0), None)
b4 = bond((m1,1), (m1,2), None)
merge = config([m1,x1,x2,c1,c2,p1], [b, b0,b1,b2,b3,b4])
runfig(merge,20)


# examples 

def genConstAlt(Value, Frequency):
   def nodeConstAlt(In, Out):
      if (In[0] == Frequency):
         Out[0] = Value
         Out[1] = 0
         return True
      #  Out[0] = '?'
      Out[1] = In[0]+1
      return True
   return node(nodeConstAlt, 1, 2, 'ALL_IN_AUTO')

# tests

m1 = genMerge( )
c1 = genConstAlt('a', 5)
c2 = genConst('b')
p1 = genPrint( )
b0 = bond((c1,1), (c1,0), 0)
b1 = bond((c1,0), (m1,0), None)
b2 = bond((c2,0), (m1,1), None)
b3 = bond((m1,0), (p1,0), None)
b4 = bond((m1,1), (m1,2), None)
merge = config([m1,c1,c2,p1], [b0, b1,b2,b3,b4])
runfig(merge,55)
 

Philip Thrift

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s