I CAN HAS SYSTEM DYNAMICZ?

IM PRETTY SURE THIS IS THE FURST EVAH SYSTEM DYNAMICZ SIMULASHUN MODEL WRITTEN IN LOLCODE.

HAI 1.2
    VISIBLE "HAI, JWF!"
    
    OBTW
     ==========================================================================
     SYSTEM DYNAMICZ INVENTORY MODEL IN LOLCODE
     TOM FIDDAMAN, METASD.COM, 2021
     INSPIRED BY THE CLASSIC BEER GAME
     AND MODEL 3.10 OF MICHAEL GOODMAN'S 
     'STUDY NOTES IN SYSTEM DYNAMICS'
     ==========================================================================
    TLDR
    
    BTW FUNKTION 4 INTEGRATIN STOCKZ WITH NET FLOW INOUT
    HOW IZ I INTEGRATIN YR STOCK AN YR INOUT AN YR TIMESTEP
        FOUND YR SUM OF STOCK AN PRODUKT OF INOUT AN TIMESTEP
    IF U SAY SO
    
    BTW FUNKTION 4 CHARACTER PLOTZ
    HOW IZ I PLOTTIN YR X AN YR SYMBOL
        I HAS A STRING ITZ ""
        I HAS A COUNT ITZ 0
        IM IN YR XLOOP
            BOTH SAEM COUNT AN BIGGR OF COUNT AN X, O RLY?
                YA RLY, GTFO
                NO WAI, STRING R SMOOSH " " STRING MKAY
            OIC
            COUNT R SUM OF COUNT AN 1
        IM OUTTA YR XLOOP
        VISIBLE SMOOSH STRING SYMBOL MKAY
    IF U SAY SO
    
    BTW INISHUL TIME - DEKLARE SUM VARIABLZ AND INIT STOCKZ

    I HAS A INV ITZ 0.0         BTW INVENTORY (WIDGETS)
    I HAS A MAKIN               BTW PRODUCTION RATE (WIDGETS/WEEK)
    I HAS A SELLIN              BTW SALES RATE (WIDGETS/WEEK)
    I HAS A TIME ITZ 0.0        BTW LOL I WISH (WEEK)
    I HAS A TIMESTEP ITZ 1.0    BTW SIMULATION TIME STEP (WEEK)
    I HAS A ZEND ITZ 50.0       BTW FINAL TIME OF THE SIM (WEEK)
    I HAS A TARGET ITZ 20.0     BTW DESIRED INVENTORY (WIDGETS)
    I HAS A ADJTIME ITZ 4.0     BTW INVENTORY ADJUSTMENT TIME (WEEK)
    I HAS A ORDERIN             BTW ORDER RATE (WIDGETS/WEEK)
    I HAS A INIORDERS ITZ 10.0  BTW INITIAL ORDER RATE (WIDGETS/WEEK)
    I HAS A STEPTIME ITZ 30.0   BTW TIME OF STEP IN ORDERS (WEEK)
    I HAS A STEPSIZE ITZ 5.0    BTW SIZE OF STEP IN ORDERS (WIDGETS/WEEK)
    I HAS A INVADJ              BTW INVENTORY ADJUSTMENT NEEDED (WIDGETS)
    I HAS A WIP ITZ 0.0         BTW WORK IN PROGRESS INVENTORY (WIDGETS)
    I HAS A SHIPPIN             BTW DELIVERIES FROM WIP (WIDGETS/WEEK)
    I HAS A PRODTIME ITZ 4.0    BTW TIME TO PRODUCE (WEEK)
    
    VISIBLE "SHOWIN RESULTZ FOR PRODUKSHUN"
    
    IM IN YR SIMLOOP        BTW MAIN SIMULASHUN LOOP
        
        BTW CALCULATE RATES AND AUXILIARIES
        
        BTW STEP IN CUSTOMER ORDERS
        BOTH SAEM TIME AN BIGGR OF TIME AN STEPTIME, O RLY?
            YA RLY, ORDERIN R SUM OF INIORDERS AN STEPSIZE
            NO WAI, ORDERIN R INIORDERS
        OIC
        
        SELLIN R SMALLR OF ORDERIN AN QUOSHUNT OF INV AN TIMESTEP
        INVADJ R DIFF OF TARGET AN INV
        MAKIN R SUM OF SELLIN AN QUOSHUNT OF INVADJ AN ADJTIME
        MAKIN R BIGGR OF MAKIN AN 0.0
        SHIPPIN R QUOSHUNT OF WIP AN PRODTIME
        
        BTW PLOT
        VISIBLE SMOOSH TIME " " MAKIN MKAY
        BTW PRODUKT WITH SCALE FACTOR FOR SIZING
        I IZ PLOTTIN YR PRODUKT OF MAKIN AN 4.0 AN YR "+" MKAY
                
        BTW INTEGRATE STOCKS
        
        TIME R I IZ INTEGRATIN YR TIME AN YR 1.0 AN YR TIMESTEP MKAY
        INV R I IZ INTEGRATIN YR INV AN YR DIFF OF SHIPPIN AN SELLIN AN YR TIMESTEP MKAY
        WIP R I IZ INTEGRATIN YR WIP AN YR DIFF OF MAKIN AN SHIPPIN AN YR TIMESTEP MKAY
        
        BTW CHECK STOPPING CONDISHUN
        BOTH SAEM TIME AN BIGGR OF TIME AN SUM OF ZEND AN TIMESTEP, O RLY?
            YA RLY, GTFO
        OIC
        
    IM OUTTA YR SIMLOOP
    
    
