# ARKitecture of Econ-ARK#

This document guides you through the structure of Econ-ARK, and explains the main ingredients.
Note that it does not explain *how* to use it—for this, please follow the example notebooks, which you can find on the left.

Econ-ARK contains the three main repositories HARK, DemARK, and REMARK. On top of that, the website combines all of them. Hence, if you want to find a notebook search them in materials.

HARK: Includes the source code as well as some example notebooks.

DemARK: Here you can find

*Dem*onstrations of tools, AgentTypes, and ModelClasses.REMARK: Here you can find

*R*[eplications/eproductions] and*E*xplorations*M*ade using*ARK*.

Before describing each repository in detail, some preliminary remarks.

HARK is written in Python, an object-oriented programming (OOP) language that is quite popular in the scientific community. A significant reason for the adoption of Python is the ** numpy** and

**packages, which offer a wide array of mathematical and statistical functions and tools; HARK makes liberal use of these libraries. Python’s object-oriented nature allows models in HARK to be easily extended: new models can inherit functions and methods existing models, eliminating the need to reproduce or repurpose code.**

*scipy*We encourage HARK users to use the `conda`

or `mamba`

package managers, which include all commonly used mathematical and scientific Python packages.

For users unfamiliar with OOP, we strongly encourage you to review the background material on OOP provided by the good people at QuantEcon (for more on them, see below) at this link: Object Oriented Programming. Unlike non-OOP languages, OOP bundles together data and functions into *objects*. These can be accessed via: ** object_name.data** and

**, respectively. For organizational purposes, definitions of multiple objects are stored in**

*object_name.method_name()**modules*, which are simply files with a

**extension. Modules can be accessed in Python via:**

*.py*```
import module_name as import_name
```

This imports the module and gives it a local name of ** import_name**. We can access a function within this module by simply typing:

**. The following example will illustrate the usage of these commands.**

*import_name.function_name()***is the function object for calculating CRRA utility supplied by the**

*CRRAutility***module.**

*HARK.rewards***is called**

*CRRAutility**attributes*of the module

**. In order to calculate CRRA utility with a consumption of 1 and a coefficient of risk aversion of 2 we run:**

*HARK.rewards*```
from HARK.rewards import CRRAutility
CRRAutility(1, 2)
```

Python modules in HARK can generally be categorized into two types: tools and models. **Tool modules** contain functions and classes with general purpose tools that have no inherent ‘’economic content’’, but that can be used in many economic models as building blocks or utilities; they could plausibly be useful in non-economic settings. Tools might include functions for data analysis (e.g. calculating Lorenz shares from data, or constructing a non-parametric kernel regression), functions to create and manipulate discrete approximations to continuous distributions, or classes for constructing interpolated approximations to non-parametric functions. The most commonly used tool modules reside in HARK’s root directory and have names like ** HARK.distribution** and

**.**

*HARK.interpolation***Model modules** specify particular economic models, including classes to represent agents in the model (and the ‘’market structure’’ in which they interact) and functions for solving the ‘’one period problem’’ of those models. For example, **ConsIndShockModel.py** concerns consumption-saving models in which agents have CRRA utility over consumption and face idiosyncratic shocks to permanent and transitory income. The module includes classes for representing ‘’types’’ of consumers, along with functions for solving (several flavors of) the one period consumption-saving problem. Model modules generally have ** Model** in their name, and the classes for representing agents all have

**at the end of their name (as instances represent a collection of ex ante homogeneous agents who share common model and parameters– a “type”). For example,**

*Type***includes the class**

*HARK.ConsumptionSaving.ConsIndShockModel***.**

*IndShockConsumerType*## HARK#

After you installed or cloned the repository of HARK, you can explore the content of it. In the subfolder HARK, you can find a range of general purpose tools, as well as the next subfolder ConsumptionSaving which has AgentType subclasses and Market subclasses.

### General Purpose Tools#

HARK’s root directory contains several tool modules, each containing a variety of functions and classes that can be used in many economic models– or even for mathematical purposes that have nothing to do with economics. Some of the tool modules are very sparely populated, while others are quite large. These modules are continuously being developed and expanded, as there are many numeric tools that are well known and understood, and programming them is usually independent of other “moving parts” in HARK.

