Pages

Sunday, December 10, 2017

Equations In Stock-Flow Consistent Models

I had some communications with a reader Adam K. who is doing some work on stock-flow consistent (SFC) models. He had some questions about the equations and variables in the Python sfc_models framework -- as described in my latest book.

One of the things I noticed late in the formatting stage of the book is that I did not give a detailed explanation of the algorithms that generate the equations. This was not entirely an oversight: I wanted the book to be survive updates to the code, and the equation generation algorithms are a target for a major refactoring. This article explains the current situation, and how it developed. The need for an easily extensible equation generation algorithm trumped the desire for formality. The structure of SFC models makes extremely formal procedures fairly brittle.

Refactoring?

The fact that I am not particularly concerned about the potential for a re-write of the equation generation code -- a core part of the library -- reflects the strength of the tools used to write the framework. The entire code base is wrapped in unit tests, which ensure that code always generated the same target results, regardless to changes elsewhere. (If we want to change the functioning of a particular function, we need to change its associated tests. The idea is that this will not have a ripple effect on other tests.)

In particular, there are end-to-end tests that validate that sfc_models generates the correct final output for a number of (simple) benchmark models from Godley and Lavoie's Monetary Economics. No matter how we generate the equations, the key output variables should line up against known results.

Therefore, we can rip apart the equation generation code, and know whether the new version is generating the correct output at every step of the way.

(More realistically, I would a build a new version of the code, and then develop it until it passes all of the existing tests.)

In the absence of these end-to-end tests, any changes to the equation generation code would effectively create an entirely new software package, and then it would take a lot of ad hoc tests to see whether it remains backwards compatible. The development time could be almost as long as the time as it took to build the existing package in the first place. (A lot of old school software projects were built in this fashion, and this explains the notorious inability of software to be delivered on time.)

Equations/Variables in sfc_models

The problem with SFC models is that they have way too many variables (and hence, equations defining them).

Take the classic model SIM, which I have written about extensively, which is the simplest recognisable economic model. The usual sfc_models implementation currently has 32 variables, although that includes some purely decorative variables (two time axis variables, fiscal balance, etc.).

