Demo models‎ > ‎MacroABM‎ > ‎

The Model class

In the JAS-mine architecture, agents are organized and managed by components called managers. There are three types of managers: Model, Collector and Observer.
  • The Model deals mainly with specification issues, creating objects, relations between objects, and defining the order of events that take place in the simulation.
  • The Collector collects the data and compute the statistics both for use by the simulation objects and for post-mortem analysis of the model outcome, after the simulation has completed.
  • The Observer allows the user to inspect the simulation in real time and monitor some pre-defined outcome variables as the simulation unfolds.
This three-layer methodological protocol allows for extensive re-use of code and facilitates model building, debugging and communication.  This is paradigm is discussed further in the JAS-mine online documentation, at Model-Collector-Observer, and the MacroABM Collector is described in Data Output, while the MacroABM Observer is introduced in Charts.
 
The Model class extends the AbstractSimulationManager class. This requires implementing the buildObjects() and the buildSchedule() methods, which are discussed below in section 2 and 3 respectively.

1 GUI Parameters



As we have seen in Parameters (and also discussed in the JAS-mine website documentation), the general rule is that parameters should not be hard-coded in the simulation. The only exception is with control parameters that can be changed from the GUI before the simulation starts or while the simulation is running in order to experiment with the model behavior in interactive mode. Control parameters are properties of a simulation, they are annotated with @GUIparameter and are automatically loaded into the JAS-mine GUI. In MacroABM there are several such parameters, as described in Box 1.  The user can change some of these parameters during the simulation, by adjusting the values show in the 'MacroModel's parameters' lists, and clicking on the 'Update parameters in the live simulation' button, which is the right-most button in the top panel of the JAS-mine GUI, as shown in Figure 1 below.  Note that not all GUI parameters can be updated during the simulation; parameters that are only used during the build or initialization phase, such as the number of CFirms and KFirms, cannot be changed as the value of these parameters are no longer in use during the simulation.  Examples of GUI parameters that can be updated during the simulation are the tax rate ('taxRate') and the interest rate ('interestRate').
Figure 1. Updating parameters by clicking on the 'Update parameters in the live simulation' button.
 
 
// Parameters changeable in the GUI

@GUIparameter(description = "Set the number of consumption-good firms")
Integer numberOfCFirms = 200;
   
@GUIparameter(description = "Set the number of capital-good firms")
Integer numberOfKFirms = 50;

@GUIparameter(description = "Set the length of the simulation")
Double endTime = 500.;

@GUIparameter(description = "Bank's reserve requirement rate")
Double reserveRequirementRate = 0.5;

@GUIparameter(description = "Loan-to-value ratio")
Double loanToValueRatio = 2.;

@GUIparameter(description = "Unemployment benefit defined as the proportion of market wage")
Double unemploymentBenefitShare = 0.4;

@GUIparameter(description = "Initial mark up rate for consumption-good firms")
Double markUpRate = 0.2;

@GUIparameter(description = "Tax rate on firms' profit")
Double taxRate = 0.1;

@GUIparameter(description = "Economy's interest rate")
Double interestRate = 0.025;

@GUIparameter(description = "Determines how firms consider debt repayment")
boolean myopicDebtRepayment = true;

@GUIparameter(description = "Mason (if set to true) or Lhuillier (if set to false) version for debt management")
boolean mason = true;

@GUIparameter(description = "Use a fixed random seed to start (pseudo) random number generator")
boolean fixRandomSeed = true;

@GUIparameter(description = "Seed of the (pseudo) random number generator if fixed")
Long seedIfFixed = 1L;

Box 1. MacroModel: GUI parameters, that are displayed in the GUI and can be adjusted by the user before building the model.  Some of them can also be updated during the simulation.

2 The buildObjects() method


The buildObjects() method contains the instructions to create all the agents and the objects that represent the virtual environment for model execution (Box 2). (The @Override annotation is used by the Java interpreter to ensure that the programmer is aware that the method declared is overriding the same method declared in the superclass). In MacroABM, this involves loading the parameters for the simulation; there are two potential initial parameter sets, depending on whether we require to estimate some of the parameters using a Markov Chain Monte Carlo technique. 

