Organizing Code for a Complex Model¶
Note (2019-July): An updated, cleaner version of this in Julia
can be found in my Julia
github
repo (module modelLH
).
Goals¶
- Be able to solve many different versions of the model. Changes to the model, such as replacing a utility function, should have no effects on most of the code.
Setting Parameters¶
The problem: one typically solves several versions of a model. How to ensure that all the right fixed / calibrated parameters are set for each version?
Example: A model is solved with different production functions, say Cobb-Douglas or CES.
Here is a possible workflow:
- Define a
ModelObject
class. It has no properties. It has methods related to setting parameters that are common to all model objects.- Methods:
set_calibrated_params
: copy calibrated parameters from a structure into the object.set_default_params
: set all potentially calibrated parameters to their default values (for testing)
- Methods:
- Define a class for each abstract model object (naturally a sub-class of
ModelObject
). Example:UtilityFunction
. - Define a class for each specific model object. Example:
UtilityLog
(naturally a sub-class for the abstractUtilityFunction
).- Properties:
- all fixed and calibrated parameters
- switches that govern its behavior, such as: which parameters are calibrated?
- Methods:
calibrated_params
: returns default values and bounds for potentially calibrated parameters.set_switches
: sets switches that are not known when the object is constructed.- Methods that are specific to the object (e.g. compute marginal utility)
- Properties:
- Write a test function for each abstract object and run it on each specific object.
- All sub-classes of
UtilityFunction
must have the same methods, so they can be tested using the same code.
- All sub-classes of
- Define a class that defines the model.
- It instantiates one object for each model object.
- For each model version, some defaults are overridden. That either means: a different object is constructed (e.g., a different utility function); or switches in the object are set.
- Run
set_switches
on all objects (now that we know all model settings). At this point, the definition of the entire model is neatly contained in this object.
- At the start of the calibration loop:
4. Make a list of all calibrated parameters.
pvectorLH
does that.- Make a vector of guesses for the calibrated parameters (
pvectorLH.guess_make
). - Feed it to the optimizer.
- Make a vector of guesses for the calibrated parameters (
- In the calibration loop:
- Recover the parameters from the guesses (
pvectorLH.guess_extract
). - Copy the calibrated parameters into the model objects (
set_calibrated_params
). - Solve the model… 4. Note that nothing in the mode code depends on which objects make up the model (e.g. log or CES utility).
- Recover the parameters from the guesses (
It is now trivial to replace a model object, such as a utility function. Simply replace the line in the model definition that calls the constructor for UtilityLog
with, say, UtilityCES
. The code automatically updates which parameters must be calibrated and which methods are called when utility needs to be computed.
Alternative Approach¶
- Assign each model version a number.
- Write a file that sets “named” choices for each model version.
- E.g.:
cS.prodFct = 'CES'
- E.g.:
- For each named choice, define an
object
with the properties / methods:- provide a list of all potentially calibrated parameters (in my code, these would be
pstructLH
objects) - provide methods that are needed to solve the model (e.g.: return marginal products etc)
- provide a list of all potentially calibrated parameters (in my code, these would be
- Collect all potentially calibrated parameters into a
pvectorLH
object- This can make a vector of guesses that can be passed into optimization functions (such as
fmincon
) - It can take the
guess
vector and transform it into the parameters to be calibrated - For model objects that can be swapped out, simply question the object to get a list of all parameters it needs.
- This can make a vector of guesses that can be passed into optimization functions (such as
- In the model solution code, you don’t need to know what kind of production function is used. Key is that all of them have the same methods.
Now, to solve the model with a different production function, simply call the constructor for the appropriate object. The rest is automatic.