Troubleshooting Circularities

Circularities or circular equations, also sometimes called simultaneous equations, result when determining the value for a variable requires that you know the value for the variable. When the circles involve stocks, they form feedback loops and are part of almost every model. When the circles do not involve stocks, or there are circles trying to initialize stocks, they represent a model error that must be corrected before the model can be simulated.

If you are working with arrays, you may also want to look at Referencing other Array Elements.

Active Circular Connections

The simplest example of a circularity that represents an error would be having two converters, a and b where a=b and b=a. As you connect variables in your model Stella always looks forward to determine whether your connection would create a circularity like this. Thus, you may get a message such as "Connecting these variables would lead to a circular connection." For this reason, direct circular connections are rarely encountered.

It is possible to create active circular connections if you use Delay Converters, but do not use the delaying or smoothing functions they are intended to encapsulate. For the most part, however, any circularity message you encounter are like to relate to initial computations.

Initial Circular Connections

Much more common are circularities that occur during model initialization. For example, suppose you have a stock that is initialized with the equation

inflow*drainage_time

This is a standard way to initialize a stock to be in steady state. If inflow, in turn, is defined as

stock * fractional_growth_rate

Then in order to determine the stock you need to know the inflow, but in order to determine the inflow, you need to know the stock. In this case you will see both the stock and the inflow have the error message:

 Initial circularity: stock --> inflow --> stock

Whether this indicates stock-->inflow-->stock or inflow-->stock-->inflow is arbitrary, though the messages will most commonly start from a stock.

Circularity Reporting

Circularities are tested only after all equations have been correctly defined. Once this happens, Stella checks to see if there are any circularities. If one is found, it is immediately reported and all of the variables involved in it are marked invalid, and thereby highlighted. This makes it relatively easy trace the circularity on the model and think about how to fix it. The error dropdown will list all of the variables involved and you can walk through them by selecting successive entries from the dropdown.

Resolving Circular Connections

To resolve a circular connection you need to break one of the links in the circular chain. For active circularities, this usually means that you need to have a stock, or a delay converter, in a link turning the circularity into a proper feedback loop.

For initial value circularities, you need to break the link by changing the initial value. For stocks this means that you need to change the initialization equation. For converters that use a smooth or delay function (such as SMTH1, DELAY and so on) you may need to add the initial argument. For example, suppose you have the two converters:

inidcated_plankton = plankton * 2

plankton= SMTH1(indicated_plankton, 5)

Where plankton was defined as a delay converter. This would give you a circular connection between the two variables. To resolve if you could use

plankton= SMTH1(indicated_plankton, 5, 100)

This would start plankton at 100, then grow from there. (This example is presented for simplicity, it would be much better to treat plankton as a stock with an explicit growth (or better growth and decay) rate.)

Resolving with Constants

You can always resolve an initial circularity by replacing an initial equation for a stock with a constant, or adding a constant as a value for the initial argument of a delay builtin such as SMTH1. This works very well if the number is meaningful (initial population for example) or has a natural value (effect of fatigue at the beginning of a simulation). In many cases, however, it is preferable to use something that depends on the model variables already defined.

Resolving with Reference Values

A very common pattern in writing initialization equation is to use a reference value. For example, suppose we have a model with a variable target inventory and a stock Inventory. To start the model in equilibrium, you may want to set the stock to its target. In a typical model, target inventory will depend on the flow out of inventory which itself depends on inventor held. For example

target_inventory = expected_shipments * inventory_coverage

expected_shipments = SMTH1(shipments, shipment_smooth_time)

shipment = reference_shipments * effect_inventory_shipments

effect_inventory_shipments = inventory/target_inventory

Inventory = target_inventory

The loop that exists above can be broken by setting the initial value of Inventory to

Inventory = reference_shipments * inventory_coverage

The loop could also be broken by adding a third argument to the expected shipments equation as in

expected_shipments = SMTH1(shipments, shipment_smooth_time, reference_shipment)

And, the loop could also be broken by specifying an initial value equation for shipments

(init)shipment = reference_shipments

What all of these approaches have in common is that they use the reference value (of shipments in this case) to start things off. Which of them is best is largely a matter of clarity. In this case, adding the third argument to the SMTH1 builtin is probably the most transparent, though changing the stock equation is generally the easiest for anyone reading the model to discover.

Conveyors

When Stella starts a simulation, conveyors are initialized so that their outflows will smoothly deliver the initial amount of material in the conveyor. If there are no leakages, that is done by spreading the initial quantity out over the transit time for the conveyor. If there are leakages, a more complicated initialization is done based on the transit time, leak fractions and leak zones for all the leakages.

This means that in order to initialize a conveyor, it is necessary to know the transit time and all the leak fractions. Commonly, however, transit times and leak fractions can depend on the amount of material in the conveyor. A simple example would be:

loss_from_breakage = reference_loss * material_in_process / reference_material_in_process

where material in process is the conveyor. To fully initialize the conveyor then, we need to know the value of the conveyor.

Fortunately, this initialization can be broken down into phases. First we initialize the total quantity of material in the conveyor, then we initialize the leakage rate, then we initialize the distribution of material across the conveyor. All of this is done automatically by the software.

However, when the leakage rates depend on the distribution of material in the conveyor, the circularity cannot be resolved automatically. To keep it simple, suppose that the loss from breakage depended not on material in process, but on the rate at which it is coming out.

loss_from_breakage = reference_loss * finishing_processing / reference_finishing

The amount coming out depends on the distribution of material within the stock. So, in this case we need to know both the amount of material in process and its distribution in order to compute our leakage fraction. Since we need to know the leakage fraction to compute the distribution, there is a circularity.

In addition to outflows and leakages, using the [] notation to look inside the contents of a conveyor requires that the distribution of material within a conveyor be completely initialized.

Conveyor circularities, because they expose some of the internal workings of the computation, are not always easy to follow. In presenting them, we try to highlight the conveyor and the flows most directly involved, but this is not always informative. When the connections involve a number of other variables they can be especially confounding.

Conveyor circularities can also be more difficult to fix, because the changing the initial value for the stock does not fix them. This is true even if you specify a distribution using the 2,2,4 notation because those values can always be overridden during simulation by specifying a controlled value for them. Additionally, you cannot specify equations for conveyor outflows, these are all computed automatically so there are limited places to intervene and break the circularity.

Generally, the best strategy for correcting these second order circularities is to find variables involved for which a reference value can be determined and use a separate initialization equation for that variable using the reference value. The most common variables to change in this manner are the transit time and leakage fractions. Specifying a separate initial value equation for these will usually resolve the circularity.

Non Negative Stocks

In order to compute the outflow of a non-negative stock, it is necessary to know the inflows so that the stock can be kept non-negative, but outflows can be as big as possible. This introduces new dependencies that can easily lead to circularities.

When circularities arise because of a non-negative stock the software automatically breaks these by marking the stock as immediately passing its inflows to its outflows.Stocks marked in this way are displayed with || in the lower right hand corner when in model mode.

You do not need to do anything to break this type of implicit circularity as Stella will do that. If you are surprised by which stocks are marked in this manner, you can experiment with allowing other stocks to go negative. Sometime the implicit loops discovered can involve multiple stocks.