Following this, a check is made to see if the number of Consumption Good Firms ('CFirms') is a multiple of the number of Capital Good Firms ('KFirms'), so that there is exactly the same number of CFirm clients for each KFirm at the beginning of the simulation.  The firms of each goods type are generally identical at the start of the simulation, except for the random assignment of CFirms to KFirms. 

Next, the random seed is set based on that specified in the GUI parameter seedIfFixed.  The simulation will follow the same trajectory for the same random seed; this aids reproducibility.  In order to assess the stochastic variability of the model in specific initial states, the parameter set can be held constant while the random seed is varied.  The distribution of trajectories can then be plotted to understand how much uncertainty grows with simulation time, as discussed in Uncertainty analysis.

A group of static variables that remain constant throughout the simulation are then assigned values, based on the model's GUI parameters that may have been adjusted by the user via the GUI before the model was built.

After this, we reach the actual creation of the agents, which is done programmatically, first with the creation of Consumption Good Firms (CFirm objects), then with Capital Good Firms (KFirm objects).  After this, the matching between Consumption Good Firm clients and their Capital Good Firm suppliers takes place when the matching() method is called.  Following from this, the single Bank object is created.

After this, the Collector class method macroInitialization() is called, which assigns values to all the macro variables based on the parameters specified in the Parameters class or the GUI parameters.

Finally, the Consumption Good Firms initial state of demand, inventories and sales can be calculated based on the parameters of the model and variables that have thus far been calculated.

@Override
public void buildObjects() {

    if(!Parameters.ESTIMATION_OF_PARAMETERS)
        Parameters.initializationWithoutEstimation();
    else
        Parameters.initializationWithEstimation();
                
    if(numberOfCFirms % numberOfKFirms != 0){
        throw new IllegalArgumentException("ERROR: The number of consumption-good firms needs to be a multiple of the number of capital-good firms!  Please adjust the number of firms in order to satisfy this.");
    }
        
    // Set the seed of the Random Number Generating Function.
    if(fixRandomSeed)     //if fixed, the model will follow the same trajectory as other                                               //executions with the same random number seed.
        SimulationEngine.getRnd().setSeed(seedIfFixed);
        
    // Compute the static (constant) variables of the economyÃ’
    returnOnFirmsDeposits = interestRate * (1 - Parameters.getCoeffMarkDownOnDepositRate_Bank());
       
    interestRateOnDebt = interestRate * (1 + Parameters.getCoeffMarkUpOnInterestRate_Bank());

    returnOnBankDepositsAtCentralBank = interestRate * (1 - Parameters.getCoeffMarkDownOnDepositRateAtCentralBank_Bank());

    tax_rate = taxRate;
   
    laborSupply = Parameters.getLaborSupply();
        
    // --- Create the three main entities ---
    // Consumption-good firms
    cFirms = new LinkedList<CFirm>();
    for(int i = 0; i < numberOfCFirms; i++){
        CFirm newCFirm = new CFirm(true);
        cFirms.add(newCFirm);
    }
        
    // Capital-good firms
    kFirms = new LinkedList<KFirm>();
    for(int i = 0; i < numberOfKFirms; i++){
        KFirm newKFirm = new KFirm(true);
        kFirms.add(newKFirm);
    }
        
    // Match customers (consumption-good firms) and producers (capital-good firms)
    matching();
        
    // The bank
    bank = new Bank(true);
        
    // Initialize the macro variables
    collector.macroInitialization();
        
    /* Once all the previous variables are initialized, one can compute the initial sales, demand and               inventories of consumption-good firms
    */
    for(CFirm cFirm : cFirms){
        cFirm.demandInitialization();
    }                       
}

Box 2. The MacroModel.buildObjects() method.
 

3 Schedule 


