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

Revise API for component parameters, constants, and states #39

Merged
merged 26 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
042e624
give access to parameters/constants in initialise/finalise methods
Apr 29, 2021
59e04c2
bring constant default value as item in _constants_info
Apr 29, 2021
de7d75a
replace DOI badge in readme with flat style
Apr 29, 2021
332c04d
add units requirement for component parameter/constant values
Apr 29, 2021
c9ba49f
remove "Sciencish" components and data from tests dir
Apr 29, 2021
44165ad
add support for sequence for component parameters/constants
Apr 30, 2021
59b5c11
encapsulate space and time subset and compare into SpaceDomain and Ti…
Apr 30, 2021
f7e0476
remove residual methods for Sciencish
Apr 30, 2021
4b5e64f
add support for non-scalar component parameter values
Apr 30, 2021
73edb58
PEP8
Apr 30, 2021
ab2b16b
remove dataset/parameter/constant from component representation
Apr 30, 2021
d13ad0e
revise docstring to reflect changes in 4b5e64ffe1737a2794a9c91a6f41e1…
Apr 30, 2021
7b487f2
revise sphinx autocomponent directive for new location of constant de…
May 3, 2021
e1b5768
add support for state divisions using constant as value
May 4, 2021
aa8dd0a
fix bug when trying to record multi-division component states
May 4, 2021
cda8ca3
add support for multiple dimensions for state divisions
May 5, 2021
0c38d5e
remove parameter default value for Fortran/C dummy components
May 5, 2021
0585120
add test to cover new support for string and/or multiple state divisions
May 5, 2021
70cea9f
fix Fortran/Dummy components so that they expect parameters as arrays
May 5, 2021
405c680
reshape mask for component records
May 5, 2021
991a8f5
fix bug in Fortran dummy openwater
May 5, 2021
ad29e60
add f2py signature files to ignored files
May 5, 2021
f7ff059
fix bug in C dummy component
May 5, 2021
f0e9297
remove installation of Fortran/C compilers and components
May 5, 2021
f39cca4
update tutorial and docs on component preparation to reflect the API …
May 5, 2021
8f581d8
add changes to changelog
May 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .github/workflows/tests.yml → .github/workflows/basic_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,6 @@ jobs:
run: |
mamba install udunits2=2.2.25
mamba install --file=requirements.txt

# build dummy components required for tests
- name: install Fortran compiler (linux)
if: matrix.os == 'ubuntu-latest'
run: |
mamba install gfortran_impl_linux-64

- name: install Fortran compiler (macos)
if: matrix.os == 'macos-latest'
run: |
mamba install gfortran_impl_osx-64

- name: build dummy C and Fortran components
run: |
mamba install cython
(cd ./tests/components && make)

# install package
- name: install package
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
__pycache__/
*.pyc
*.pyf
*.so

# ignore documentation built because copied in docs/ root already
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Community Model for the Terrestrial Water Cycle
.. image:: https://img.shields.io/pypi/v/cm4twc?style=flat-square&color=00b0f0
:target: https://pypi.python.org/pypi/cm4twc
:alt: PyPI Version
.. image:: https://zenodo.org/badge/234523723.svg
.. image:: https://img.shields.io/badge/dynamic/json?url=https://zenodo.org/api/records/4726695&label=DOI&query=doi&style=flat-square&color=00b0f0
:target: https://zenodo.org/badge/latestdoi/234523723
:alt: DOI
.. image:: https://img.shields.io/github/license/hydro-jules/cm4twc?style=flat-square&color=00b0f0
Expand Down
14 changes: 14 additions & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ v0.1.0

Released on 2021, ???.

.. rubric:: API changes

* add units requirement for component parameters and constants
(`#21 <https://github.com/hydro-jules/cm4twc/issues/21>`_)

.. rubric:: Bug fixes

* fix dump file update bug due to missing 'divisions' dimension
(`#32 <https://github.com/hydro-jules/cm4twc/issues/32>`_)

.. rubric:: Enhancements

* add support for arrays for component parameters
(`#21 <https://github.com/hydro-jules/cm4twc/issues/21>`_)
* add support for multiple divisions for component states
(`#39 <https://github.com/hydro-jules/cm4twc/pull/39>`_)
* add support for customisable divisions for component states
(`#31 <https://github.com/hydro-jules/cm4twc/issues/31>`_)

.. rubric:: Documentation

* document 'divisions' for component states in preparation page
Expand Down
93 changes: 67 additions & 26 deletions cm4twc/components/_utils/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ def _delta_to_frequency_str(delta):

class Record(object):

def __init__(self, name, units, **kwargs):
def __init__(self, name, units, divisions=(), **kwargs):
self.name = name
self.units = units
self.divisions = divisions
self.streams = []


Expand Down Expand Up @@ -131,6 +132,8 @@ def __init__(self, delta):
self.methods = {}
# mapping to store record arrays (keys are record names)
self.arrays = {}
# mapping to store record masks (keys are record names)
self.masks = {}
# mapping for integer tracker to know where in array to write next
# (keys are record names)
self.array_trackers = {}
Expand All @@ -139,6 +142,22 @@ def __init__(self, delta):
self.trigger = None
self.trigger_tracker = None