However, if we treated the system using standard systems theory, with the input variable being the exogenous government consumption variable, the system collapses to a linear state-space system with a single state variable (the previous period money balance held by the household sector). That is, given the inherited money balance, and the current period level of government consumption, we can calculate every other variable (including the end-of-period money balance, which goes into the next period's calculation).

Based on some analysis I have seen, this leads some people with physical sciences or engineering into a trap. They do what they always do: strip down the mathematical system to its simplest state-space form, and view that as "the model." This ignores a critical problem with state space models: they are a completely non-robust way of looking at system dynamics.

Any deviation of any defining equation will inject new dynamics into the system, presumably adding new state variables. The dimensionality of the state space changes, and there is no way to compare the original dynamics to the new one.

Defining the system as circuit elements and plopping the system into a circuit CAD package is not much of a help, even if the CAD package can do frequency domain analysis. The system will still collapse to the same state-space model, and almost all of the dynamics implied by the equations were thrown out.

In summary, we are stuck with having to keep all of the equations if we want to do the analysis properly. The fact that the equations collapse to a state space representation with a single state variable in the baseline model is very useful if you insist on doing equations by hand, as Godley and Lavoie did in the textbook. However, this hand-derived collapsed model may bear no resemblance to the model which results from the slightest tweak to the model. (Doing equations by hand is an old school academic thing across disciplines. The electrical engineering curriculum caught up to the existence of digital computers a couple of decades ago, and they now tend to only inflict that on undergraduates in their first couple of years. If all you have are analog computers and slide rules -- a form of analog computer -- you have to do equations by hand.)

How Does sfc_models Build Equations?

Once you realise that you have to set up all the equations, SFC models start to look ugly. The number of equations ends up being very large. Given my aversion to hand calculations, I needed a system to generate the equations. Hence, sfc_models was born.

For my purposes, I needed a framework where I could easily change the model structure. One strategy would be to write down an insanely complex model, and then look at what happens when we make simplifying assumptions. Since that is an extremely brittle strategy, I opted for a framework where we can switch around the sectors within a country, and where we can drop in other countries if desired. Such a framework could easily be extended towards a "build a model" graphical user interface.

If I were stuck with the old school computer languages I learned when I was younger, such a framework would have been implemented with a giant heap of spaghetti code -- "if this sector is in the model, do this..., else ...". Such an implementation would work for the simplest models, but the code complexity would explode as new types of sector behaviour would be added. The system would inexorably march towards uselessness.

Using object oriented techniques, we can isolate the implementation of each sector, greatly simplifying code structure. New sectors can use existing sectors as initial templates, and changes to one are naturally isolated from others. This makes the project feasible.

The next issue is: where does the equation generation logic reside?
  1. One strategy is to centralise the equation generation structure. Each sector makes its contribution to the structure, but there is one block of code setting the rules.
  2. The next is to decentralise the equation generation. There is a central container for all equations, but each sector does its contribution to the list of equations without worrying about rules.
Since I had a hard time discerning the formal principles for equation generation, I opted for the decentralised approach. This is why there is no formal structure for equation generation; from a casual glance at the Model object code, the equation generation algotrithm looks like a free-for-all.

However, such a description is slightly misleading: there are some basic principles in play. Most recognisable economic sectors -- households, businesses, governmental entities -- define their own behaviour using variables that are internal to that sector. For example, a household sector typically has a consumption function that depends upon its own income, and its own stock of financial assets. Occasionally, the sector has to look up variables from elsewhere: perhaps it needs the rate of interest to determine its portfolio allocation decision.

Other "sectors" handle the interaction between the economic sector. (They are defined as "Sector" objects in the code, and at a high level are indistinguishable from economic sectors, but they are not sectors of the economy as recognised by economists.) The most important of these are Market objects: they align supply and demand in each market, and stitch together the cash and commodity flows between sectors. This includes the labour market; it matches up the supply and demand for labour (of a given type, a multi-sector labour market can be implemented), and it ensures that businesses pay the wages, and the household sector gets the wages added to their pre-tax income. The other major object to handle interactions is the TaxFlow object, which manages the taxation rules set by the government.

This creates general principles of how sectors are supposed to generate equations. The sectors packaged with sfc_models obey these principles, and act as a template for user extensions. However, users are free to plop in equations as they wish in order to make extensions; they obviously do so at their own risk. This flexibility is useful: it is a lot easier to make a quick test of something by sneaking in a few arbitrary equations than it is to build new sectors. There is no point spending a lot of time building a new sector model if the equations will not work, so doing feasibility analysis is a useful first step.

However, these are only software engineering principles, there is no formal structure to the equations. Since I hold myself out as a very serious formal mathematician, what's up with that?

Classification of Variables

One obvious classification is that we should divide variables into state variables versus other variables that can be inferred from the state variables and inputs. In my discussion with Adam K., he argued that if we are looking at flows and stocks, only stock variables should be state variables; we can infer flows from stocks. (Other variables, such as expectational variables and prices, may become state variables.) Unfortunately, this might be true for some models, but not all.

For example, the amount of financial savings in the period -- a flow -- is equal to the change of the stock of financial assets. We could infer the savings in each period by just looking at the change in the stock, and drop the savings from our state space representation. However, this would not work if for some reason we wanted to work with the previous period's saving; we would need to create a new variable that stores that value for the current period's calculation. (I cannot think of an obvious use for that information, but it is more obvious for some flow variables, such as income.)

Since we do not know in advance which flow variables we need to reclassify as state variables, we do not gain a lot by treating them differently. In fact, the framework makes no formal distinction between stock and flow variables, since that distinction does not have any operative effect on the solution to the model. (Users could add a decoration to distinguish the variables, but that would only be useful for making a prettier output.) From a systems engineering perspective, my initial instinct would be that we have to strictly divide state variables from other variables, However, as I worked with these models, I came to the view that we need to be flexible when working with the model. Once a particular model is frozen mathematically, we can be more formal, but most of the code has to work with the model when the equation structure is still largely arbitrary.

For example, a sector may need to use its after-tax income as a variable in various equations (e.g., consumption function). However, it does not know the exact form of the equation that determines its after-tax income until the model is finalised. For example, it could be in a model without income taxes, and so there is no tax term within the after-tax income equation. This uncertainty is handled in the present framework by the sector just declaring the existence of an after-tax income variable and then using that variable within equations; it lets the framework fill in the missing equation.

In fact, we cannot normally distinguish between "inputs" and "state variables" in these models. We can change a parameter value from being fixed for all time to having a shock applied at some time point: we have promoted the variable from being a (constant) endogenous variable to being an exogenous input! Such a change completely alters the state space representation of the model.

The only formal distinction between variables (other than exogenous/endogenous) within the framework is that a heuristic is used to prune "decorative" variables at each time step before solving the system equation (but after the model structure is frozen). For example, there are a lot variables that are of accounting interest -- such as the fiscal deficit -- that may have no effect on behaviour on a particular model implementation. Such variables are pruned. However, if we want to model the effect of dimwitted fiscal hawks, we would have behaviour affected by the level of the fiscal deficit, and so we cannot drop the variable from the system before the solution step.

The financial interactions between sectors -- mediated by the net holdings of financial assets (F) -- is handled almost entirely by the Market and TaxFlow sectors. (Interactions between sectors is what generates monetary inflows and outflows.) These equations largely cover most of the discussion of stock and flow variables. We could break out these variables as being special, but that would be largely decorative, and not help the model solution.

Behavioural versus Non-Behavioural

If I were to attack the formalisation of the structure, I would classify variables as either behavioural or non-behavioural. Non-behavioural equations would include the accounting identity and stock-flow relationships that SFC modellers love writing about, but also things like production functions and even the definition of the time axis.

The behavioural variables are the more interesting. In most cases, we would create new sectors by adding new behavioural rules; the non-behavioural relationships remain the same. For example, we plop in a new consumption function into the household sector, and see what happens.

To be fair, this is pretty close to the existing behaviour. All you need to do to build a new Sector is to sub-class it, and then change out the behavioural equations you wish. The new sector will inherit all of the non-behavioural relationships from its parent implementation. As such, a formal re-definition might be of mathematical interest, but would have almost no effect on workflow.

A Note on the Solver

Following the suggestion of Adam K., I may look into adding in the MINPACK solver from the scipy Python package. The user would have the option of switching over to its use, assuming that scipy is installed. I will probably leave in my clunky iterative solver in the package so that the user can run basic examples without any external dependencies. (The advantage of the current setup for me is that I know how to use it to detect badly-posed equations; I would need to add debugging hooks for the new solver.)

(c) Brian Romanchuk 2017

5 comments:

  1. Brian,

    Than you very much for engaging in the discussion and providing valuable explanations.Unfortunately I haven't made one point clear enough, let me re-state the argument again. I haven't said that only stocks are state variables. This is the reason my continuous-time implementation didn't work for 3 months. Stocks and expectational variables are state variables of a model. If we don't have expectational state variables we can't properly implement consumption from the current income in a continuous-time implementation of the same model. I am not arguing we have to use continuous time, discrete time is perfectly good for a computer and for me but this is a sanity check. A simple way to implement adaptive expectations is to use a 1st order integrator which integrates error signal between the current and expected values. We have a hidden stock variable there (the result of the integration). This is less obvious in a discrete-time model as past flows can appear on the right-hand side of equations. In this case these are delayed values - but previous values do not make sense in a continuous-time abstraction and we end up with the simultaneous determination problem. Flows "proper" can be evaluated from stocks and expectational variables at any point of time. Regarding the argument that "we could infer the savings in each period by just looking at the change in the stock, and drop the savings from our state space representation" it is rather the other way around. In a simple case current saving (a flow) is the difference between disposable income (a flow) and consumption (another flow). Disposable income is a sum of wages and dividends, which are determined by stocks and expectations. Consumption is determined by the expectations about the current disposable income (which hasn't been realised yet - it will be realised "in the next period" in period analysis) and the stock of wealth. If we convert a model to continuous time domain we instantly see that we cannot use "previous period" flows as these are equal (on limit) to current flows

    lim flow(t-delta t) = flow(t) for lim delta t = 0
    if flow(t) a continuous function at t

    in a discrete time model we usually have delta t = 1 year

    so flow variables should not be treated as state variables because the behaviour of the model depends on delta t. We can either use lagged expectations or time-delayed value of the flow which can be approximated with a high-order low pass filter.

    I promise to contribute more when I clear the backlog...

    I see a lot of value in the overall idea of the package and like the idea of test-driven development.

    Regards,
    Adam K

    ReplyDelete
    Replies
    1. Looking at what I wrote, I should have reworded my description; I was only referring to stocks and flows only; there could be other state variables. The problem is that we do not know when flows will be promoted to stocks, it may only happen at the final stage of model building,

      As for the discrete time behaviour changing, as a result of the time step redefinition, I think that’s relatively realistic. It implies that we need to re-estimate model parameters if we changed frequency. In my view, that is probably the expected behaviour. You can go from a high frequency to a lower frequency relatively safely, but not the other way around - we are missing information. This would be distressing if we had accurate continuous time models, as in circuit theory, be we do no have them in economics.

      To a limited extent, the distinction between state variables and ones that can be computed is buried in my solver. I use heuristics to determine which variables do not affect the other variables (“decorative”), and they are only calculated once we have iterated to the final solution.

      Calculating most interesting flows is also very difficult. A sector does not actually know what the expression for its after-tax income is until the model is built, since that depends upon which other sectors are present. Therefore, it cannot buld any equations which depend upon after-tax income until the final model binding stage, which is going to make the sector definition nearly impossible to understand. Instead, the sector just declares an after-tax variable, and uses it within equations. It knows that the final expression for after-tax income will be filled in later.

      In other words, it would be very confusing for users to not have access to flow variables during equation declaration. It might be possible to segregate such variables later, but I am unsure about the feasibility of that. I would need to impose more structure on the equations; currently, they end up as arbitrary Python functions (embedded in a string).

      Delete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete

Note: Posts are manually moderated, with a varying delay. Some disappear.

The comment section here is largely dead. My Substack or Twitter are better places to have a conversation.

Given that this is largely a backup way to reach me, I am going to reject posts that annoy me. Please post lengthy essays elsewhere.