Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make t and n optional arguments in store_checkpoint #178

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# FEniCS-preCICE adapter changelog

## latest
## v2.2.0

* Use `copy(deepcopy=True)` when checkpointing to make checkpointing more user-friendly and secure. IMPORTANT: might increase runtime, please open an issue if you face serious problems. [#172](https://github.com/precice/fenics-adapter/pull/172)
* Add unit tests for checkpointing. [#173](https://github.com/precice/fenics-adapter/pull/173)
* Add required version of `mpi4py` to `<4` to avoid crash during installation. [#181](https://github.com/precice/fenics-adapter/pull/181)
* Remove checks for FEniCS installation and python3 from `setup.py` since the approach is deprecated. [#182](https://github.com/precice/fenics-adapter/pull/182)

## 2.1.0

Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ abstract: >-
preCICE-adapter for the open source computing platform
FEniCS.
license: LGPL-3.0
version: 2.1.0
version: 2.2.0
preferred-citation:
title: "FEniCS-preCICE: Coupling FEniCS to other Simulation Software"
type: "article"
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@ preCICE-adapter for the open source computing platform FEniCS.
It is recommended to install [fenicsprecice from PyPI](https://pypi.org/project/fenicsprecice/) via

```bash
pip3 install --user fenicsprecice
pip3 install fenicsprecice
```

For more recent pip versions you may encounter the error `error: externally-managed-environment` during installation of the fenicsprecice. You can read why [here](https://packaging.python.org/en/latest/specifications/externally-managed-environments/). In this case, it is recommended to create a virtual environment and install the package in the virtual environment. The following commands will create the virtual environment `venv_name` at the location `path/of/your/venv/`:

```bash
python3 -m venv --system-site-packages path/of/your/venv/venv_name
. path/of/your/venv/venv_name/bin/activate
```

You can replace `path/of/your/venv/venv_name` with the location and name you want to use for your virtual environment. The command `--system-size-packages` will allow the virtual environment to access the FEniCS installation on your system. Refer to the [Python documentation](https://docs.python.org/3/library/venv.html) for further details on virtual environments.

This should work out of the box, if all dependencies are installed correctly. If you face problems during installation or you want to run the tests, see below for a list of dependencies and alternative installation procedures

### Clone this repository and use pip3
Expand Down
31 changes: 30 additions & 1 deletion docs/ReleaseGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,39 @@ Before starting this process make sure to check that all relevant changes are in

a) If a pre-release is made: Directly hit the "Publish release" button in your Release Draft. Now you can check the artifacts (e.g. release on [PyPI](https://pypi.org/project/fenicsprecice/#history)) of the release. *Note:* As soon as a new tag is created github actions will take care of deploying the new version on PyPI using [this workflow](https://github.com/precice/fenics-adapter/actions?query=workflow%3A%22Upload+Python+Package%22).

b) If this is a "real" release: As soon as one approving review is made, merge the release PR (`fenics-adapter-vX.X.X`) into `master`.
b) If this is a "real" release: As soon as one approving review is made, merge the release PR (`fenics-adapter-vX.X.X`) into `master`. Use **Merge pull request**, don't squash the commits.

5. Merge `master` into `develop` for synchronization of `develop`.

6. If everything is in order up to this point then the new version can be released by hitting the "Publish release" button in your Release Draft.

7. Now there should be a tag for the release. Re-run the [docker release workflow `build-docker.yml` via dispatch](https://github.com/precice/fenics-adapter/actions/workflows/build-docker.yml) such that the correct version is picked up by `versioneer`. Check the version in the container via `docker pull precice/fenics-adapter`, then `docker run -ti precice/fenics-adapter`, and inside the container `$ python3 -c "import fenicsprecice; print(fenicsprecice.__version__)"`.

8. Add an empty commit (details see [here](https://github.com/precice/python-bindings/issues/109)) on master by running the steps:

```bash
git checkout master
git commit --allow-empty -m "post-tag bump"
git push
```

Check that everything is in order via `git log`. Important: The `tag` and `origin/master` should not point to the same commit. For example:

```bash
commit 9d0d6bf978b2363c7ee041201df4322f930dd456 (HEAD -> master)
Author: Benjamin Rodenberg <[email protected]>
Date: Thu Oct 31 08:52:07 2024 +0100

post-tag bump

commit 0d8eecb54b4bc582f33f5f38fca77dfe6161a237 (origin/master)
Merge: f3abeb0 8ca28ae
Author: Benjamin Rodenberg <[email protected]>
Date: Thu Oct 31 08:41:36 2024 +0100

Merge pull request #184 from precice/fenics-adapter-v2.2.0

Release v2.2.0
```

For more details refer to [this issue](https://github.com/precice/python-bindings/issues/109) and [this issue](https://github.com/python-versioneer/python-versioneer/issues/217).
23 changes: 17 additions & 6 deletions fenicsprecice/fenicsprecice.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,17 +432,17 @@ def initialize(self, coupling_subdomain, read_function_space=None, write_object=

self._participant.initialize()

def store_checkpoint(self, payload, t, n):
def store_checkpoint(self, payload, t = None, n = None):
"""
Defines an object of class SolverState which stores the current state of the variable and the time stamp.

Parameters
----------
payload : fenics.function or a list of fenics.functions
Current state of the physical variable(s) of interest for this participant.
t : double
t : double (optional)
Current simulation time.
n : int
n : int (optional)
Current time window (iteration) number.
"""
if self._first_advance_done:
Expand All @@ -459,14 +459,25 @@ def retrieve_checkpoint(self):
-------
u : FEniCS Function
Current state of the physical variable of interest for this participant.
t : double
t : double (optional)
Current simulation time.
n : int
n : int (optional)
Current time window (iteration) number.
"""
assert (not self.is_time_window_complete())
logger.debug("Restore solver state")
return self._checkpoint.get_state()

# since t and n are optional, they should not be returned, if not specified
payload, t, n = self._checkpoint.get_state()
match (t, n):
case (None, None):
return payload
case (_, None):
return payload, t
case (None, _):
return payload, n
case _:
return payload, t, n
Comment on lines +470 to +480
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested this PR with the perpendicular flap. Generally, it seems to work. But I'm not so happy with the implications on the return value of the retrieve_checkpoint function: If I call

cp, t_or_n = adapter.retrieve_checkpoint()

the second argument could be anything. The actual return value depends on how I called store_checkpoint before. This does not look right to me. Sorry to bring this up so late.

Non-breaking solution

As a non-breaking solution, I can only imagine returning None, if t or n has not been provided previously. This results in always calling

cp, t, n = adapter.retrieve_checkpoint()

this would be independent from what we did with store_checkpoint before.

Breaking solutions

I think a more reasonable way would be the following:

cp = adapter.retrieve_checkpoint()

and then

value = cp.value
t = cp.t
n = cp.n

Meaning: cp would be a named tuple (see https://stackoverflow.com/questions/2970608/what-are-named-tuples-in-python). Scipy uses something similar in integrate.solve_ivp and calls it a "bunch object" (see https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html).

This brings us to the next question: Why not just use the following API:

store_checkpoint(self, payload: namedtuple)

retreive_checkpoint() -> namedtuple

We would then have to allow for FEniCS Functions or simple float or int values in the payload. But this would be an implementation detail and the user just gets back whatever was provided in the original payload.

Important about both proposed solutions: They would be breaking (and therefore, we can only release them with fenicsprecice 3.0.0).

What to do

I would suggest to implement and merge the non-breaking solution. We can open an issue describing the breaking solution and schedule it for version 3.0.0 (probably this will happen not too soon).


def advance(self, dt):
"""
Expand Down
25 changes: 1 addition & 24 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
import os
from setuptools import setup
import versioneer
import warnings

# from https://stackoverflow.com/a/9079062
import sys
if sys.version_info[0] < 3:
raise Exception("fenicsprecice only supports Python3. Did you run $python setup.py <option>.? "
"Try running $python3 setup.py <option>.")

if sys.version_info[1] == 6 and sys.version_info[2] == 9:
warnings.warn("It seems like you are using Python version 3.6.9. There is a known bug with this Python version "
"when running the tests (see https://github.com/precice/fenics-adapter/pull/61). If you want to "
"run the tests, please install a different Python version.")

try:
from fenics import *
except ModuleNotFoundError:
warnings.warn("No FEniCS installation found on system. Please install FEniCS and check the installation.\n\n"
"You can check this by running the command\n\n"
"python3 -c 'from fenics import *'\n\n"
"Please check https://fenicsproject.org/download/ for installation guidance.\n"
"The installation will continue, but please be aware that your installed version of the "
"fenics-adapter might not work as expected.")

this_directory = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
Expand All @@ -39,6 +16,6 @@
author_email='[email protected]',
license='LGPL-3.0',
packages=['fenicsprecice'],
install_requires=['pyprecice>=3.0.0.0', 'scipy', 'numpy>=1.13.3, <2', 'mpi4py'],
install_requires=['pyprecice>=3.0.0.0', 'scipy', 'numpy>=1.13.3, <2', 'mpi4py<4'],
test_suite='tests',
zip_safe=False)
77 changes: 76 additions & 1 deletion tests/integration/test_write_read.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from unittest.mock import MagicMock, patch
from unittest import TestCase
from tests import MockedPrecice
from fenics import Expression, UnitSquareMesh, FunctionSpace, VectorFunctionSpace, interpolate, SubDomain, near
from fenics import Expression, UnitSquareMesh, FunctionSpace, VectorFunctionSpace, interpolate, SubDomain, near, UnitIntervalMesh
import numpy as np

x_left, x_right = 0, 1
Expand Down Expand Up @@ -185,3 +185,78 @@ def return_dummy_data(n_points):
self.fail(f"Unexpected combination of arg: {arg}, expected_arg: {expected_arg}")

np.testing.assert_almost_equal(list(read_data.values()), return_dummy_data(self.n_vertices))

def test_optional_parameters(self):
from precice import Participant
import fenicsprecice

def test_all_parameters(adapter):
#init variables that are tested against
nx = 10
mesh = UnitIntervalMesh(nx)
V = FunctionSpace(mesh, 'P', 2)
dummy_value = 1
E = Expression("t", t=dummy_value, degree=2)
u = interpolate(E, V)
t = 0.5
n = 42
# test adapter
adapter.store_checkpoint(u, t, n) # is the old implementation still working?
res_u, res_t, res_n = adapter.retrieve_checkpoint()
self.assertEqual(res_t, t)
self.assertEqual(res_n, n)
np.testing.assert_array_equal(res_u.vector(), u.vector())

def test_opt_parameters(adapter):
#init variables that are tested against
nx = 10
mesh = UnitIntervalMesh(nx)
V = FunctionSpace(mesh, 'P', 2)
dummy_value = 1
E = Expression("t", t=dummy_value, degree=2)
u = interpolate(E, V)
t = 0.5
n = 42
# test adapter
adapter.store_checkpoint(u, t) # without n
res = adapter.retrieve_checkpoint()
self.assertEqual(len(res), 2) # correct number of return values
res_u, res_t = res
self.assertEqual(res_t, t)
np.testing.assert_array_equal(res_u.vector(), u.vector())

adapter.store_checkpoint(u, n) # without t
res = adapter.retrieve_checkpoint()
self.assertEqual(len(res), 2) # correct number of return values
res_u, res_n = res
self.assertEqual(res_n, n)
np.testing.assert_array_equal(res_u.vector(), u.vector())

def test_payload_only(adapter):
nx = 10
mesh = UnitIntervalMesh(nx)
V = FunctionSpace(mesh, 'P', 2)
dummy_value = 1
E = Expression("t", t=dummy_value, degree=2)
u = interpolate(E, V)
# test adapter
adapter.store_checkpoint(u) # no optional parameters
res = adapter.retrieve_checkpoint()
np.testing.assert_array_equal(res.vector(), u.vector())



Participant.is_time_window_complete = MagicMock(return_value=False)

precice = fenicsprecice.Adapter(self.dummy_config)

# call the tests
test_all_parameters(precice)
test_opt_parameters(precice)
test_payload_only(precice)






Loading