Skip to content

Commit

Permalink
feat!: multiple scenarios run (#64)
Browse files Browse the repository at this point in the history
* changed initialisation and setup of SimulationModel class
* added intermediary methods to the SimulationModel class in order to facilitate inheritance
* moved runSummary in SimulationScenario class
* added a blank logger and a function for displaying a horizontal bar in terminal
  • Loading branch information
leo-desbureaux-tellae authored Aug 18, 2022
1 parent 1454989 commit c98d2f6
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 108 deletions.
14 changes: 7 additions & 7 deletions starling_sim/basemodel/output/output_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def setup(self, simulation_model):
"""
Setup method called during simulation setup.
Sets values of the output factory attributes.
Set values of the output factory attributes.
:param simulation_model:
"""
Expand Down Expand Up @@ -155,7 +155,7 @@ def extract_simulation(self, simulation_model):
logging.warning(self.GENERATION_ERROR_FORMAT.format("visualisation"))

# run summary output
self.generate_run_summary(simulation_model)
self.generate_run_summary(simulation_model.scenario)

def generate_geojson_output(self, simulation_model):
"""
Expand Down Expand Up @@ -220,17 +220,17 @@ def generate_trace_output(self, simulation_model):

self.sim.outputFactory.new_output_file(filepath, "text/plain", content="traces")

def generate_run_summary(self, simulation_model):
def generate_run_summary(self, simulation_scenario):
"""
Generate a summary file of the simulation run.
:param simulation_model:
:param simulation_scenario: SimulationScenario object
"""
filepath = simulation_model.scenario.outputs_folder + RUN_SUMMARY_FILENAME
filepath = simulation_scenario.outputs_folder + RUN_SUMMARY_FILENAME

# add run summary to output files
self.sim.outputFactory.new_output_file(filepath, "application/json", content="run_summary")

# set output files in run summary and dump it in a file
simulation_model.runSummary["output_files"] = self.output_files
json_pretty_dump(simulation_model.runSummary, filepath)
simulation_scenario.runSummary["output_files"] = self.output_files
json_pretty_dump(simulation_scenario.runSummary, filepath)
210 changes: 153 additions & 57 deletions starling_sim/basemodel/simulation_model.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import random
import logging
import numpy
import datetime
import time

from starling_sim.basemodel.population.dict_population import DictPopulation
from starling_sim.basemodel.environment.environment import Environment
from starling_sim.basemodel.input.dynamic_input import DynamicInput
from starling_sim.basemodel.output.output_factory import OutputFactory
from starling_sim.basemodel.schedule.scheduler import Scheduler
from starling_sim.basemodel.trace.trace import trace_simulation_end
from starling_sim.utils.utils import import_gtfs_feed, get_git_revision_hash
from starling_sim.utils.utils import import_gtfs_feed, display_horizontal_bar
from starling_sim.utils.constants import BASE_LEAVING_CODES
from starling_sim.utils.config import config
from starling_sim.version import __version__


class SimulationModel:
Expand Down Expand Up @@ -57,59 +56,169 @@ def __init__(self, scenario):
# simulation parameters
self.scenario = scenario

# run_summary
self.runSummary = self.init_run_summary()
# random seed for the simulation setup and run
self.randomSeed = scenario["seed"]

# information to be completed for the specific models

# elements of the model
self.environment = None
self.agentPopulation = None
self.dynamicInput = None
self.outputFactory = None

# event manager
self.scheduler = None

# complete, unfiltered gtfs timetable, if relevant
self.gtfs = None

self._base_init(scenario)

def _base_init(self, scenario):
self._init_model(scenario)
self._init_scenario_run(scenario)

def _init_model(self, scenario):
"""
Initialise the simulation model with element that do not depend on the scenario.
This method is called once, during the model initialisation.
:param scenario: SimulationScenario object
"""

# add the base leaving codes
self.add_base_leaving_codes()

# random seed for the simulation setup and run
# create the simulation environment
self._init_environment(scenario)

def _init_scenario_run(self, scenario):
"""
Initialise the scenario-related elements of the model.
This method should be called before running a new simulation scenario.
:param scenario: SimulationScenario object
"""
# base scenario information
self.scenario = scenario
self.randomSeed = scenario["seed"]

# information to be completed for the specific models
# event manager
self._init_scheduler()

# elements of the model
self.agentPopulation = self.population_class.__new__(self.population_class)
self.agentPopulation.__init__(self.agent_type_class.keys())
# model elements
self._init_agent_population()
self._init_dynamic_input()

# maybe we could move the output factory to the model elements ?
self._init_output_factory()

def _init_environment(self, scenario):
"""
Initialise the simulation environment using the environment_class attribute.
:param scenario: SimulationScenario object
"""
self.environment = self.environment_class.__new__(self.environment_class)
self.environment.__init__(scenario)

# inputs and outputs
def _init_agent_population(self):
"""
Initialise the simulation agent population using the population_class attribute.
"""
self.agentPopulation = self.population_class.__new__(self.population_class)
self.agentPopulation.__init__(self.agent_type_class.keys())

def _init_dynamic_input(self):
"""
Initialise the simulation dynamic input using the input_class attribute.
"""
self.dynamicInput = self.input_class.__new__(self.input_class)
self.dynamicInput.__init__(self.agent_type_class)

def _init_output_factory(self):
"""
Initialise the simulation output factory using the output_class attribute.
"""
self.outputFactory = self.output_class.__new__(self.output_class)
self.outputFactory.__init__()

# event manager
def _init_scheduler(self):
"""
Initialise the simulation scheduler.
"""
self.scheduler = Scheduler()

# complete, unfiltered gtfs timetable, if relevant
self.gtfs = None

def setup(self):
"""
Sets the entries of the simulation model and prepares it for the simulation run.
It is here that the files and data are effectively read and imported in the model.
This method can be extended to manage and setup other elements of the model
"""
self._setup_model()
self._setup_scenario_run()

def _setup_model(self):
"""
Set up the simulation model elements that do not depend on the scenario.
This method is called once, during the model setup.
"""
start = time.time()

logging.info("Setting up model")
# setup model elements
self._setup_environment()
self.setup_gtfs()

duration = round(time.time() - start, 1)
logging.info("End of model setup. Elapsed time : {} seconds\n".format(duration))
self.scenario.set_stat("model_setup_time", duration)

def _setup_scenario_run(self):
"""
Set up the scenario-related elements of the model.
This method should be called before running a new simulation scenario.
"""

display_horizontal_bar()
logging.info("Setting up run of scenario: {}".format(self.scenario.name))
start = time.time()
# set the parameters and initialize the random seed
self.setup_seeds()

# start the simulation setup
# setup scenario elements
self._setup_dynamic_input()
self._setup_output_factory()

logging.info("Simulation environment setup")
self.environment.setup(self)
duration = round(time.time() - start, 1)
logging.info("End of scenario setup. Elapsed time : {} seconds\n".format(duration))
self.scenario.set_stat("scenario_setup_time", duration)

if "gtfs_timetables" in self.scenario:
logging.info("GTFS tables setup")
self.setup_gtfs()
def _setup_environment(self):
"""
Set up the simulation environment.
"""
logging.debug("Simulation environment setup")
self.environment.setup(self)

logging.info("Dynamic input setup")
def _setup_dynamic_input(self):
"""
Set up the simulation dynamic input.
"""
logging.debug("Dynamic input setup")
self.dynamicInput.setup(self)

logging.info("Output factory setup")
def _setup_output_factory(self):
"""
Set up the simulation output factory.
"""
logging.debug("Output factory setup")
self.outputFactory.setup(self)

def run(self):
Expand All @@ -119,6 +228,10 @@ def run(self):
This method can be extended to run other elements of the model
"""

logging.info("Starting simulation run")

start = time.time()

# if asked, add a process that logs the simulation time every hour
if "time_log" in self.scenario and self.scenario["time_log"]:
self.scheduler.new_process(self.periodic_hour_log())
Expand All @@ -132,11 +245,22 @@ def run(self):
# trace the end of the simulation for all agents
trace_simulation_end(self)

duration = round(time.time() - start, 1)

logging.info("End of simulation run. Elapsed time : {} seconds\n".format(duration))
self.scenario.set_stat("execution_time", duration)

shortest_path_count = 0
for topology in self.environment.topologies.values():
shortest_path_count += topology.shortest_path_count
self.scenario.set_stat("shortest_paths", shortest_path_count)

def generate_output(self):
"""
Generate an output of the current simulation
"""

logging.info("Generating outputs")
self.outputFactory.extract_simulation(self)

def add_base_leaving_codes(self):
Expand Down Expand Up @@ -166,42 +290,14 @@ def periodic_hour_log(self):

def setup_gtfs(self):
"""
Setup a gtfs timetable for the simulation.
"""

# import the gtfs timetable from the zip given in the parameters
restrict_transfers = config["transfer_restriction"]
self.gtfs = import_gtfs_feed(self.scenario["gtfs_timetables"], restrict_transfers)

def init_run_summary(self):
"""
Initialise the run summary.
Set up a gtfs timetable for the simulation.
"""

summary = dict()

# get run date
summary["date"] = str(datetime.datetime.today())

# get starling version
summary["starling_version"] = __version__

# get current commit
summary["commit"] = get_git_revision_hash()

# copy scenario parameters
summary["parameters"] = self.scenario.copy_parameters()

# copy config
summary["config"] = config.copy()

# scenario output files
summary["output_files"] = dict()

# run statistics
summary["stats"] = dict()

return summary
if "gtfs_timetables" in self.scenario:
logging.debug("GTFS tables setup")
# import the gtfs timetable from the zip given in the parameters
restrict_transfers = config["transfer_restriction"]
self.gtfs = import_gtfs_feed(self.scenario["gtfs_timetables"], restrict_transfers)

@classmethod
def get_agent_type_schemas(cls):
Expand Down
36 changes: 8 additions & 28 deletions starling_sim/model_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ def init_simulator_from_parameters(simulation_scenario, pkg):
# get the Model class
model_class = ModelSimulator.get_model_class(simulation_scenario["code"], pkg)

logging.info(
"Initialising simulation model: {} ({})\n".format(
model_class.name, simulation_scenario.model
)
)

# create a new instance of the simulation model
try:
simulation_model = model_class(simulation_scenario)
Expand Down Expand Up @@ -135,44 +141,18 @@ def launch_simulation(scenario_path, pkg):
# read simulation parameters
simulation_scenario.get_scenario_parameters()

if "multiple" in simulation_scenario:
create_sub_scenarios(simulation_scenario)
exit(0)

# init the simulator
logging.info(
"Initializing simulator for the model code "
+ simulation_scenario["code"]
+ ", scenario "
+ simulation_scenario["scenario"]
+ "\n"
)
simulator = ModelSimulator.init_simulator_from_parameters(simulation_scenario, pkg)

# setup the simulator
logging.info("Setting entries for: " + simulator.simulationModel.name)
start = time.time()
simulator.setup_simulation()
duration = time.time() - start
logging.info("End of setup. Elapsed time : {:.2f} seconds\n".format(duration))
simulator.simulationModel.runSummary["stats"]["setup_time"] = duration

# run the simulation
logging.info("Starting the simulation\n")
start = time.time()
simulator.run_simulation()
duration = time.time() - start
logging.info("End of simulation run. Elapsed time : {:.2f} seconds\n".format(duration))
simulator.simulationModel.runSummary["stats"]["execution_time"] = duration

shortest_path_count = 0
for topology in simulator.simulationModel.environment.topologies.values():
shortest_path_count += topology.shortest_path_count
logging.info("Number of shortest_path computed : {}".format(shortest_path_count))
simulator.simulationModel.runSummary["stats"]["shortest_paths"] = shortest_path_count

# generate simulation output
logging.info("Generating outputs of the simulation\n")
simulator.generate_output()

logging.info("End of Starling execution\n")

logging.shutdown()
Loading

0 comments on commit c98d2f6

Please sign in to comment.