def add_record(self, record, methods):
name = record.name
# store link to record object
self.records[name] = record
# store sequence of aggregation methods
methods_ = set()
for method in methods:
if method in _methods_map:
methods_.add(_methods_map[method])
else:
raise ValueError('method {} for record {} aggregation '
'unknown'.format(method, name))
self.methods[name] = methods_
# map this very stream in the record
record.streams.append(self)

def initialise(self, timedomain, spacedomain, _skip_trackers=False):
# check delta / timedomain resolution compatibility
if (self.delta % timedomain.timedelta) != timedelta(seconds=0):
Expand All @@ -164,11 +183,31 @@ def initialise(self, timedomain, spacedomain, _skip_trackers=False):

# initialise record arrays for accumulating values
self.trigger = 0
for name in self.records:
for name, record in self.records.items():
self.array_trackers[name] = 0
arr = np.zeros((self.length, *spacedomain.shape), dtype_float())

d = record.divisions

# initialise array
arr = np.zeros(
(self.length, *spacedomain.shape, *d), dtype_float()
)
arr[:] = np.nan
self.arrays[name] = arr

# process array mask
if spacedomain.land_sea_mask is None:
msk = None
else:
msk = ~spacedomain.land_sea_mask
if d:
axes = [-(a + 1) for a in range(len(d))]
msk = np.broadcast_to(
np.expand_dims(msk, axis=axes),
(*spacedomain.shape, *d)
)
self.masks[name] = msk

# add on length of stream to the record trigger
self.trigger += self.length

Expand All @@ -177,22 +216,6 @@ def initialise(self, timedomain, spacedomain, _skip_trackers=False):
self.time_tracker = 0
self.trigger_tracker = 0

def add_record(self, record, methods):
name = record.name
# store link to record object
self.records[name] = record
# store sequence of aggregation methods
methods_ = set()
for method in methods:
if method in _methods_map:
methods_.add(_methods_map[method])
else:
raise ValueError('method {} for record {} aggregation '
'unknown'.format(method, name))
self.methods[name] = methods_
# map this very stream in the record
record.streams.append(self)

def update_record(self, name, value):
self.arrays[name][self.array_trackers[name], ...] = value
self.array_trackers[name] += 1
Expand Down Expand Up @@ -237,11 +260,21 @@ def create_record_stream_file(self, filepath):
b.calendar = self.timedomain.calendar

for name, record in self.records.items():
d = record.divisions
if d:
dims = []
for n, v in enumerate(d):
dim_name = '_'.join([name, 'divisions', str(n + 1)])
f.createDimension(dim_name, v)
dims.append(dim_name)
dims = ('time', *axes, *dims)
else:
dims = ('time', *axes)

# record variable
for method in self.methods[name]:
name_method = '_'.join([name, method])
v = f.createVariable(name_method, dtype_float(),
('time', *axes))
v = f.createVariable(name_method, dtype_float(), dims)
v.standard_name = name
v.units = record.units
v.cell_methods = "time: {} over {}".format(
Expand Down Expand Up @@ -281,9 +314,7 @@ def update_record_to_stream_file(self):

# store result in file
f.variables[name_method][t] = np.ma.array(
value, mask=(~self.spacedomain.land_sea_mask
if self.spacedomain.land_sea_mask
is not None else None)
value, mask=self.masks[name]
)

# reset array tracker to point to start of array again
Expand Down Expand Up @@ -335,8 +366,18 @@ def create_record_stream_dump(self, filepath):

# records
for name, record in self.records.items():
s = f.createVariable(name, dtype_float(),
('time', 'length', *axes),
d = record.divisions
if d:
dims = []
for n, v in enumerate(d):
dim_name = '_'.join([name, 'divisions', str(n + 1)])
f.createDimension(dim_name, v)
dims.append(dim_name)
dims = ('time', 'length', *axes, *dims)
else:
dims = ('time', 'length', *axes)

s = f.createVariable(name, dtype_float(), dims,
fill_value=9.9692099683868690E36)
s.standard_name = name
s.units = record.units
Expand Down
19 changes: 11 additions & 8 deletions cm4twc/components/_utils/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,18 @@ def create_states_dump(filepath, states_info, solver_history,

# state variables
for var in states_info:
d = states_info[var].get('divisions', 1)
if d > 1:
f.createDimension('{}_divisions'.format(var), d)
s = f.createVariable(var, dtype_float(),
('time', 'history', *axes,
'{}_divisions'.format(var)))
d = states_info[var]['divisions']
if d:
dims = []
for n, v in enumerate(d):
dim_name = '_'.join([var, 'divisions', str(n + 1)])
f.createDimension(dim_name, v)
dims.append(dim_name)
dims = ('time', 'history', *axes, *dims)
else:
s = f.createVariable(var, dtype_float(),
('time', 'history', *axes))
dims = ('time', 'history', *axes)

s = f.createVariable(var, dtype_float(), dims)

s.standard_name = var
s.units = states_info[var]['units']
Expand Down
Loading