#### HARK.core#

A key goal of the project is to create modularity and interoperability between models, making them easy to combine, adapt, and extend. To this end, the ** HARK.core** module specifies a framework for economic models in HARK, creating a common structure for them on two levels that can be called ‘’microeconomic’’ and ‘’macroeconomic’’.

Microeconomic models in HARK use the ** AgentType** class to represent agents with an intertemporal optimization problem. Each of these models specifies a subclass of

**; an instance of the subclass represents agents who are**

*AgentType**ex-ante*homogeneous– they have common values for all parameters that describe the problem. For example,

**specifies the**

*ConsIndShockModel***class, which has methods specific to consumption-saving models with idiosyncratic shocks to income; an instance of the class might represent all consumers who have a CRRA of 3, discount factor of 0.98, etc. The**

*IndShockConsumerType***class has a**

*AgentType***method that acts as a ‘’universal microeconomic solver’’ for any properly formatted model, making it easier to set up a new model and to combine elements from different models; the solver is intended to encompass any model that can be framed as a sequence of one period problems. For a complete description, see section AgentType Class.**

*solve*Macroeconomic models in HARK use the ** Market** class to represent a market (or other aggregator) that combines the actions, states, and/or shocks (generally, outcomes) of individual agents in the model into aggregate outcomes that are ‘’passed back’’ to the agents. For example, the market in a consumption-saving model might combine the individual asset holdings of all agents in the market to generate aggregate capital in the economy, yielding the interest rate on assets (as the marginal product of capital); the individual agents then learn the aggregate capital level and interest rate, conditioning their next action on this information. Objects that microeconomic agents treat as exogenous when solving (or simulating) their model are thus endogenous at the macroeconomic level. Like

**, the**

*AgentType***class also has a**

*Market***method, which seeks out a dynamic general equilibrium: a ‘’rule’’ governing the dynamic evolution of macroeconomic objects such that if agents believe this rule and act accordingly, then their collective actions generate a sequence of macroeconomic outcomes that justify the belief in that rule. For a more complete description, see section Market Class.**

*solve*#### HARK.metric#

** HARK.metric** defines a superclass called

**that is used throughout HARK’s tools and models. When solving a dynamic microeconomic model with an infinite horizon (or searching for a dynamic general equilibrium), it is often required to consider whether two solutions are sufficiently close to each other to warrant stopping the process (i.e. approximate convergence). It is thus necessary to calculate the ‘’distance’’ between two solutions, so HARK specifies that classes should have a**

*MetricObject***method that takes a single input and returns a non-negative value representing the (generally unitless) distance between the object in question and the input to the method. As a convenient default,**

*distance***provides a ‘’universal distance metric’’ that should be useful in many contexts. (Roughly speaking, the universal distance metric is a recursive supnorm, returning the largest distance between two instances, among attributes named in**

*MetricObject***. Those attributes might be complex objects themselves rather than real numbers, generating a recursive call to the universal distance metric. ) When defining a new subclass of**

*distance_criteria***, the user simply defines the attribute**

*MetricObject***as a list of strings naming the attributes of the class that should be compared when calculating the distance between two instances of that class. For example, the class**

*distance_criteria***has**

*ConsumerSolution***, indicating that only the consumption function attribute of the solution matters when comparing the distance between two instances of**

*distance_criteria = [‘cFunc’]***. See here for further documentation.**

*ConsumerSolution*#### HARK.utilities#

The ** HARK.utilities** module contains a variety of general purpose tools, including some data manipulation tools (e.g. for calculating an average of data conditional on being within a percentile range of different data), basic kernel regression tools, convenience functions for retrieving information about functions, and basic plotting tools using

**. See here for further documentation.**

*matplotlib.pyplot*#### HARK.distribution#

The ** HARK.distribution** module includes classes for representing continuous distributions in a relatively consistent framework. Critically for numeric purposes, it also has methods and functions for constructing discrete approximations to those distributions (e.g.

**to approximate a log-normal distribution) as well as manipulating these representations (e.g. appending one outcome to an existing distribution, or combining independent univariate distributions into one multivariate distribution). As a convention in HARK, continuous distributions are approximated as finite discrete distributions when solving models. This both simplifies solution methods (reducing numeric integrals to simple dot products) and allows users to easily test whether their chosen degree of discretization yields a sufficient approximation to the full distribution. See here for further documentation.**

