Tutorial part 1: Running your first simulation

In this first part of the tutorial, we will run a simulation of Conway’s Game of Life, known simply as “Life”. Life is a very popular and simple simulation to get started with. In spite of its simplicity, we’ll be able to see interesting behaviors in our Life simulations. (Try spotting a glider!) This simulation is called Life because the patterns that emerge from it can seem like living cells and organisms.

Where to get help

If you’re having trouble going through this tutorial, you may ask for help in GarlicSim’s mailing list.

Basic simulation commands using the life simpack

For making a Life simulation, we will use the life simpack.

What is a simpack?

For every kind of simulation there’s a simulation package. We will usually abbreviate “simulation package” to simpack. For simulating Life, there’s a life simpack. If you’ll want to simulate Quantum Mechanics, you will need a simpack for Quantum Mechanics. (Which might as well be named quantum_mechanics.) There’s a collection of various simpacks in the garlicsim_lib.simpacks package, and from there we will obtain our life simpack.

So:

>>> import garlicsim
>>> from garlicsim_lib.simpacks import life

That’s it, we now have our life simpack at our disposal. Let’s make a life state:

>>> state = life.State.create_messy_root()

The create_messy_root method makes a state with lots of randomness and mess. This is good for our purposes, because we want to see some nice chaos in our simulation.

What is a state?

A state is the most basic data unit in GarlicSim. Sometimes called a “world state”, a state is the description of the simulation world in one frozen moment in time. A State object will contain all the information there is about that moment in time in the simulation.

Let’s check out this state:

>>> state
   ##  #  #  ##    ##    ## #  ##  ##  #
 ###### #  ### ## # # ###########     #
  #   # ### # ####     ### #    #  #####
#  ###   ###    #  ###  # ##   ####  # #
  ####### #        ##  ######  ### #  ##
#   ## ## ###  #  ### ## #  #  ## ###  #
  #  # #   # #  ## #  ## ##    ### #   #
#   #### ##    #  ####    # # # ## ###
##  # #  # ## ## #      ##   ##  ## #
 ##  #      ### ## # ####  ##  # #   # #
   ## ###     ##  # #  # ###  ## #### ##
###  ##  ## #    ##  ####      ########
  ##       ####   # ###### ## ## ###
### #  ## ####  ## ### ####   # # #### #
# #  # ##  # # #  #   # # ##  ### ## # #
>>> type(state)
<class 'garlicsim_lib.simpacks.life.state.State'>

We can see the board of the state is drawn nicely for us in ASCII art, with # marking a “live” cell and an empty space marking a “dead” cell. We can see that this board is random, with no seeming structure.

(Of course, since the board is generated randomly, the one you’ll get will be different from the one displayed here, and the same will be true for the next few boards. But they will have the same style as the boards displayed here.)

There are exactly 4 objects that are defined in the root garlicsim namespace. There are many classes and functions that are available in deeper namespaces, but only four that are available in the root namespace. These four definitions were chosen because they represent the most important and elementary functionalities of GarlicSim. The four definitions are garlicsim.simulate(), garlicsim.list_simulate(), garlicsim.iter_simulate() and garlicsim.Project.

Let’s experiment with simulate first:

>>> new_state = garlicsim.simulate(state)
>>> new_state
         ##     ### # #           #### #
##    # #         # ###      ##  #
      # #     #   #          #    ###
##              #### ##
 ##         #               # #      #
###       # #   ##          # #     #
 # #          ####     # ##  ##      ###
#  ##  # #   # #   #####  ##  #      # #
  ###  ### ##    #      ###   #       ##
 ##     #  ##    # ####    ##  #     # #
   ##  ### ## ##    #    #### #
##   ## ### #  # ##         ##        #
    #####     # #          # ##
    # #####    ###          #        # #
  #  #         ####   #      ##      #

What the simulate function did is apply the step function of life exactly one time. This means this new board is what you get from one iteration of the Life simulation. We can see it has a bit more structure in it; there are clusters of dead and of live cells.

What we can do now is supply the simulate function with a second parameter, which specifies how many iterations we want. Here we specify 20:

>>> new_state = garlicsim.simulate(state, 20)
>>> new_state
       #            #  #  #   #  #     #
                     ##       ## ##    #
                           ##  # #     #
            ###              ##   #   #
            ###                    ###
           #
  # #       #                    ##
# ## #       # ##                ##
###           ###            #        ##
##  #          #  ##         ##      ##
               # ####      # # #    # ##
                #         #    #   ##  #
          ##     ###       #  ##  ## ##
          ##      #           #   ##   #
      ##             ##    # #     #

We can see the new state, which is a result of 20 iterations of the simulation and is therefore more “mature” than the state we’ve seen before. It contains a few typical Life creatures.

Now let’s try list_simulate. It is similar to simulate, except it returns the entire list of states, from the first given one to the last.

