diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index e596c578..275817cd 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -20,8 +20,8 @@ jobs: pip install setuptools wheel twine - name: Build and publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: | python setup.py sdist twine upload dist/* diff --git a/CHANGELOG.md b/CHANGELOG.md index c784ef1d..b1d6b9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## latest +- Pass an ID to the micro simulation object so that it is aware of its own uniqueness https://github.com/precice/micro-manager/pull/66 +- Resolve bug which led to an error when global adaptivity was used with unequal number of simulations on each rank https://github.com/precice/micro-manager/pull/78 +- Make the `initialize()` method of the MicroManager class private https://github.com/precice/micro-manager/pull/77 +- Add reference paper via a CITATION.cff file https://github.com/precice/micro-manager/commit/6c08889c658c889d6ab5d0867802522585abcee5 +- Add JOSS DOI badge https://github.com/precice/micro-manager/commit/2e3c2a4c77732f56a957abbad9e4d0cb64029725 +- Update pyprecice API calls to their newer variants https://github.com/precice/micro-manager/pull/51 + ## v0.3.0 - Add global variant to adaptivity (still experimental) https://github.com/precice/micro-manager/pull/42 diff --git a/docs/micro-simulation-convert-to-library.md b/docs/micro-simulation-convert-to-library.md index 7ed8dd48..a145852f 100644 --- a/docs/micro-simulation-convert-to-library.md +++ b/docs/micro-simulation-convert-to-library.md @@ -14,9 +14,14 @@ Restructure your micro simulation code into a Python class with the structure gi ```python class MicroSimulation: # Name is fixed - def __init__(self): + def __init__(self, sim_id): """ Constructor of class MicroSimulation. + + Parameters + ---------- + sim_id : int + ID of the simulation instance, that the Micro Manager has set for it. """ def solve(self, macro_data: dict, dt: float) -> dict: diff --git a/docs/running.md b/docs/running.md index a1ba74ca..cf0e9dd3 100644 --- a/docs/running.md +++ b/docs/running.md @@ -18,8 +18,6 @@ from micro_manager import MicroManager manager = MicroManager("micro-manager-config.json") -manager.initialize() - manager.solve() ``` diff --git a/examples/cpp-dummy/micro_cpp_dummy.cpp b/examples/cpp-dummy/micro_cpp_dummy.cpp index df922470..dd0b6863 100644 --- a/examples/cpp-dummy/micro_cpp_dummy.cpp +++ b/examples/cpp-dummy/micro_cpp_dummy.cpp @@ -13,7 +13,7 @@ #include "micro_cpp_dummy.hpp" // Constructor -MicroSimulation::MicroSimulation() : _micro_scalar_data(0), _state(0) {} +MicroSimulation::MicroSimulation(int sim_id) : _sim_id(sim_id), _micro_scalar_data(0), _state(0) {} // Solve py::dict MicroSimulation::solve(py::dict macro_data, double dt) @@ -64,7 +64,7 @@ PYBIND11_MODULE(micro_dummy, m) { m.doc() = "pybind11 micro dummy plugin"; py::class_(m, "MicroSimulation") - .def(py::init()) + .def(py::init()) .def("solve", &MicroSimulation::solve) .def("get_state", &MicroSimulation::get_state) .def("set_state", &MicroSimulation::set_state) @@ -77,7 +77,7 @@ PYBIND11_MODULE(micro_dummy, m) { throw std::runtime_error("Invalid state!"); /* Create a new C++ instance */ - MicroSimulation ms; + MicroSimulation ms(0); ms.set_state(t); diff --git a/examples/cpp-dummy/micro_cpp_dummy.hpp b/examples/cpp-dummy/micro_cpp_dummy.hpp index d370afc2..fb230ea1 100644 --- a/examples/cpp-dummy/micro_cpp_dummy.hpp +++ b/examples/cpp-dummy/micro_cpp_dummy.hpp @@ -14,7 +14,7 @@ namespace py = pybind11; class MicroSimulation { public: - MicroSimulation(); + MicroSimulation(int sim_id); // solve takes a python dict data, and the timestep dt as inputs, and returns a python dict py::dict solve(py::dict macro_write_data, double dt); @@ -22,6 +22,7 @@ class MicroSimulation py::list get_state() const; private: + int _sim_id; double _micro_scalar_data; std::vector _micro_vector_data; double _state; diff --git a/examples/cpp-dummy/run_micro_manager.py b/examples/cpp-dummy/run_micro_manager.py index e6afd394..6c7336bd 100644 --- a/examples/cpp-dummy/run_micro_manager.py +++ b/examples/cpp-dummy/run_micro_manager.py @@ -11,6 +11,4 @@ manager = MicroManager(args.config) -manager.initialize() - manager.solve() diff --git a/examples/python-dummy/micro_dummy.py b/examples/python-dummy/micro_dummy.py index f267d3b9..eeed2984 100644 --- a/examples/python-dummy/micro_dummy.py +++ b/examples/python-dummy/micro_dummy.py @@ -6,10 +6,11 @@ class MicroSimulation: - def __init__(self): + def __init__(self, sim_id): """ Constructor of MicroSimulation class. """ + self._sim_id = sim_id self._dims = 3 self._micro_scalar_data = None self._micro_vector_data = None diff --git a/examples/python-dummy/run_micro_manager.py b/examples/python-dummy/run_micro_manager.py index a3d0dd6c..3cffab3a 100644 --- a/examples/python-dummy/run_micro_manager.py +++ b/examples/python-dummy/run_micro_manager.py @@ -11,6 +11,4 @@ manager = MicroManager(args.config) -manager.initialize() - manager.solve() diff --git a/micro_manager/adaptivity/adaptivity.py b/micro_manager/adaptivity/adaptivity.py index 00ddd6ca..0edb3779 100644 --- a/micro_manager/adaptivity/adaptivity.py +++ b/micro_manager/adaptivity/adaptivity.py @@ -8,6 +8,7 @@ from typing import Callable import re import xml.etree.ElementTree as ET +from warnings import warn class AdaptivityCalculator: @@ -172,9 +173,15 @@ def _update_active_sims( self._coarse_const = self._get_adaptive_similarity_const(self._coarse_const_input) if self._adaptive_refine_const: self._refine_const = self._get_adaptive_similarity_const(self._refine_const_input) - self._coarse_tol = self._coarse_const * self._refine_const * \ - np.amax(similarity_dists) + max_similarity_dist = np.amax(similarity_dists) + + if max_similarity_dist == 0.0: + warn("All similarity distances are zero, probably because all the data for adaptivity is the same. Coarsening tolerance will be manually set to minimum float number.") + self._coarse_tol = sys.float_info.min + else: + self._coarse_tol = self._coarse_const * self._refine_const * max_similarity_dist + _is_sim_active = np.copy(is_sim_active) # Input is_sim_active is not longer used after this point # Update the set of active micro sims diff --git a/micro_manager/adaptivity/global_adaptivity.py b/micro_manager/adaptivity/global_adaptivity.py index a1f58b18..8fba0f9f 100644 --- a/micro_manager/adaptivity/global_adaptivity.py +++ b/micro_manager/adaptivity/global_adaptivity.py @@ -18,8 +18,7 @@ def __init__( self, configurator, logger, - is_sim_on_this_rank: list, - rank_of_sim: np.ndarray, + global_number_of_sims: float, global_ids: list, rank: int, comm) -> None: @@ -32,10 +31,8 @@ def __init__( Object which has getter functions to get parameters defined in the configuration file. logger : object of logging Logger defined from the standard package logging - is_sim_on_this_rank : list + global_number_of_sims : float List of booleans. True if simulation is on this rank, False otherwise. - rank_of_sim : numpy array - 1D array consisting of rank on which the simulation lives. global_ids : list List of global IDs of simulations living on this rank. rank : int @@ -44,12 +41,26 @@ def __init__( Global communicator of MPI. """ super().__init__(configurator, logger) - self._is_sim_on_this_rank = is_sim_on_this_rank - self._rank_of_sim = rank_of_sim self._global_ids = global_ids self._comm = comm self._rank = rank + local_number_of_sims = len(global_ids) + + # Create a map of micro simulation global IDs and the ranks on which they are + micro_sims_on_this_rank = np.zeros(local_number_of_sims, dtype=np.intc) + for i in range(local_number_of_sims): + micro_sims_on_this_rank[i] = self._rank + + self._rank_of_sim = np.zeros(global_number_of_sims, dtype=np.intc) # DECLARATION + + self._comm.Allgatherv(micro_sims_on_this_rank, self._rank_of_sim) + + self._is_sim_on_this_rank = [False] * global_number_of_sims # DECLARATION + for i in range(global_number_of_sims): + if self._rank_of_sim[i] == self._rank: + self._is_sim_on_this_rank[i] = True + def compute_adaptivity( self, dt: float, @@ -94,11 +105,15 @@ def compute_adaptivity( is_sim_active = self._update_active_sims(similarity_dists, is_sim_active_nm1) is_sim_active, sim_is_associated_to = self._update_inactive_sims( - similarity_dists, is_sim_active_nm1, sim_is_associated_to_nm1, micro_sims) + similarity_dists, is_sim_active, sim_is_associated_to_nm1, micro_sims) + + print("sim_is_associated_to: {}".format(sim_is_associated_to)) sim_is_associated_to = self._associate_inactive_to_active( similarity_dists, is_sim_active, sim_is_associated_to) + print("sim_is_associated_to: {}".format(sim_is_associated_to)) + self._logger.info( "{} active simulations, {} inactive simulations".format( np.count_nonzero( diff --git a/micro_manager/adaptivity/local_adaptivity.py b/micro_manager/adaptivity/local_adaptivity.py index 08c85811..e00d9f30 100644 --- a/micro_manager/adaptivity/local_adaptivity.py +++ b/micro_manager/adaptivity/local_adaptivity.py @@ -61,7 +61,7 @@ def compute_adaptivity( is_sim_active = self._update_active_sims(similarity_dists, is_sim_active_nm1) is_sim_active, sim_is_associated_to = self._update_inactive_sims( - similarity_dists, is_sim_active_nm1, sim_is_associated_to_nm1, micro_sims) + similarity_dists, is_sim_active, sim_is_associated_to_nm1, micro_sims) sim_is_associated_to = self._associate_inactive_to_active( similarity_dists, is_sim_active, sim_is_associated_to) diff --git a/micro_manager/config.py b/micro_manager/config.py index 6e98f921..a62accd6 100644 --- a/micro_manager/config.py +++ b/micro_manager/config.py @@ -4,6 +4,7 @@ import json import os +from warnings import warn class Config: @@ -125,6 +126,12 @@ def read_json(self, config_filename): for dname in data["simulation_params"]["adaptivity"]["data"]: self._data_for_adaptivity[dname] = exchange_data[dname] + if self._data_for_adaptivity.keys() == self._write_data_names.keys(): + warn( + "Only micro simulation data is used for similarity computation in adaptivity. This would lead to the" + " same set of active and inactive simulations for the entire simulation time. If this is not intended," + " please include macro simulation data as well.") + self._adaptivity_history_param = data["simulation_params"]["adaptivity"]["history_param"] self._adaptivity_coarsening_constant = data["simulation_params"]["adaptivity"]["coarsening_constant"] self._adaptivity_refining_constant = data["simulation_params"]["adaptivity"]["refining_constant"] diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index 3b9ec38e..fa71e579 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -119,132 +119,12 @@ def __init__(self, config_file: str) -> None: self._adaptivity_in_every_implicit_step = self._config.is_adaptivity_required_in_every_implicit_iteration() self._micro_sims_active_steps = None + self._initialize() + # ************** # Public methods # ************** - def initialize(self) -> None: - """ - Initialize the Micro Manager by performing the following tasks: - - Decompose the domain if the Micro Manager is executed in parallel. - - Initialize preCICE. - - Gets the macro mesh information from preCICE. - - Create all micro simulation objects and initialize them if an initialize() method is available. - - If required, write initial data to preCICE. - """ - # Decompose the macro-domain and set the mesh access region for each partition in preCICE - assert len(self._macro_bounds) / 2 == self._participant.get_mesh_dimensions( - self._macro_mesh_name), "Provided macro mesh bounds are of incorrect dimension" - if self._is_parallel: - domain_decomposer = DomainDecomposer( - self._logger, self._participant.get_mesh_dimensions(self._macro_mesh_name), self._rank, self._size) - coupling_mesh_bounds = domain_decomposer.decompose_macro_domain(self._macro_bounds, self._ranks_per_axis) - else: - coupling_mesh_bounds = self._macro_bounds - - self._participant.set_mesh_access_region(self._macro_mesh_name, coupling_mesh_bounds) - - # initialize preCICE - self._participant.initialize() - - self._mesh_vertex_ids, mesh_vertex_coords = self._participant.get_mesh_vertex_ids_and_coordinates( - self._macro_mesh_name) - assert (mesh_vertex_coords.size != 0), "Macro mesh has no vertices." - - self._local_number_of_sims, _ = mesh_vertex_coords.shape - self._logger.info("Number of local micro simulations = {}".format(self._local_number_of_sims)) - - if self._local_number_of_sims == 0: - if self._is_parallel: - self._logger.info( - "Rank {} has no micro simulations and hence will not do any computation.".format( - self._rank)) - self._is_rank_empty = True - else: - raise Exception("Micro Manager has no micro simulations.") - - nms_all_ranks = np.zeros(self._size, dtype=np.int64) - # Gather number of micro simulations that each rank has, because this rank needs to know how many micro - # simulations have been created by previous ranks, so that it can set - # the correct global IDs - self._comm.Allgather(np.array(self._local_number_of_sims), nms_all_ranks) - - # Get global number of micro simulations - self._global_number_of_sims = np.sum(nms_all_ranks) - - if self._is_adaptivity_on: - for name, is_data_vector in self._adaptivity_data_names.items(): - if is_data_vector: - self._data_for_adaptivity[name] = np.zeros( - (self._local_number_of_sims, self._participant.get_data_dimensions( - self._macro_mesh_name, name))) - else: - self._data_for_adaptivity[name] = np.zeros((self._local_number_of_sims)) - - # Create lists of local and global IDs - sim_id = np.sum(nms_all_ranks[:self._rank]) - self._global_ids_of_local_sims = [] # DECLARATION - for i in range(self._local_number_of_sims): - self._global_ids_of_local_sims.append(sim_id) - sim_id += 1 - - self._micro_sims = [None] * self._local_number_of_sims # DECLARATION - - micro_problem = getattr( - __import__( - self._config.get_micro_file_name(), - fromlist=["MicroSimulation"]), - "MicroSimulation") - - if self._is_adaptivity_on: - # Create micro simulation objects - for i in range(self._local_number_of_sims): - self._micro_sims[i] = create_simulation_class( - micro_problem)(self._global_ids_of_local_sims[i]) - - # Create a map of micro simulation global IDs and the ranks on which they are - micro_sims_on_this_rank = np.zeros(self._local_number_of_sims, dtype=np.intc) - for i in range(self._local_number_of_sims): - micro_sims_on_this_rank[i] = self._rank - - self._rank_of_sim = np.zeros(self._global_number_of_sims, dtype=np.intc) # DECLARATION - self._comm.Allgather(micro_sims_on_this_rank, self._rank_of_sim) - - self._is_sim_on_this_rank = [False] * self._global_number_of_sims # DECLARATION - for i in range(self._global_number_of_sims): - if self._rank_of_sim[i] == self._rank: - self._is_sim_on_this_rank[i] = True - - if self._adaptivity_type == "local": - self._adaptivity_controller = LocalAdaptivityCalculator( - self._config, self._logger) - self._number_of_sims_for_adaptivity = self._local_number_of_sims - elif self._adaptivity_type == "global": - self._adaptivity_controller = GlobalAdaptivityCalculator( - self._config, - self._logger, - self._is_sim_on_this_rank, - self._rank_of_sim, - self._global_ids_of_local_sims, - self._rank, - self._comm) - self._number_of_sims_for_adaptivity = self._global_number_of_sims - - self._micro_sims_active_steps = np.zeros(self._local_number_of_sims) - else: - for i in range(self._local_number_of_sims): - self._micro_sims[i] = ( - create_simulation_class(micro_problem)(self._global_ids_of_local_sims[i])) - - self._logger.info("Micro simulations with global IDs {} - {} created.".format( - self._global_ids_of_local_sims[0], self._global_ids_of_local_sims[-1])) - - self._micro_sims_have_output = False - if hasattr(micro_problem, 'output') and callable(getattr(micro_problem, 'output')): - self._micro_sims_have_output = True - - self._dt = self._participant.get_max_time_step_size() - def solve(self) -> None: """ Solve the problem using preCICE. @@ -361,6 +241,110 @@ def solve(self) -> None: # Private methods # *************** + def _initialize(self) -> None: + """ + Initialize the Micro Manager by performing the following tasks: + - Decompose the domain if the Micro Manager is executed in parallel. + - Initialize preCICE. + - Gets the macro mesh information from preCICE. + - Create all micro simulation objects and initialize them if an initialize() method is available. + - If required, write initial data to preCICE. + """ + # Decompose the macro-domain and set the mesh access region for each partition in preCICE + assert len(self._macro_bounds) / 2 == self._participant.get_mesh_dimensions( + self._macro_mesh_name), "Provided macro mesh bounds are of incorrect dimension" + if self._is_parallel: + domain_decomposer = DomainDecomposer( + self._logger, self._participant.get_mesh_dimensions(self._macro_mesh_name), self._rank, self._size) + coupling_mesh_bounds = domain_decomposer.decompose_macro_domain(self._macro_bounds, self._ranks_per_axis) + else: + coupling_mesh_bounds = self._macro_bounds + + self._participant.set_mesh_access_region(self._macro_mesh_name, coupling_mesh_bounds) + + # initialize preCICE + self._participant.initialize() + + self._mesh_vertex_ids, mesh_vertex_coords = self._participant.get_mesh_vertex_ids_and_coordinates( + self._macro_mesh_name) + assert (mesh_vertex_coords.size != 0), "Macro mesh has no vertices." + + self._local_number_of_sims, _ = mesh_vertex_coords.shape + self._logger.info("Number of local micro simulations = {}".format(self._local_number_of_sims)) + + if self._local_number_of_sims == 0: + if self._is_parallel: + self._logger.info( + "Rank {} has no micro simulations and hence will not do any computation.".format( + self._rank)) + self._is_rank_empty = True + else: + raise Exception("Micro Manager has no micro simulations.") + + nms_all_ranks = np.zeros(self._size, dtype=np.int64) + # Gather number of micro simulations that each rank has, because this rank needs to know how many micro + # simulations have been created by previous ranks, so that it can set + # the correct global IDs + self._comm.Allgather(np.array(self._local_number_of_sims), nms_all_ranks) + + # Get global number of micro simulations + self._global_number_of_sims = np.sum(nms_all_ranks) + + if self._is_adaptivity_on: + for name, is_data_vector in self._adaptivity_data_names.items(): + if is_data_vector: + self._data_for_adaptivity[name] = np.zeros( + (self._local_number_of_sims, self._participant.get_data_dimensions( + self._macro_mesh_name, name))) + else: + self._data_for_adaptivity[name] = np.zeros((self._local_number_of_sims)) + + # Create lists of local and global IDs + sim_id = np.sum(nms_all_ranks[:self._rank]) + self._global_ids_of_local_sims = [] # DECLARATION + for i in range(self._local_number_of_sims): + self._global_ids_of_local_sims.append(sim_id) + sim_id += 1 + + self._micro_sims = [None] * self._local_number_of_sims # DECLARATION + + micro_problem = getattr( + __import__( + self._config.get_micro_file_name(), + fromlist=["MicroSimulation"]), + "MicroSimulation") + + # Create micro simulation objects + for i in range(self._local_number_of_sims): + self._micro_sims[i] = create_simulation_class( + micro_problem)(self._global_ids_of_local_sims[i]) + + self._logger.info("Micro simulations with global IDs {} - {} created.".format( + self._global_ids_of_local_sims[0], self._global_ids_of_local_sims[-1])) + + if self._is_adaptivity_on: + if self._adaptivity_type == "local": + self._adaptivity_controller = LocalAdaptivityCalculator( + self._config, self._logger) + self._number_of_sims_for_adaptivity = self._local_number_of_sims + elif self._adaptivity_type == "global": + self._adaptivity_controller = GlobalAdaptivityCalculator( + self._config, + self._logger, + self._global_number_of_sims, + self._global_ids_of_local_sims, + self._rank, + self._comm) + self._number_of_sims_for_adaptivity = self._global_number_of_sims + + self._micro_sims_active_steps = np.zeros(self._local_number_of_sims) + + self._micro_sims_have_output = False + if hasattr(micro_problem, 'output') and callable(getattr(micro_problem, 'output')): + self._micro_sims_have_output = True + + self._dt = self._participant.get_max_time_step_size() + def _read_data_from_precice(self) -> list: """ Read data from preCICE. @@ -523,8 +507,6 @@ def main(): manager = MicroManager(config_file_path) - manager.initialize() - manager.solve() diff --git a/micro_manager/micro_simulation.py b/micro_manager/micro_simulation.py index df505bb3..902eaba1 100644 --- a/micro_manager/micro_simulation.py +++ b/micro_manager/micro_simulation.py @@ -21,13 +21,10 @@ def create_simulation_class(micro_simulation_class): """ class Simulation(micro_simulation_class): def __init__(self, global_id): - micro_simulation_class.__init__(self) + micro_simulation_class.__init__(self, global_id) self._global_id = global_id def get_global_id(self) -> int: return self._global_id - def set_global_id(self, global_id) -> None: - self._global_id = global_id - return Simulation diff --git a/tests/integration/test_unit_cube/micro_dummy.py b/tests/integration/test_unit_cube/micro_dummy.py index 59bef26c..433967ff 100644 --- a/tests/integration/test_unit_cube/micro_dummy.py +++ b/tests/integration/test_unit_cube/micro_dummy.py @@ -6,10 +6,11 @@ class MicroSimulation: - def __init__(self): + def __init__(self, sim_id): """ Constructor of MicroSimulation class. """ + self._sim_id = sim_id self._micro_scalar_data = None self._micro_vector_data = None self._checkpoint = None diff --git a/tests/integration/test_unit_cube/run_micro_manager.py b/tests/integration/test_unit_cube/run_micro_manager.py index a3d0dd6c..3cffab3a 100644 --- a/tests/integration/test_unit_cube/run_micro_manager.py +++ b/tests/integration/test_unit_cube/run_micro_manager.py @@ -11,6 +11,4 @@ manager = MicroManager(args.config) -manager.initialize() - manager.solve() diff --git a/tests/unit/test_adaptivity_parallel.py b/tests/unit/test_adaptivity_parallel.py index f237feb8..2f1b2a4e 100644 --- a/tests/unit/test_adaptivity_parallel.py +++ b/tests/unit/test_adaptivity_parallel.py @@ -17,14 +17,11 @@ def test_update_inactive_sims_global_adaptivity(self): Run this test in parallel using MPI with 2 ranks. """ if self._rank == 0: - is_sim_on_this_rank = [True, True, True, False, False] global_ids = [0, 1, 2] elif self._rank == 1: - is_sim_on_this_rank = [False, False, False, True, True] global_ids = [3, 4] is_sim_active = np.array([False, False, True, True, False]) - rank_of_sim = [0, 0, 0, 1, 1] sim_is_associated_to = [3, 3, -2, -2, 2] expected_is_sim_active = np.array([True, False, True, True, True]) expected_sim_is_associated_to = [-2, 3, -2, -2, -2] @@ -34,8 +31,7 @@ def test_update_inactive_sims_global_adaptivity(self): adaptivity_controller = GlobalAdaptivityCalculator( configurator, MagicMock(), - is_sim_on_this_rank, - rank_of_sim, + 5, global_ids, rank=self._rank, comm=self._comm) @@ -78,6 +74,63 @@ def get_state(self): elif self._rank == 1: self.assertTrue(np.array_equal([2, 2, 2], dummy_micro_sims[1].get_state())) + def test_update_all_active_sims_global_adaptivity(self): + """ + Test functionality to calculate adaptivity when all simulations are active, for a global adaptivity setting. + Run this test in parallel using MPI with 2 ranks. + """ + if self._rank == 0: + global_ids = [0, 1, 2] + data_for_adaptivity = {"data1": [1.0, 1.0, 1.0], "data2": [13.0, 13.0, 13.0]} + elif self._rank == 1: + global_ids = [3, 4] + data_for_adaptivity = {"data1": [1.0, 1.0], "data2": [13.0, 13.0]} + + similarity_dists = np.zeros((5, 5)) + is_sim_active = np.array([True, True, True, True, True]) + sim_is_associated_to = [-2, -2, -2, -2, -2] + expected_is_sim_active = np.array([False, False, False, False, True]) + expected_sim_is_associated_to = [4, 4, 4, 4, -2] + + configurator = MagicMock() + configurator.get_adaptivity_hist_param = MagicMock(return_value=0.1) + configurator.get_adaptivity_refining_const = MagicMock(return_value=0.05) + configurator.get_adaptivity_coarsening_const = MagicMock(return_value=0.2) + configurator.get_adaptivity_similarity_measure = MagicMock(return_value="L2rel") + adaptivity_controller = GlobalAdaptivityCalculator( + configurator, + MagicMock(), + 5, + global_ids, + rank=self._rank, + comm=self._comm) + + adaptivity_controller._adaptivity_data_names = {"data1": "scalar", "data2": "scalar"} + + class MicroSimulation(): + def __init__(self, global_id) -> None: + self._global_id = global_id + self._state = [global_id] * 3 + + def get_global_id(self): + return self._global_id + + def set_state(self, state): + self._state = state + + def get_state(self): + return self._state.copy() + + dummy_micro_sims = [] + for i in global_ids: + dummy_micro_sims.append(MicroSimulation(i)) + + _, is_sim_active, sim_is_associated_to = adaptivity_controller.compute_adaptivity( + 0.1, dummy_micro_sims, similarity_dists, is_sim_active, sim_is_associated_to, data_for_adaptivity) + + self.assertTrue(np.array_equal(expected_is_sim_active, is_sim_active)) + self.assertTrue(np.array_equal(expected_sim_is_associated_to, sim_is_associated_to)) + def test_communicate_micro_output(self): """ Test functionality to communicate micro output from active sims to their associated inactive sims, for a global adaptivity setting. @@ -87,18 +140,15 @@ def test_communicate_micro_output(self): output_1 = {"data1.1": 10.0, "data1.2": [10.0, 20.0]} if self._rank == 0: - is_sim_on_this_rank = [True, True, True, False, False] global_ids = [0, 1, 2] sim_output = [None, None, output_0] expected_sim_output = [output_1, output_1, output_0] elif self._rank == 1: - is_sim_on_this_rank = [False, False, False, True, True] global_ids = [3, 4] sim_output = [output_1, None] expected_sim_output = [output_1, output_0] is_sim_active = np.array([False, False, True, True, False]) - rank_of_sim = [0, 0, 0, 1, 1] sim_is_associated_to = [3, 3, -2, -2, 2] configurator = MagicMock() @@ -106,8 +156,7 @@ def test_communicate_micro_output(self): adaptivity_controller = GlobalAdaptivityCalculator( configurator, MagicMock(), - is_sim_on_this_rank, - rank_of_sim, + 5, global_ids, rank=self._rank, comm=self._comm) diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index c020f09d..ade67b32 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -5,7 +5,7 @@ class MicroSimulation: - def __init__(self): + def __init__(self, sim_id): self.very_important_value = 0 def initialize(self): @@ -46,14 +46,12 @@ def test_micromanager_constructor(self): self.assertDictEqual(self.fake_write_data_names, manager._write_data_names) self.assertEqual(manager._micro_n_out, 10) - def test_initialize(self): + def test_initialization(self): """ Test if the initialize function of the MicroManager class initializes member variables to correct values """ manager = micro_manager.MicroManager('micro-manager-config.json') - manager.initialize() - self.assertEqual(manager._dt, 0.1) # from Interface.initialize self.assertEqual(manager._global_number_of_sims, 4) self.assertListEqual(manager._macro_bounds, self.macro_bounds) @@ -85,7 +83,7 @@ def test_solve_mico_sims(self): """ manager = micro_manager.MicroManager('micro-manager-config.json') manager._local_number_of_sims = 4 - manager._micro_sims = [MicroSimulation() for _ in range(4)] + 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)