The step function is the most important part of a simpack. The step function contains the world laws and tells GarlicSim how to calculate the next world state in a simulation.
Writing a simple step function is easy and we’ll show how to do it below; after you’ve mastered that, we’ll show you more advanced ways to define and use step functions. We’ll also show you the different kinds of step functions that GarlicSim allows.
Below we will show the different kinds of step functions. Keep in mind that your step function’s type will be determined automatically from its name; for example, a step function called step (or my_step or step_alternate) will be automatically identified as a simple step function; as another example, a step function called history_step_generator (or faster_history_step_generator or my_awesome_history_step_generator) will be automatically identified as a history step generator.
If you wish to give a different name to your step function and specify its type manually, you can do that by decorating the step function with the step type.
The simple step function is also known as garlicsim.misc.simpack_grokker.step_types.SimpleStep.
Here’s an example of a State class that defines a simple step function which is only two lines long:
class State(garlicsim.data_structures.State):
def __init__(self, height):
self.height = height
def step(self):
return State(self.height + 1)
In this step function we created a new state object, set one of its attributes, and returned it. This is enough for GarlicSim to use this simpack to generate states.
Do notice one thing we didn’t do: We didn’t set clock readings to our states. When you simulate with GarlicSim, every state always gets a .clock attribute which says its clock reading. The step function usually assigns this .clock attribute, but when it doesn’t, (such as in the example above,) GarlicSim automatically gives each state a clock reading. Newly-created states get a clock of 0 by default, and on every step the new state’s clock reading will be higher by 1 than that of the original state.
The inplace step function is also known as garlicsim.misc.simpack_grokker.step_types.InplaceStep.
If you’ve written a simpack or two, you may have noticed that you often keep many attributes on your world states. It can be a drag to recreate all of these attributes in the step function. If you use an inplace step function, you don’t have to. Example:
class State(garlicsim.data_structures.State):
def __init__(self, height, weight, spin):
self.height = height
self.weight = weight
self.spin = spin
def inplace_step(self):
self.height += 1
The inplace step function changes the state in place instead of returning a new state. An inplace step function doesn’t return anything. (i.e., it returns None.) The modified state will still have all of the old attributes it had. (In this example weight and spin.)
But wait, you say. How can we modify a state in place? GarlicSim needs to keep all the states in a time tree as constants, if states will be modified in place, it will ruin the whole thing!
Well, GarlicSim is smart, and when it uses an inplace step function, it duplicates the state before feeding it to the step function on every iteration, so that the original state and all produced states that go to the tree are left unaltered.
GarlicSim will also advance the new state’s clock by one if the inplace step function didn’t advance it itself. This feature is available on all step function types.
One more advantage that the inplace step has is that if you use garlicsim.simulate() with it, the step will actually be done in place for better performance, without duplicating on every iteration. For example, if you give garlicsim.simulate() an initial state and tell it to do 100 iterations, it will first deepcopy your initial state, then perform 100 iterations of the inplace step function in place, and then return the resulting state. This is done only in garlicsim.simulate(), and not in garlicsim.Project, because garlicsim.simulate() is the only function that doesn’t reveal the interim states to the user, so we can afford not to keep them as constants.
The step generator is also known as garlicsim.misc.simpack_grokker.step_types.StepGenerator.
You can write your step as a generator. Example:
class State(garlicsim.data_structures.State):
def __init__(self, height):
self.height = height
def step_generator(self):
current_state = self
while True:
current_state = State(current_state.height + 1)
yield state
Instead of a step function that returns states, we have a generator that keeps yielding states indefinitely.
Some steps are easier to write as a step generator, because every time they yield a state they remember in which line they paused and what the values of all the local variables are.
The inplace step generator is also known as garlicsim.misc.simpack_grokker.step_types.InplaceStepGenerator.
The inplace step generator combines the advantages of the inplace step function with those of the step generator. Example:
class State(garlicsim.data_structures.State):
def __init__(self, height, weight, spin):
self.height = height
self.weight = weight
self.spin = spin
def inplace_step_generator(self):
while True:
self.height += 1
yield
The inplace step generator changes the state in place, and after every iteration it yields, and after being called again it modifies the state in place again, ad infinitum.
Note that the inplace step generator doesn’t yield any states; it always yields None. It has no need to yield any states, since it’s changing the state itself, not creating any new state objects.
Similarly to the inplace step function, GarlicSim will take care to duplicate any states before they are being fed into the inplace step generator; this will be done in garlicsim.Project, garlicsim.list_simulate() and garlicsim.iter_simulate(), but not in garlicsim.simulate() which will actually perform the state in place to enjoy better performance. (Note that the initial state will be duplicated, but all the simulation iterations will be done in place on the copy, and then it will be returned as the return value of garlicsim.simulate().)
The history step function is also known as garlicsim.misc.simpack_grokker.step_types.HistoryStep.
The history step function is very different from all the other step functions. In all the other step functions, it is assumed that all the step function needs to have is the last world state, and from that it can calculate the new state. That is true for many kinds of simulations, but not for all. Some simulation steps need access to the complete history of the simulation in order to calculate the next state. (This is true, for example, in some simulations of radiation.)
Here’s an example of a history step function:
class State(garlicsim.data_structures.State):
def __init__(self, height):
self.height = height
@staticmethod
def history_step(history_browser):
last_state = history_browser.get_last_state()
state_three_seconds_ago = history_browser.get_state_by_clock(
last_state.clock - 3
)
return State(
height=(state_three_seconds_ago.height + 1)
)
As you can see, the history step function doesn’t even receive a state object; it’s a static method.
What the history step function does receive is a history browser, of the class BaseHistoryBrowser. A history browser lets you explore the history of the simulation by retrieving states from it. For example, the .get_state_by_clock method shown above gets a state from the timeline according to its clock reading. You may also request states by index number, like so:
second_to_last_state = history_browser[-2]
A simpack which uses a history step function is also called a “history-dependent” simpack, and it may not use any non-history-dependent step functions. Note that you cannot use ProcessCruncher with history-dependent simpacks.
The history step generator is also known as garlicsim.misc.simpack_grokker.step_types.HistoryStepGenerator.
The history step generator combines the advantages of a step generator with the history-dependent approach.
As of GarlicSim 0.6.3, the history step generator is not yet implemented. Sorry.
Step functions always take the world state (or history browser) as a first argument. But, step functions can also take other optional arguments. For example:
class State(garlicsim.data_structures.State):
def __init__(self, height):
self.height = height
def step(self, time_interval=1):
new_state = State(self.height + time_interval)
new_state.clock = self.clock + time_interval
return new_state
In this example we used an argument called time_interval, which determined how big of a time interval we use between states in the timeline. You can invent your own step function arguments for all kinds of purposes. A few examples are: Changing physical constants in a Physics simulation, changing birth/survival numbers in cellular automata, changing the number of customers that come to a queue every second in a Queueing Theory simulation, etc.
Default values for arguments
Note that if a step function allows arguments, each and every argument must have a default value. (as in t=1.) This is so the step function could always be run with just a state, without requiring the user to figure out which arguments the simpack is expecting.
To learn how to use step function arguments when running a simulation, check out the article about step profiles.
You can define more than one step function in your simpack. For example:
class State(garlicsim.data_structures.State):
def __init__(self, height):
self.height = height
def step(self):
return State(self.height + 1)
def my_other_step(self):
new_state = State(self.height + 1)
return new_state
def inplace_step_generator(self):
while True:
self.height += 1
yield
As you can see above, you can have different kinds of step functions in the same State class.
This is useful because sometimes you want to implement several simulation algorithms and to be able to use them all in one simulation time-tree in order to compare their results side-by-side.
When running the simulation, you can specify which step function you want to use. For example, when using garlicsim.simulate(), you may give an argument step_function=my_simpack.State.my_cool_inplace_step to make it use the step function you specify.