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.
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.
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.
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.
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.)
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.
A very common pattern in writing initialization equation is to use a reference value. For example support 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.
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.
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.