KTHXBYE

YOU CAN RUN IT IN THE TUTORIALSPOINT ONLINE INTERPRETER, OR GET JUSTIN MEZA’S DESKTOP LCI.

SD INVENTORY LOLCODE.TXT

$lci main.lo
HAI, JWF!
SHOWIN RESULTZ FOR PRODUKSHUN
0.00 5.00
                    +
1.00 5.00
                    +
2.00 5.93
                        +
3.00 6.64
                           +
4.00 7.34
                              +
5.00 8.00
                                 +
6.00 8.62
                                   +
7.00 9.22
                                     +
8.00 9.78
                                        +
9.00 10.31
                                          +
10.00 10.82
                                            +
11.00 11.30
                                              +
12.00 11.75
                                                +
13.00 12.18
                                                 +
14.00 12.46
                                                  +
15.00 12.30
                                                  +
16.00 12.03
                                                 +
17.00 11.68
                                               +
18.00 11.29
                                              +
19.00 10.89
                                            +
20.00 10.51
                                           +
21.00 10.17
                                         +
22.00 9.89
                                        +
23.00 9.66
                                       +
24.00 9.49
                                      +
25.00 9.39
                                      +
26.00 9.35
                                      +
27.00 9.35
                                      +
28.00 9.40
                                      +
29.00 9.47
                                      +
30.00 14.56
                                                           +
31.00 15.91
                                                                +
32.00 14.12
                                                         +
33.00 14.07
                                                         +
34.00 14.45
                                                          +
35.00 14.73
                                                           +
36.00 15.01
                                                             +
37.00 15.27
                                                              +
38.00 15.51
                                                               +
39.00 15.75
                                                                +
40.00 15.97

I THINK THIS SHOULD BE A PART OF EVERY SYSTEM THINKERZ LITTERBOX TOOLBOX.

Dynamics of Hoarding

“I’m not hoarding, I’m just stocking up before the hoarders get here.”
Behavioral causes of phantom ordering in supply chains
John D. Sterman
Gokhan Dogan

