fractal and the beauty of nature

9
DM 502 Programming A

Upload: tutorapex-projecthelp

Post on 26-Aug-2014

425 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Fractal and the Beauty of Nature

DM 502 Programming A

Page 2: Fractal and the Beauty of Nature

Specification

This program is the implementation of the requirements specified by the “DM 502 Programming

A” document. It implements the functionalities described by the “Fractals and the Beauty of Nature”

section.

The goal is to produce a program that draws a fractal which characteristics are described in a .fld file.

This file describes the initial states of the iterative fractal procedure along with the core of the

computation loop. This loop defines the fractal process expanding from the current step from the previous

step.

After the final step completes, the last state describes the resulting fractal. Each state value is associated

to a given drawing command that is used to issue the fractal’s picture.

Here is a sample of the drawing resulting from the specification found in the “Koch.fdl” file:

Page 3: Fractal and the Beauty of Nature

Design

Using the same bottom-up approach as for testing, the program was carefully design in small

steps. Starting from the necessary tools needed to complete the task, it grew until all was combined with

the proper abstractions being in place.

Some simple data types have been introduced to encapsulate the necessary data.

1. Fractal data type

The Fractal data type holds all the information and behavior associated with the fractal computation

and settings.

It encapsulates the functions that are used to compute a given step from a previous step and the main

fractal loop. The two elements are clearly separated to properly abstract each computation unit.

It also contains the binding with the execution of the final “drawing commands” from the final state of

computation obtained after everything completed.

2. Command data type

The Command data type is responsible for interpreting a given command and dispatching it to the

necessary drawing commands to the turtle functions. It acts as a small interpreter for commands.

3. Rule data type

The Rule data type is responsible for encapsulating a given rule. It parses an initialization string that

is supposed to represent the text description of a rule and offers an “apply” function that from a given

input (list of single characters) produces a list of characters after the rule has be properly applied to each

list member.

A function “read_fdl” has also been added that is used to bridge the configuration file (.fdl file) with the

proper runtime environment. It parses the configuration file and properly initializes a Fractal object with

the corresponding rules, commands and settings.

Page 4: Fractal and the Beauty of Nature

Implementation

The program has been written by a careful partitioning of roles and responsibilities among different

classes.

1. Rule data type

The Rule data type is a class that takes a rule representation in its constructor. This representation

corresponds to the rule description taken from the configuration file e.g. 'F -> F L F L'.

The input string is split and the left and right side (from the arrow’s perspective) are kept into two

separate object variables for further reference.

The rule application function is the used with a list comprehension to generate a list based on the input list

with the characters for which the rule applies replaced by the right side of the rule.

2. Fractal data type

The Fractal data type is a class the basically wraps everything that is needed to compute a given

fractal given the description found in a configuration file.

It computes a given step from the previous one applying the necessary rule to all the elements of the

current state and contains the main loop.

On interesting trick is being used here.

The Fractal class actually contains a “length” parameter that is used by the commands to scale up and

down the drawings. This parameter is a plain value and has to be passed to command objects that might

actually change its value. In Python, parameters are passed by value (the value being the reference to the

object in case of an object being passed). So in order to allow the length parameter to be changed, a list of

size one is used. The corresponding object has his reference being passed by value to the commands and

the value that it contains can then be modified.

A dictionary is used to hole the mapping between a given fractal character and the associated command.

3. Command data type

The Command data type is responsible for making the connection between a given binding between a

command and the resulting turtle action.

Page 5: Fractal and the Beauty of Nature

Testing

A bottom-up approach has been chosen to perform the tests. Each function and individual piece

of functionality has been tested independently before being assembled as the end product.

For each piece of functionality, a careful set of input data has been chosen to check if the basic

functionality was working properly. Once these passed, a sample of tests has been chosen for the edge

cases to give a basic guarantee regarding the safety of the function.

For example, here is a function that expects a list of lists and flattens it:

def flatten (l):

flattened_list = []

for element in l:

flattened_list.extend (element)

return flattened_list

It has been tested against the following valid inputs:

> flatten ([[1], [1, 2, 3]])

[1, 1, 2, 3]

> flatten ([[1, 2, 3], [1, 2, 3]])

[1, 2, 3, 1, 2, 3]

Some edge cases have been used to further test the behavior of the function:

> flatten ([[], [1, 2, 3]])

[1, 2, 3]

> flatten ([[], []])