The buildSchedule() method contains the plan of events for the simulation. Events are planned based on a discrete event simulation paradigm. This means that events can be scheduled dynamically at specific points in time. The frequency of repetition of an event can be specified in the case of repeated, periodic events. An event can be created and managed by the simulation engine (a system event e.g. terminating the simulation), it can be sent to all the components of a collection or list of agents or it can be sent to a specific object/instance. Events can be grouped together if they share the same schedule.
 
In MacroABM, all events are scheduled right from the beginning of the simulation (there is no dynamic scheduling), and occur once per time-step. They are grouped in an EventGroup called modelEventGroup, which is scheduled at every simulation period starting at 0 with the scheduleRepeat(Event event, double atTime, int withOrdering, double timeBetweenEvents) method: 
 
getEngine().getEventQueue().scheduleRepeat(modelEventGroup, 0., Parameters.MODEL_ORDERING, 1.); 
 
The withOrdering argument refers to the order in which events scheduled at the same time should be fired.  We use the convention that the model fires events before the observer and collector, and this ordering is specified by the MODEL_ORDERING, COLLECTOR_ORDERING and OBSERVER_ORDERING values in the Parameters class.

The events of MacroABM are typically directed to a collection of objects – KFirms, CFirms and Banks – and are inserted into an EventGroup with the instruction 
 
modelEventGroup.addCollectionEvent(Object object, [some action the object must perform]);
 
The actions to be performed can be specified in two ways. The simplest is to use Java reflection and simply specify the object’s method name to be invoked. For instance, asking all persons to perform the aging() method would require the instruction:
 
modelSchedule.addCollectionEvent(kFirms, KFirm.class, “machineProduction”); 
 
Java reflection, however, generally has a reputation for being quite slow. A better approach is to use the EventListener interface. When an object implements this interface, it must define an onEvent() method that will receive specific enumerations to be interpreted. We will describe how the KFirm class implements the onEvent() method in The Capital Good Firm class. For now, we simply note that by using the EventListener interface, the scheduling of the machineProduction() method becomes: 
 
modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.MachineProduction);
 
 
The order of the events in the simulation is specified as in Box 3 and has also been discussed in Timeline of Events (the Schedule).
 
 
        

 @Override
 public void buildSchedule() {
 

        EventGroup modelEventGroup = new EventGroup();
        
        /* Exit & entry: c-firms with too-low market share & / or negative liquid assets exit the market

            and are replaced by random copy of current incumbents. K-firms with no clients exit as well,

            and are replaced in the same fashion.  Note: entry & exit take place at the beginning of the                schedule so that exiting firms are recorded in the database before they die.   

        */
        modelEventGroup.addEvent(this, Processes.Exit);
        modelEventGroup.addEvent(this, Processes.Entry);
        
        /* Entities update their variables (e.g. set some of them equal to 0, or update their optimal                   prices, the credit supply)

        */

        modelEventGroup.addEvent(collector, MacroCollector.Processes.Update);
        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.Update);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.Update);
        modelEventGroup.addEvent(bank, Bank.Processes.Update);
        
        // Capital-good firms undertake their R&D activity. The collector updates the aggregate variables  
        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.Research);
        modelEventGroup.addEvent(collector, MacroCollector.Processes.TechFrontier);
        
        /* Capital-good firms send brochures to consumption-good firms to promote their machines.                         Consumption-good firms choose their supplier

        */

        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.Brochure);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.ChooseSupplier);
        
        /* Consumption-good firms form their initial plans given their demand expectation. Initially,                   financial constraints are not taken into account. Then, they consider their borrowing

           capacity, which may lead to downward adjustments (a priori adjustments)

        */
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.InitialExpenditures);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.APrioriAdjustments);
        
        /* The bank observes the aggregate credit demand. If it exceeds its credit supply, the economy is                credit rationed. The bank then sorts firms depending on their net worth to sale ratio.
            Once firms have received their loan and know their actual resources, they update their                    production and investment plans.  With their level of investment known, consumption-good firms             send their orders to their suppliers.

        */
        modelEventGroup.addEvent(bank, Bank.Processes.CreditAllocation);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.ExpendituresUpdate);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.InvestmentOrder);
        
        /* Hidden assumption in Dosi et al. (2013): the production function is of the Leontieff form. Thus,             if the (aggregate) labor demand exceeds the labor supply, firms scale down their production                plans.

        */
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.LaborDemand);
        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.LaborDemand);
        modelEventGroup.addEvent(this, Processes.LaborMarket);
        
        /* Capital market.
           1. Once the actual level of investment is determined (i.e. the level of investment that can be                 funded and produced), consumption-good firms pay their supplier and capital-good firms

             deliver the machines.
           2. Consumption-good firms scrap the machines they are able to replace.

        */
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.MachineScrapping);       
        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.MachineProduction);      
        
        /* Good market.
           1. Consumption-good firms undertake their production process
           2. The competitiveness of each firm is determined
           3. The consumption allocation starts, determining the demand & the sales of consumption-good                     firms        

        */
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.Production);        
        modelEventGroup.addEvent(this, Processes.GoodMarketCompetitiveness);
        modelEventGroup.addEvent(this, Processes.ConsumptionAllocation);
        
        /* Firms in both sectors compute their profit, their new stock of liquid assets, and pay their

            debt, if any. 

        */

        modelEventGroup.addCollectionEvent(kFirms, KFirm.Processes.Accounting);
        modelEventGroup.addCollectionEvent(cFirms, CFirm.Processes.Accounting);
        modelEventGroup.addEvent(bank, Bank.Processes.Accounting);
        
        /* Compute the macroeconomic variables. Store them in the MacroStatistics class to then export them             in the .csv file or output database

        */

        modelEventGroup.addEvent(collector, MacroCollector.Processes.AggregateComputation);
        modelEventGroup.addEvent(collector, MacroCollector.Processes.DumpInStatistics);


        // Schedule the Event Group       

        getEngine().getEventQueue().scheduleRepeat(modelEventGroup, 0., Parameters.MODEL_ORDERING, 1.);
        
        //For termination of simulation
        SystemEvent end = new SystemEvent(SimulationEngine.getInstance(), SystemEventType.End);
        getEngine().getEventQueue().scheduleOnce(end, endTime, Order.AFTER_ALL.getOrdering());
}

Box 3. The MacroModel.buildSchedule() method.
 
 
The exit of firms - those that are either bankrupt or have too small a market share - is performed centrally by the MacroModel class itself in the exit() method. Consequently, this event is a single target event, instead of a collection event, and is inserted into our EventGroup modelSchedule with the instruction 
 
modelEventGroup.addEvent(this, Processes.Exit);
 
Similarly, the entrance of new firms is also managed directly by the Model, with the method entry().

A final special 'System Event' is scheduled for the last time-step of the simulation with the method scheduleOnce(Event event, double atTime, int withOrdering): its target is the Model itself and brings the simulation to a halt: 
 
SystemEvent end = new SystemEvent(SimulationEngine.getInstance(), SystemEventType.End);

getEngine().getEventQueue().scheduleOnce(end, endTime, Order.AFTER_ALL.getOrdering());

4 The EventListener interface

 
Since the Model performs actions during the simulation, as with the KFirm, CFirm and Bank classes, it implements the EventListener interface. This requires first to enumerate all the actions that the Model is supposed to perform (this is done by defining the specific enum Processes), and then to specify the method onEvent() – see Box 4.
 
 
 
 public enum Processes {
        Exit,
        Entry,
        LaborMarket,
        GoodMarketCompetitiveness,
        ConsumptionAllocation;
 }

 public void onEvent(Enum<?> type) {
        switch ((Processes) type) {
        
        case Exit:
            exit();
            break;

        case Entry:
            entry();
            break;
            
        case LaborMarket:
            laborMarket();
            break;
            
        case GoodMarketCompetitiveness:
            marketCompetitiveness();
            break;
            
        case ConsumptionAllocation:
            consumptionAllocation();
            break;

        }
 }
 
Box 4. Implementation of the EventListener interface in MacroModel.
 