>>> result = garlicsim.list_simulate(state, 20)
>>> type(result)
<type 'list'>
>>> len(result)
21

The list contains 21 states, comprising of the 1 initial state plus the 20 states creates in 20 iterations. We can see our initial random state is the first member of this list:

>>> result[0]
   ##  #  #  ##    ##    ## #  ##  ##  #
 ###### #  ### ## # # ###########     #
  #   # ### # ####     ### #    #  #####
#  ###   ###    #  ###  # ##   ####  # #
  ####### #        ##  ######  ### #  ##
#   ## ## ###  #  ### ## #  #  ## ###  #
  #  # #   # #  ## #  ## ##    ### #   #
#   #### ##    #  ####    # # # ## ###
##  # #  # ## ## #      ##   ##  ## #
 ##  #      ### ## # ####  ##  # #   # #
   ## ###     ##  # #  # ###  ## #### ##
###  ##  ## #    ##  ####      ########
  ##       ####   # ###### ## ## ###
### #  ## ####  ## ### ####   # # #### #
# #  # ##  # # #  #   # # ##  ### ## # #
>>> result[0] == state # This is our original state
True

And the same “mature” state we saw before is the list’s last member:

>>> result[-1]
       #            #  #  #   #  #     #
                     ##       ## ##    #
                           ##  # #     #
            ###              ##   #   #
            ###                    ###
           #
  # #       #                    ##
# ## #       # ##                ##
###           ###            #        ##
##  #          #  ##         ##      ##
               # ####      # # #    # ##
                #         #    #   ##  #
          ##     ###       #  ##  ## ##
          ##      #           #   ##   #
      ##             ##    # #     #
>>> result[-1] == new_state
True

We can also check out any state in between:

>>> result[7]
  #   #             #  #           #
    ##               ##        ######
                 ##              # ## #
  #            ## #                 ##
  # #      #### #              ##
          ## ## #      #    ## ##
# #      #    #  ##    #     #   #    ##
#    ## ##   #    #### #    #  ###   ###
   ####  #  #    ##   ##    #       ###
     ##                ### ##       ###
   #               ##    ## # ##    ## #
  ##      #               ##
#         # #                       #  #
   ### ##  #                  #     ## #
  #     #            ##      ###

Now, what is iter_simulate? It is similar to list_simulate, except it returns a generator instead of a list, and instead of crunching the 20 states in advance, it crunches them one by one, “lazily” as they say, every time we “pull” a state from it.

>>> garlicsim.iter_simulate(state, 5)
<generator object _non_history_iter_simulate at 0x013DC3F0>
>>> list(garlicsim.iter_simulate(state, 5)) == garlicsim.list_simulate(state, 5)
True

This is useful because sometimes list_simulate will take too long before it finishes, so using iter_simulate we can have every state calculated when we need it instead of having all the states calculated in advance.


The functions that we’ve been playing with now, simulate, list_simulate and iter_simulate, are the simplest tools that GarlicSim provides for simulating. You could probably see that they are not much different than applying the step function yourself.

Now it’s time to introduce Project.

The Project class

Before we’ll create a Project and play around with it, I’d like to show you a diagram that describes the inner workings of a Project. You’re not expected to understand what it all means right now; we’re just taking a quick peek under the hood.

../_images/project.png

Impressed? Now we’ll play around with it some.

Project is a class, and to instantiate it you have to pass the simpack into it, like so:

>>> project = garlicsim.Project(life)

Project is the main device used for doing simulations in GarlicSim. A project contains a Tree within it; this is a time tree, in which we organize our simulation data. A time tree is generalization of a time line. Trees are useful, because they give you the ability to “split” or “fork” the simulation at any node you wish, allowing you to explore and analyze different scenarios in parallel in the same simulation.

Aside from the tree, Project provides powerful tools for crunching the simulation. We’re going to explain these tools in greater depth later, but for now let’s just play around with our new project.

In the last section, we played around with a random Life board; this time, we will use a specific, famous board called “Diehard”:

>>> state = life.State.create_diehard()
>>> state












                            #
                      ##
                       #   ###










>>>

Diehard is a kind of Metushelah; a small, seemingly innocent state that creates a lot of chaos before it eventually dies down, after many simulation steps.

Let’s take this state we created before and use it as a “root” in our project:

>>> root = project.root_this_state(state)

What is a “root”? It’s just a term for describing a node that has no parent, and can therefore be thought of as “a root of the tree”, (though a tree may have more than one root.) Similarly, “leaf” describes a node which has no children.

When we called the root_this_state method, we created a node that has our state contained in it, and added that node to the tree as a root. The method returned the new node. Let’s see some info about our node:

>>> root
<garlicsim.data_structures.Node with clock 0,
root, leaf, touched, blockless, at 0x1c68ed0>

The clock 0 thing means that the clock reading of this node’s state is zero. The states that will succeed it will have incrementing clock readings. The “blockless” thing means that this node is not part of a Block; more on blocks later.

