Coverage for wsimod\nodes\catchment.py: 23%
43 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-24 11:16 +0100
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-24 11:16 +0100
1# -*- coding: utf-8 -*-
2"""Created on Mon Nov 15 14:20:36 2021.
4@author: bdobson
6Converted to totals on 2022-05-03
7"""
8from wsimod.core import constants
9from wsimod.nodes.nodes import Node
12class Catchment(Node):
13 """"""
15 def __init__(
16 self,
17 name,
18 data_input_dict={},
19 ):
20 """Node that reads input data to create VQIPs that are pushed downstream and
21 tracks abstractions made from the node, adjusting pushes accordingly.
23 Args:
24 name (str): Node name
25 data_input_dict (dict, optional): Dictionary of data inputs relevant for
26 the node. Keys are tuples where first value is the name of the
27 variable to read from the dict and the second value is the time.
28 Defaults to {}.
30 Functions intended to call in orchestration:
31 route
33 Key assumptions:
34 - Flows from `Catchment` nodes are simply read from data, thus
35 assumed to be unresponsive to wider water cycle changes.
37 Input data and parameter requirements:
38 - Flow data in the `data_input_dict` at the model timestep.
39 _Units_: cubic metres/timestep
40 - Values for each variable defined in `constants.POLLUTANTS` also
41 stored in `data_input_dict` at the model timestep.
42 _Units_: kg/m3/timestep (additive pollutants)
43 """
44 # Update args
45 super().__init__(name)
46 self.data_input_dict = data_input_dict
48 # Update handlers
49 self.pull_set_handler["default"] = self.pull_set_abstraction
50 self.pull_check_handler["default"] = self.pull_check_abstraction
51 self.push_set_handler["default"] = self.push_set_deny
52 self.push_check_handler["default"] = self.push_set_deny
53 self.unrouted_water = self.empty_vqip()
54 # Mass balance
55 self.mass_balance_in.append(lambda: self.get_flow())
56 self.mass_balance_out.append(lambda: self.unrouted_water)
57 self.end_timestep = self.end_timestep_
59 def get_flow(self):
60 """Read volume data, read pollutant data, convert additibve pollutants from
61 kg/m3 to kg.
63 Returns:
64 vqip (dict): Return read data as a VQIP
65 """
66 # TODO (if used) - note that if flow is < float accuracy then it won't
67 # get pushed, and the pollutants will 'disappear', causing a mass balance error
68 vqip = {"volume": self.data_input_dict[("flow", self.t)]}
69 for pollutant in constants.POLLUTANTS:
70 vqip[pollutant] = self.data_input_dict[(pollutant, self.t)]
71 for pollutant in constants.ADDITIVE_POLLUTANTS:
72 vqip[pollutant] *= vqip["volume"]
74 return vqip
76 def route(self):
77 """Send any water that has not already been abstracted downstream."""
78 # Get amount of water
79 avail = self.get_avail()
80 # Route excess flow onwards
81 reply = self.push_distributed(avail, of_type=["Node", "River", "Waste"])
82 self.unrouted_water = self.sum_vqip(self.unrouted_water, reply)
83 if reply["volume"] > constants.FLOAT_ACCURACY:
84 pass
85 # print('Catchment unable to route')
87 def get_avail(self):
88 """Water available for abstraction (Read data and subtract pre-existing
89 abstractions).
91 Returns:
92 avail (dict): A VQIP of water available for abstraction
93 """
94 # Get available vqip
95 avail = self.get_flow()
97 # Remove abstractions already made
98 for name, arc in self.out_arcs.items():
99 avail = self.v_change_vqip(avail, avail["volume"] - arc.vqip_in["volume"])
101 return avail
103 def pull_check_abstraction(self, vqip=None):
104 """Check wrapper for get_avail that updates response if VQIP is given.
106 Args:
107 vqip (dict, optional): A VQIP that is compared with get_avail and the
108 minimum is returned. Only the 'volume' key is used. Defaults to None.
110 Returns:
111 avail (dict): A VQIP of water available for abstraction
112 """
113 # Respond to abstraction check request
114 avail = self.get_avail()
116 if vqip:
117 avail = self.v_change_vqip(avail, min(avail["volume"], vqip["volume"]))
119 return avail
121 def pull_set_abstraction(self, vqip):
122 """Request set wrapper for get_avail where VQIP is specified.
124 Args:
125 vqip (dict): A VQIP of water to pull. Only the 'volume' key is used.
127 Returns:
128 avail (dict): A VQIP of water abstracted
129 """
130 # Respond to abstraction set request
131 avail = self.get_avail()
132 avail = self.v_change_vqip(avail, min(avail["volume"], vqip["volume"]))
134 return avail
136 def end_timestep_(self):
137 """Reset unrouted water."""
138 self.unrouted_water = self.empty_vqip()