From c42e66e04f73ff4e2582d9217bd0151c4e4b4e77 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 18:27:53 -0700 Subject: [PATCH 01/13] Add shape property This makes it easier to implement the BMI. --- heat/heat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/heat/heat.py b/heat/heat.py index ea6ea56..4621c97 100644 --- a/heat/heat.py +++ b/heat/heat.py @@ -139,8 +139,13 @@ def time_step(self, time_step): self._time_step = time_step @property - def spacing(self): + def shape(self): """Shape of the model grid.""" + return self._shape + + @property + def spacing(self): + """Spacing between nodes of the model grid.""" return self._spacing @property From 786d06df9e2fa74ccada53a706c0ce2676f377b6 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 18:28:33 -0700 Subject: [PATCH 02/13] Fix typo --- heat/heat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heat/heat.py b/heat/heat.py index 4621c97..36c9dc0 100644 --- a/heat/heat.py +++ b/heat/heat.py @@ -86,7 +86,7 @@ def __init__( ): """Create a new heat model. - Paramters + Parameters --------- shape : array_like, optional The shape of the solution grid as (*rows*, *columns*). From 236d336de09a170fe2886bace476e5356671efea Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 19:26:20 -0700 Subject: [PATCH 03/13] Use array parameter in grid shape, spacing, and origin This better matches the BMI form in bmipy. --- heat/bmi_heat.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/heat/bmi_heat.py b/heat/bmi_heat.py index c206e35..f219de6 100644 --- a/heat/bmi_heat.py +++ b/heat/bmi_heat.py @@ -279,18 +279,21 @@ def get_output_var_names(self): """Get names of output variables.""" return self._output_var_names - def get_grid_shape(self, grid_id): + def get_grid_shape(self, grid_id, shape): """Number of rows and columns of uniform rectilinear grid.""" var_name = self._grids[grid_id][0] - return self.get_value_ptr(var_name).shape + shape[:] = self.get_value_ptr(var_name).shape + return shape - def get_grid_spacing(self, grid_id): + def get_grid_spacing(self, grid_id, spacing): """Spacing of rows and columns of uniform rectilinear grid.""" - return self._model.spacing + spacing[:] = self._model.spacing + return spacing - def get_grid_origin(self, grid_id): + def get_grid_origin(self, grid_id, origin): """Origin of uniform rectilinear grid.""" - return self._model.origin + origin[:] = self._model.origin + return origin def get_grid_type(self, grid_id): """Type of grid.""" From c81dbecdaf30973a99fd5b655ecad551fa0087c0 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 19:28:05 -0700 Subject: [PATCH 04/13] Set grid type to uniform_rectilinear This matches the docs. --- heat/bmi_heat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heat/bmi_heat.py b/heat/bmi_heat.py index f219de6..b000e6c 100644 --- a/heat/bmi_heat.py +++ b/heat/bmi_heat.py @@ -49,7 +49,7 @@ def initialize(self, filename=None): self._var_units = {"plate_surface__temperature": "K"} self._var_loc = {"plate_surface__temperature": "node"} self._grids = {0: ["plate_surface__temperature"]} - self._grid_type = {0: "uniform_rectilinear_grid"} + self._grid_type = {0: "uniform_rectilinear"} def update(self): """Advance model by one time step.""" From 099560c8e9c7d7e62649e93637d0db54710fbafc Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 19:33:35 -0700 Subject: [PATCH 05/13] Use heat shape property for grid rank and size This is a convenience. Also cast the return from *get_grid_size* to `int` to pass the bmi-tester. --- heat/bmi_heat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/heat/bmi_heat.py b/heat/bmi_heat.py index b000e6c..8173a5f 100644 --- a/heat/bmi_heat.py +++ b/heat/bmi_heat.py @@ -167,7 +167,7 @@ def get_grid_rank(self, grid_id): int Rank of grid. """ - return len(self.get_grid_shape(grid_id)) + return len(self._model.shape) def get_grid_size(self, grid_id): """Size of grid. @@ -182,7 +182,7 @@ def get_grid_size(self, grid_id): int Size of grid. """ - return np.prod(self.get_grid_shape(grid_id)) + return int(np.prod(self._model.shape)) def get_value_ptr(self, var_name): """Reference to values. From f908de6dcc414d81b41ec292f994959e2451d0e5 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 19:36:01 -0700 Subject: [PATCH 06/13] Use dest parameter in get_value methods This better matches the form from bmipy. --- heat/bmi_heat.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/heat/bmi_heat.py b/heat/bmi_heat.py index 8173a5f..5a04ce9 100644 --- a/heat/bmi_heat.py +++ b/heat/bmi_heat.py @@ -199,28 +199,33 @@ def get_value_ptr(self, var_name): """ return self._values[var_name] - def get_value(self, var_name): + def get_value(self, var_name, dest): """Copy of values. Parameters ---------- var_name : str Name of variable as CSDMS Standard Name. + dest : ndarray + A numpy array into which to place the values. Returns ------- array_like Copy of values. """ - return self.get_value_ptr(var_name).copy() + dest[:] = self.get_value_ptr(var_name).flatten() + return dest - def get_value_at_indices(self, var_name, indices): + def get_value_at_indices(self, var_name, dest, indices): """Get values at particular indices. Parameters ---------- var_name : str Name of variable as CSDMS Standard Name. + dest : ndarray + A numpy array into which to place the values. indices : array_like Array of indices. @@ -229,7 +234,8 @@ def get_value_at_indices(self, var_name, indices): array_like Values at indices. """ - return self.get_value_ptr(var_name).take(indices) + dest[:] = self.get_value_ptr(var_name).take(indices) + return dest def set_value(self, var_name, src): """Set model values. From a758e0919ccca3f6224021e0590182cc0b53bc0d Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 19:45:32 -0700 Subject: [PATCH 07/13] Update tests for updated BMI functions --- tests/test_get_value.py | 18 +++++++++++++----- tests/test_grid_info.py | 37 ++++++++++++------------------------- tests/test_irf.py | 17 ++++++++++++----- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/tests/test_get_value.py b/tests/test_get_value.py index 3bbaa12..35f9260 100644 --- a/tests/test_get_value.py +++ b/tests/test_get_value.py @@ -1,5 +1,6 @@ #!/usr/bin/env python from numpy.testing import assert_array_almost_equal, assert_array_less +import numpy as np from heat import BmiHeat @@ -17,8 +18,11 @@ def test_get_value_copy(): model = BmiHeat() model.initialize() - z0 = model.get_value("plate_surface__temperature") - z1 = model.get_value("plate_surface__temperature") + dest0 = np.empty(model.get_grid_size(0), dtype=float) + dest1 = np.empty(model.get_grid_size(0), dtype=float) + + z0 = model.get_value("plate_surface__temperature", dest0) + z1 = model.get_value("plate_surface__temperature", dest1) assert z0 is not z1 assert_array_almost_equal(z0, z1) @@ -28,11 +32,13 @@ def test_get_value_pointer(): model = BmiHeat() model.initialize() + dest1 = np.empty(model.get_grid_size(0), dtype=float) + z0 = model.get_value_ptr("plate_surface__temperature") - z1 = model.get_value("plate_surface__temperature") + z1 = model.get_value("plate_surface__temperature", dest1) assert z0 is not z1 - assert_array_almost_equal(z0, z1) + assert_array_almost_equal(z0.flatten(), z1) for _ in range(10): model.update() @@ -44,8 +50,10 @@ def test_get_value_at_indices(): model = BmiHeat() model.initialize() + dest = np.empty(3, dtype=float) + z0 = model.get_value_ptr("plate_surface__temperature") - z1 = model.get_value_at_indices("plate_surface__temperature", [0, 2, 4]) + z1 = model.get_value_at_indices("plate_surface__temperature", dest, [0, 2, 4]) assert_array_almost_equal(z0.take((0, 2, 4)), z1) diff --git a/tests/test_grid_info.py b/tests/test_grid_info.py index 59f7436..cf856c4 100644 --- a/tests/test_grid_info.py +++ b/tests/test_grid_info.py @@ -1,5 +1,7 @@ #!/usr/bin/env python import pytest +import numpy as np +from numpy.testing import assert_array_equal from heat import BmiHeat @@ -47,49 +49,34 @@ def test_grid_var_rank(): assert model.get_grid_rank(grid_id) == 2 -def test_grid_var_rank_fail(): - model = BmiHeat() - model.initialize() - with pytest.raises(KeyError): - model.get_grid_rank(invalid_grid_id) - - def test_grid_var_size(): model = BmiHeat() model.initialize() assert model.get_grid_size(grid_id) == 200 -def test_grid_var_size_fail(): - model = BmiHeat() - model.initialize() - with pytest.raises(KeyError): - model.get_grid_size(invalid_grid_id) - - def test_grid_var_shape(): model = BmiHeat() model.initialize() - assert model.get_grid_shape(grid_id) == (10, 20) - - -def test_grid_var_shape_fail(): - model = BmiHeat() - model.initialize() - with pytest.raises(KeyError): - model.get_grid_shape(invalid_grid_id) + ndim = model.get_grid_rank(0) + shape = np.empty(ndim, dtype=np.int32) + assert_array_equal(model.get_grid_shape(grid_id, shape), (10, 20)) def test_grid_var_spacing(): model = BmiHeat() model.initialize() - assert model.get_grid_spacing(grid_id) == (1.0, 1.0) + ndim = model.get_grid_rank(0) + spacing = np.empty(ndim, dtype=np.int32) + assert_array_equal(model.get_grid_spacing(grid_id, spacing), (1.0, 1.0)) def test_grid_var_origin(): model = BmiHeat() model.initialize() - assert model.get_grid_origin(grid_id) == (0.0, 0.0) + ndim = model.get_grid_rank(0) + origin = np.empty(ndim, dtype=np.int32) + assert_array_equal(model.get_grid_origin(grid_id, origin), (0.0, 0.0)) def test_grid_var_type(): @@ -101,4 +88,4 @@ def test_grid_var_type(): def test_grid_type(): model = BmiHeat() model.initialize() - assert model.get_grid_type(grid_id) == "uniform_rectilinear_grid" + assert model.get_grid_type(grid_id) == "uniform_rectilinear" diff --git a/tests/test_irf.py b/tests/test_irf.py index 7c61522..80fc47f 100644 --- a/tests/test_irf.py +++ b/tests/test_irf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from io import StringIO -from numpy.testing import assert_almost_equal, assert_array_less +from numpy.testing import assert_almost_equal, assert_array_less, assert_array_equal import numpy as np import yaml @@ -36,8 +36,9 @@ def test_initialize_defaults(): model.initialize() assert_almost_equal(model.get_current_time(), 0.0) - assert_array_less(model.get_value("plate_surface__temperature"), 1.0) - assert_array_less(0.0, model.get_value("plate_surface__temperature")) + z0 = model.get_value_ptr("plate_surface__temperature") + assert_array_less(z0, 1.0) + assert_array_less(0.0, z0) def test_initialize_from_file_like(): @@ -45,7 +46,10 @@ def test_initialize_from_file_like(): model = BmiHeat() model.initialize(config) - assert model.get_grid_shape(0) == (7, 5) + ndim = model.get_grid_rank(0) + shape = np.empty(ndim, dtype=np.int32) + + assert_array_equal(model.get_grid_shape(0, shape), (7, 5)) def test_initialize_from_file(): @@ -62,7 +66,10 @@ def test_initialize_from_file(): os.remove(name) - assert model.get_grid_shape(0) == (7, 5) + ndim = model.get_grid_rank(0) + shape = np.empty(ndim, dtype=np.int32) + + assert_array_equal(model.get_grid_shape(0, shape), (7, 5)) def test_update(): From c0719637b6a564d220aa1ccb49957119dde923ed Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 20:06:07 -0700 Subject: [PATCH 08/13] Add six as a requirement It's found by conda but not by pip. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 55594fb..c3853ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ coveralls flake8 pytest pytest-cov +six From 8f755d291247150decd40ac9b75935398dab5692 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 21:52:48 -0700 Subject: [PATCH 09/13] Remove unused import --- tests/test_grid_info.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_grid_info.py b/tests/test_grid_info.py index cf856c4..d44c79b 100644 --- a/tests/test_grid_info.py +++ b/tests/test_grid_info.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -import pytest import numpy as np from numpy.testing import assert_array_equal From f038472b3c8a503506a4c2284562eb6039e6f4db Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 21:54:08 -0700 Subject: [PATCH 10/13] Make pretty --- heat/bmi_heat.py | 1 - setup.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/heat/bmi_heat.py b/heat/bmi_heat.py index 5a04ce9..c1fcb50 100644 --- a/heat/bmi_heat.py +++ b/heat/bmi_heat.py @@ -2,7 +2,6 @@ """Basic Model Interface implementation for the 2D heat model.""" import numpy as np - from bmipy import Bmi from .heat import Heat diff --git a/setup.py b/setup.py index 476635d..53f7f96 100644 --- a/setup.py +++ b/setup.py @@ -27,5 +27,3 @@ packages=find_packages(), cmdclass=versioneer.get_cmdclass(), ) - - From 91db972429df89d71cae475bc51c909cb79ad36d Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Mon, 14 Dec 2020 21:55:07 -0700 Subject: [PATCH 11/13] Test on py38 and py39 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e6403b..eb7da2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ os: - osx env: matrix: - - CONDA_ENV=3.7 - - CONDA_ENV=3.6 + - CONDA_ENV=3.8 + - CONDA_ENV=3.9 sudo: false jobs: include: From 14819dcd57654ad083c7bfd26ab267a98a1719b5 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 15 Dec 2020 14:50:39 -0700 Subject: [PATCH 12/13] Split off testing requirements to new file And added bmi-tester as a testing requirement. --- requirements-testing.txt | 6 ++++++ requirements.txt | 9 ++++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 requirements-testing.txt diff --git a/requirements-testing.txt b/requirements-testing.txt new file mode 100644 index 0000000..16911dd --- /dev/null +++ b/requirements-testing.txt @@ -0,0 +1,6 @@ +coveralls +flake8 +pytest +pytest-cov +six +bmi-tester diff --git a/requirements.txt b/requirements.txt index c3853ee..7105f42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -coveralls -flake8 -pytest -pytest-cov -six +numpy +scipy +pyyaml +bmipy From e8115760a4df49c8871a0cdcc4b5a7f855254069 Mon Sep 17 00:00:00 2001 From: Mark Piper Date: Tue, 15 Dec 2020 14:58:20 -0700 Subject: [PATCH 13/13] Run bmi-tester in the CI I chose to keep everything in conda to try to avoid mixing package sources. --- .travis.yml | 7 +++++-- examples/heat.yaml | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 examples/heat.yaml diff --git a/.travis.yml b/.travis.yml index eb7da2c..3748f5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,10 +49,13 @@ before_install: - export PATH="$HOME/anaconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no -- conda create -n test_env python=$CONDA_ENV +- conda create -n test_env python=$CONDA_ENV --file=requirements.txt -c conda-forge - source activate test_env install: -- pip install . -r requirements.txt +- pip install . +before_script: +- conda install --file=requirements-testing.txt -c conda-forge script: - pytest --cov=heat --cov-report=xml:$(pwd)/coverage.xml -vvv +- bmi-test heat:BmiHeat --config-file=./examples/heat.yaml --root-dir=./examples -vvv after_success: coveralls diff --git a/examples/heat.yaml b/examples/heat.yaml new file mode 100644 index 0000000..a5bd435 --- /dev/null +++ b/examples/heat.yaml @@ -0,0 +1,11 @@ +# Heat model configuration +shape: + - 6 + - 8 +spacing: + - 1.0 + - 1.0 +origin: + - 0.0 + - 0.0 +alpha: 1.0