Ruby classes for modelling fractal systems: NetworkNode and Network

QuadCopter

Whilst messing around with genetic fractals, I have been trying to turn them into useful systems. From a system perspective (L-system) genetic fractals are just trees of nodes where the position of the nodes, as well as other properties such as shape and colour, are functions of the evolution of these trees. In doing so, it occurred to me that transition from one node to the next is essentially a transfer function.

When producing these fractals, I wrote software that simply generated the node coordinates and rendered in a ray tracer to create pretty and useless images. Now going one step further, I have developed a few classes that implement the system aspect of these genetic fractals. Although the fractals are trees, the implementation allows for any sort of network and this explains the name: NetworkNode and Network.

We see that a Network is a set of interrelated NetworkNodes. The relationships may be one-to-many and many-to-one.

network class

Incoming links are stored in an inLinks hash table and outgoing links in the outLinks hash table. The keys of these hash tables are the nodes from which or to which the NetworkNode is connected. The values of these hash tables, which are associated with the node keys may be any object but typically they contain a data object that is passed to or from the next node. Either side of such a connection has the same data object.

The NetworkNodes also have a payload which can be any type object. The data state of this object will in many cases be determined by the inLinks.

Two NetworkNodes may have their inLinks and outLinks mutually connected to created bidirectional relationship which may or may not be symmetric as determined by the inLinks and outLinks object methods and variables.

Basically, what we are trying to create is a network system of nodes where data can be passed into any direction, as determined by the order in which we navigate the network. These data objects can then be manipulated at the NetworkNodes.

As a test case we use these Network and NetworkNode classes to create a simple tree fractal. This is a very convoluted way to create such fractals but it serves as a test and a preparation for creating fractal systems with object flows.

In addition to the Network and NetworkNode classes, we also create a Position class that holds position and angle of fractal nodes and provides a method for calculating the next node position.

The payload of the NetworkNode is simply the Position (i.e. (x,y) coordinate) of the node.

The Ruby code is shown below. Using the NetworkNode, Network and Position classes it creates a fractal tree where the node positions (coordinates) propagate and evolve through the tree. It is then printed in a POVRay compatible format. The POVRay output is shown below as well.

NetworkTestFractal

Ruby code below. Remember, this is a test for a Network and NetworkNode class system and not the smartest way for drawing a fractal tree. There are easier ways to create such a fractal tree (It could be done in less than 10 lines). I should also note that these classes have since evolved.

include Math

class NetworkNode
# Each NetworkNode has an arbitrary payload, inLinks and outLink hashes and a unique id
# (which is will be the index of the nodes array array in the Network) attr_accessor :id, :payload, :outLinks, :inLinks
# This method takes a variable number of arguments whih will come in handy later.
def initialize(*args) # args: id, payload, outLinks, inlinks
if args.size>=1 then @id = args[0] end
if args.size>=2 then @payload = args[1] end
if args.size>=3 then @outLinks = args[2] end
if args.size>=3 then @inLinks = args[3] end
end

# The update method ensures coherence between inLinks, outLinks and th epaylkoad. This is a
# specific implementation for this type of payload
def update
@inLinks.each {|key, value| @payload = value}
return @payload
end
end

class Network
# nodes: an array of NetworkNodes
# id: a growing list of unique id's for the NetworkNodes
attr_accessor :nodes, :id

# Create an empty nodes array and set the id register to 0.
def initialize
@nodes=Array.new
@id=0
end

# the addNode method creates a new node in the Network that may be connected to parents
# and children explicitly. We may also set up the payload. This method takes a variable
# number of arguments whih will come in handy later.
def addNode(*args) # args: payload, outLinks, inlinks
if args.size>=1 then payload = args[0] else payload = nil end
if args.size>=2 then outLinks = args[1] else outLinks = {} end
if args.size>=3 then inLinks = args[2] else inLinks = {} end
@nodes[@id]=NetworkNode.new(@id,payload, outLinks, inLinks)
@id=@id+1
return @id-1
end

# The addChild method is an extension of addNode and ensures that the relationship between
# parent and child is adequately defined.
def addChild(node,transfer,payload)
# create new node
id = addNode(payload)

# ensure outLink of parent and inLink of child have the transfer object
@nodes[node].outLinks[id]=transfer
@nodes[id].inLinks[node]=transfer

# make sure the child node has the latest state based onLinks
@nodes[id].update
return id
end

# hadChildren? returns true if the node has at least one child node. Otherwise it returns false.
def hasChildren?(node)
if @nodes[node].outLinks.size == 0
return false
else
return true
end
end

