1010import logging
1111from datetime import date
1212import cPickle
13- from collections import Counter
13+ from collections import Counter , MutableMapping
1414
1515from .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+
15721654class MultiCropParameterProvider (ParameterProvider ):
15731655 """Parameter provider that allows multiple crop
15741656 parameter sets to be specified. This ParameterProvider is
0 commit comments