*approx_lognormal()*#### HARK.interpolation#

The ** HARK.interpolation** module defines classes for representing interpolated function approximations. Interpolation methods in HARK all inherit from a superclass such as

**or**

*HARKinterpolator1D***, wrapper classes that ensures interoperability across interpolation methods. For example,**

*HARKinterpolator2D***specifies the methods**

*HARKinterpolator1D***_**and

**call**_**to accept an arbitrary array as an input and return an identically shaped array with the interpolated function evaluated at the values in the array or its first derivative, respectively. However, these methods do little on their own, merely reshaping arrays and referring to the**

*derivative***and**

*_evaluate***methods, which are**

*_der**not actually defined in*

**. Each subclass of**

*HARKinterpolator1D***specifies their own implementation of**

*HARKinterpolator1D***and**

*_evaluate***particular to that interpolation method, accepting and returning only 1D arrays. In this way, subclasses of**

*_der***are easily interchangeable with each other, as all methods that the user interacts with are identical, varying only by ‘’internal’’ methods.**

*HARKinterpolator1D*When evaluating a stopping criterion for an infinite horizon problem, it is often necessary to know the ‘’distance’’ between functions generated by successive iterations of a solution procedure. To this end, each interpolator class in HARK must define a ** distance** method that takes as an input another instance of the same class and returns a non-negative real number representing the ‘’distance’’ between the two. As each of the

**classes inherits from**

*HARKinterpolatorXD***, all interpolator classes have the default ‘’universal’’ distance method; the user must simply list the names of the relevant attributes in the attribute**

*MetricObject***of the class.**

*distance_criteria*Interpolation methods currently implemented in HARK include (multi)linear interpolation up to 4D, 1D cubic spline interpolation, (multi)linear interpolation over 1D interpolations (up to 4D total), (multi)linear interpolation over 2D interpolations (up to 4D total), linear interpolation over 3D interpolations, 2D curvilinear interpolation over irregular grids, interpolors for representing functions whose domain lower bound in one dimension depends on the other domain values, and 1D lower/upper envelope interpolators. See here for further documentation.

#### HARK.estimation#

Functions for optimizing an objective function for the purposes of estimating a model can be found in ** HARK.estimation**. As of this writing, the implementation includes only minimization by the Nelder-Mead simplex method, minimization by a derivative-free Powell method variant, and two small tools for resampling data (i.e. for a bootstrap); the minimizers are merely convenience wrappers (with result reporting) for optimizers included in

**. The module also has functions for a parallel implementation of the Nelder-Mead simplex algorithm, as described in Wiswall and Lee (2011). Future functionality will include more robust global search methods, including genetic algorithms, simulated annealing, and differential evolution. See here for full documentation.**

*scipy.optimize*#### HARK.parallel#

By default, processes in Python are single-threaded, using only a single CPU core. The ** HARK.parallel** module provides basic tools for using multiple CPU cores simultaneously, with minimal effort. In particular, it provides the function

**, which takes two arguments: a list of**

*multi_thread_commands***s and a list of commands as strings; each command should be a method of the**

*AgentType***s. The function simply distributes the**

*AgentType***s across threads on different cores and executes each command in order, returning no output (the**

*AgentType***s themselves are changed by running the commands). Equivalent results would be achieved by simply looping over each type and running each method in the list. Indeed,**

*AgentType***also has a function called**

*HARK.parallel***that does just that, with identical syntax to**

*multi_thread_commands_fake***. Multithreading in HARK can thus be easily turned on and off. See here for full documentation.**

*multi_thread*\commands_#### HARK.rewards#

The ** HARK.rewards** module has a variety of functions and classes for representing commonly used utility (or reward) functions, along with their derivatives and inverses.

### AgentType Class#

The core of our microeconomic dynamic optimization framework is a flexible object-oriented representation of economic agents. The ** HARK.core** module defines a superclass called

**; each model defines a subclass of**