[]

The same idea has been applied to check that:

- the Fractal class was properly initialized from a given “.fdl” file,

- a given command was properly parsed and executed,

- a given rule was properly parsed and executed,

Page 6: Fractal and the Beauty of Nature

Conclusion

All the fractal shapes have been tested and the results are really satisfying. The program performs

properly and without crashes.

Here is a sample of the construction process for the “tree.fdl” file:

Figure 1 Result for "tree.fdl" file

Page 7: Fractal and the Beauty of Nature

Appendix # Project - Fractal and the beauty of nature

# basic turtle commands

def lt (turtle, n):

turtle.lt (n)

def fd (turtle, n):

turtle.forward (n)

def bk (turtle, n):

turtle.backward (n)

def rt (turtle, n):

turtle.rt (n)

# the rule class that exposes a way to apply a given rule

# to a list of elements

class Rule(object):

# initializing the rule based on a description string (e.g. 'F -> F L F L')

def __init__(self, repr):

# the left and right part of the rule in 'F -> F L F L'

self.left, self.right = [a.strip() for a in repr.split ('->')]

if self.left is None or self.right is None:

print ('Invalid rule description')

def apply_rule_on_elements (self, elements):

return [self._apply_rule_for_element (element) for element in elements]

# helper function that only works on one element at a time

def _apply_rule_for_element (self, element):

if element == self.left:

return self.right.split ()

return element

# a very simple helper function, handy in some cases

# this allows one to perform assignment multiple values & grouping

# e.g.

# a, b = split_command ('s', [1, 2, 3, 4])

def split_command (command, *args):

return command, args

# A command class tha wraps the execution of a given command

class Command (object):

def __init__(self, command):

# the name and number of arguments for the given command

self.name, self.args = split_command (*command.split ())

def execute (self, turtle, length):

if self.name == "lt":

lt (turtle, int (self.args[0]))

Page 8: Fractal and the Beauty of Nature

elif self.name == "scale":

length[0] = length[0] * float (self.args[0])

elif self.name == "fd":

fd (turtle, length[0])

elif self.name == "bk":

bk (turtle, length[0])

elif self.name == "rt":

rt (turtle, int (self.args[0]))

elif self.name == "nop":

pass

# the main Fractal class

class Fractal (object):

def __init__(self):

# initial & current state

self.state = []

# rules associated with the current fractal

self.rules = []

# commands associated with the current fractal

self.commands = {}

# current lenght argument

# a little trick here is used to allow the modification of the length

# variable outside of the Fractal class (by the command class)

# since values are immutable and passed by value, we use an array (passed by reference

value)

# to allow the modification of the variable

self.length = [0]

# current depth

self.depth = 0

# executes the command associated w/ the current states stored in the fractal

def execute_commands (self, turtle, states):

for state in states:

self.commands[state].execute (turtle, self.length)

# flattens a list

def _flatten (self, l):

flattened_list = []

for element in l:

flattened_list.extend (element)

return flattened_list

# compute the fractal, does the actual iteration work

# returns the state of the fractal after the computation

def compute (self):

current_depth = self.depth

current_state = self.state

while self.depth != 0:

current_state = self._compute_next_state (current_state)

self.depth -= 1

return current_state

def _compute_next_state (self, state):

Page 9: Fractal and the Beauty of Nature

for rule in self.rules:

state = rule.apply_rule_on_elements (state)

return self._flatten (state)

# parse the fdl file, creates a fractal and set it up with the values

# read in the fdl file

def read_fdl (filename):

import os

f = Fractal ()

if os.path.exists (filename):

lines = open (filename).readlines ()

for line in lines:

if not len (line.strip()) == 0:

name, arguments = split_command (*line.strip ().split ())

if name == "start":

f.state = arguments

elif name == "rule":

f.rules.append (Rule (" ".join (arguments)))

elif name == "length":

f.length = [int (arguments[0])]

elif name == "depth":

f.depth = int (arguments[0])

elif name == 'cmd':

f.commands[arguments[0]] = Command(" ".join (arguments[1:]))

else:

print ("File does not exist")

# no check is made, to see if we have a fracal that was completely initialized

return f

# run the program and expect the name of the fractal file (.fdl) as the first

# argument on the command line

import sys

import turtle

if len (sys.argv) > 1:

f = read_fdl (sys.argv[1])

f.execute_commands (turtle, f.compute ())