From 13819a9033971ee860ffc4cf357fbb3a70585100 Mon Sep 17 00:00:00 2001 From: erikscheurer <84399192+erikscheurer@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:33:07 +0200 Subject: [PATCH] Update preCICE API calls for `precice:develop` (#51) * [WIP] Update preCICE API calls * Changing mocked preCICE to v3 * Explicitly mention v3.0.0.0dev0 for pyprecice * Use correct API function get_mesh_vertex_ids_and_coordinates * Use pyprecice v3.0.0.0dev1 * Formatting * Work with pyprecice v3.0.0.0dev2 * Partially fixing tests * Fix tests * Formatting * Fix precice-configs * Move dimensions parameter back to precice-configuration * Move initialize after requires_initial_data() * Remove initialize() from micro simulations, along with the writing of initial data to preCICE * In time loop, get dt from preCICE * Make precice findable in Action, remove initialize() from pybind11 wrapper, other fixes * Port unit_cube.py to v3 * Port config used in adaptivity integration test * Port config to v3, remove use of su for parallel tests * Allow mpiexec to run as root for parallel tests, fix error in unit_cube.py for integration tests * Modify LD_LIBRARY_PATH in Action, fix unit_cube.py * Remove user precice in adaptivity parallel tests * Use correct container and proper working directories in Actions * Move dimensions attribute into mesh tag * Pass correct dt to read_data(...) command --------- Co-authored-by: Ishaan Desai --- .github/workflows/run-adaptivity-test.yml | 41 ++++--- .../run-domain-decomposition-tests.yml | 36 +++--- .github/workflows/run-macro-micro-dummy.yml | 2 +- .github/workflows/run-unit-tests.yml | 4 +- docs/micro-simulation-convert-to-library.md | 10 -- examples/.gitignore | 2 +- examples/cpp-dummy/micro_cpp_dummy.cpp | 10 -- examples/cpp-dummy/micro_cpp_dummy.hpp | 1 - examples/macro_dummy.py | 56 ++++----- examples/precice-config-adaptivity.xml | 17 ++- examples/precice-config.xml | 19 ++- examples/python-dummy/micro_dummy.py | 5 - micro_manager/micro_manager.py | 115 ++++++------------ setup.py | 2 +- .../integration/test_unit_cube/clean-test.sh | 1 + .../test_unit_cube/precice-config.xml | 23 ++-- tests/integration/test_unit_cube/unit_cube.py | 103 ++++++---------- tests/unit/.gitignore | 1 + tests/unit/precice.py | 87 ++++++------- tests/unit/test_micro_manager.py | 2 + 20 files changed, 201 insertions(+), 336 deletions(-) create mode 100644 tests/unit/.gitignore diff --git a/.github/workflows/run-adaptivity-test.yml b/.github/workflows/run-adaptivity-test.yml index b5ec9145..1ffb3e61 100644 --- a/.github/workflows/run-adaptivity-test.yml +++ b/.github/workflows/run-adaptivity-test.yml @@ -1,4 +1,4 @@ -name: Test adaptivity +name: Test for functions in classes AdaptivityCalculator, LocalAdaptivityCalculator, GlobalAdaptivityCalculator on: push: branches: @@ -8,10 +8,10 @@ on: branches: - "*" jobs: - integration_test: - name: Run integration test + adaptivity_integration_tests: + name: Run adaptivity integration tests runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout repository uses: actions/checkout@v3 @@ -33,17 +33,21 @@ jobs: - name: Run integration test with local adaptivity timeout-minutes: 3 working-directory: micro-manager/tests/integration/test_unit_cube - run: python3 unit_cube.py & python3 run_micro_manager.py --config micro-manager-config-local-adaptivity.json + run: | + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + python3 unit_cube.py & python3 run_micro_manager.py --config micro-manager-config-local-adaptivity.json - - name: Run integration test serially with global adaptivity + - name: Run integration test with global adaptivity timeout-minutes: 3 working-directory: micro-manager/tests/integration/test_unit_cube - run: python3 unit_cube.py & python3 run_micro_manager.py --config micro-manager-config-global-adaptivity.json + run: | + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + python3 unit_cube.py & python3 run_micro_manager.py --config micro-manager-config-global-adaptivity.json - unit_tests_serial: - name: Run unit tests (serial variants) + adaptivity_unit_tests_serial: + name: Run adaptivity unit tests in serial runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout Repository uses: actions/checkout@v3 @@ -66,10 +70,10 @@ jobs: working-directory: micro-manager/tests/unit run: python3 -m unittest test_adaptivity_serial.py - unit_tests_parallel: - name: Run unit tests (parallel variants) + adaptivity_unit_tests_parallel: + name: Run adaptivity unit tests in parallel runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout Repository uses: actions/checkout@v3 @@ -85,21 +89,18 @@ jobs: - name: Use mpi4py uses: mpi4py/setup-mpi@v1 - - name: Add user precice - run: useradd -m -s /bin/bash precice - - name: Install Dependencies working-directory: micro-manager run: | apt-get -qq update apt-get -qq install python3-dev python3-pip git python-is-python3 pkg-config - su -c "python -m pip install --upgrade pip" precice - su -c "pip install setuptools wheel twine" precice + python -m pip install --upgrade pip + pip install setuptools wheel twine - name: Install Micro Manager working-directory: micro-manager - run: su -c "pip3 install --user ." precice + run: pip3 install --user . - name: Run unit tests working-directory: micro-manager/tests/unit - run: su -c "mpiexec -n 2 python3 -m unittest test_adaptivity_parallel.py" precice + run: mpiexec -n 2 --allow-run-as-root python3 -m unittest test_adaptivity_parallel.py diff --git a/.github/workflows/run-domain-decomposition-tests.yml b/.github/workflows/run-domain-decomposition-tests.yml index 444ebff3..560e2c79 100644 --- a/.github/workflows/run-domain-decomposition-tests.yml +++ b/.github/workflows/run-domain-decomposition-tests.yml @@ -1,4 +1,4 @@ -name: Test domain decomposition +name: Test functions in class DomainDecomposer on: push: branches: @@ -8,10 +8,10 @@ on: branches: - "*" jobs: - integration_test: - name: Run integration tests + domain_decomposition_integration_tests: + name: Run domain decomposition integration tests runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout repository uses: actions/checkout@v3 @@ -27,39 +27,31 @@ jobs: - name: Use mpi4py uses: mpi4py/setup-mpi@v1 - - name: Add user precice - run: useradd -m -s /bin/bash precice - - name: Install Dependencies working-directory: micro-manager run: | apt-get -qq install python3-dev python3-pip git python-is-python3 pkg-config - su -c "python -m pip install --upgrade pip" precice - su -c "pip install setuptools wheel twine" precice + python -m pip install --upgrade pip + pip install setuptools wheel twine - name: Install micro-manager working-directory: micro-manager - run: su -c "pip3 install --user ." precice + run: pip3 install --user . - name: Run integration test (variant 1) timeout-minutes: 3 - working-directory: micro-manager/tests/integration - run: | - chown -R precice test_unit_cube/ - cd test_unit_cube/ - su -c "mpiexec -n 2 python3 run_micro_manager.py --config micro-manager-config-parallel-1.json & python3 unit_cube.py" precice + working-directory: micro-manager/tests/integration/test_unit_cube + run: mpiexec -n 2 --allow-run-as-root python3 run_micro_manager.py --config micro-manager-config-parallel-1.json & python3 unit_cube.py - name: Run integration test (variant 2) timeout-minutes: 3 - working-directory: micro-manager/tests/integration - run: | - cd test_unit_cube/ - su -c "mpiexec -n 6 --oversubscribe python3 run_micro_manager.py --config micro-manager-config-parallel-2.json & python3 unit_cube.py" precice + working-directory: micro-manager/tests/integration/test_unit_cube + run: mpiexec -n 6 --oversubscribe --allow-run-as-root python3 run_micro_manager.py --config micro-manager-config-parallel-2.json & python3 unit_cube.py - unit_tests: - name: Run unit tests + domain_decomposition_unit_tests: + name: Run domain decomposition unit tests runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout Repository uses: actions/checkout@v3 diff --git a/.github/workflows/run-macro-micro-dummy.yml b/.github/workflows/run-macro-micro-dummy.yml index 2394f853..1ddf751e 100644 --- a/.github/workflows/run-macro-micro-dummy.yml +++ b/.github/workflows/run-macro-micro-dummy.yml @@ -11,7 +11,7 @@ jobs: run_dummy: name: Run dummy runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - name: Checkout Repository diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index daa0d23b..e099e4c4 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -1,4 +1,4 @@ -name: Run unit tests +name: Run unit tests for functions in micro_manager.py on: push: branches: @@ -10,7 +10,7 @@ on: jobs: unit-tests: runs-on: ubuntu-latest - container: precice/precice + container: precice/precice:develop steps: - uses: actions/checkout@v3 with: diff --git a/docs/micro-simulation-convert-to-library.md b/docs/micro-simulation-convert-to-library.md index 8cafb9ec..7ed8dd48 100644 --- a/docs/micro-simulation-convert-to-library.md +++ b/docs/micro-simulation-convert-to-library.md @@ -19,16 +19,6 @@ class MicroSimulation: # Name is fixed Constructor of class MicroSimulation. """ - def initialize(self) -> dict: - """ - Initialize the micro simulation. This function is *optional*. - - Returns - ------- - data : dict - Python dictionary with names of micro data as keys and the data as values at the initial condition - """ - def solve(self, macro_data: dict, dt: float) -> dict: """ Solve one time step of the micro simulation for transient problems or solve until steady state for steady-state problems. diff --git a/examples/.gitignore b/examples/.gitignore index cc479d19..accb11e2 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,7 +1,7 @@ # preCICE related files *.log *events.json -precice-run/ +precice-events/ # PyCharm related files .idea diff --git a/examples/cpp-dummy/micro_cpp_dummy.cpp b/examples/cpp-dummy/micro_cpp_dummy.cpp index 78b473ba..df922470 100644 --- a/examples/cpp-dummy/micro_cpp_dummy.cpp +++ b/examples/cpp-dummy/micro_cpp_dummy.cpp @@ -15,15 +15,6 @@ // Constructor MicroSimulation::MicroSimulation() : _micro_scalar_data(0), _state(0) {} -// Initialize -void MicroSimulation::initialize() -{ - std::cout << "Initialize micro problem\n"; - _micro_scalar_data = 0; - _micro_vector_data.clear(); - _state = 0; -} - // Solve py::dict MicroSimulation::solve(py::dict macro_data, double dt) { @@ -74,7 +65,6 @@ PYBIND11_MODULE(micro_dummy, m) { py::class_(m, "MicroSimulation") .def(py::init()) - .def("initialize", &MicroSimulation::initialize) .def("solve", &MicroSimulation::solve) .def("get_state", &MicroSimulation::get_state) .def("set_state", &MicroSimulation::set_state) diff --git a/examples/cpp-dummy/micro_cpp_dummy.hpp b/examples/cpp-dummy/micro_cpp_dummy.hpp index 4e9828d7..03e7b005 100644 --- a/examples/cpp-dummy/micro_cpp_dummy.hpp +++ b/examples/cpp-dummy/micro_cpp_dummy.hpp @@ -15,7 +15,6 @@ class MicroSimulation { public: MicroSimulation(); - void initialize(); // 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); MicroSimulation __deepcopy__(py::dict memo); diff --git a/examples/macro_dummy.py b/examples/macro_dummy.py index 82917f2c..0cafc06b 100644 --- a/examples/macro_dummy.py +++ b/examples/macro_dummy.py @@ -15,73 +15,59 @@ def main(): t = t_checkpoint = 0 # preCICE setup - interface = precice.Interface("Macro-dummy", "precice-config.xml", 0, 1) + interface = precice.Participant("Macro-dummy", "precice-config.xml", 0, 1) # define coupling meshes read_mesh_name = write_mesh_name = "macro-mesh" - read_mesh_id = interface.get_mesh_id(read_mesh_name) read_data_names = {"micro-scalar-data": 0, "micro-vector-data": 1} - write_mesh_id = interface.get_mesh_id(write_mesh_name) write_data_names = {"macro-scalar-data": 0, "macro-vector-data": 1} # Coupling mesh - coords = np.zeros((nv, interface.get_dimensions())) + coords = np.zeros((nv, interface.get_mesh_dimensions(write_mesh_name))) for x in range(nv): - for d in range(interface.get_dimensions()): + for d in range(interface.get_mesh_dimensions(write_mesh_name)): coords[x, d] = x # Define Gauss points on entire domain as coupling mesh - vertex_ids = interface.set_mesh_vertices(read_mesh_id, coords) - - read_data_ids = dict() - # coupling data - for name, dim in read_data_names.items(): - read_data_ids[name] = interface.get_data_id(name, read_mesh_id) - - write_data_ids = dict() - for name, dim in write_data_names.items(): - write_data_ids[name] = interface.get_data_id(name, write_mesh_id) - - # initialize preCICE - dt = interface.initialize() + vertex_ids = interface.set_mesh_vertices(read_mesh_name, coords) write_scalar_data = np.zeros(nv) - write_vector_data = np.zeros((nv, interface.get_dimensions())) + write_vector_data = np.zeros((nv, interface.get_data_dimensions(write_mesh_name, "macro-vector-data"))) for i in range(nv): write_scalar_data[i] = i - for d in range(interface.get_dimensions()): + for d in range(interface.get_data_dimensions(write_mesh_name, "macro-vector-data")): write_vector_data[i, d] = i - if interface.is_action_required(precice.action_write_initial_data()): + if interface.requires_initial_data(): for name, dim in write_data_names.items(): if dim == 0: - interface.write_block_scalar_data(write_data_ids[name], vertex_ids, write_scalar_data) + interface.write_block_scalar_data(name, vertex_ids, write_scalar_data) elif dim == 1: - interface.write_block_vector_data(write_data_ids[name], vertex_ids, write_vector_data) - interface.mark_action_fulfilled(precice.action_write_initial_data()) + interface.write_block_vector_data(name, vertex_ids, write_vector_data) - interface.initialize_data() + # initialize preCICE + interface.initialize() + dt = interface.get_max_time_step_size() # time loop while interface.is_coupling_ongoing(): # write checkpoint - if interface.is_action_required(precice.action_write_iteration_checkpoint()): + if interface.requires_writing_checkpoint(): print("Saving macro state") t_checkpoint = t n_checkpoint = n - interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint()) for name, dim in read_data_names.items(): if dim == 0: - read_scalar_data = interface.read_block_scalar_data(read_data_ids[name], vertex_ids) + read_scalar_data = interface.read_data(read_mesh_name, name, vertex_ids, 1) elif dim == 1: - read_vector_data = interface.read_block_vector_data(read_data_ids[name], vertex_ids) + read_vector_data = interface.read_data(read_mesh_name, name, vertex_ids, 1) write_scalar_data[:] = read_scalar_data[:] for i in range(nv): - for d in range(interface.get_dimensions()): + for d in range(interface.get_data_dimensions(read_mesh_name, "micro-vector-data")): write_vector_data[i, d] = read_vector_data[i, d] if t > 1: # to trigger adaptivity after some time # ensure that the data is different from the previous time step @@ -90,22 +76,22 @@ def main(): for name, dim in write_data_names.items(): if dim == 0: - interface.write_block_scalar_data(write_data_ids[name], vertex_ids, write_scalar_data) + interface.write_data(write_mesh_name, name, vertex_ids, write_scalar_data) elif dim == 1: - interface.write_block_vector_data(write_data_ids[name], vertex_ids, write_vector_data) + interface.write_data(write_mesh_name, name, vertex_ids, write_vector_data) # do the coupling - dt = interface.advance(dt) + interface.advance(dt) + dt = interface.get_max_time_step_size() # advance variables n += 1 t += dt - if interface.is_action_required(precice.action_read_iteration_checkpoint()): + if interface.requires_reading_checkpoint(): print("Reverting to old macro state") t = t_checkpoint n = n_checkpoint - interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint()) interface.finalize() diff --git a/examples/precice-config-adaptivity.xml b/examples/precice-config-adaptivity.xml index a3764b26..4418fe36 100644 --- a/examples/precice-config-adaptivity.xml +++ b/examples/precice-config-adaptivity.xml @@ -1,13 +1,11 @@ - + - - @@ -16,7 +14,7 @@ - + @@ -27,7 +25,7 @@ - + @@ -35,7 +33,7 @@ - + @@ -45,20 +43,19 @@ - + - - + + - diff --git a/examples/precice-config.xml b/examples/precice-config.xml index 9c487c7c..9bebd403 100644 --- a/examples/precice-config.xml +++ b/examples/precice-config.xml @@ -1,12 +1,10 @@ - + - - - + @@ -14,7 +12,7 @@ - + @@ -23,7 +21,7 @@ - + @@ -31,7 +29,7 @@ - + @@ -39,20 +37,19 @@ - + - - + + - diff --git a/examples/python-dummy/micro_dummy.py b/examples/python-dummy/micro_dummy.py index 783d36d8..f267d3b9 100644 --- a/examples/python-dummy/micro_dummy.py +++ b/examples/python-dummy/micro_dummy.py @@ -15,11 +15,6 @@ def __init__(self): self._micro_vector_data = None self._state = None - def initialize(self): - self._micro_scalar_data = 0 - self._micro_vector_data = [] - self._state = 0 - def solve(self, macro_data, dt): assert dt != 0 self._micro_vector_data = [] diff --git a/micro_manager/micro_manager.py b/micro_manager/micro_manager.py index ef747d7c..452599b5 100644 --- a/micro_manager/micro_manager.py +++ b/micro_manager/micro_manager.py @@ -64,26 +64,23 @@ def __init__(self, config_file: str) -> None: self._logger.info("Provided configuration file: {}".format(config_file)) self._config = Config(self._logger, config_file) - # Define the preCICE interface - self._interface = precice.Interface( + # Define the preCICE Participant + self._participant = precice.Participant( "Micro-Manager", self._config.get_config_file_name(), self._rank, self._size) - self._macro_mesh_id = self._interface.get_mesh_id(self._config.get_macro_mesh_name()) + micro_file_name = self._config.get_micro_file_name() + self._micro_problem = getattr(__import__(micro_file_name, fromlist=["MicroSimulation"]), "MicroSimulation") - # Data names and ids of data written to preCICE + self._macro_mesh_name = self._config.get_macro_mesh_name() + + # Data names of data written to preCICE self._write_data_names = self._config.get_write_data_names() - self._write_data_ids = dict() - for name in self._write_data_names.keys(): - self._write_data_ids[name] = self._interface.get_data_id(name, self._macro_mesh_id) - # Data names and ids of data read from preCICE + # Data names of data read from preCICE self._read_data_names = self._config.get_read_data_names() - self._read_data_ids = dict() - for name in self._read_data_names.keys(): - self._read_data_ids[name] = self._interface.get_data_id(name, self._macro_mesh_id) self._macro_bounds = self._config.get_macro_domain_bounds() @@ -136,20 +133,22 @@ def initialize(self) -> None: - 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._interface.get_dimensions(), "Provided macro mesh bounds are of incorrect dimension" + 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._interface.get_dimensions(), self._rank, self._size) + 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._interface.set_mesh_access_region(self._macro_mesh_id, coupling_mesh_bounds) + self._participant.set_mesh_access_region(self._macro_mesh_name, coupling_mesh_bounds) - # Initialize preCICE - self._dt = self._interface.initialize() + # initialize preCICE + self._participant.initialize() - self._mesh_vertex_ids, mesh_vertex_coords = self._interface.get_mesh_vertices_and_ids(self._macro_mesh_id) + self._mesh_vertex_ids, mesh_vertex_coords = self._participant.get_mesh_vertex_ids_and_coordinates( + self._macro_mesh_name) self._local_number_of_sims, _ = mesh_vertex_coords.shape self._logger.info("Number of local micro simulations = {}".format(self._local_number_of_sims)) @@ -175,7 +174,8 @@ def initialize(self) -> None: 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._interface.get_dimensions())) + (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)) @@ -187,7 +187,6 @@ def initialize(self) -> None: sim_id += 1 self._micro_sims = [None] * self._local_number_of_sims # DECLARATION - micro_sims_output = [None] * self._local_number_of_sims micro_problem = getattr( __import__( @@ -235,37 +234,14 @@ def initialize(self) -> None: self._micro_sims[i] = ( create_simulation_class(micro_problem)(self._global_ids_of_local_sims[i])) - # Initialize micro simulations if initialize() method exists - if hasattr(micro_problem, 'initialize') and callable(getattr(micro_problem, 'initialize')): - for i in range(self._local_number_of_sims): - micro_sims_output[i] = self._micro_sims[i].initialize() - if micro_sims_output[i] is not None: - if self._is_micro_solve_time_required: - micro_sims_output[i]["micro_sim_time"] = 0.0 - if self._is_adaptivity_on: - micro_sims_output[i]["active_state"] = 0 - micro_sims_output[i]["active_steps"] = 0 - else: - micro_sims_output[i] = dict() - for name, is_data_vector in self._write_data_names.items(): - if is_data_vector: - micro_sims_output[i][name] = np.zeros(self._interface.get_dimensions()) - else: - micro_sims_output[i][name] = 0.0 - - self._logger.info("Micro simulations with global IDs {} - {} initialized.".format( + 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 - # Write initial data if required - if self._interface.is_action_required(precice.action_write_initial_data()): - self._write_data_to_precice(micro_sims_output) - self._interface.mark_action_fulfilled(precice.action_write_initial_data()) - - self._interface.initialize_data() + self._dt = self._participant.get_max_time_step_size() def solve(self) -> None: """ @@ -297,9 +273,9 @@ def solve(self) -> None: sim_is_associated_to_cp = None sim_states_cp = [None] * self._local_number_of_sims - while self._interface.is_coupling_ongoing(): + while self._participant.is_coupling_ongoing(): # Write a checkpoint - if self._interface.is_action_required(precice.action_write_iteration_checkpoint()): + if self._participant.requires_writing_checkpoint(): for i in range(self._local_number_of_sims): sim_states_cp[i] = self._micro_sims[i].get_state() t_checkpoint = t @@ -325,9 +301,6 @@ def solve(self) -> None: for active_id in active_sim_ids: self._micro_sims_active_steps[active_id] += 1 - self._interface.mark_action_fulfilled( - precice.action_write_iteration_checkpoint()) - micro_sims_input = self._read_data_from_precice() if self._is_adaptivity_on: @@ -351,13 +324,14 @@ def solve(self) -> None: self._write_data_to_precice(micro_sims_output) - self._dt = self._interface.advance(self._dt) + self._participant.advance(self._dt) + self._dt = self._participant.get_max_time_step_size() t += self._dt n += 1 # Revert micro simulations to their last checkpoints if required - if self._interface.is_action_required(precice.action_read_iteration_checkpoint()): + if self._participant.requires_reading_checkpoint(): for i in range(self._local_number_of_sims): self._micro_sims[i].set_state(sim_states_cp[i]) n = n_checkpoint @@ -370,8 +344,6 @@ def solve(self) -> None: is_sim_active = np.copy(is_sim_active_cp) sim_is_associated_to = np.copy(sim_is_associated_to_cp) - self._interface.mark_action_fulfilled( - precice.action_read_iteration_checkpoint()) else: # Time window has converged, now micro output can be generated self._logger.info("Micro simulations {} - {} have converged at t = {}".format( self._micro_sims[0].get_global_id(), self._micro_sims[-1].get_global_id(), t)) @@ -381,7 +353,7 @@ def solve(self) -> None: for sim in self._micro_sims: sim.output() - self._interface.finalize() + self._participant.finalize() # *************** # Private methods @@ -400,13 +372,9 @@ def _read_data_from_precice(self) -> list: for name in self._read_data_names.keys(): read_data[name] = [] - for name, is_data_vector in self._read_data_names.items(): - if is_data_vector: - read_data.update({name: self._interface.read_block_vector_data( - self._read_data_ids[name], self._mesh_vertex_ids)}) - else: - read_data.update({name: self._interface.read_block_scalar_data( - self._read_data_ids[name], self._mesh_vertex_ids)}) + for name in self._read_data_names.keys(): + read_data.update({name: self._participant.read_data( + self._macro_mesh_name, name, self._mesh_vertex_ids, self._dt)}) if self._is_adaptivity_on: if name in self._adaptivity_macro_data_names: @@ -428,25 +396,16 @@ def _write_data_to_precice(self, data: list) -> None: for name in data[0]: data_dict[name] = [] - for output_dict in data: - for name, values in output_dict.items(): + for d in data: + for name, values in d.items(): data_dict[name].append(values) - for dname, is_data_vector in self._write_data_names.items(): - if is_data_vector: - self._interface.write_block_vector_data( - self._write_data_ids[dname], self._mesh_vertex_ids, data_dict[dname]) - else: - self._interface.write_block_scalar_data( - self._write_data_ids[dname], self._mesh_vertex_ids, data_dict[dname]) + for dname in self._write_data_names.keys(): + self._participant.write_data( + self._macro_mesh_name, dname, self._mesh_vertex_ids, data_dict[dname]) else: - for dname, is_data_vector in self._write_data_names.items(): - if is_data_vector: - self._interface.write_block_vector_data( - self._write_data_ids[dname], [], np.array([])) - else: - self._interface.write_block_scalar_data( - self._write_data_ids[dname], [], np.array([])) + for dname in self._write_data_names.keys(): + self._participant.write_data(self._macro_mesh_name, dname, [], np.array([])) def _solve_micro_simulations(self, micro_sims_input: list) -> list: """ diff --git a/setup.py b/setup.py index 38030dcb..7c78e615 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ packages=find_packages( exclude=['examples']), install_requires=[ - 'pyprecice==2.5.0.4', + 'pyprecice>=3.0.0.0dev2', 'numpy>=1.13.3', 'mpi4py'], test_suite='tests', diff --git a/tests/integration/test_unit_cube/clean-test.sh b/tests/integration/test_unit_cube/clean-test.sh index 5a0def56..2c6a7a6b 100755 --- a/tests/integration/test_unit_cube/clean-test.sh +++ b/tests/integration/test_unit_cube/clean-test.sh @@ -2,6 +2,7 @@ rm -fv *-events-summary.json rm -fv *-events.json rm -fv *.log rm -r -fv precice-run/ +rm -r -fv precice-events/ rm -fv *.vtk rm -fv *.out rm -fv *.err diff --git a/tests/integration/test_unit_cube/precice-config.xml b/tests/integration/test_unit_cube/precice-config.xml index e98d801f..c4c1b0af 100644 --- a/tests/integration/test_unit_cube/precice-config.xml +++ b/tests/integration/test_unit_cube/precice-config.xml @@ -1,13 +1,11 @@ - + - + - - - - + + @@ -16,7 +14,7 @@ - + @@ -27,7 +25,7 @@ - + @@ -35,7 +33,7 @@ - + @@ -46,7 +44,7 @@ - + @@ -54,9 +52,8 @@ - - + + - diff --git a/tests/integration/test_unit_cube/unit_cube.py b/tests/integration/test_unit_cube/unit_cube.py index 1e65ac43..e605b372 100644 --- a/tests/integration/test_unit_cube/unit_cube.py +++ b/tests/integration/test_unit_cube/unit_cube.py @@ -15,29 +15,21 @@ def main(): t_end = 10 # preCICE setup - interface = precice.Interface("macro-cube", "precice-config.xml", 0, 1) - - # define coupling meshes - read_mesh_name = write_mesh_name = "macro-cube-mesh" - read_mesh_id = interface.get_mesh_id(read_mesh_name) + participant = precice.Participant("macro-cube", "precice-config.xml", 0, 1) + mesh_name = "macro-cube-mesh" read_data_names = {"micro-scalar-data": 0, "micro-vector-data": 1} - - write_mesh_id = interface.get_mesh_id(write_mesh_name) write_data_names = {"macro-scalar-data": 0, "macro-vector-data": 1} # Coupling mesh - unit cube with 5 points in each direction - np_axis = 2 + np_axis = 5 x_coords, y_coords, z_coords = np.meshgrid( np.linspace(0, 1, np_axis), np.linspace(0, 1, np_axis), np.linspace(0, 1, np_axis) ) - nv = np_axis ** interface.get_dimensions() - coords = np.zeros((nv, interface.get_dimensions())) - - write_scalar_data = np.zeros(nv) - write_vector_data = np.zeros((nv, interface.get_dimensions())) + nv = np_axis ** participant.get_mesh_dimensions(mesh_name) + coords = np.zeros((nv, participant.get_mesh_dimensions(mesh_name))) # Define unit cube coordinates for z in range(np_axis): @@ -48,6 +40,13 @@ def main(): coords[n, 1] = y_coords[x, y, z] coords[n, 2] = z_coords[x, y, z] + # Define points on entire domain as coupling mesh + vertex_ids = participant.set_mesh_vertices(mesh_name, coords) + + write_data = [] + write_data.append(np.zeros(nv)) + write_data.append(np.zeros((nv, participant.get_mesh_dimensions(mesh_name)))) + # Define initial data to write to preCICE scalar_value = 1.0 vector_value = [2.0, 3.0, 4.0] @@ -55,58 +54,38 @@ def main(): for y in range(np_axis): for x in range(np_axis): n = x + y * np_axis + z * np_axis * np_axis - write_scalar_data[n] = scalar_value - write_vector_data[n, 0] = vector_value[0] - write_vector_data[n, 1] = vector_value[1] - write_vector_data[n, 2] = vector_value[2] + write_data[0][n] = scalar_value + write_data[1][n, 0] = vector_value[0] + write_data[1][n, 1] = vector_value[1] + write_data[1][n, 2] = vector_value[2] scalar_value += 1 vector_value = [x + 1 for x in vector_value] - # Define Gauss points on entire domain as coupling mesh - vertex_ids = interface.set_mesh_vertices(read_mesh_id, coords) - - read_data_ids = dict() - # coupling data - for name, dim in read_data_names.items(): - read_data_ids[name] = interface.get_data_id(name, read_mesh_id) - - write_data_ids = dict() - for name, dim in write_data_names.items(): - write_data_ids[name] = interface.get_data_id(name, write_mesh_id) - - # initialize preCICE - dt = interface.initialize() + # Write initial data to preCICE + if participant.requires_initial_data(): + for count, data_name in enumerate(write_data_names.keys()): + participant.write_data(mesh_name, data_name, vertex_ids, write_data[count]) - # Set initial data to write to preCICE - if interface.is_action_required(precice.action_write_initial_data()): - for name, dim in write_data_names.items(): - if dim == 0: - interface.write_block_scalar_data(write_data_ids[name], vertex_ids, write_scalar_data) - elif dim == 1: - interface.write_block_vector_data(write_data_ids[name], vertex_ids, write_vector_data) - interface.mark_action_fulfilled(precice.action_write_initial_data()) + participant.initialize() - interface.initialize_data() + read_data = [None, None] + dt = participant.get_max_time_step_size() # time loop - while interface.is_coupling_ongoing(): + while participant.is_coupling_ongoing(): # write checkpoint - if interface.is_action_required(precice.action_write_iteration_checkpoint()): + if participant.requires_writing_checkpoint(): print("Saving macro state") t_checkpoint = t n_checkpoint = n - interface.mark_action_fulfilled(precice.action_write_iteration_checkpoint()) # Read data from preCICE - for name, dim in read_data_names.items(): - if dim == 0: - read_scalar_data = interface.read_block_scalar_data(read_data_ids[name], vertex_ids) - elif dim == 1: - read_vector_data = interface.read_block_vector_data(read_data_ids[name], vertex_ids) + for count, data_name in enumerate(read_data_names.keys()): + read_data[count] = participant.read_data(mesh_name, data_name, vertex_ids, 1.) # Set the read data as the write data with an increment - write_scalar_data = read_scalar_data + 1 - write_vector_data = read_vector_data + 1 + write_data[0] = read_data[0] + 1 + write_data[1] = read_data[1] + 1 # Define new data to write to preCICE midway through the simulation if t == t_end / 2: @@ -116,32 +95,28 @@ def main(): for y in range(np_axis): for x in range(np_axis): n = x + y * np_axis + z * np_axis * np_axis - write_scalar_data[n] = scalar_value - write_vector_data[n, 0] = vector_value[0] - write_vector_data[n, 1] = vector_value[1] - write_vector_data[n, 2] = vector_value[2] + write_data[0][n] = scalar_value + write_data[1][n, 0] = vector_value[0] + write_data[1][n, 1] = vector_value[1] + write_data[1][n, 2] = vector_value[2] # Write data to preCICE - for name, dim in write_data_names.items(): - if dim == 0: - interface.write_block_scalar_data(write_data_ids[name], vertex_ids, write_scalar_data) - elif dim == 1: - interface.write_block_vector_data(write_data_ids[name], vertex_ids, write_vector_data) + for count, data_name in enumerate(write_data_names.keys()): + participant.write_data(mesh_name, data_name, vertex_ids, write_data[count]) - # do the coupling - dt = interface.advance(dt) + participant.advance(dt) + dt = participant.get_max_time_step_size() # advance variables n += 1 t += dt - if interface.is_action_required(precice.action_read_iteration_checkpoint()): + if participant.requires_reading_checkpoint(): print("Reverting to old macro state") t = t_checkpoint n = n_checkpoint - interface.mark_action_fulfilled(precice.action_read_iteration_checkpoint()) - interface.finalize() + participant.finalize() if __name__ == '__main__': diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore new file mode 100644 index 00000000..bf0824e5 --- /dev/null +++ b/tests/unit/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/tests/unit/precice.py b/tests/unit/precice.py index 210bf9ba..e5cbb82d 100644 --- a/tests/unit/precice.py +++ b/tests/unit/precice.py @@ -1,78 +1,61 @@ # This file mocks pyprecice, the Python bindings for preCICE, and is used _only_ for unit testing the Micro Manager. +from typing import Any import numpy as np -def action_write_initial_data(): - return "ActionWriteInitialData" - - -def action_write_iteration_checkpoint(): - return "ActionWriteIterationCheckpoint" - - -class Interface: +class Participant: def __init__(self, solver_name, config_file_name, solver_process_index, solver_process_size): self.read_write_vector_buffer = [] self.read_write_scalar_buffer = [] - def get_mesh_id(self, mesh_name): - return 0 - - def get_data_id(self, data_name, mesh_id): - return int(data_name == "micro-scalar-data") - - def get_dimensions(self): - return 3 - - def set_mesh_access_region(self, mesh_id, bounds): - pass - def initialize(self): return 0.1 # dt - def get_mesh_vertices_and_ids(self, mesh_id): - return np.array([0, 1, 2, 3]), np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) - - def is_action_required(self, action): - return True - - def mark_action_fulfilled(self, action): + def advance(self, dt): pass - def initialize_data(self): + def finalize(self): pass - def write_block_scalar_data(self, data_id, vertex_ids, data): - if data_id == 1: # micro-scalar-data not micro_sim_time - self.read_write_scalar_buffer = data + def get_mesh_dimensions(self, mesh_name): + return 3 - def write_block_vector_data(self, data_id, vertex_ids, data): - self.read_write_vector_buffer = data + def get_data_dimensions(self, mesh_name, data_name): + return 3 - def write_scalar_data(self, data_id, vertex_id, data): - pass + def is_coupling_ongoing(self): + yield True + yield False - def write_vector_data(self, data_id, vertex_id, data): - pass + def is_time_window_complete(self): + return True - def read_block_scalar_data(self, data_id, vertex_ids): - return self.read_write_scalar_buffer + def get_max_time_step_size(self): + return 0.1 # dt - def read_block_vector_data(self, data_id, vertex_ids): - return self.read_write_vector_buffer + def requires_initial_data(self): + return True - def read_scalar_data(self, data_id, vertex_id): - return 0 + def requires_writing_checkpoint(self): + return True - def read_vector_data(self, data_id, vertex_id): - return [0, 0] + def requires_reading_checkpoint(self): + return True - def finalize(self): + def set_mesh_access_region(self, mesh_name, bounds): pass - def is_coupling_ongoing(self): - yield True - yield False + def get_mesh_vertex_ids_and_coordinates(self, mesh_name): + return np.array([0, 1, 2, 3]), np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) - def advance(self, dt): - pass + def write_data(self, mesh_name, data_name, vertex_ids, data): + if data_name == "micro-scalar-data": + self.read_write_scalar_buffer = data + elif data_name == "micro-vector-data": + self.read_write_vector_buffer = data + + def read_data(self, mesh_name, data_name, vertex_ids, relative_read_time): + if data_name == "macro-scalar-data": + return self.read_write_scalar_buffer + elif data_name == "macro-vector-data": + return self.read_write_vector_buffer diff --git a/tests/unit/test_micro_manager.py b/tests/unit/test_micro_manager.py index af5d5fc8..c020f09d 100644 --- a/tests/unit/test_micro_manager.py +++ b/tests/unit/test_micro_manager.py @@ -72,6 +72,8 @@ def test_read_write_data_from_precice(self): manager._write_data_to_precice(self.fake_write_data) read_data = manager._read_data_from_precice() + print("read_data: {}".format(read_data)) + for data, fake_data in zip(read_data, self.fake_read_data): self.assertEqual(data["macro-scalar-data"], 1) self.assertListEqual(data["macro-vector-data"].tolist(),