*AgentType***, specifying additional model-specific features and methods while inheriting the methods of the superclass. Most importantly, the method**

*AgentType***acts as a ‘’universal solver’’ applicable to any (properly formatted) discrete time model. This section describes the format of an instance of**

*solve***as it defines a dynamic microeconomic problem. Note that each instance of**

*AgentType***represents an**

*AgentType**ex-ante*heterogeneous ‘’type’’ of agent;

*ex-post*heterogeneity is achieved by simulating many agents of the same type, each of whom receives a unique sequence of shocks.

#### Attributes of an AgentType#

A discrete time model in our framework is characterized by a sequence of ‘’periods’’ that the agent will experience. A well-formed instance of ** AgentType** includes the following attributes:

: A function representing the solution method for a single period of the agent’s problem. The inputs passed to a*solve_one_period*function include all data that characterize the agent’s problem in that period, including the solution to the subsequent period’s problem (designated as*solveOnePeriod*). The output of these functions is a single*solution_next*object, which can be passed to the solver for the previous period.*Solution*: A list of strings containing all of the variable names that are passed to at least one function in*time_inv*but do*solveOnePeriod**not*vary across periods. Each of these variables resides in a correspondingly named attribute of theinstance.*AgentType*: A list of strings naming the attributes of this instance that vary across periods. Each of these attributes is a list of period-specific values, which should be of the same length.*time_vary*: An object representing the solution to the ‘’terminal’’ period of the model. This might represent a known trivial solution that does not require numeric methods, the solution to some previously solved ‘’next phase’’ of the model, a scrap value function, or an initial guess of the solution to an infinite horizon model.*solution_terminal*: A Boolean flag indicating that*pseudo_terminal*is not a proper terminal period solution (rather an initial guess, ‘’next phase’’ solution, or scrap value) and should not be reported as part of the model’s solution.*solution_terminal*: A non-negative integer indicating the number of times the agent will experience the sequence of periods in the problem. For example,*cycles*means that the sequence of periods is analogous to a lifecycle model, experienced once from beginning to end. An infinite horizon problem in which the sequence of periods repeats indefinitely is indicated with*cycles = 1*. For any*cycles = 0*, the agent experiences the sequence N times, with the first period in the sequence following the last; this structure is uncommon, and almost all applications with use a lifecycle or infinite horizon format.*cycles > 1*: A positive real number indicating convergence tolerance, representing the maximum acceptable ‘’distance’’ between successive cycle solutions in an infinite horizon model; it is irrelevant when*tolerance*. As the distance metric on the space of solutions is model-specific, the value of*cycles > 0*is generally dimensionless.*tolerance*

An instance of ** AgentType** also has the attributes named in

**and**

*time_vary***, and may have other attributes that are not included in either (e.g. values not used in the model solution, but instead to construct objects used in the solution). Note that**

*time_inv***may include attributes that are never used by a function in**

*time_vary***. Most saliently, the attribute**

*solveOnePeriod***is time-varying but is not used to solve individual periods.**

*solution*#### A Universal Solver#

When an instance of ** AgentType** invokes its

**method, the solution to the agent’s problem is stored in the attribute**

*solve***. The solution is computed by recursively solving the sequence of periods defined by the variables listed in**

*solution***and**

*time_vary***using the functions in**

*time_inv***. The time-varying inputs are updated each period, including the successive period’s solution as**

*solve_one_period***; the same values of time invariant inputs in**

*solution_next***are passed to the solver in every period. The first call to**

*time_inv***uses**

*solve_one_period***as**

*solution_terminal***. In an infinite horizon problem (**

*solution_next***), the sequence of periods is solved until the solutions of successive cycles have a ‘’distance’’ of less than**

*cycles*=0**. Usually, the “sequence” of periods in such models is just**

*tolerance**one*period long.

The output from a function in ** solve_one_period** is an instance of a model-specific solution class. The attributes of a solution to one period of a problem might include behavioral functions, (marginal) value functions, and other variables characterizing the result. Each solution class must have a method called

**, which returns the ‘’distance’’ between itself and another instance of the same solution class, so as to define convergence as a stopping criterion; for many models, this will be the ‘’distance’’ between a policy or value function in the solutions. If the solution class is defined as a subclass of**