# the printPOVRay prints out a recursive list of POVRay instructions (branch coordinates) of the
# type: branch(x1,y1,z1,x2,y2,z2) hich matches a macro in POVRay. This a specific implementation
# and mabe modified to suit your tool for x,y plotting.
def printPOVRayTree(parent, node, depth)
#make sure we have the latest position
@nodes[node].update

# The "node!=0" is a fix to ensure that there are no coordinate pairs made up of two equal coordinates.It also passes the rho for scaling the fractal ‘tubes’ in POVRay.
# This would trigger a "degenerate cylinder" error in POVRay
if node!=0 then print "branch("+@nodes[parent].payload.to_POVRay+","+@nodes[node].payload.to_POVRay+","+@nodes[parent].payload.rho.to_s+")n" end
if hasChildren?(node) and depth <=10 # protection against circular references
depth=depth+1
@nodes[node].outLinks.keys.each {|id|
printPOVRayTree(node, id, depth)
}
end
end
end

# The Position class is the payload of the NetworkNodes and contains positional information (coordinates
# and angles) as well as the incremental (decremental) offsets for the next branch length (rho * dhro) and the
# angle (dphi, w.r.t. phi)
class Position
attr_accessor :x, :y, :phi, :rho, :drho, :dphi

# This method simply returns x,y. POVray plots in 3D, hence the additional (nil) z coordinate.
def to_POVRay
return @x.to_s+","+ @y.to_s+",0"
end

# This method calculates the next branch coordinate based on the direction (left or right). It also ensures that
# all the position variables are carried forward.
def nextPosition(direction)
newPosition = Position.new
newPosition.rho = @rho * @drho
newPosition.phi = @phi + @dphi * direction
newPosition.x = @x+newPosition.rho*cos(newPosition.phi)
newPosition.y = @y+newPosition.rho*sin(newPosition.phi)
newPosition.drho = @drho
newPosition.dphi = @dphi
return newPosition
end
end
#---------------------------------------------------------------------------------------------------------
# end of class definitions
#---------------------------------------------------------------------------------------------------------

# Create empty fractal network
F = Network.new

# define origine of the network, plus the branch angles (dphi) and contraction (drho)
origin = Position.new
origin.x=0
origin.y=0
origin.rho=1
origin.phi=0
origin.drho = 0.7
origin.dphi = PI/6

# Create the first node of F
node = F.addNode(origin)

# define a few constants
Left = -1
Right = 1
FractalDepth = 7

# Define an recursive function for creating the fractal network tree. The addChild method creates two
# branches for each node. It passes the new position calculated from the parent's position in the payload
# and also gives the child a new payload, i.e. empty position.
def iterateFractal(node, depth)
if depth < FractalDepth
node1 = F.addChild(node,F.nodes[node].payload.nextPosition(Left), Position.new)
node2 = F.addChild(node,F.nodes[node].payload.nextPosition(Right), Position.new)
depth=depth+1
iterateFractal(node1,depth)
iterateFractal(node2,depth)
end
end

#---------------------------------------------------------------------------------------------------------
# end of programme definitions. Time to execute.
#---------------------------------------------------------------------------------------------------------

#create the fractal system network
iterateFractal(node,0)

#print the tree
F.printPOVRayTree(0,0,0)

POVRay code below:

// Persistence of Vision Ray Tracer Scene Description File (refer to http://www.povray.org)
// Desc: Test rendering for Fractal Network
// Date: 21/7/2013
// Auth: GeneticFractals.org
//
#version 3.6;

#include “colors.inc”
global_settings {
assumed_gamma 1.0
}

// —————————————-
camera {
location
direction 1.5*z
right     x*image_width/image_height
look_at
}

sky_sphere {
pigment {
gradient y
color_map {
[0.0 rgb ]
[0.7 rgb ]
}
}
}

light_source {
// light’s position (translated below)
color rgb  // light’s color
translate
}
// —————————————-

plane {
y, 0
pigment { color rgb }
}

#macro  branch(x1,y1,z1,x2,y2,z2, rho)
cylinder {,,rho/40
texture {
pigment {
color rgb
}
finish{
diffuse 0.3
ambient 0.0
specular 0.6
reflection {
0.8
metallic
}
conserve_energy
}
}
}
sphere {

rho/20
texture {
pigment {
color rgb
}
finish{
diffuse 0.3
ambient 0.0
specular 0.6
reflection {
0.8
metallic
}
conserve_energy
}
}
}
#end
union {

//Insert output from ruby code below
// branch(… etc

// ruby output finishes here
rotate
}

Advertisements

2 thoughts on “Ruby classes for modelling fractal systems: NetworkNode and Network

So what do you think?

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