diff --git a/gridappsd-python-lib/gridappsd/simulation.py b/gridappsd-python-lib/gridappsd/simulation.py index bf46edf..c41df64 100644 --- a/gridappsd-python-lib/gridappsd/simulation.py +++ b/gridappsd-python-lib/gridappsd/simulation.py @@ -28,6 +28,13 @@ def asdict(self): for k, v in self.__dict__.items(): if isinstance(v, ConfigBase): built[k] = v.asdict() + elif isinstance(v, list): + built[k] = [] + for item in v: + if isinstance(item, ConfigBase): + built[k].append(item.asdict()) + else: + built[k].append(item) else: built[k] = v return built @@ -49,17 +56,20 @@ class ModelCreationConfig(ConfigBase): @dataclass class SimulationArgs(ConfigBase): - start_time: str = field(default="1655321830") - duration: str = field(default="300") - simulator: str = field(default="GridLAB-D") - timestep_frequency: str = field(default="1000") - timestep_increment: str = field(default="1000") - run_realtime: bool = field(default=True) - pause_after_measurements: bool = field(default=False) - simulation_name: str = field(default="ieee13nodeckt") - power_flow_solver_method: str = field(default="NR") - model_creation_config: ModelCreationConfig = field(default_factory=ModelCreationConfig) - + start_time: str = field(default = "1655321830") + duration: str = field(default = "300") + timestep_frequency: str = field(default = "1000") + timestep_increment: str = field(default = "1000") + run_realtime: bool = field(default = True) + pause_after_measurements: bool = field(default = False) + simulation_name: str = field(default = "ieee13nodeckt") + + +@dataclass +class SimulatorArgs(ConfigBase): + simulator: str = field(default = "GridLAB-D") + model_creation_config: ModelCreationConfig = field(default_factory = ModelCreationConfig) + power_flow_solver_method: str = field(default = "NR") # __default_simulation_args__ = SimulationArgs() @@ -94,13 +104,14 @@ class ServiceConfig(ConfigBase): @dataclass class PowerSystemConfig(ConfigBase): Line_name: str - GeographicalRegion_name: str | None = field(default=None) - SubGeographicalRegion_name: str | None = field(default=None) + GeographicalRegion_name: str = field(default = None) + SubGeographicalRegion_name: str = field(default = None) + simulator_config: SimulatorArgs = field(default_factory=SimulatorArgs) @dataclass class SimulationConfig(ConfigBase): - power_system_config: PowerSystemConfig + power_system_configs: list[PowerSystemConfig] = field(default_factory=list) application_configs: list[ApplicationConfig] = field(default_factory=list) simulation_config: SimulationArgs = field(default_factory=SimulationArgs) service_configs: list[ServiceConfig] = field(default_factory=list) diff --git a/gridappsd-python-lib/tests/test_simulation.py b/gridappsd-python-lib/tests/test_simulation.py index b6c9365..4dbdcac 100644 --- a/gridappsd-python-lib/tests/test_simulation.py +++ b/gridappsd-python-lib/tests/test_simulation.py @@ -1,43 +1,64 @@ -# import json -# # from pprint import pprint -# import logging -# import os -# import sys -# import time -# import pytest +import json +import logging +import os +import sys +import time +import pytest +from datetime import datetime, timezone -# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) -# from gridappsd import GridAPPSD, topics as t -# from gridappsd.simulation import Simulation +from gridappsd import GridAPPSD, topics as t +from gridappsd.simulation import Simulation, PowerSystemConfig, SimulationArgs, SimulationConfig -# # The directory containing this file -# HERE = os.path.dirname(__file__) +simulation_is_complete = False +measurements_received = 0 -# def base_config(): -# data = {"power_system_config":{"SubGeographicalRegion_name":"_ABEB635F-729D-24BF-B8A4-E2EF268D8B9E","GeographicalRegion_name":"_73C512BD-7249-4F50-50DA-D93849B89C43","Line_name":"_49AD8E07-3BF9-A4E2-CB8F-C3722F837B62"},"simulation_config":{"power_flow_solver_method":"NR","duration":120,"simulation_name":"ieee13nodeckt","simulator":"GridLAB-D","start_time":1605418946,"run_realtime":False,"simulation_output":{},"model_creation_config":{"load_scaling_factor":1.0,"triplex":"y","encoding":"u","system_frequency":60,"voltage_multiplier":1.0,"power_unit_conversion":1.0,"unique_names":"y","schedule_name":"ieeezipload","z_fraction":0.0,"i_fraction":1.0,"p_fraction":0.0,"randomize_zipload_fractions":False,"use_houses":False},"simulation_broker_port":51044,"simulation_broker_location":"127.0.0.1"},"application_config":{"applications":[]},"service_configs":[],"test_config":{"randomNum":{"seed":{"value":185213303967438},"nextNextGaussian":0.0,"haveNextNextGaussian":False},"events":[],"testInput":True,"testOutput":True,"appId":"","testId":"1468836560","testType":"simulation_vs_expected","storeMatches":False},"simulation_request_type":"NEW"} -# # with open("{HERE}/simulation_fixtures/13_node_2_min_base.json".format(HERE=HERE)) as fp: -# # data = json.load(fp) -# return data +@pytest.fixture +def createGadObject(): + gad_user = os.environ.get('GRIDAPPSD_USER') + if gad_user is None: + os.environ['GRIDAPPSD_USER'] = 'system' + gad_password = os.environ.get('GRIDAPPSD_PASSWORD') + if gad_password is None: + os.environ['GRIDAPPSD_PASSWORD'] = 'manager' + return GridAPPSD() -# def test_simulation_no_duplicate_measurement_timestamps(gridappsd_client: GridAPPSD): -# num_measurements = 0 -# timestamps = set() - -# def measurement(sim, timestamp, measurement): -# nonlocal num_measurements -# num_measurements += 1 -# assert timestamp not in timestamps -# timestamps.add(timestamp) - -# gapps = gridappsd_client -# sim = Simulation(gapps, base_config()) -# sim.add_onmeasurement_callback(measurement) -# sim.start_simulation() -# sim.run_loop() - -# # did we get a measurement? -# assert num_measurements > 0 - -# # if empty then we know the simulation did not work. -# assert timestamps +def test_createSimulations(createGadObject): + gadObj = createGadObject + response = gadObj.query_model_info() + models = response.get("data", {}).get("models", {}) + start_time = int(datetime(year=2025, month=1, day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=timezone.utc).timestamp()) + simulationArgs = SimulationArgs(start_time=f"{start_time}", + duration="120", + run_realtime=False, + pause_after_measurements=False) + sim_config = SimulationConfig(simulation_config=simulationArgs) + modelsToRun = [ + "49AD8E07-3BF9-A4E2-CB8F-C3722F837B62", # IEEE 13 Node Test Feeder + "C1C3E687-6FFD-C753-582B-632A27E28507" # IEEE 123 Node Test Feeder + ] + for m in models: + if m.get("modelId") not in modelsToRun: + continue + line_name = m.get("modelId") + subregion_name = m.get("subRegionId") + region_name = m.get("regionId") + psc = PowerSystemConfig(Line_name=line_name, + SubGeographicalRegion_name=subregion_name, + GeographicalRegion_name=region_name) + sim_config.power_system_configs.append(psc) + sim_obj = Simulation(gapps=gadObj, run_config=sim_config) + def on_measurement(sim, ts, m): + global measurements_received + measurements_received += 1 + def on_simulation_complete(sim): + global simulation_is_complete + simulation_is_complete = True + sim_obj.add_onmeasurement_callback(on_measurement) + sim_obj.add_oncomplete_callback(on_simulation_complete) + sim_obj.start_simulation() + while not simulation_is_complete: + time.sleep(1) + assert measurements_received == 1 + gadObj.disconnect() \ No newline at end of file