We discuss in more detail, the processes specified in the Model class below.  Note that all Model processes are scheduled by the Model itself, that is, the Model class adds these processes to the schedule of events in the Model's buildSchedule() method.

5 Exit


The Exit process is scheduled in the Model class as the first process of each time-step.  Based on the state of Firms at the end of the previous time-step, Firms fulfilling certain conditions are assigned to leave the simulation.  The Exit process calls the Model's exit() method.  This method iterates through the set of Consumption Good Firms (CFirms) and Capital Good Firms (KFirms), calling the firms' survival checking methods.  Both types of Firm will exit if their liquid assets (i.e. net worth or equity) is negative.  A Consumption Good Firm will also exit if their market share of consumption good sales is below a certain threshold.  When a capital-good firm loses all its consumption-good firm clients because the clients exit the economy, then we assume that this capital-good firm also leaves the market.  Thus, the consumption-good firm exit process needs to be coded before the capital-good firm exit one.

6 Entry


After the Model's Exit process has been fired, new firms enter the simulation to replace firms that have exited, as specified in the Model's entry() method.  These new firms are clones of firms randomly chosen from the remaining set of firms.  The new Capital Good Firms have a list of clients randomly chosen from the existing set of Consumption Good Firms (including possibly new entrants) to send brochures of their machines to.

7 Labor Market


The Labor market is updated in the Model's laborMarket() method, and is scheduled to occur after both types of Firms have calculated their desired production levels.  Labor supply is fixed and inelastic in the model, and the laborMarket() method aggregates all demand for labor from the KFirms and CFirms.  After first deducting labor demand for research and development from the labor supply, if there is more labor demand for producing goods than the remaining labor supply, labor rationing occurs, with demand for labor used in producing goods and production scaled down so that labor demand equals supply.  In this event, no unempoyment exists.  Also note that the KFirms still maintain their labor demand for research and development, which is not scaled down due to rationing.  Because the production of the KFirm is equal to the investments of its CFirm clients, the latter investments have to be reduced.  Because investments are rounded down to make an integer multiple of machine units, the sum of the new investments may be smaller than what can actually be produced by the KFirms, i.e. there are "leftovers".  The way we handle this situation, which is not specified in the Dosi papers, is for each KFirm to ranks its clients from the biggest one to the smallest one in terms of their machine orders, and distributes those leftover allocations until they become nil.

8 Goods Market Competitiveness


The Model's marketCompetitiveness() method is called by the GoodsMarketCompetitiveness process, which is scheduled near the end of the time-step, after production, but before the ConsumptionAllocation process (see 3.8 below), and before the Firm and Bank accounts have been updated, along with the aggregate statistics of the model.  The marketCompetitiveness() method calculates the CFirms' individual competitiveness (based on the price of the goods that they sell and their unfilled demand) and the mean competitiveness of CFirm sector.  The market share of each CFirm is then updated based on their previous market share and their current competitiveness, and CFirms who have a market share below a certain threshold are declared to leave exit the simulation at the next time the Model.Exit process is fired (at the beginning of the next time-step).  The exiting CFirms financial variables are considered, with their liquid assets used to pay of any debt that they owe to the Bank.  Any debt that the CFirm cannot pay-off in this way is labelled as 'bad debt' and recorded by both the CFirm and aggregated to the Bank's total stock of bad debt.

9 Consumption Allocation


After the GoodsMarketCompetitiveness process, the ConsumptionAllocation process is fired.  This method computes aggregate consumption, price indices (both consumer price index, CPI, relevant for the Consumption Good Firms, and producer price index, PPI, relevant for Capital Good Firms), and calculates the real consumption level.  Consumption is then allocated to the CFirms based on their market share.  It might be that once all CFirms receive their corresponding demand (real consumption * market share), the stock of consumption is still positive, while some firms still have some goods in their stocks. This is why we iterate over CFirms until either real consumption or the total stock of goods reach zero.  Note that the unfilled demand of a CFirm, as used to measure the CFirm's competitiveness, is determined only in the first iteration.




Previous: Parameters        Next: The Capital Good Firm class