When suppliers are unable to fill orders, delivery delays increase and customers receive less than they desire. Customers often respond by seeking larger safety stocks (hoarding) and by ordering more than they need to meet demand (phantom ordering). Such actions cause still longer delivery times, creating positive feedbacks that intensify scarcity and destabilize supply chains. Hoarding and phantom ordering can be rational when customers compete for limited supply in the presence of uncertainty or capacity constraints. But they may also be behavioral and emotional responses to scarcity. To address this question we extend Croson et al.’s (2014) experimental study with the Beer Distribution Game. Hoarding and phantom ordering are never rational in the experiment because there is no horizontal competition, randomness, or capacity constraint; further, customer demand is constant and participants have common knowledge of that fact. Nevertheless 22% of participants place orders more than 25 times greater than the known, constant demand. We generalize the ordering heuristic used in prior research to include the possibility of endogenous hoarding and phantom ordering. Estimation results strongly support the hypothesis, with hoarding and phantom ordering particularly strong for the outliers who placed extremely large orders. We discuss psychiatric and neuroanatomical evidence showing that environmental stressors can trigger the impulse to hoard, overwhelming rational decision‐making. We speculate that stressors such as large orders, backlogs or late deliveries trigger hoarding and phantom ordering for some participants even though these behaviors are irrational. We discuss implications for supply chain design and behavioral operations research.

Seven Deadly Sins of SD Structure

Obey these simple rules to avoid garbage-in->garbage-out.

There’s a lot of art to modeling, and more generally to managing complex systems. But there’s also some craft to it: simple, mechanical steps that must be followed, almost without exception.  Woodworkers know that when you’re using a chisel or plane, you cut with the grain, not across it. Knowing that isn’t sufficient to make a nice-looking chair, but at least your funny-looking chair won’t have ugly tearout.