So now our tree has only one node, which is its root. Let’s do some crunching to add more nodes.

>>> project.begin_crunching(root, 50)

This will order the project to start crunching from our node. The number 50 means it will crunch until it reaches a clock reading of 50. In our example it is equivalent to performing 50 iterations as we did in the previous section.

However, crunching has not yet started. To start it, we will call:

>>> project.sync_crunchers()
<0 nodes were added to the tree>

The sync_crunchers method causes the project’s crunching manager to review the work that needs to be done on the project, recruit workers to work on it, and take any work that has been completed from the workers to implement into the tree. We will explain all about CrunchingManager later on. For now we will note that by default, the crunching of a Project is done on an auxiliary Thread; but by using different cruncher types, it can be done on an auxiliary Process or even on a cloud.

After a few seconds have passed, we call sync_crunchers again:

>>> project.sync_crunchers()
<50 nodes were added to the tree>

We see that sync_crunchers tells us how many nodes were added to the tree. This return value is actually a subclass of int, with a pretty representation which makes it easy to understand its context. It can be manipulated like any other int.

(If you got less than 50 nodes added to the tree, don’t worry; just hit sync_crunchers again after a few seconds, and by then it should get you the rest of the nodes.)

Okay, now that the results of our simulation are ready, let’s take a look at our tree:

>>> project.tree
<garlicsim.data_structures.Tree with 1 roots,
51 nodes and 1 possible paths at 0x1c68810>

Our tree has only one possible Path in it. A Path represents one timeline inside a time tree; a direct line of nodes which succeed each other.

Since we didn’t make any forks in our tree, it is currently a degenerate tree– It’s simply a timeline. So there is only one possible path that goes through it.

Let’s fork our tree. But before that, let’s get that single path:

>>> (path,) = project.tree.all_possible_paths()
>>> path
<garlicsim.data_structures.Path of length 51 at 0x1ccfb30>

Path is a list-like object. We can get any node we want from it using its index number. Let’s see how our diehard is doing at clock 50:

>>> path[-1]
<garlicsim.data_structures.Node with clock 50, leaf, untouched,
belongs to a block, crunched with life.State.step_generator(<state>),
at 0x1cd9070>
>>>
>>>
>>> path[-1].state









                 #
                #   ###
                    ##
                       ##
               # #     ##
                ## #   # #
                 #  #    #

                  # #
                   #






>>>

Grown quite a bit, hasn’t it?

Okay, now let’s do some forking. Let’s catch diehard at clock 27:

>>> node = path[27]
>>> node.state











                             #
                             ###
                      ###       ##
                      # #   #    #
                      ###   #
                            #   #
                               #







>>>

And now we’re going to fork the tree, and modify that state a bit.

>>> new_node = project.fork_to_edit(node)

The fork_to_edit method duplicates the node we feed it, and gives us a fresh copy we can play with. That way we don’t lose any of our simulation data while doing experiments like this one.

>>> new_node.state.board.set(28, 13, True)
>>> new_node.state











                             #
                             ###
                      ###   #   ##
                      # #   #    #
                      ###   #
                            #   #
                               #







>>>

Did you notice the little change we did? We flipped one cell from dead to alive.

Now after we edited the new node, we need to finalize it to tell GarlicSim that it can start crunching from that node.

>>> new_node.finalize()

We can see our tree now has two possible paths:

>>> project.tree
<garlicsim.data_structures.Tree with 1 roots,
52 nodes and 2 possible paths at 0x1c68810>

The fork happens on clock 27, since that is the one we duplicated for editing.

Now we want to crunch from our new node up to a clock of 50, like in the original timeline:

>>> project.ensure_buffer(root, 50)

ensure_buffer makes sure there is a buffer of the specified clock time (in our case 50) on all paths that go out of that node. This will cause our new path to be calculated up to clock 50. After we call sync_crunchers, of course:

>>> project.sync_crunchers()
<0 nodes were added to the tree>
>>> # Give it a few seconds...
>>> project.sync_crunchers()
<23 nodes were added to the tree>

It’s ready! So what we did here, is create an alternate timeline. We have already seen the future (clock 50) of the original timeline, and now we’re asking, “How would that future have looked if we make a little change at clock 27?”

Let’s check the new path:

>>> new_path = new_node.make_containing_path()

And now, to see what happens in clock 50 in our alternate timeline:

>>> new_path[-1].state






                       #
                      ###
                      ####
                          #
                 #####    ##
                 ## ###  ###
                #  ###  # #
              ##  #   #
              ##   #
              ##   #   #    #
                # #       ##
                   #      ##







>>>

This is a very different state from the one in the original timeline! It has about twice as many live cells. This is a reminder of how a small change in the present can cause a big change in the future.

What’s next

In the next part of the tutorial, we’re going to write our own simpack! Let’s go!