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

Fix bug when to_gis() is called on a network with a leak #458

Merged
merged 11 commits into from
Nov 19, 2024
42 changes: 28 additions & 14 deletions wntr/epanet/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3053,36 +3053,50 @@ def _read_control_line(line, wn, flow_units, control_name):
current = line.split()
if current == []:
return
link_name = current[1]
link = wn.get_link(link_name)

element_name = current[1]

# For the case of leak demands
if current[0] == 'JUNCTION':
element = wn.get_node(element_name)
else:
element = wn.get_link(element_name)

if current[5].upper() != 'TIME' and current[5].upper() != 'CLOCKTIME':
node_name = current[5]
current = [i.upper() for i in current]
current[1] = link_name # don't capitalize the link name
current[1] = element_name # don't capitalize the link name

# Create the control action object

status = current[2].upper()
if status == 'OPEN' or status == 'OPENED' or status == 'CLOSED' or status == 'ACTIVE':
setting = LinkStatus[status].value
action_obj = wntr.network.ControlAction(link, 'status', setting)
action_obj = wntr.network.ControlAction(element, 'status', setting)
else:
if isinstance(link, wntr.network.Pump):
action_obj = wntr.network.ControlAction(link, 'base_speed', float(current[2]))
elif isinstance(link, wntr.network.Valve):
if link.valve_type == 'PRV' or link.valve_type == 'PSV' or link.valve_type == 'PBV':
if isinstance(element, wntr.network.Pump):
action_obj = wntr.network.ControlAction(element, 'base_speed', float(current[2]))
elif isinstance(element, wntr.network.Valve):
if element.valve_type == 'PRV' or element.valve_type == 'PSV' or element.valve_type == 'PBV':
setting = to_si(flow_units, float(current[2]), HydParam.Pressure)
elif link.valve_type == 'FCV':
elif element.valve_type == 'FCV':
setting = to_si(flow_units, float(current[2]), HydParam.Flow)
elif link.valve_type == 'TCV':
elif element.valve_type == 'TCV':
setting = float(current[2])
elif link.valve_type == 'GPV':
elif element.valve_type == 'GPV':
setting = current[2]
else:
raise ValueError('Unrecognized valve type {0} while parsing control {1}'.format(link.valve_type, line))
action_obj = wntr.network.ControlAction(link, 'setting', setting)
raise ValueError('Unrecognized valve type {0} while parsing control {1}'.format(element.valve_type, line))
action_obj = wntr.network.ControlAction(element, 'setting', setting)
elif isinstance(element, wntr.network.Node):
if status=="TRUE":
setting = True
elif status=="FALSE":
setting = False
action_obj = wntr.network.ControlAction(element, 'leak_status', setting)

else:
raise RuntimeError(('Links of type {0} can only have controls that change\n'.format(type(link))+
raise RuntimeError(('Links of type {0} can only have controls that change\n'.format(type(element))+
'the link status. Control: {0}'.format(line)))

# Create the control object
Expand Down
5 changes: 5 additions & 0 deletions wntr/gis/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ def _extract_geodataframe(df, crs=None, valid_base_names=None,
if 'node_type' in df.columns:
geom = [Point((x,y)) for x,y in df['coordinates']]
del df['node_type']

# do not carry over leak attributes to dataframe.
del df['leak']
del df['leak_area']
del df['leak_discharge_coeff']
elif 'link_type' in df.columns:
geom = []
for link_name in df['name']:
Expand Down
9 changes: 7 additions & 2 deletions wntr/network/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ def leak_status(self):
# @leak_status.setter
# def leak_status(self, value):
# self._leak_status = value

@property
def leak(self):
"""float: (read-only) the current simulation leak area at the node"""
return self._leak

@property
def leak_area(self):
Expand Down Expand Up @@ -257,8 +262,8 @@ def to_dict(self):
d['node_type'] = self.node_type
for k in dir(self):
if not k.startswith('_') and \
k not in ['demand', 'head', 'leak_area', 'leak_demand',
'leak_discharge_coeff', 'leak_status', 'level', 'pressure', 'quality', 'vol_curve', 'head_timeseries']:
k not in ['demand', 'head', 'leak_demand', 'leak_status',
'level', 'pressure', 'quality', 'vol_curve', 'head_timeseries']:
try:
val = getattr(self, k)
if not isinstance(val, types.MethodType):
Expand Down
6 changes: 4 additions & 2 deletions wntr/network/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import abc
from wntr.utils.ordered_set import OrderedSet
from collections import OrderedDict
from .elements import Tank, Junction, Valve, Pump, Reservoir, Pipe
from .elements import Tank, Junction, Valve, Pump, Reservoir, Pipe, Link
from wntr.utils.doc_inheritor import DocInheritor
import warnings
from typing import Hashable, Dict, Any, Tuple, MutableSet, Iterable
Expand Down Expand Up @@ -1753,7 +1753,9 @@ def __repr__(self):
return '<ControlAction: {}, {}, {}>'.format(str(self._target_obj), str(self._attribute), str(self._repr_value()))

def __str__(self):
return "{} {} {} IS {}".format(self._target_obj.link_type.upper(),
target_obj_type = (self._target_obj.link_type if isinstance(self._target_obj, Link) else
self._target_obj.node_type)
return "{} {} {} IS {}".format(target_obj_type.upper(),
self._target_obj.name,
self._attribute.upper(),
self._repr_value())
Expand Down
5 changes: 5 additions & 0 deletions wntr/network/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ def from_dict(d: dict, append=None):
j.pressure_exponent = node.setdefault("pressure_exponent")
j.required_pressure = node.setdefault("required_pressure")
j.tag = node.setdefault("tag")

j._leak = node.setdefault("leak", False)
j._leak_area = node.setdefault("leak_area", 0.0)
j._leak_discharge_coeff = node.setdefault("leak_discharge_coeff", 0.0)

# custom additional attributes
for attr in list(set(node.keys()) - set(dir(j))):
setattr( j, attr, node[attr] )
Expand Down
25 changes: 24 additions & 1 deletion wntr/tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ def setUpClass(self):

self.wntr = wntr

inp_file = join(ex_datadir, "Net6.inp")
self.inp_file = join(ex_datadir, "Net3.inp")
self.inp_files = [join(ex_datadir, f) for f in ["Net1.inp", "Net2.inp", "Net3.inp", "Net6.inp"]]

@classmethod
Expand All @@ -979,6 +979,29 @@ def test_json_pattern_dump(self):
wn = wntr.network.WaterNetworkModel()
wn.add_pattern('pat0', [0,1,0,1,0,1,0])
self.wntr.network.write_json(wn, f'temp.json')

def test_dict_with_leak(self):
# This covers a bug where writing controls to a dictionary broke if a leak was added
wn = wntr.network.WaterNetworkModel(self.inp_file)
junction_name = wn.junction_name_list[0]
junction = wn.get_node(junction_name)
junction.add_leak(wn, area=1, start_time=0, end_time=3600)
wn_dict = wn.to_dict()
wn2 = wntr.network.from_dict(wn_dict)

# check leak controls and node properties
start_control = wn.get_control(wn.control_name_list[18])
start_control2 = wn2.get_control(wn2.control_name_list[18])
assert str(start_control) == str(start_control2)

end_control = wn.get_control(wn.control_name_list[19])
end_control2 = wn2.get_control(wn2.control_name_list[19])
assert str(end_control) == str(end_control2)

junction2 = wn2.get_node(junction_name)
assert junction2._leak
assert junction2._leak_area == 1
assert junction2._leak_discharge_coeff == 0.75


@unittest.skipIf(not has_geopandas,
Expand Down
Loading