So what are the rules for classic System Dynamics? Here are a few:

  1. Unbalanced or missing units. It’s possible to build a correct model without units, but most people (including me) are unlikely to manage it. Even if the model is right in some sense, without units it’s still unintelligible to others.
  2. No FONFOO. Every physical stock needs First-Order Negative Feedback On the Outflows. This means the equations ensure that the outflow goes to 0 as the stock goes to 0 – not after a while, but now and forever. This ensures conservation of stuff: no inventory -> no sales. Nonphysical stocks often require this treatment as well, unless negative values are permitted by definition.
  3. Embedded parameters. A colleague just found an equation in a spreadsheet model reading something like =A2*EXP(-C4/C1) + 4. The “4” was just an arbitrary fudge factor on the answer. This should never happen; anything more complex than the 1 in 1/x should always be exposed as a distinct, named variable with appropriate units.
    • Corollary: the embedded parameter often represents an implicit goal. For example, in inventory adjustment = (1000-inventory)/inventory adj time, the goal of 1000 units should be made explicit.
  4. Discrete time. Generally, your model should be independent of the TIME STEP and simulation method. Decision rules should integrate information smoothly, not at arbitrary point lags.
  5. Discrete logic. Sometimes I see equations that involve big cascades of logical statements: IF THEN ELSE( inventory < 100 :AND: price > 2, do x, IF THEN ELSE( inventory > 200 :AND: expected sales > inventory/desired coverage, do y, IF THEN ELSE( …  Constructions like this are hard to read and hard to debug, and they often fail important reality checks. They might be appropriate in tactical cases where reality has discernible, discrete rules. But they’re seldom helpful in strategic models involving the aggregate behavior of many agents or objects.
  6. Overuse of delays. Every feedback loop must include a stock. This is a consequence of “time is what keeps everything from happening at once.” If there’s no integration in a loop, then feedback would run infinitely fast. Sometimes, confronted with an apparently simultaneous loop, modelers just insert a SMOOTHI or similar function that contains a stock. This may not be good enough; the stock in the loop can’t be arbitrary; it has to have real meaning.
    • It’s also possible to commit the opposite sin: underuse of delays. Perceptions lag reality, and people often underestimate the extent to which this is true. Decision rules in your model should reflect this, but I think it’s more a matter of art than craft.
  7. Taking the cream out of the coffee. Suppose you have a stock of people, with a coflow of money used to keep track of the average wealth of people in the stock. It’s then tempting to handle a thought experiment like, “ok, what if all the rich people leave the country?” by siphoning off a greater-than-average share of the money alongside each departing person. This violates the assumption that a stock is the complete representation of system state. What if, for example, the rich people already left, so that the remainder are uniformly poor? If the distinction is important, you simply must disaggregate the people into classes.

Like all rules, these are made to be broken, but exceptions are rare, and require that you really know what you’re doing. They are important because they ensure compliance with Reality Checks that should remain inviolate for strong reasons. If your population model isn’t conserving people, you have a problem.

Incidentally, at least half of these are mentioned in Appendix O of Industrial Dynamics, “Beginners’ Difficulties.” However, these are not just tricks for beginners: everyone can benefit from keeping them in mind, just as professional pilots rely on checklists.

I’m eager to hear your thoughts in the comments. What rules did I miss?

See also:

How to critique a model (and build a model that withstands critique)

Towards Principles for Subscripting in Models

Dynamics of the last Twinkie

Misadventures with Little’s Law

* Update: edited slightly for parallelism of the headers.

The importance of FONFOO

Every physical stock needs First Order Negative Feedback On Outflows.

I’ve been approached several times recently with questions about stocks behaving badly. All involved a construction something like the following:

This is a simple inventory control system, in which I’ve short-circuited the production start feedback by making Starting exogenous and equal to the desired sales rate. Therefore, there are really only two interesting equations:

Shipping=desired sales rate
Units: widgets/Month

Completing=DELAY3( Starting, production time )
Units: widgets/Month

Notice that there’s a violation of standard practice here, in that there’s a flow-to-flow connection from Starting to Completing. This is due to the DELAY3 function, which is shorthand for an explicit 3rd-order delay:

The 3rd-order delay is often a realistic compromise between a 1st-order system, in which the first completions arrive too quickly after Starting, and a pipeline delay or conveyor, which has too little dispersion to represent an aggregation of many items. (See the Delay Sandbox and Erlang models for examples.)

So, how can we break this model?

I always like to start with some tests in Synthesim. A good one is to stress the system with a step in the desired sales rate, here from 100 to 120. You can immediately spot a problem:

Inventory goes negative, because Shipping proceeds, even when inventory is exhausted. That can’t happen in reality, but it happens here because Shipping is not a function of Inventory. There’s a simple fix:

Shipping=MIN(desired sales rate, Inventory/min shipping time)
Units: widgets/Month

Above, min shipping time is a time constant representing the minimum time needed to deplete inventory. It’s common to set min shipping time = TIME STEP in situations where you want to prevent negative inventory, and the precise dynamics of inventory exhaustion are not central to the model. (If it matters, see Dynamics of the Last Twinkie.)

This is FONFOO. The “first order negative feedback” refers to the balancing loop created by the Inventory/min shipping time term in the fixed equation:

The tricky thing about this situation is that if Starting had been endogenous, the negative inventory problem would have been much harder to spot. Here’s the same model with a simple decision rule for Starting that maintains Inventory and WIP and desired levels:

Now, a modest step in sales doesn’t cause negative inventory, as long as the production process can replenish it in time. It takes a huge step (from 100 to 400 widgets/month) to reveal the problem:

This means that experiments on a model as a whole may not reveal problems that lurk in the details of the model, unless they’re quite extreme. I recommend extreme tests, but prevention is more important. Simply make it a habit to implement FONFOO everywhere, and you won’t have problems. (Note that we could automate this in Vensim, but we don’t, because doing so can easily mask other formulation problems, fall short of the control that’s really needed, or impede situations in which nonphysical stocks are intentionally negative.)

Now let’s take a look at the 3rd-order production delay surrounding WIP. As presented above, it works fine – it’s mathematically equivalent to the explicit 3rd-order aging chain. However, there are consistency issues to be aware of. Consider the following augmentation of the structure, representing stock losses (the flow of Breaking) from WIP:

Completing=DELAY3( Starting, production time )
Units: widgets/Month
Breaking=DELAY3(Starting*loss fraction,production time)
Units: widgets/Month

Completing is still a delayed function of Starting. But Completing is not directly aware of WIP and therefore unaware of the consequences of Breaking. This is a violation of FONFOO because the DELAY3 function contains internal states that are independent of the WIP stock. Consider what happens if the loss fraction is nonzero. In equilibrium, the output of DELAY3 is equal to the inflow. So, the outflow from WIP would be Breaking+Completing, which equals Starting+Starting*loss fraction, which is of course greater than starting for any nonzero loss.

A step in the loss function from 0 to 0.2 causes WIP to go negative:

Again, the remedy is simple. In most cases, you can keep the DELAY function if you ensure that the inflows and outflows are conserved. For example, adding a term:

Completing=DELAY3( Starting*(1-loss fraction), production time )
 Units: widgets/Month
 Breaking=DELAY3(Starting*loss fraction,production time)
 Units: widgets/Month

In some situations, it may be desirable to switch to an explicit aging chain in order to handle an idiosyncratic distribution of losses across the WIP process, or other complexities. Often arrays are useful for such purposes.

You may encounter the DELAY1 function in similar circumstances. DELAY1 is just like DELAY3, except that it’s first order. So, the system:

inflow = 10 ~ widgets/month
stock = INTEG(inflow-outflow, inflow*tau) ~ widgets
outflow = stock/tau ~ widgets/month
tau = 6 ~ months

is identical to the system:

inflow = 10 ~ widgets/month
stock = INTEG(inflow-outflow, inflow*tau) ~ widgets
outflow = DELAY1(inflow,tau) ~ widgets/month
tau = 6 ~ months

In this case, there’s really no reason to use the DELAY1 – it just obfuscates the first-order stock dynamics. However, there’s still a potential pitfall, which also applies to DELAY3. The initialization is important. The DELAY functions generally initialize their internal stocks in equilibrium, as if the inflow had been at its initial level historically. Therefore the stock above needs to be initialized the same way, to inflow*tau. If you want to use some other value, like zero, you need to use DELAY3i (or its equivalent) to set the stock and delay function to a consistent set of assumptions.

In reviewing other models, you may also find hybrid approaches, like:

inflow = 10 ~ widgets/month
stock = INTEG(inflow-outflow, inflow*tau) ~ widgets
outflow = DELAY1(stock/tau,tau) ~ widgets/month
tau = 6 ~ months

This is another FONFOO violation. The outflow is indeed a function of the stock, which ensures that the outflow eventually goes to zero when the stock is exhausted. But this does not create a 1st-order negative feedback loop; the DELAY1 contains an additional stock. So, this is SONFOO (second order negative feedback on the outflow), which might be useful for creating an oscillator, but won’t solve your supply chain problems.

If you make FONFOO a habit, you’ll have one less thing to worry about when you start exploring the interesting, complex behaviors of your models.

Facing the Blank Sheet

Modeling projects usually start with the dreaded blank sheet of paper (or blank screen). How to get started? Just do it. Write stuff down, and see what organization emerges.

Here are some concrete approaches that I’ve often used:

  • Start with the question. Inventory is unstable? OK, put inventory on the diagram. It’s a stock, so what are the flows? Put them on the diagram. Are the inflows and outflows unstable, or just one? Follow the unstable direction….
  • Start with the data. We get this a lot in marketing science projects. There’s typically a big pile of Nielsen or IMS data on price, promotion and distribution. How does that drive sales? You can do a little data mining for insight, but typically the data describes less than half of what’s going on, so more importantly, what else drives sales? How do brand equity, supply chain performance, and other dynamics introduce feedback into the picture?
  • Start with a spreadsheet. There’s always a spreadsheet. It’s probably open loop and static, but it captures features that someone thought were important. Audit the spreadsheet to discover its structure, then make it dynamic.
  • Start with the goal. You want to maximize profit? Write down a P&L, then trace each item. Where does revenue come from? What drives costs? When you answer these questions, look for the key strategic stocks that govern the behavior – people, capital, perceptions, etc.
  • Start with the physics. What are the key stocks of scarce resources in the system? Equipment, people, money, knowledge? What makes them change, and where are the decisions?
  • Start with the stakeholders. What are the major constituencies in the problem domain. What do they want, and what stocks are they looking at to guide how they get it?

The key thing is to remember that modeling is an iterative process at every level. The data might be wrong. The equations will be wrong. The equations might be in the wrong structure. The structure might describe the wrong problem. This is normal. Don’t be afraid to back up and start over.

The blank sheet of paper

Confronting the dreaded blank canvas

6 more reasons to apply SD to medical research

@SDWisdom Ken Cooper lists 6 good reasons to apply System Dynamics to medical research. I think there are more if you broaden the definition of ‘medical’ :

7. Dose titration can be dynamically complex and subject to misperceptions of feedback; models make it easy.

8. Chronic autoimmune and mental health problems are embedded in a nest of feedback between the disease and the person’s environment.

9. ERs, hospitals and other delivery systems are loaded with delays, feedback and nonlinearity.

10. Smoking, diet, exercise, and other big health drivers are social phenomena.

11. Diet and exercise are entangled with other systems, like urban design and energy efficiency.

12. The health insurance system, especially in the US where it has evolved into a mess, can’t be redesigned without a systemic perspective.

4 Faces of Medical Modeling

I enjoyed the biomedical modeling plenary at #ISDC2019 more than most. I was struck by the continuum of behavior involved in the system:

  • True biomedical modeling is a bit funny, because it’s not typical System Dynamics, in the sense that it’s nonlinear dynamic simulation, but it’s not behavioral, so it’s missing one of the cornerstones of SD. Nevertheless, I think the way we think about complex systems is a useful complement to other approaches coming more from biology and mathematics (nonlinear dynamics).
  • Behavior enters one level “up”, in problems like Jim Rogers & Ed Gallaher’s work on dose titration in anemia. This is a classic case of smart people having trouble managing a system with fairly simple dynamics – essentially a single pipeline delay in the case of anemia. There may be many similar cases, where large performance improvements are available from simple models (but complicated people management).
  • Next, there are problems that combine behavioral dynamics and misperceptions of feedback with an underlying system that is also quite complex. Gizem Aktas’ work on stress and hormonal regulation is an example, as are diabetes and mental health models.
  • At the far end of the scale, there are health system models, like ReThink Health, which abstract away from the biomedical details of any particular disease. In its place, there’s an extremely complex network of human resources, incentives and decisions.

I think the opportunities are large in all of these areas. Once challenge for the field is that each requires a different interface to other researchers, health practitioners and managers. That’s a lot for relatively few modelers to manage. How can we team up to be more effective?

Closing loops – practicalities

Hybrid models are the solution to blending endogenous elegance with practicality.

My last post probably sounds like I disagree with Jack Homer’s recommendation to tolerate some exogenous drivers and consideration of policy feasibility. Actually I don’t. In fact, we at Ventana probably do more data-intensive SD than anyone. I build hybrid models all the time.

When philosophizing about the best way to change the world, it’s easy to lose sight of some practical considerations that influence choices:

  • Cost. It’s expensive to develop an elegant, endogenous theory for things like interest rates that you might normally think of as exogenous to a firm. On the other hand, it’s also expensive to collect and use data – often 1/3 of project cost in our experience.
  • Clarity. Exogenous variables complicate the analysis of a model, because you have driven behavior on top of the model’s endogenous dynamics. I think this makes it harder to understand the basic behavior, because you lose the insight you might gain from starting a model in equilibrium and perturbing it with policies.
  • Calibration. On the other hand, using exogenous drivers increases your ability to gain insight from comparison of model behavior to data. This is not a definitive test, but you can definitely use it to estimate uncertain parameters and weed out certain dumb ideas.
  • Client. You have to meet people where they are. If, historically, they think R^2 is the definitive measure of success, you’d better deliver. You can explain why that’s a bad metric and present a more endogenous view of the situation later, after you’ve established trust.

I think there’s no clear answer – the extent to which endogenous or exogenous elements are preferred has to be a situation-specific decision. In my own work, I often use a two-pronged approach, and two ways to structure that have emerged:

  • Build a single, large, calibrated model with some exogenous drivers. Build endogenous submodels or metamodels for equilibrium experiments and to  explain key features of the big model.
  • Build a single, elegant endogenous model, with few drivers. Use smaller exogenous models, or statistical and machine learning tools, to understand local features of the data, incorporating those insights into the endogenous model without using the data directly.