Skip to content

Commit b21eb8e

Browse files
committed
Added __str__ to ParameterProvider.
1 parent cf8bb40 commit b21eb8e

File tree

1 file changed

+99
-17
lines changed

1 file changed

+99
-17
lines changed

pcse/base_classes.py

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import logging
1111
from datetime import date
1212
import cPickle
13-
from collections import Counter
13+
from collections import Counter, MutableMapping
1414

1515
from .traitlets import (HasTraits, Any, Float, Int, Instance, Dict, Bool,
1616
Enum, AfgenTrait)
@@ -1456,18 +1456,20 @@ def zerofy(self):
14561456
simobj.zerofy()
14571457

14581458

1459-
class ParameterProvider(HasTraits):
1460-
"""Simple class providing a dictionary-like single interface for parameter values.
1459+
class ParameterProvider(MutableMapping):
1460+
"""Class providing a dictionary-like interface over all parameter sets (crop, soil, site).
1461+
It acts very much like a ChainMap with some additional features.
14611462
1462-
The idea behind this class is twofold. First of all by encapsulating the four
1463-
different parameter types (e.g. sitedata, timerdata, etc) into a single object,
1463+
The idea behind this class is threefold. First of all by encapsulating the
1464+
different parameter sets (sitedata, cropdata, soildata) into a single object,
14641465
the signature of the `initialize()` method of each `SimulationObject` can be
14651466
harmonized across all SimulationObjects. Second, the ParameterProvider itself
14661467
can be easily adapted when different sets of parameter values are needed. For
1467-
example when running PCSE with crop rotations, different sets of timerdata and
1468-
cropdata are needed, this can now be handled easily by enhancing
1469-
ParameterProvider to rotate new sets of timerdata and cropdata on a CROP_FINISH
1470-
signal.
1468+
example when running PCSE with crop rotations, different sets of cropdata
1469+
are needed, this can now be handled easily by enhancing
1470+
ParameterProvider to rotate a new set of cropdata when the engine receives a
1471+
CROP_START signal. Finally, specific parameter values can be easily changed
1472+
by setting an `override` on that parameter.
14711473
14721474
See also the `MultiCropParameterProvider`
14731475
"""
@@ -1477,6 +1479,8 @@ class ParameterProvider(HasTraits):
14771479
_cropdata = dict()
14781480
_timerdata = dict()
14791481
_override = dict()
1482+
_unique_parameters = list()
1483+
_iter = 0 # Counter for iterator
14801484

14811485
def __init__(self, sitedata=None, timerdata=None, soildata=None, cropdata=None):
14821486
if sitedata is not None:
@@ -1514,9 +1518,9 @@ def set_crop_type(self, crop_id=None, crop_start_type=None, crop_end_type=None):
15141518
self._test_uniqueness()
15151519

15161520
def set_override(self, varname, value, check=True):
1517-
""""Override the value of variable varname in the parameterprovider.
1521+
""""Override the value of parameter varname in the parameterprovider.
15181522
1519-
Overriding the value of particular variable is often useful for example
1523+
Overriding the value of particular parameter is often useful for example
15201524
when running for different sets of parameters or for calibration
15211525
purposes.
15221526
@@ -1534,7 +1538,10 @@ def set_override(self, varname, value, check=True):
15341538
self._override[varname] = value
15351539

15361540
def clear_override(self, varname=None):
1537-
"""Removes variable varname from override, without arguments all variables are removed."""
1541+
"""Removes parameter varname from the set of overridden parameters.
1542+
1543+
Without arguments all overridden parameters are removed.
1544+
"""
15381545

15391546
if varname is None:
15401547
self._override.clear()
@@ -1543,24 +1550,46 @@ def clear_override(self, varname=None):
15431550
self._override.pop(varname)
15441551
else:
15451552
msg = "Cannot clear varname '%s' from override" % varname
1553+
raise exc.PCSEError(msg)
15461554

15471555
def _test_uniqueness(self):
1548-
# Check if parameter names are unique
1556+
"""Check if parameter names are unique and raise an error if duplicates occur.
1557+
1558+
Note that the uniqueness is not tested for parameters in self._override as this
1559+
is specifically meant for overriding parameters.
1560+
"""
15491561
parnames = []
15501562
for mapping in [self._sitedata, self._timerdata, self._soildata, self._cropdata]:
15511563
parnames.extend(mapping.keys())
15521564
unique = Counter(parnames)
15531565
for parname, count in unique.items():
15541566
if count > 1:
15551567
msg = "Duplicate parameter found: %s" % parname
1556-
raise RuntimeError(msg)
1568+
raise exc.PCSEError(msg)
1569+
1570+
@property
1571+
def _unique_parameters(self):
1572+
"""Returns a list of unique parameter names across all sets of parameters.
1573+
1574+
This includes the parameters in self._override in order to be able to
1575+
iterate over all parameters in the ParameterProvider.
1576+
"""
1577+
s = []
1578+
for mapping in self._maps:
1579+
s.extend(mapping.keys())
1580+
return sorted(list(set(s)))
15571581

15581582
def __getitem__(self, key):
1583+
"""Returns the value of the given parameter (key).
1584+
1585+
Note that the search order in self._map is such that self._override is tested first for the
1586+
existence of the key. Thus ensuring that overridden parameters will be found first.
1587+
1588+
:param key: parameter name to return
1589+
"""
15591590
for mapping in self._maps:
1560-
try:
1591+
if key in mapping:
15611592
return mapping[key]
1562-
except KeyError:
1563-
pass
15641593
raise KeyError(key)
15651594

15661595
def __contains__(self, key):
@@ -1569,6 +1598,59 @@ def __contains__(self, key):
15691598
return True
15701599
return False
15711600

1601+
def __str__(self):
1602+
msg = "ParameterProvider providing %i parameters, %i parameters overridden: %s."
1603+
return msg % (len(self), len(self._override), self._override.keys())
1604+
1605+
def __setitem__(self, key, value):
1606+
"""Override an existing parameter (key) by value.
1607+
1608+
The parameter that is overridden is added to self._override, note that only *existing*
1609+
parameters may be overridden this way. If it is needed to really add a *new* parameter
1610+
than use: ParameterProvider.set_override(key, value, check=False)
1611+
1612+
:param key: The name of the parameter to override
1613+
:param value: the value of the parameter
1614+
"""
1615+
if key in self:
1616+
self._override[key] = value
1617+
else:
1618+
msg = ("Cannot override parameter '%s', parameter does not exist. "
1619+
"to bypass this check use: set_override(parameter, value, check=False)") % key
1620+
raise exc.PCSEError(msg)
1621+
1622+
def __delitem__(self, key):
1623+
"""Deletes a parameter from self._override.
1624+
1625+
Note that only parameters that exist in self._override can be deleted. This also means that
1626+
if an parameter is overridden its original value will return after a parameter is deleted.
1627+
1628+
:param key: The name of the parameter to delete
1629+
"""
1630+
if key in self._override:
1631+
self._override.pop(key)
1632+
elif key in self:
1633+
msg = "Cannot delete default parameter: %s" % key
1634+
raise exc.PCSEError(msg)
1635+
else:
1636+
msg = "Parameter not found!"
1637+
raise KeyError(msg)
1638+
1639+
def __len__(self):
1640+
return len(self._unique_parameters)
1641+
1642+
def __iter__(self):
1643+
return self
1644+
1645+
def next(self):
1646+
i = self._iter
1647+
if i < len(self):
1648+
self._iter += 1
1649+
return self._unique_parameters[self._iter-1]
1650+
else:
1651+
self._iter = 0
1652+
raise StopIteration
1653+
15721654
class MultiCropParameterProvider(ParameterProvider):
15731655
"""Parameter provider that allows multiple crop
15741656
parameter sets to be specified. This ParameterProvider is

0 commit comments

Comments
 (0)