In SEL, there are many ways to =SUM()

Containers are events that store numeric value -- i.e. the results they receive from other events. In other contexts, you may think of containers as assets, like a bank account or investment. What happens to the value stored in a container depends on the type of container you choose.


A Pool is an event that collects all of the values it receives, has no limit to the amount it can store, and reports, but does not release, that value to downstream (connected) events.

You can create a pool with the following function:


The 0 is the amount of value the pool contains at the start of the simulation.

A good use of a pool might be a bank account or storage system that has no limits.


Optionally, a pool can take a second argument, for example:

=Pool(100k, 0)

If this event crosses below a balance of 0 (second argument), the simulation will halt.

This can be useful when the value of an event represents solvency or a minimum success criterion which you are controlling for while building your board.

We will refer to this type of setting elsewhere as a halt condition.


Mixing numbers and lists of numbers (don't).

The first value a pool receive casts it as able to receive (and sum) lists of numbers or individual numbers (in math terms, scalars).

As a consequence of this, if you send 6 to a Pool and then [1,2,3], you will get an error.

Similarly, you will also get an error if you send [1,2,3] and then [6,], as these lists are not the same length. If you want to add 6 to each list element, use a basic expression instead and send the result to the pool:

Array([1,2,3]) -> + 6-> =Pool(0)


A Pipe relays any and all values it receives downstream. A Pipe does not carry a balance. We can define a Pipe like so:


The first and only required argument sets an initial value. If, for example, you were simulating a sales pipeline, you'd use this argument to indicate how many deals are at this stage when the simulation begins. In many cases, this will simply be zero.

A pipe can take a second, optional argument, like so:

=Pipe(0, 100)

This second argument sets a constraint on the amount that will be passed along to any connected (downstream) events. In this case, this pipe will only pass along 100, even if it receives a value such as 102.

You can also add a halt condition ! to this second argument, to indicate that the simulation should halt if this value is exceeded. You can think of this as a pipe burst. Here's an example:

=Pipe(5, 53!)

This pipe will begin by immediately sending a 5 to any downstream events, and will limit any other values to a maximum of 53. If this pipe receives a value greater than 53, it will halt the simulation.


A Bucket is an event that collects all of the value it receives up to a specified limit. Beyond that limit, value is passed to downstream events.

You can create a bucket like so:

=Bucket(0, 100)

The second argument, 100, tells the event to store only up to 100 units of value before sending any overflow downstream. We can also refer to this as the event's threshold.

A good use of a bucket could be a checking account where you want any funds over a threshold to flow into a savings account.

A bucket could also represent things with a finite capacity, or things that get saturated.



Buckets are the first event type in this guide to introduce the concept of a constraint -- a point or place in your model where a limitation needs to be enforced.

Halt Condition

You may optionally attached an ! to the second argument of a bucket, like so:

=Bucket(0, 100!)

This ! turns the second argument into a halt condition, meaning the simulation will stop as soon as the value of the bucket reaches this threshold (in this example, 100).

This is useful if you are using the bucket size as a success or failure criterion.

The halt condition on a bucket may not be combined with uncertainty, e.g. ~100! will cause a parsing error.

Tipping Bucket

A Tippingbucket is an event that collects value up to a threshold and then empties all of its value into downstream events once the threshold is reached or exceeded.

To make a tipping bucket, use the following function:

=Tippingbucket(0, 100)

Once >= 100 is stored in the event, the tipping bucket will reset to 0 and pass all of its value to downstream events.

Tipping buckets are useful to represent savings goals where you want to simulate action as soon as the goal is reached. For example, you may want to set aside a portion of your savings for a planned trip to Hawaii, and then use those funds as soon as the total expense of the trip is reached.

Halt Condition

You may optionally attach an ! to the second argument of a tipping bucket, like so:

=Tippingbucket(0, 100!)

This ! turns the second argument into a halt condition, meaning the simulation will stop as soon as the bucket tips.

This is useful if you are trying to find the earliest date that the bucket tips.

The halt condition on a tipping bucket may not be combined with uncertainty, e.g. ~100! will cause a parsing error.


A Funnel is an event that releases a specific amount of value from its store each time it's triggered.

To make a funnel, we use the following:

=Funnel(100_000, 10)

This funnel will release 10 each time its called. So after the first execution, the funnel will contain 99,990. Once the funnel reaches 0, it stops sending value downstream.

You can replenish a funnel by sending it additional values from events upstream.

A funnel is great way to represent a container that depletes over time but is mined or drawn from in a steady manner. This could be something like a rock quarry, a grain silo, an oil well, or a trust fund.


A Gate opens on a schedule (defined by its start date, end date, and recurring pattern). On schedule, it opens and releases all of the value it has stored since its last execution date.

A practical example of a gate is a profit-sharing program, where profits are stored until the same date every year, and then released in full. Gates are also useful for payroll cycles, where you want to store or accrue all of the payments owed to contractors or employees until payroll is run (e.g. biweekly or semimonthly or monthly).

A gate is created like so:


The single value passed into the gate is any value you want to store immediately.

A Gate always opens the first time at midnight (00:00:00) of its start date. This means it will only include any values provided in the initial construction of the Gate. Any values passed to the Gate on its start date will therefore be included in its first release date after its start date.


A Silo stores value with a minimum and maximum specified as a second argument, like so:

=Silo(100, [-50, 1k])


=Silo(100, [-100,])

The second item of the second argument (the maximum balance of the silo) is optional. If omitted, the silo will act like a Pool, albeit with a minimum.

Any value above the optional maximum or below the minimum will be passed along to any downstream events. This makes silos an excellent choice for storing value that needs to be 'dealt with' first. For example, interest on debt often works this way: interest must be paid first, and only the remaining amount can be passed along to reduce the principal balance.


A Wheel stores values in a rotating list. Each time the event is triggered, it sends the sum of all the values stored in the active position.

The length of the rotating list is determined by the first (required) argument, like so:


In this case, this is a rotating list of length 12, which will start as twelve 0's: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Each time this wheel occurs, it will report the value in the list at its active position. The active position is based on the number of times the event has occurred. So the first time it occurs it will report the value at position 1. The second time, position 2, and so forth. On its 13th time, it will report the value at position 1 again. This is what makes the wheel a wheel.

A wheel will add whatever values it receives to the value at its active position. So if position 1 has 12 in it, and the active position is the 1st position, and I send -1, it will now be 11. If I send 4, the value as position 1 will now be 15.

The active position (i.e. the starting value in the simulation) will be determined by the current month. If the wheel is monthly recurring with a length of 12, the active position is the current month of the year. If the wheel is quarterly recurring with length 4, the active position will be the current quarter. Weekly recurring of length 52 (phew!), the current week of the year.

This makes a wheel a great event type to store information that changes over time-based on a daily, monthly, weekly, quarterly calendar. For example, I could use a wheel to model invoice amounts for annual subscriptions by having a length 12 wheel recur monthly. Each month I send it the total amount of new annual invoices and it reports the total invoice amount for the given month, while keeping each month internally distinct.

Wheels can take a second argument, which pre-populates the list:

=Wheel(4, [100, 200, 300, 100])

The values in the second argument represent the starting values in a list as in a calendar. For example, if you apply a second argument to a monthly-recurring wheel with length 12, the first position always represents January. The second, February, etc.

Growth rates can also be applied to the values stored in each position of a wheel (either with or without a list). A growth rate on a wheel with length 12, without a list, can be written like so:

=Wheel(12, [] @ 5%)

This means the value stored in each position will increase by 5% in subsequent years. For example, if the value in the January position is 100 in the first year of the simulation, the value will return 105 the following year.


A Base stores the values it receives into a list, and sends the sum of all the values in this list to any downstream events.

The only argument to a base is an initial list of values (which may be empty) and a list of percentages. For example:

=Base([] @ [95, 65, 54, 32, 23]%)

This will create an empty base where values provided to the base decrease over time. So for example, if this base received the number 100, it would reduce to 95, then 65, then 54, 32, and finally 23 by the end of the 5th occurrence of the event.

This feature of bases makes them ideal for storing cohorts -- batches whose sizes change over time. For example, the above may be a subscriber cohort, where 23 subscribers remain after month five (assuming the base recurs monthly).