*distance()***, it automatically inherits the default**

*MetricObject***method, so that the user must only list the relevant object attributes in**

*distance***.**

*distance_criteria*The ** AgentType** also has methods named

**and**

*pre_solve***, both of which take no arguments and do absolutely nothing. A subclass of**

*post_solve***can overwrite these blank methods with its own model specific methods.**

*AgentType***is automatically called near the beginning of the**

*pre_solve***method, before solving the sequence of periods. It is used for specifying tasks that should be done before solving the sequence of periods, such as pre-constructing some objects repeatedly used by the solution method or finding an analytical terminal period solution. For example, the**

*solve***class in**

*IndShockConsumerType***has a**

*ConsIndShockModel***method that calls its**

*pre_solve***method to ensure that**

*update_solution_terminal***is consistent with the model parameters. The**

*solution_terminal***method is called shortly after the sequence of periods is fully solved; it can be used for ‘’post-processing’’ of the solution or performing a step that is only useful after solution convergence. For example, the**

*post_solve***in**

*TractableConsumerType***has a**

*TractableBufferStockModel***method that constructs an interpolated consumption function from the list of stable arm points found during solution.**

*post_solve*Our universal solver is written in a very general way that should be applicable to any discrete time optimization problem– because Python is so flexible in defining objects, the time-varying inputs for each period can take any form. Indeed, the solver does no ‘’real work’’ itself, but merely provides a structure for describing models in the HARK framework, allowing interoperability among current and future modules.

The base ** AgentType** is sparsely defined, as most ‘’real’’ methods will be application-specific. One method of note, however, is

**, which simply resets the**

*reset_rng***’s random number generator (as the attribute**

*AgentType***) using the value in the attribute**

*RNG***. (Every instance of**

*seed***is created with a random number generator as an instance of the class**

*AgentType***, with a default**

*numpy.random.RandomState***of zero.) This method is useful for (**

*seed**inter alia*) ensuring that the same underlying sequence of shocks is used for every simulation run when a model is solved or estimated.

### Market Class#

The modeling framework of ** AgentType** is deemed ‘’microeconomic’’ because it pertains only to the dynamic optimization problem of agents, treating all inputs of the problem as exogenously fixed. In what we label as ‘’macroeconomic’’ models, some of the inputs for the microeconomic models are endogenously determined by the collective states and controls of agents in the model. In a dynamic general equilibrium, there must be consistency between agents’ beliefs about these macroeconomic objects, their individual behavior, and the realizations of the macroeconomic objects that result from individual choices.

The ** Market** class in

**provides a framework for such macroeconomic models, with a**

*HARK.core***method that searches for a dynamic general equilibrium. An instance of**

*solve***includes a list of**

*Market***s that compose the economy, a method for transforming microeconomic outcomes (states, controls, and/or shocks) into macroeconomic outcomes, and a method for interpreting a history or sequence of macroeconomic outcomes into a new ‘’dynamic rule’’ for agents to believe. Agents treat the dynamic rule as an input to their microeconomic problem, conditioning their optimal policy functions on it. A dynamic general equilibrium is a fixed point dynamic rule: when agents act optimally while believing the equilibrium rule, their individual actions generate a macroeconomic history consistent with the equilibrium rule.**

*AgentType*#### Down on the Farm#

The ** Market** class uses a farming metaphor to conceptualize the process for generating a history of macroeconomic outcomes in a model. Suppose all

**in the economy believe in some dynamic rule (i.e. the rule is stored as attributes of each**

*AgentTypes***, which directly or indirectly enters their dynamic optimization problem), and that they have each found the solution to their microeconomic model using their**

*AgentType***method. Further, the macroeconomic and microeconomic states have been reset to some initial orientation.**

*solve*To generate a history of macroeconomic outcomes, the ** Market** repeatedly loops over the following steps a set number of times:

: Distribute the macroeconomic state variables to all*sow*s in the market.*AgentType*: Each*cultivate*executes their*AgentType*method, likely corresponding to simulating one period of the microeconomic model.*market_action*: Microeconomic outcomes are gathered from each*reap*in the market.*AgentType*: Data gathered by*mill*is processed into new macroeconomic states according to some ‘’aggregate market process’’.*reap*: Relevant macroeconomic states are added to a running history of outcomes.*store*

