Tutorial part 2: Writing your first simulation package

In part 1 of the tutorial, we were playing around with someone else’s simpack. In part 2, we are going to make our own.

Motivation for our simpack

A few years ago, I was talking with a friend of mine about gambling. He said, “I thought of a really sure-fire way to win money at a coinflip game. (Where it’s 50/50 chance between doubling your money and losing it.) You bet $100. If you win, you won $100 and you’re done. If you lose, you bet $200. If you win that, you’ve still got a net win of $100, and you’re done. If you lose, you bet $400. If you win that, you still have a net win of $100, etc.”

“I guess you have a small chance of losing all your money, that’s the drawback.”

“Yeah, but you’re almost sure to win the $100. So let’s say you come to the casino with $5,000, and you want to make $1,000. You use my method 10 times in a row, and you almost certainly win $1,000!”

“Well, I don’t know...”

We’re going to make a simpack that will test whether my friend’s method for beating the casino is any good.

Starting a simpack using the start_simpack.py script

A simpack is a Python package, and to create it you need to create a folder with all the __init__.py files and everything. To save you that time, garlicsim comes with a script called start_simpack.py, which does all this for you.

The Scripts folder:

When you installed garlicsim, the start_simpack.py script got installed in your Scripts directory inside your Python distribution. Normally, the Scripts directory will be placed on your system’s PATH variable, and this will allow you to use start_simpack.py directly from anywhere on your filesystem. If you try to run start_simpack.py and get an error like “file not found”, ensure that your Scripts directory is added to your system’s PATH.

Where do you want to put your simpack? Open up your shell and go to that folder.

C:\>start_simpack.py coin_flip
`coin_flip` simpack created successfully! Explore the `coin_flip`
folder and start filling in the contents of your new simpack.

Alright! In the coin_clip directory we will find the new files. This is its structure:

coin_flip/
    __init__.py
    settings.py
    state.py
    wx/
        ...
        (The wx folder isn't important now)

You should browse this folder yourself, and go over the files. These are the abridged contents of the most important file, state.py, which you should read:

import garlicsim.data_structures


class State(garlicsim.data_structures.State):
    # This is your `State` subclass. Your state objects should contain all the
    # information there is about a moment of time in your simulation.

    def __init__(self):
        pass


    def step(self):
        # This function is the heart of your simpack. What it does is take an
        # existing world state, and output the next world state.
        #
        # This is where all the crunching gets done. This function defines the
        # laws of your simulation world.
        #
        # The step function is one of the very few things that your simpack
        # **must** define. Almost all of the other definitions are optional.
        pass


    #@staticmethod
    #def create_root():
        # In this function you create a root state. This usually becomes the
        # first state in your simulation. You can make this function do
        # something simple: For example, if you're simulating Life, you can make
        # this function create an empty board.
        #
        # This function may take arguments, if you wish, to be used in making
        # the state. For example, in a Life simulation you may want to specify
        # the width and height of the board using arguments to this function.
        #
        # This function returns the newly-created state.


    #@staticmethod
    #def create_messy_root():
        # In this function you create a messy root state. This usually becomes the
        # first state in your simulation.
        #
        # Why messy? Because sometimes you want to have fun in your simulations.
        # You want to create a world where there's a lot of mess, with many
        # objects interacting with each other. This is a good way to test-drive
        # your simulation.
        #
        # This function may take arguments, if you wish, to be used in making
        # the state. For example, in a Life simulation you may want to specify
        # the width and height of the board using arguments to this function.
        #
        # This function returns the newly-created state.

So now we can start filling in our simpack! But before that, we need to think about the laws of our simulation and how we’re going to represent them in the State.

Laying down the rules for our simulation

Let’s think about our simulation: We go into the casino, with a given amount of money, say $5,000. Every turn we make a bet on a certain amount of dollars. (That we must have at hand– No loans at this casino.) We double our money or lose it, with a 50/50 chance.

At first we bet $100. If we lose, we bet $200. If we lose again, we bet $400, etc. If we win one of those before going bankrupt, we made a net profit of $100, and then we start the cycle and bet $100 again.

Our goal is to make $1,000, i.e. have $6,000 in our account. If we reach that, we win. If we lose all our money, we lose.

Here is an attempt to write this simpack. (You can fill this in to your state.py file in the coin_flip folder.) Read the code and the comments:

import random
import garlicsim.data_structures


class State(garlicsim.data_structures.State):

    def __init__(self, balance, last_bet_result=0):

        garlicsim.data_structures.State.__init__(self)

        self.balance = balance
        '''The current balance of our account, i.e. how much money we have.'''

        self.last_bet_result = last_bet_result
        '''How much we won/lost in the last bet. `-100` means we lost $100.'''


    def step(self):

        if self.balance >= 6000:
            raise garlicsim.misc.WorldEnded

        # First we need to calculate how much we're going to bet in this round.

        if self.last_bet_result >= 0:
            # Meaning either (1) we just started the simulation or (2) we
            # just won $100.
            amount_to_bet = 100 # We're starting a new cycle

        else:
            # If the flow reached here it means we just lost. So we
            # should bet double the amount:
            amount_to_bet = - 2 * self.last_bet_result

        if amount_to_bet > self.balance:
            # If we don't have the amount we should bet, we stop the simulation.
            # True, we can try to bet whatever's left, but for simplicity's sake
            # we won't do that now.
            raise garlicsim.misc.WorldEnded

        # Let's bet!
        bet_result = random.choice([amount_to_bet, - amount_to_bet])

        new_balance = self.balance + bet_result

        new_state = State(new_balance, bet_result)

        return new_state


    @staticmethod
    def create_root():
        return State(balance=5000)

What is that WorldEnded exception that we’re raising there? It means that, well, the world has ended. Notice we raise it either (a) when we get $6,000 or (b) when we run out of money to bet. What this exception means is that the simulation has ended, and no further simulating should be done.

Ending the simulation

Notice that the simulation we explored before, Life, had no concept of ending the simulation. Life simulations continue to infinity, regardless if the board becomes empty or repetitive or whatever.

Some simulations have a concept of “simulation end,” and some just don’t.

Let’s explore our new simpack!

Now we’re going to play with our simpack using the Python shell. Be sure that the new simpack is on your sys.path; an easy way would be to launch the Python interpreter from the folder in which your simpack resides. (For example, if your simpack is at C:\coin_flip, you’ll want to launch Python from C:\.)

Got Python running? Let’s get started:

>>> from __future__ import division # To make sure we get real division
>>> import garlicsim
>>> import coin_flip

Let’s create a root state:

>>> state = coin_flip.State.create_root()
>>> vars(state)
{'balance': 5000, 'last_bet_result': 0}

Let’s simulate:

>>> new_state = garlicsim.simulate(state, 5)
>>> vars(new_state)
{'balance': 5400, 'clock': 5, 'last_bet_result': 100}

Our lucky gambler made $400 in only 5 turns, not bad.

You might notice that a clock attribute was added to the state. This clock helps us keep track on what time point we’re looking at. In turn-based simulations such as these, the clock is usually an int and denotes the turn number. In other simulations, like Physics simulations, the clock may by a float that tells the exact time in seconds in the state.

Notice that you didn’t have to set the clock in your simpack: Since you didn’t set a clock, one was created automatically for you.

Simulating without a state limit

Okay, but let’s say we don’t want to simulate just 5 states. We want to simulate until the gambler either gets $6,000 or loses too much money and quits. To do this, we simulate “to infinity”:

>>> from garlicsim.general_misc.infinity import infinity
>>> new_state = garlicsim.simulate(state, infinity)

That infinity simply represents infinity, and it means: “Simulate until the simulation ends itself with a WorldEnded.” (If the simulation won’t raise WorldEnded, it will indeed keep on simulating until the end of time, or until you kill the Python process, whichever comes sooner.)

(The reason we have to import this infinity thing is just for backwards-compatibility with Python 2.5; if you use Python 2.6 or above, you may simply use the builtin float('inf') instead of infinity.)

So anyway, let’s check out that new state:

>>> vars(new_state)
{'balance': 6000, 'clock': 26, 'last_bet_result': 1600}

This gambler made it to $6,000. Let’s check another:

>>> new_state = garlicsim.simulate(state, infinity)
>>> vars(new_state)
{'balance': 2200, 'clock': 8, 'last_bet_result': -1600}

Ah, not everyone is lucky... This gambler lost $3,800 and doesn’t have enough money to continue the gambling scheme.

Let’s play with list_simulate:

>>> states = garlicsim.list_simulate(state, infinity)
>>> len(states)
26
>>> [s.balance for s in states]
[5000, 4900, 4700, 4300, 3500, 5100, 5200, 5100, 5300, 5200, 5000, 5400,
5500, 5400, 5200, 4800, 5600, 5700, 5600, 5400, 5000, 4200, 5800, 5700,
5900, 6000]

Here we can see the story of another lucky gambler who made it to $6,000. We see how his account balance changed at every step in the way.

Results analysis

Okay, now it’s time to do what we started this simpack for: Checking if this betting scheme is any good. Let’s run the simulation 1,000 times and get a list of the end balances:

>>> def get_end_balance():
...     return garlicsim.simulate(state, infinity).balance
...
>>> results = [get_end_balance() for i in range(1000)]
>>> results
[6000, 2300, 6000, 6000, 6000, 2100, 6000, 6000, <... Truncated>]

Let’s get the average amount of money that a gambler ends up with. This should let us get a good approximation to the expected value of this scheme:

>>> sum(results) / len(results)
4931.3000000000002

Just a little below $5,000. Let’s try with another sample:

>>> results = [get_end_balance() for i in range(1000)]
>>> sum(results) / len(results)
5016.1999999999998

Just a little above.

>>> results = [get_end_balance() for i in range(1000)]
>>> sum(results) / len(results)
4990.3000000000002

Again a little below. This is good enough indication that the expected value is zero. To get an exact value, we’ll need to solve this problem analytically, of course, and that’s not what garlicsim is about.

So the expected value of this scheme is $0, meaning you aren’t really “getting anything” with it. It has the same expected value as not betting at all, or betting the entire $5,000 on the first turn. This is not surprising, since the expected value of each single bet is $0, so it doesn’t matter what combination of these bets you will do, the expected value will stay $0.

Sometimes though you care less about the expected value. For example, let’s say you need to buy something for $6,000 very urgently, and you don’t care about anything else. Then this is your chance to make $6,000 with this scheme:

>>> results.count(6000)/len(results)
0.70799999999999996

Around 70%. Not bad, assuming you don’t mind losing a lot of money on the other 30 percent. Though it can be easily shown that you might as well have bet $1,000 from the beginning, and then double that if you lost, and you would have had the same chance of getting $6,000 at the end. Though that is the subject for another simulation...

What’s next

Check out tutorial part 3, where we’ll learn how to load our new simpack into the GarlicSim GUI!