Run#

The Run submodule of polaris-studio is used to setup both individual and iterative runs of the core POLARIS simulation engine.

The philosophy used to design this module is to make simple model runs “easy” while allowing significant complexity to be added by the user to achieve their experimental objectives. Running the model can be as simple as three lines of code:

from polaris import Polaris

model = Polaris.from_dir("/folder/that/contains/our/model")
model.run(num_threads=4, do_abm_init=True, do_skim=True, num_abm_runs=1)

Important

This will use the model run parameters that are specified in /folder/that/contains/our/model/convergence_control.yaml with the overrides that are specified as keyword arguments to the run method. The run method accepts any keyword argument that corresponds to a parameter of the ConvergenceConfig class (see below).

The iterative process#

The main form of a convergence run is shown in the image below and described here:

  1. Determine which iterations will run

  2. Do any pre-loop setup

  3. For each iteration

    1. Do any pre-processing required for this iteration

    2. Build an iteration specific scenario.modified.json configuration (starting from the template)

    3. Use the POLARIS engine to do a full day simulation using that scenario.modified.json

    4. Copy back results from the simulation sub-directory to the model root directory

    5. Run any post-processing for this iteration. There are two sets of these:

      1. In-line processing (i.e. required prior to running subsequent iterations)

      2. Asynchronous processing which can be run in a background thread while the next iteration starts (e.g. zipping up outputs of previous iteration for archival)

  4. Do any post-loop cleanup

Convergence Flow

Model run configurations#

The model run configurations control the entire model run and are stored in the convergence_control.yaml file. There are over 40 parameters that can be set, but they all mainly have sensible default values such that a typical configuration used in practice will often look like the following:

do_abm_init: true
do_skim: true
num_abm_runs: 1
population_scale_factor: 0.25
num_threads: 12

When running the model, the user can see and modify the run configurations by calling the run_config attribute of the model object, as shown below.

from polaris import Polaris
model = Polaris.from_dir("/folder/that/contains/our/model")

my_model_configs = model.run_config

Detailed configuration for these configurations can be found below:

ConvergenceConfig

Configuration class for the POLARIS iterative convergence process

WorkplaceStabilizationConfig

Configuration class for the POLARIS workplace stabilization process.

CalibrationConfig

Configuration class for the POLARIS calibration procedure.

Customising the process#

The iterative process used by POLARIS is fully customizable using callback functions.

In the following example we demonstrate how non-default callbacks can be inserted to achieve specific modelling objectives.

from polaris.runs.convergence.convergence_callback_functions import default_end_of_loop_fn, default_start_of_loop_fn, do_nothing
from polaris.runs.convergence.scenario_mods import get_scenario_for_iteration

def scenario_json_fn(config, current_iteration):
    # A method that returns a base scenario json file and some modifications to make it appropriate for this iteration
    return get_scenario_for_iteration(config, current_iteration)

def my_custom_start_of_loop_fn(config, current_iteration, mods, scenario_file):
    # User code goes here -----------------------------------------
    logging.info("  I'm about to do some pre-processing")
    # -------------------------------------------------------------
    
    # Call the default start of loop function from the standard library
    default_start_of_loop_fn(config, current_iteration, mods, scenario_file)
    
def my_custom_end_of_loop_fn(config, current_iteration, output_dir, polaris_inputs):

    # Call the default end of loop function from the standard library
    default_end_of_loop_fn(config, current_iteration, mods, scenario_file)

    # User code goes here -----------------------------------------
    logging.info("  I'm about to do something with my run outputs")
    # -------------------------------------------------------------

These callbacks are then inserted into the run function

model.run(num_threads=4, 
          do_abm_init=False, 
          do_skim=False, 
          num_abm_runs=1,
          get_scenario_json_fn=get_scenario_for_iteration, 
          end_of_loop_fn=my_custom_end_of_loop_fn, 
          start_of_loop_fn=my_custom_start_of_loop_fn)

This allows almost infinite flexibility in how the iterative process is structured and an interested reader is encouraged to use the examples provided in the polaris.hpc.eqsql.examples.run_convergence module and in the various study repositories as a starting point for their first serious customization.