This procedure is conducted by the ** make_history** method of

**as a subroutine of its**

*Market***method. After making histories of the relevant macroeconomic variables, the market then executes its**

*solve***function with the macroeconomic history as inputs, generating a new dynamic rule to distribute to the ****

*calc_dynamics**AgentType***s in the market. The process then begins again, with the agents solving their updated microeconomic models given the new dynamic rule; the

**loop continues until the ‘’distance’’ between successive dynamic rules is sufficiently small.**

*solve*#### Attributes of a Market#

To specify a complete instance of ** Market**, the user should give it the following attributes:

: A list of ***agents**AgentType***s, representing the agents in the market. Each element inrepresents an*agents**ex-ante*heterogeneous type; each type could have many*ex-post*heterogeneous agents.: A list of strings naming variables that are output from the aggregate market process, representing the macroeconomic outcomes. These variables will be distributed to the*sow_vars*in the*agents*step.*sow*: A list of strings naming variables to be collected from the*reap_vars*in the*agents*step, to be used as inputs for the aggregate market process.*reap*: A list of strings naming variables used by the aggregate market process that*const_vars**do not*come from; they are constant or come from the*agents*itself.*Market*: A list of strings naming variables generated by the aggregate market process that should be tracked as a history, to be used when calculating a new dynamic rule. Usually a subset of*track_vars*.*sow_vars*: A list of strings naming the variables that constitute a dynamic rule. These will be stored as attributes of the*dyn_vars*whenever a new rule is calculated.*agents*: A function for the ‘’aggregate market process’’, transforming microeconomic outcomes into macroeconomic outcomes. Its inputs are named in*mill_rule*and*reap_vars*, and it returns a single object with attributes named in*const_vars*and/or*sow_vars*. Can be defined as a method of a subclass of*track_vars*.*Market*: A function that generates a new dynamic rule from a history of macroeconomic outcomes. Its inputs are named in*calc_dynamics*, and it returns a single object with attributes named in*track_vars*.*dyn_vars*: The number of times that the*act_T*method should execute the ‘’farming loop’’ when generating a new macroeconomic history.*make_history*: The minimum acceptable ‘’distance’’ between successive dynamic rules produced by*tolerance*to constitute a sufficiently converged solution.*calc_dynamics*

Further, each ** AgentType** in

**must have two methods not necessary for microeconomic models; neither takes any input (except**

*agents***):**

*self*: The microeconomic process to be run in the*market_action*step. Likely uses the new macroeconomic outcomes named in*cultivate*; should store new values of relevant microeconomic outcomes in the attributes (of*sow_vars*) named in*self*.*reap_vars*: Reset, initialize, or prepare for a new ‘’farming loop’’ to generate a macroeconomic history. Might reset its internal random number generator, set initial state variables, clear personal histories, etc.*reset*

When solving macroeconomic models in HARK, the user should also define classes to represent the output from the aggregate market process in ** mill_rule** and for the model-specific dynamic rule. The latter should have a

**method to test for solution convergence; if the class inherits from**

*distance***, the user need only list relevant attributes in**

*MetricObject***. For some purposes, it might be useful to specify a subclass of**

*distance_criteria***, defining**

*Market***and/or**

*millRule***as methods rather than functions.**

*calcDynamics*## DemARK#

If you want to get a feeling for how the code works and what you can do with it, check out the DemARK repository which contains many useful demonstrations of tools, AgentTypes, and ModelClasses.

If you want to run the notebooks on your own machine make sure to install the necessary packages described in the readme file. Afterwards you can dive in the notebook folder. Each example has a markdown (.md) version with explanatory notes. The notebook (.ipynb) describes the method and runs (part of the) code.

## REMARK#

HARK can be used to replicate papers as well. For this purpose the *R*[eplications/eproductions] and *E*xplorations *M*ade using *ARK* (REMARK) repository was created.

Each replication consists of a *metadata file* (.md) with an overview, a *notebook* which replicates the paper, and a *requirement.txt* file with the necessary packages to run the notebooks on your local mashine.