Skip to content

Commit eb90b4f

Browse files
authored
scenario api (#20)
1 parent bbb2435 commit eb90b4f

10 files changed

+694
-821
lines changed

swmmnetwork/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
__author__ = 'Austin Orr'
66
__email__ = '[email protected]'
7-
__version__ = '0.2.0'
7+
__version__ = '0.2.1'
88

99
from .swmmnetwork import SwmmNetwork
1010
from .convert import (
@@ -14,5 +14,5 @@
1414
pandas_edgelist_to_edgelist,
1515
pandas_node_attrs_from_swmm_inp,
1616
)
17-
from .scenario import Scenario, ScenarioLoading
17+
from .scenario import Scenario
1818
from .tests import test

swmmnetwork/compat.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
from_pandas_edgelist = nx.from_pandas_edgelist
77
set_node_attributes = nx.set_node_attributes
88

9-
else: # pragma: no cover
9+
else: # pragma: no cover
1010
from networkx.convert import _prep_create_using
1111

12-
# this code is slightly modified from the source code for NetworkX version 2.0
12+
# this code is slightly modified from the source code for NetworkX version
13+
# 2.0
1314

14-
def from_pandas_edgelist(df, source='source', target='target', edge_attr=None,
15-
create_using=None):
15+
def from_pandas_edgelist(df, source='source', target='target',
16+
edge_attr=None, create_using=None):
1617
"""Return a graph from Pandas DataFrame containing an edge list.
1718
1819
The Pandas DataFrame should contain at least two columns of node names and

swmmnetwork/convert.py

+27-18
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44

55
import hymo
66

7-
from .util import (
8-
_upper_case_index,
9-
_upper_case_column,
10-
_validate_hymo_inp,
11-
)
7+
from .util import _upper_case_column, _validate_hymo_inp
128
from .compat import from_pandas_edgelist, set_node_attributes
139

1410

@@ -65,7 +61,7 @@ def network_to_df(G, index_col=None):
6561
if index_col is not None:
6662
df = df.set_index(index_col)
6763
df.index = df.index.map(str)
68-
return (df.sort_index())
64+
df = df.sort_index()
6965
return df
7066

7167

@@ -76,8 +72,7 @@ def pandas_edgelist_from_swmm_inp(inp):
7672

7773
catchment_links = (
7874
inp.subcatchments
79-
.pipe(_upper_case_index)
80-
.pipe(_upper_case_column, ['Outlet'])
75+
.pipe(_upper_case_column, cols='Outlet', include_index=True)
8176
.assign(Inlet_Node=lambda df: df.index)
8277
.assign(id=lambda df: df.index.map(lambda s: '^' + s))
8378
.assign(xtype='dt')
@@ -95,8 +90,7 @@ def pandas_edgelist_from_swmm_inp(inp):
9590
df = (
9691
df
9792
.rename(columns={'From_Node': 'Inlet_Node', 'To_Node': 'Outlet_Node'})
98-
.pipe(_upper_case_index)
99-
.pipe(_upper_case_column, ['Inlet_Node', 'Outlet_Node'])
93+
.pipe(_upper_case_column, cols=['Inlet_Node', 'Outlet_Node'], include_index=True)
10094
.loc[:, ['Inlet_Node', 'Outlet_Node']]
10195
.assign(id=lambda df: df.index)
10296
.assign(xtype=xtype if xtype[-1] != 's' else xtype[:-1])
@@ -125,6 +119,11 @@ def pandas_edgelist_to_edgelist(df, source='source', target='target', cols=None)
125119
return edge_list
126120

127121

122+
def pandas_nodelist_to_nodelist(df):
123+
124+
return list(df.to_dict('index').items())
125+
126+
128127
def pandas_node_attrs_from_swmm_inp(inp):
129128
"""
130129
"""
@@ -138,9 +137,9 @@ def pandas_node_attrs_from_swmm_inp(inp):
138137
if df is not None:
139138
df = (
140139
df
141-
.pipe(_upper_case_index)
140+
.pipe(_upper_case_column, include_index=True)
142141
.assign(xtype=xtype if xtype[-1] != 's' else xtype[:-1])
143-
.loc[:, [ 'xtype']]
142+
.loc[:, ['xtype']]
144143
.rename(columns=lambda s: s.lower())
145144
)
146145
node_dfs.append(df)
@@ -155,8 +154,6 @@ def add_edges_from_swmm_inp(G, inp):
155154
----------
156155
G : nx.Graph-like object
157156
inp : file_path or hymo.SWMMInpFile
158-
159-
160157
"""
161158

162159
inp = _validate_hymo_inp(inp)
@@ -215,14 +212,26 @@ def from_swmm_inp(inp, create_using=None):
215212
return G
216213

217214

218-
def inp_layout(inp):
219-
"""
215+
def swmm_inp_layout_to_pos(inp):
216+
"""Reads and converts swmm node coordinates and subcatchment from inp
217+
file to networkx drawing `pos` format, i.e., a dict of node names with
218+
x, y coordinates as values.
219+
Parameters
220+
----------
221+
inp : string or hymo.SwmmInputFile
222+
this file will be read to pull the node coordinates and subcatchment
223+
positions. Polygons are converted to coordinate pairs through their
224+
centroid.
225+
226+
Returns
227+
-------
228+
dict suitable for use as the `pos` kwarg of networkx drawing methods.
220229
"""
221230

222231
inp = _validate_hymo_inp(inp)
223232

224-
coords = inp.coordinates.pipe(_upper_case_index)
225-
polys = inp.polygons.pipe(_upper_case_index)
233+
coords = inp.coordinates.pipe(_upper_case_column, include_index=True)
234+
polys = inp.polygons.pipe(_upper_case_column, include_index=True)
226235

227236
pos = (
228237
coords

swmmnetwork/core.py

+46-46
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from __future__ import division
2+
13
import warnings
2-
from collections import OrderedDict
34

45
import pandas
56
import networkx as nx
@@ -115,12 +116,12 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
115116
on the `G` graph object to retrieve results.
116117
'''
117118

118-
if load_cols is None:
119-
load_cols = []
120-
if tmnt_flags is None:
121-
tmnt_flags = []
122-
if vol_reduced_flags is None:
123-
vol_reduced_flags = []
119+
node_obj = G.node[node_name]
120+
121+
load_cols = _to_list(load_cols)
122+
tmnt_flags = _to_list(tmnt_flags)
123+
vol_reduced_flags = _to_list(vol_reduced_flags)
124+
124125
if bmp_performance_mapping_conc is None:
125126
bmp_performance_mapping_conc = {}
126127

@@ -139,7 +140,7 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
139140

140141
# subcatchments have no volume from in_edges, but they do have a
141142
# node_vol.
142-
node_vol = G.node[node_name].get(vol_col, 0)
143+
node_vol = node_obj.get(vol_col, 0)
143144

144145
edge_vol_in = _sum_edge_attr(
145146
G, node_name, vol_col, method='in_edges', split_on=split_on)
@@ -164,15 +165,15 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
164165

165166
if ck_vol_col is not None:
166167
vol_diff_ck_col = vol_col + "_diff_ck"
167-
ck_vol = G.node[node_name].get(ck_vol_col, node_vol)
168+
ck_vol = node_obj.get(ck_vol_col, node_vol)
168169
vol_diff_ck = vol_in - ck_vol
169-
G.node[node_name][vol_diff_ck_col] = vol_diff_ck
170+
node_obj[vol_diff_ck_col] = vol_diff_ck
170171

171172
vol_in = node_vol + edge_vol_in
172173

173-
G.node[node_name][vol_in_col] = vol_in
174-
G.node[node_name][vol_out_col] = edge_vol_out
175-
G.node[node_name][vol_gain_col] = edge_vol_out - edge_vol_in
174+
node_obj[vol_in_col] = vol_in
175+
node_obj[vol_out_col] = edge_vol_out
176+
node_obj[vol_gain_col] = edge_vol_out - edge_vol_in
176177
# Negative values in this field
177178
# indicate internal node losses. There should
178179
# be a load reduction here if there is load in the inflow.
@@ -181,18 +182,18 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
181182
vol_captured = vol_treated + vol_reduced
182183

183184
# assign node vol attributes
184-
G.node[node_name][vol_eff_col] = vol_eff
185+
node_obj[vol_eff_col] = vol_eff
185186

186-
G.node[node_name][vol_red_col] = vol_reduced
187-
G.node[node_name][pct_vol_red_col] = 100 * \
187+
node_obj[vol_red_col] = vol_reduced
188+
node_obj[pct_vol_red_col] = 100 * \
188189
_safe_divide(vol_reduced, vol_in)
189190

190-
G.node[node_name][vol_tmnt_col] = vol_treated
191-
G.node[node_name][pct_vol_tmnt_col] = 100 * \
191+
node_obj[vol_tmnt_col] = vol_treated
192+
node_obj[pct_vol_tmnt_col] = 100 * \
192193
_safe_divide(vol_treated, vol_in)
193194

194-
G.node[node_name][vol_cap_col] = vol_captured
195-
G.node[node_name][pct_vol_cap_col] = 100 * \
195+
node_obj[vol_cap_col] = vol_captured
196+
node_obj[pct_vol_cap_col] = 100 * \
196197
_safe_divide(vol_captured, vol_in)
197198

198199
#-----solve volume weighted loads-----#
@@ -207,13 +208,14 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
207208
conc_eff_col = load_col + '_conc_eff'
208209
pct_conc_red_col = load_col + '_conc_pct_reduced'
209210

210-
# all nodes except subcatchments should return zero here.
211-
node_load = G.node[node_name].get(load_col, 0)
211+
# all nodes except pollutant sources (typically subcatchments)
212+
# should return zero here.
213+
node_load = node_obj.get(load_col, 0)
212214

213215
node_load_in = _sum_edge_attr(G, node_name, load_eff_col,
214216
method='in_edges', split_on=split_on) + node_load
215217

216-
G.node[node_name][load_in_col] = node_load_in
218+
node_obj[load_in_col] = node_load_in
217219

218220
if vol_in > 0 and node_load_in > 0: # skip the math if it's unnecessary
219221

@@ -261,9 +263,9 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
261263
flag = '_no_tmnt_fxn'
262264

263265
if data.get('_bmp_tmnt_flag') is None:
264-
data['_bmp_tmnt_flag'] = OrderedDict()
266+
data['_bmp_tmnt_flag'] = {}
265267
if data['_bmp_tmnt_flag'].get(flag) is None:
266-
data['_bmp_tmnt_flag'].update({flag:[]})
268+
data['_bmp_tmnt_flag'].update({flag: []})
267269
data['_bmp_tmnt_flag'][flag].append(load_col)
268270

269271
link_conc_eff = fxn(node_conc_in)
@@ -294,25 +296,25 @@ def solve_node(G, node_name, edge_name_col='id', split_on='-',
294296
node_conc_eff = 0
295297

296298
# assign node load and concentration attributes
297-
G.node[node_name][conc_in_col] = node_conc_in
298-
G.node[node_name][conc_eff_col] = node_conc_eff
299-
G.node[node_name][pct_conc_red_col] = 100 * \
299+
node_obj[conc_in_col] = node_conc_in
300+
node_obj[conc_eff_col] = node_conc_eff
301+
node_obj[pct_conc_red_col] = 100 * \
300302
_safe_divide((node_conc_in - node_conc_eff), node_conc_in)
301303

302304
node_load_reduced = node_load_in - node_load_eff
303305

304-
G.node[node_name][load_eff_col] = node_load_eff
305-
G.node[node_name][load_red_col] = node_load_reduced
306-
G.node[node_name][pct_load_red_col] = 100 * \
306+
node_obj[load_eff_col] = node_load_eff
307+
node_obj[load_red_col] = node_load_reduced
308+
node_obj[pct_load_red_col] = 100 * \
307309
_safe_divide(node_load_reduced, node_load_in)
308310

309311
return
310312

311313

312314
def solve_network(G, edge_name_col='id', split_on='-',
313-
vol_col='volume', ck_vol_col=None, tmnt_flags=None,
314-
vol_reduced_flags=None, load_cols=None,
315-
bmp_performance_mapping_conc=None):
315+
vol_col='volume', tmnt_flags=['TR'],
316+
vol_reduced_flags=['INF'], ck_vol_col=None,
317+
load_cols=None, bmp_performance_mapping_conc=None):
316318

317319
validate_swmmnetwork(G)
318320

@@ -324,17 +326,15 @@ def solve_network(G, edge_name_col='id', split_on='-',
324326
bmp_performance_mapping_conc = {}
325327

326328
for node in nx.topological_sort(G):
327-
solve_node(
328-
G,
329-
node,
330-
edge_name_col=edge_name_col,
331-
vol_col=vol_col,
332-
ck_vol_col=ck_vol_col,
333-
tmnt_flags=tmnt_flags,
334-
vol_reduced_flags=vol_reduced_flags,
335-
load_cols=load_cols,
336-
bmp_performance_mapping_conc=bmp_performance_mapping_conc,
337-
split_on=split_on,
338-
)
329+
solve_node(G, node,
330+
edge_name_col=edge_name_col,
331+
split_on=split_on,
332+
vol_col=vol_col,
333+
tmnt_flags=tmnt_flags,
334+
vol_reduced_flags=vol_reduced_flags,
335+
ck_vol_col=ck_vol_col,
336+
load_cols=load_cols,
337+
bmp_performance_mapping_conc=bmp_performance_mapping_conc,
338+
)
339339

340340
return

0 commit comments

Comments
 (0)