"""
Copyright 2020 Marco Dal Molin et al.
This file is part of SuperflexPy.
SuperflexPy is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SuperflexPy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with SuperflexPy. If not, see <https://www.gnu.org/licenses/>.
This file is part of the SuperflexPy modelling framework. For details about it,
visit the page https://superflexpy.readthedocs.io
CODED BY: Marco Dal Molin
DESIGNED BY: Marco Dal Molin, Fabrizio Fenicia, Dmitri Kavetski
This file contains the implementation of a class that implements methods that
are useful for Unit, Node, and Network.
"""
[docs]
class GenericComponent(object):
"""
This is the abstract class for the creation of the components Unit, Node,
and Network. It defines a series of methods that are common among the
components.
"""
_content_pointer = {}
"""
Dictionary that maps the id of the components to their location
"""
_content = [] # Note that it can also be a dictionary
"""
List (or dictionary) of the component contained
"""
_local_parameters = {}
"""
Dictionary that contains the parameters that are specific to the component
"""
_local_states = {}
"""
Dictionary that contains the states that are specific to the component
"""
_init_local_states = {}
"""
Dictionary that contains the value of the states, which that are specific
to the component, at initialization.
"""
_prefix_local_parameters = ''
"""
Prefix applied to local parameters
"""
_prefix_local_states = ''
"""
Prefix applied to local states
"""
[docs]
def get_parameters(self, names=None):
"""
This method returns the parameters of the component and of the ones
contained.
Parameters
----------
names : list(str)
Names of the parameters to return. The names must be the ones
returned by the method get_parameters_name. If None, all the
parameters are returned.
Returns
-------
dict:
Parameters of the element.
"""
parameters = {}
if names is None:
# Add local parameters
for k in self._local_parameters.keys():
parameters[k] = self._local_parameters[k]
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
cont_pars = self._content[position].get_parameters()
except AttributeError:
continue
for k in cont_pars:
if k not in parameters:
parameters[k] = cont_pars[k]
else:
for n in names:
position = self._find_content_from_name(n)
if position is None:
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
cont_pars = self._content[position].get_parameters([n])
break
except (AttributeError, KeyError): # Attribute error because the content may not have the method, Key error because the parameter may not belong to the content
continue
elif position == -1: # it means local
cont_pars = {n: self._local_parameters[n]}
else:
cont_pars = self._content[position].get_parameters([n])
parameters = {**parameters, **cont_pars}
return parameters
[docs]
def get_parameters_name(self):
"""
This method returns the names of the parameters of the component and of
the ones contained.
Returns
-------
list(str):
List with the names of the parameters.
"""
return list(self.get_parameters().keys())
[docs]
def _find_content_from_name(self, name):
"""
This method finds a component using the name of the parameter or the
state.
Parameters
----------
name : str
Name to use for the search
Returns
-------
int or tuple
Index of the component in self._content
"""
splitted_name = name.split('_')
try:
class_id = self.id
except AttributeError: # We are in a Model
class_id = None
if class_id is not None:
if class_id in splitted_name:
if (len(splitted_name) - splitted_name.index(class_id)) == 2: # It is a local parameter
position = -1
else:
# HRU or Catchment
ind = splitted_name.index(class_id)
position = self._content_pointer[splitted_name[ind + 1]]
else:
position = self._content_pointer[splitted_name[0]]
else:
# Network. The network doesn't have local parameters
for c in self._content_pointer.keys():
if c in splitted_name:
position = self._content_pointer[c]
break
else:
position = None
return position
[docs]
def set_parameters(self, parameters):
"""
This method sets the values of the parameters.
Parameters
----------
parameters : dict
Contains the parameters of the element to be set. The keys must be
the ones returned by the method get_parameters_name. Only the
parameters that have to be changed should be passed.
"""
for p in parameters.keys():
position = self._find_content_from_name(p)
if position is None:
for c in self._content_pointer.keys():
try:
position = self._content_pointer[c]
self._content[position].set_parameters({p: parameters[p]})
break
except (KeyError, ValueError):
continue
elif position == -1:
self._local_parameters[p] = parameters[p]
else:
self._content[position].set_parameters({p: parameters[p]})
[docs]
def get_states(self, names=None):
"""
This method returns the states of the component and of the ones
contained.
Parameters
----------
names : list(str)
Names of the states to return. The names must be the ones
returned by the method get_states_name. If None, all the
states are returned.
Returns
-------
dict:
States of the element.
"""
states = {}
if names is None:
# Add local states
for k in self._local_states.keys():
states[k] = self._local_states[k]
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
cont_st = self._content[position].get_states()
except AttributeError:
continue
for k in cont_st:
if k not in states:
states[k] = cont_st[k]
else:
for n in names:
position = self._find_content_from_name(n)
if position is None:
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
cont_st = self._content[position].get_states([n])
break
except (AttributeError, KeyError): # Attribute error because the content may not have the method, Key error because the parameter may not belong to the content
continue
elif position == -1:
cont_st = {n: self._local_states[n]}
else:
cont_st = self._content[position].get_states([n])
states = {**states, **cont_st}
return states
[docs]
def get_states_name(self):
"""
This method returns the names of the states of the component and of the
ones contained.
Returns
-------
list(str):
List with the names of the states.
"""
return list(self.get_states().keys())
[docs]
def set_states(self, states):
"""
This method sets the values of the states.
Parameters
----------
states : dict
Contains the states of the element to be set. The keys must be
the ones returned by the method get_states_name. Only the
states that have to be changed should be passed.
"""
for s in states.keys():
position = self._find_content_from_name(s)
if position is None:
for c in self._content_pointer.keys():
try:
position = self._content_pointer[c]
self._content[position].set_states({s: states[s]})
break
except (KeyError, ValueError):
continue
elif position == -1:
self._local_states[s] = states[s]
else:
self._content[position].set_states({s: states[s]})
[docs]
def reset_states(self, id=None):
"""
This method sets the states to the values provided to the __init__
method. If a state was initialized as None, it will not be reset.
Parameters
----------
id : list(str)
List of element's id where the method is applied.
"""
try:
local_id = self.id
except AttributeError:
local_id = None
if id is None:
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
self._content[position].reset_states()
except AttributeError:
continue
if self._init_local_states:
self.set_states(states=self._init_local_states)
else:
if isinstance(id, str):
id = [id]
for i in id:
if i == local_id and self._init_local_states:
self.set_states(states=self._init_local_states)
elif i != local_id:
i += '_X' # Needed to work with find_content_from_name
position = self._find_content_from_name(i)
self._content[position].reset_states()
[docs]
def get_timestep(self):
"""
This method returns the timestep used by the element.
Returns
-------
float
Timestep
"""
return self._dt
[docs]
def set_timestep(self, dt):
"""
This method sets the timestep used by the element.
Parameters
----------
dt : float
Timestep
"""
self._dt = dt
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
self._content[position].set_timestep(dt)
except AttributeError:
continue
[docs]
def define_solver(self, solver):
"""
This method define the solver to use for the differential equation.
Parameters
----------
solver : superflexpy.utils.root_finder.RootFinder
Solver used to find the root(s) of the differential equation(s).
Child classes may implement their own solver, therefore the tipe
of the solver is not enforced.
"""
for c in self._content_pointer.keys():
position = self._content_pointer[c]
try:
self._content[position].define_solver(solver)
except AttributeError:
continue