From 26dd92d5c8e1c2f4a467f52063b32d512836616c Mon Sep 17 00:00:00 2001 From: Ishaan Desai Date: Tue, 11 Jun 2024 16:53:23 +0200 Subject: [PATCH] Set micro simulation dt in the configuration (#112) * Add option for the user to provide initial time window size of the micro simulation * Put config parameter micro_time_window_size in coupling_params group * Paasing dt correctly * Fix unit tests * Fix crash handling tests * Add CHANGELOG entry * Use micro_dt instead of micro_time_window_size * Use dt=0 when reading initial data --- CHANGELOG.md | 1 + docs/configuration.md | 4 +- .../micro-manager-cpp-adaptivity-config.json | 3 +- examples/micro-manager-cpp-config.json | 3 +- ...icro-manager-python-adaptivity-config.json | 3 +- examples/micro-manager-python-config.json | 3 +- micro_manager/config.py | 14 +++++ micro_manager/micro_manager.py | 51 ++++++++++--------- micro_manager/micro_manager_base.py | 2 + ...ger-config-global-adaptivity-parallel.json | 3 +- ...icro-manager-config-global-adaptivity.json | 3 +- ...micro-manager-config-local-adaptivity.json | 3 +- .../micro-manager-config-parallel-1.json | 3 +- .../micro-manager-config-parallel-2.json | 3 +- tests/unit/micro-manager-config.json | 3 +- tests/unit/micro-manager-config_crash.json | 3 +- tests/unit/test_micro_manager.py | 6 +-- .../test_micro_simulation_crash_handling.py | 4 +- 18 files changed, 75 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c80e9c..4536e551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## latest +- Set time step of micro simulation in the configuration, and use it in the coupling https://github.com/precice/micro-manager/pull/112 - Add a base class called `MicroManager` with minimal API and member function definitions, rename the existing `MicroManager` class to `MicroManagerCoupling` https://github.com/precice/micro-manager/pull/111 - Handle calling `initialize()` function of micro simulations written in languages other than Python https://github.com/precice/micro-manager/pull/110 - Check if initial data returned from the micro simulation is the data that the adaptivity computation requires https://github.com/precice/micro-manager/pull/109 diff --git a/docs/configuration.md b/docs/configuration.md index d2243f29..2575d81d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,8 @@ The Micro Manager is configured with a JSON file. An example configuration file "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"temperature": "scalar", "heat-flux": "vector"}, - "write_data_names": {"porosity": "scalar", "conductivity": "vector"} + "write_data_names": {"porosity": "scalar", "conductivity": "vector"}, + "micro_time_window_size": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 1.0, 0.0, 1.0, 0.0, 1.0], @@ -41,6 +42,7 @@ Parameter | Description `macro_mesh_name` | Name of the macro mesh as stated in the preCICE configuration. `read_data_names` | A Python dictionary with the names of the data to be read from preCICE as keys and `"scalar"` or `"vector"` as values depending on the nature of the data. `write_data_names` | A Python dictionary with the names of the data to be written to preCICE as keys and `"scalar"` or `"vector"` as values depending on the nature of the data. +`micro_dt` | Initial time window size (dt) of the micro simulation. ## Simulation Parameters diff --git a/examples/micro-manager-cpp-adaptivity-config.json b/examples/micro-manager-cpp-adaptivity-config.json index c657d11c..2f36e07d 100644 --- a/examples/micro-manager-cpp-adaptivity-config.json +++ b/examples/micro-manager-cpp-adaptivity-config.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config-adaptivity.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0], diff --git a/examples/micro-manager-cpp-config.json b/examples/micro-manager-cpp-config.json index 3114de7f..01a15e06 100644 --- a/examples/micro-manager-cpp-config.json +++ b/examples/micro-manager-cpp-config.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0] diff --git a/examples/micro-manager-python-adaptivity-config.json b/examples/micro-manager-python-adaptivity-config.json index 3a81d828..595aee8f 100644 --- a/examples/micro-manager-python-adaptivity-config.json +++ b/examples/micro-manager-python-adaptivity-config.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config-adaptivity.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0], diff --git a/examples/micro-manager-python-config.json b/examples/micro-manager-python-config.json index e59ec20c..cc170d87 100644 --- a/examples/micro-manager-python-config.json +++ b/examples/micro-manager-python-config.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0] diff --git a/micro_manager/config.py b/micro_manager/config.py index 4024b059..55aef232 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -30,6 +30,7 @@ def __init__(self, logger, config_filename): self._macro_mesh_name = None self._read_data_names = dict() self._write_data_names = dict() + self._micro_dt = None self._macro_domain_bounds = None self._ranks_per_axis = None @@ -114,6 +115,8 @@ def read_json(self, config_filename): "No read data names provided. Micro manager will only write data to preCICE." ) + self._micro_dt = data["coupling_params"]["micro_dt"] + self._macro_domain_bounds = data["simulation_params"]["macro_domain_bounds"] try: @@ -431,3 +434,14 @@ def is_adaptivity_required_in_every_implicit_iteration(self): True if adaptivity needs to be calculated in every time iteration, False otherwise. """ return self._adaptivity_every_implicit_iteration + + def get_micro_dt(self): + """ + Get the size of the micro time window. + + Returns + ------- + micro_time_window : float + Size of the micro time window. + """ + return self._micro_dt diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index e18c3827..f75fc6b6 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -70,7 +70,6 @@ def __init__(self, config_file: str) -> None: self._crash_threshold = 0.2 self._number_of_nearest_neighbors = 4 - self._dt = 0 self._mesh_vertex_ids = None # IDs of macro vertices as set by preCICE self._micro_n_out = self._config.get_micro_output_n() @@ -117,6 +116,8 @@ def solve(self) -> None: sim_is_associated_to_cp = None sim_states_cp = [None] * self._local_number_of_sims + dt = min(self._participant.get_max_time_step_size(), self._micro_dt) + if self._is_adaptivity_on: similarity_dists = np.zeros( ( @@ -140,7 +141,7 @@ def solve(self) -> None: is_sim_active, sim_is_associated_to, ) = self._adaptivity_controller.compute_adaptivity( - self._dt, + dt, self._micro_sims, similarity_dists, is_sim_active, @@ -150,9 +151,7 @@ def solve(self) -> None: while self._participant.is_coupling_ongoing(): - self._dt = ( - self._participant.get_max_time_step_size() - ) # ask preCICE at beginning of time step for allowed time step size + dt = min(self._participant.get_max_time_step_size(), self._micro_dt) # Write a checkpoint if self._participant.requires_writing_checkpoint(): @@ -168,7 +167,7 @@ def solve(self) -> None: is_sim_active, sim_is_associated_to, ) = self._adaptivity_controller.compute_adaptivity( - self._dt, + dt, self._micro_sims, similarity_dists, is_sim_active, @@ -197,7 +196,7 @@ def solve(self) -> None: for active_id in active_sim_ids: self._micro_sims_active_steps[active_id] += 1 - micro_sims_input = self._read_data_from_precice() + micro_sims_input = self._read_data_from_precice(dt) if self._is_adaptivity_on: if self._adaptivity_in_every_implicit_step: @@ -206,7 +205,7 @@ def solve(self) -> None: is_sim_active, sim_is_associated_to, ) = self._adaptivity_controller.compute_adaptivity( - self._dt, + dt, self._micro_sims, similarity_dists, is_sim_active, @@ -230,10 +229,10 @@ def solve(self) -> None: self._micro_sims_active_steps[active_id] += 1 micro_sims_output = self._solve_micro_simulations_with_adaptivity( - micro_sims_input, is_sim_active, sim_is_associated_to + micro_sims_input, is_sim_active, sim_is_associated_to, dt ) else: - micro_sims_output = self._solve_micro_simulations(micro_sims_input) + micro_sims_output = self._solve_micro_simulations(micro_sims_input, dt) # Check if more than a certain percentage of the micro simulations have crashed and terminate if threshold is exceeded crashed_sims_on_all_ranks = np.zeros(self._size, dtype=np.int64) @@ -257,11 +256,11 @@ def solve(self) -> None: self._write_data_to_precice(micro_sims_output) - t += self._dt # increase internal time when time step is done. + t += dt # increase internal time when time step is done. n += 1 # increase counter self._participant.advance( - self._dt - ) # notify preCICE that time step of size self._dt is complete + dt + ) # notify preCICE that time step of size dt is complete # Revert micro simulations to their last checkpoints if required if self._participant.requires_reading_checkpoint(): @@ -427,7 +426,7 @@ def initialize(self) -> None: self._micro_sims_init = False # DECLARATION # Read initial data from preCICE, if it is available - initial_data = self._read_data_from_precice() + initial_data = self._read_data_from_precice(dt=0) if not initial_data: is_initial_data_available = False @@ -556,16 +555,19 @@ def initialize(self) -> None: ): self._micro_sims_have_output = True - self._dt = self._participant.get_max_time_step_size() - # *************** # Private methods # *************** - def _read_data_from_precice(self) -> list: + def _read_data_from_precice(self, dt) -> list: """ Read data from preCICE. + Parameters + ---------- + dt : float + Time step size at which data is to be read from preCICE. + Returns ------- local_read_data : list @@ -579,7 +581,7 @@ def _read_data_from_precice(self) -> list: read_data.update( { name: self._participant.read_data( - self._macro_mesh_name, name, self._mesh_vertex_ids, self._dt + self._macro_mesh_name, name, self._mesh_vertex_ids, dt ) } ) @@ -621,7 +623,7 @@ def _write_data_to_precice(self, data: list) -> None: self._macro_mesh_name, dname, [], np.array([]) ) - def _solve_micro_simulations(self, micro_sims_input: list) -> list: + def _solve_micro_simulations(self, micro_sims_input: list, dt: float) -> list: """ Solve all micro simulations and assemble the micro simulations outputs in a list of dicts format. @@ -630,6 +632,8 @@ def _solve_micro_simulations(self, micro_sims_input: list) -> list: micro_sims_input : list List of dicts in which keys are names of data and the values are the data which are required inputs to solve a micro simulation. + dt : float + Time step size. Returns ------- @@ -645,9 +649,7 @@ def _solve_micro_simulations(self, micro_sims_input: list) -> list: # Attempt to solve the micro simulation try: start_time = time.time() - micro_sims_output[count] = sim.solve( - micro_sims_input[count], self._dt - ) + micro_sims_output[count] = sim.solve(micro_sims_input[count], dt) end_time = time.time() # Write solve time of the macro simulation if required and the simulation has not crashed if self._is_micro_solve_time_required: @@ -689,6 +691,7 @@ def _solve_micro_simulations_with_adaptivity( micro_sims_input: list, is_sim_active: np.ndarray, sim_is_associated_to: np.ndarray, + dt: float, ) -> list: """ Solve all micro simulations and assemble the micro simulations outputs in a list of dicts format. @@ -702,6 +705,8 @@ def _solve_micro_simulations_with_adaptivity( 1D array having state (active or inactive) of each micro simulation sim_is_associated_to : numpy array 1D array with values of associated simulations of inactive simulations. Active simulations have None + dt : float + Time step size. Returns ------- @@ -741,7 +746,7 @@ def _solve_micro_simulations_with_adaptivity( try: start_time = time.time() micro_sims_output[active_id] = self._micro_sims[active_id].solve( - micro_sims_input[active_id], self._dt + micro_sims_input[active_id], dt ) end_time = time.time() # Write solve time of the macro simulation if required and the simulation has not crashed diff --git a/micro_manager/micro_manager_base.py b/micro_manager/micro_manager_base.py index 3db8ec65..29dfdf86 100644 --- a/micro_manager/micro_manager_base.py +++ b/micro_manager/micro_manager_base.py @@ -76,6 +76,8 @@ def __init__(self, config_file): self._logger.info("Provided configuration file: {}".format(config_file)) self._config = Config(self._logger, config_file) + self._micro_dt = self._config.get_micro_dt() + def initialize(self): """ Initialize micro simulations. Not implemented diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json index 62f2006d..50da60fb 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity-parallel.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0, 1, 0, 1, 0, 1], diff --git a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json index 43e63601..835b9835 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json +++ b/tests/integration/test_unit_cube/micro-manager-config-global-adaptivity.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0, 1, 0, 1, 0, 1], diff --git a/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json b/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json index acd0563c..5a6581ca 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json +++ b/tests/integration/test_unit_cube/micro-manager-config-local-adaptivity.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0, 1, 0, 1, 0, 1], diff --git a/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json b/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json index bcbe34b6..aaece104 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json +++ b/tests/integration/test_unit_cube/micro-manager-config-parallel-1.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0, 1, 0, 1, 0, 1], diff --git a/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json b/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json index 63b5c1f0..7c8b065d 100644 --- a/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json +++ b/tests/integration/test_unit_cube/micro-manager-config-parallel-2.json @@ -4,7 +4,8 @@ "config_file_name": "precice-config.xml", "macro_mesh_name": "macro-cube-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0, 1, 0, 1, 0, 1], diff --git a/tests/unit/micro-manager-config.json b/tests/unit/micro-manager-config.json index d1a4bbaf..e5c5aa72 100644 --- a/tests/unit/micro-manager-config.json +++ b/tests/unit/micro-manager-config.json @@ -4,7 +4,8 @@ "config_file_name": "dummy-config.xml", "macro_mesh_name": "dummy-macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 0.1 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0], diff --git a/tests/unit/micro-manager-config_crash.json b/tests/unit/micro-manager-config_crash.json index e5b8ac32..455bb1de 100644 --- a/tests/unit/micro-manager-config_crash.json +++ b/tests/unit/micro-manager-config_crash.json @@ -4,7 +4,8 @@ "config_file_name": "dummy-config.xml", "macro_mesh_name": "dummy-macro-mesh", "read_data_names": {"macro-scalar-data": "scalar", "macro-vector-data": "vector"}, - "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"} + "write_data_names": {"micro-scalar-data": "scalar", "micro-vector-data": "vector"}, + "micro_dt": 1.0 }, "simulation_params": { "macro_domain_bounds": [0.0, 25.0, 0.0, 25.0, 0.0, 25.0], diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index 711d8f90..ad8285e8 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -67,7 +67,7 @@ def test_initialization(self): manager = micro_manager.MicroManagerCoupling("micro-manager-config.json") manager.initialize() - self.assertEqual(manager._dt, 0.1) # from Interface.initialize + self.assertEqual(manager._micro_dt, 0.1) # from Interface.initialize self.assertEqual(manager._global_number_of_sims, 4) self.assertListEqual(manager._macro_bounds, self.macro_bounds) self.assertListEqual(manager._mesh_vertex_ids.tolist(), [0, 1, 2, 3]) @@ -85,7 +85,7 @@ def test_read_write_data_from_precice(self): manager = micro_manager.MicroManagerCoupling("micro-manager-config.json") manager._write_data_to_precice(self.fake_write_data) - read_data = manager._read_data_from_precice() + read_data = manager._read_data_from_precice(1.0) for data, fake_data in zip(read_data, self.fake_read_data): self.assertEqual(data["macro-scalar-data"], 1) @@ -105,7 +105,7 @@ def test_solve_micro_sims(self): manager._micro_sims = [MicroSimulation(i) for i in range(4)] manager._micro_sims_active_steps = np.zeros(4, dtype=np.int32) - micro_sims_output = manager._solve_micro_simulations(self.fake_read_data) + micro_sims_output = manager._solve_micro_simulations(self.fake_read_data, 1.0) for data, fake_data in zip(micro_sims_output, self.fake_write_data): self.assertEqual(data["micro-scalar-data"], 2) diff --git a/tests/unit/test_micro_simulation_crash_handling.py b/tests/unit/test_micro_simulation_crash_handling.py index 3474c09a..ff2d63a8 100644 --- a/tests/unit/test_micro_simulation_crash_handling.py +++ b/tests/unit/test_micro_simulation_crash_handling.py @@ -51,7 +51,7 @@ def test_crash_handling(self): ) manager._micro_sims = [MicroSimulation(i) for i in range(4)] - micro_sims_output = manager._solve_micro_simulations(macro_data) + micro_sims_output = manager._solve_micro_simulations(macro_data, 1.0) # Crashed simulation has interpolated value data_crashed = micro_sims_output[2] @@ -99,7 +99,7 @@ def test_crash_handling_with_adaptivity(self): is_sim_active = np.array([True, True, True, True, False]) sim_is_associated_to = np.array([-2, -2, -2, -2, 2]) micro_sims_output = manager._solve_micro_simulations_with_adaptivity( - macro_data, is_sim_active, sim_is_associated_to + macro_data, is_sim_active, sim_is_associated_to, 1.0 ) # Crashed simulation has interpolated value