Source code for wntr.sim.hydraulics

"""WNTR Simulator hydraulics model."""

import pandas as pd
import numpy as np
import scipy.sparse as sparse
import math
import warnings
import logging
from wntr.network.model import WaterNetworkModel
from wntr.network.base import NodeType, LinkType, LinkStatus
from wntr.network.elements import Junction, Tank, Reservoir, Pipe, Pump, HeadPump, PowerPump, PRValve, PSValve, FCValve, \
    TCValve, GPValve, PBValve
from collections import OrderedDict
from wntr.utils.ordered_set import OrderedSet
from wntr.sim import aml
from wntr.sim.models import constants, var, param, constraint
from wntr.sim.models.utils import ModelUpdater

logger = logging.getLogger(__name__)


[docs] def create_hydraulic_model(wn, HW_approx='default'): """ Parameters ---------- wn: WaterNetworkModel mode: str HW_approx: str Specifies which Hazen-Williams headloss approximation to use. Options are 'default' and 'piecewise'. Please see the WNTR documentation on hydraulics for details. Returns ------- m: wntr.aml.Model model_updater: wntr.models.utils.ModelUpdater """ m = aml.Model() model_updater = ModelUpdater() # Global constants constants.hazen_williams_constants(m) constants.head_pump_constants(m) constants.leak_constants(m) constants.pdd_constants(m) param.source_head_param(m, wn) param.expected_demand_param(m, wn) mode = wn.options.hydraulic.demand_model if mode in ['DD', 'DDA']: pass elif mode in ['PDD', 'PDA']: param.pmin_param.build(m, wn, model_updater) param.pnom_param.build(m, wn, model_updater) param.pdd_poly_coeffs_param.build(m, wn, model_updater) param.leak_coeff_param.build(m, wn, model_updater) param.leak_area_param.build(m, wn, model_updater) param.leak_poly_coeffs_param.build(m, wn, model_updater) param.elevation_param.build(m, wn, model_updater) if wn.options.hydraulic.headloss == 'C-M': raise NotImplementedError('C-M headloss is not currently supported in the WNTRSimulator') if wn.options.hydraulic.headloss == 'D-W': raise NotImplementedError('D-W headloss is not currently supported in the WNTRSimulator') param.hw_resistance_param.build(m, wn, model_updater) param.minor_loss_param.build(m, wn, model_updater) param.tcv_resistance_param.build(m, wn, model_updater) param.pump_power_param.build(m, wn, model_updater) param.valve_setting_param.build(m, wn, model_updater) if mode in ['DD','DDA']: pass elif mode in ['PDD','PDA']: var.demand_var(m, wn) var.flow_var(m, wn) var.head_var(m, wn) var.leak_rate_var(m, wn) if mode in ['DD','DDA']: constraint.mass_balance_constraint.build(m, wn, model_updater) elif mode in ['PDD','PDA']: constraint.pdd_mass_balance_constraint.build(m, wn, model_updater) constraint.pdd_constraint.build(m, wn, model_updater) else: raise ValueError('mode not recognized: ' + str(mode)) if HW_approx == 'default': constraint.approx_hazen_williams_headloss_constraint.build(m, wn, model_updater) elif HW_approx == 'piecewise': constraint.piecewise_hazen_williams_headloss_constraint.build(m, wn, model_updater) else: raise ValueError('Unexpected value for HW_approx: ' + str(HW_approx)) constraint.head_pump_headloss_constraint.build(m, wn, model_updater) constraint.power_pump_headloss_constraint.build(m, wn, model_updater) constraint.prv_headloss_constraint.build(m, wn, model_updater) constraint.psv_headloss_constraint.build(m, wn, model_updater) constraint.tcv_headloss_constraint.build(m, wn, model_updater) constraint.fcv_headloss_constraint.build(m, wn, model_updater) if len(wn.pbv_name_list) > 0: raise NotImplementedError('PBV valves are not currently supported in the WNTRSimulator') if len(wn.gpv_name_list) > 0: raise NotImplementedError('GPV valves are not currently supported in the WNTRSimulator') constraint.leak_constraint.build(m, wn, model_updater) # TODO: Document that changing a curve with controls does not do anything; you have to change the pump_curve_name attribute on the pump return m, model_updater
[docs] def update_model_for_controls(m, wn, model_updater, change_tracker): """ Parameters ---------- m: wntr.aml.Model wn: wntr.network.WaterNetworkModel model_updater: wntr.models.utils.ModelUpdater change_tracker: wntr.network.controls.ControlChangeTracker """ for obj, attr in change_tracker.get_changes(ref_point='model'): model_updater.update(m, wn, obj, attr) change_tracker.reset_reference_point(key='model')
[docs] def update_network_previous_values(wn): """ Parameters ---------- wn: wntr.network.WaterNetworkModel """ wn._prev_sim_time = wn.sim_time for link_name, link in wn.valves(): link._prev_setting = link.setting for tank_name, tank in wn.tanks(): tank._prev_head = tank.head
[docs] def update_tank_heads(wn): """ Parameters ---------- wn: wntr.network.WaterNetworkModel """ dt = wn.sim_time - wn._prev_sim_time for tank_name, tank in wn.tanks(): q_net = tank.demand dV = q_net * dt if tank.vol_curve is None: delta_h = 4.0 * dV / (math.pi * tank.diameter ** 2) else: vcurve = np.array(tank.vol_curve.points) level_x = vcurve[:,0] volume_y = vcurve[:,1] # I had to include this because the _prev_head is the reference # point needed if the tank.head (and tank.level) have already # been updated. This isn't a problem for cases with no volume curve. if tank.head == tank._prev_head: cur_level = tank.level else: cur_level = tank._prev_head - (tank.head - tank.level) V0 = np.interp(cur_level,level_x,volume_y) V1 = V0 + dV level_new = np.interp(V1,volume_y,level_x) delta_h = level_new - cur_level tank._head = tank._prev_head + delta_h
[docs] def initialize_results_dict(wn): """ Parameters ---------- wn: wntr.network.WaterNetworkModel Returns ------- res: dict """ node_res = OrderedDict() link_res = OrderedDict() node_res['head'] = OrderedDict((name, list()) for name, obj in wn.nodes()) node_res['demand'] = OrderedDict((name, list()) for name, obj in wn.nodes()) node_res['pressure'] = OrderedDict((name, list()) for name, obj in wn.nodes()) node_res['leak_demand'] = OrderedDict((name, list()) for name, obj in wn.nodes()) link_res['flowrate'] = OrderedDict((name, list()) for name, obj in wn.links()) link_res['velocity'] = OrderedDict((name, list()) for name, obj in wn.links()) link_res['status'] = OrderedDict((name, list()) for name, obj in wn.links()) link_res['setting'] = OrderedDict((name, list()) for name, obj in wn.links()) return node_res, link_res
[docs] def save_results(wn, node_res, link_res): """ Parameters ---------- wn: wntr.network.WaterNetworkModel node_res: OrderedDict link_res: OrderedDict """ for name, node in wn.junctions(): node_res['head'][name].append(node.head) node_res['demand'][name].append(node.demand) if node._is_isolated: node_res['pressure'][name].append(0.0) else: node_res['pressure'][name].append(node.head - node.elevation) node_res['leak_demand'][name].append(node.leak_demand) for name, node in wn.tanks(): node_res['head'][name].append(node.head) node_res['demand'][name].append(node.demand) node_res['pressure'][name].append(node.head - node.elevation) node_res['leak_demand'][name].append(node.leak_demand) for name, node in wn.reservoirs(): node_res['head'][name].append(node.head) node_res['demand'][name].append(node.demand) node_res['pressure'][name].append(0.0) node_res['leak_demand'][name].append(0.0) for name, link in wn.pipes(): link_res['flowrate'][name].append(link.flow) link_res['velocity'][name].append(abs(link.flow)*4.0 / (math.pi*link.diameter**2)) link_res['status'][name].append(link.status) link_res['setting'][name].append(link.roughness) for name, link in wn.head_pumps(): link_res['flowrate'][name].append(link.flow) link_res['velocity'][name].append(0) link_res['status'][name].append(link.status) link_res['setting'][name].append(1) A, B, C = link.get_head_curve_coefficients() if link.flow > (A/B)**(1.0/C): start_node_name = link.start_node_name end_node_name = link.end_node_name start_node = wn.get_node(start_node_name) end_node = wn.get_node(end_node_name) start_head = start_node.head end_head = end_node.head warnings.warn('Pump ' + name + ' has exceeded its maximum flow.') logger.warning( 'Pump {0} has exceeded its maximum flow. Pump head: {1}; Pump flow: {2}; Max pump flow: {3}'.format( name, end_head - start_head, link.flow, (A/B)**(1.0/C))) for name, link in wn.power_pumps(): link_res['flowrate'][name].append(link.flow) link_res['velocity'][name].append(0) link_res['status'][name].append(link.status) link_res['setting'][name].append(1) # power pumps have no speed for name, link in wn.valves(): link_res['flowrate'][name].append(link.flow) link_res['velocity'][name].append(abs(link.flow)*4.0 / (math.pi*link.diameter**2)) link_res['status'][name].append(link.status) link_res['setting'][name].append(link.setting)
[docs] def get_results(wn, results, node_res, link_res): """ Parameters ---------- wn: wntr.network.WaterNetworkModel results: wntr.sim.results.SimulationResults node_res: OrderedDict link_res: OrderedDict """ ntimes = len(results.time) nnodes = wn.num_nodes nlinks = wn.num_links node_names = wn.junction_name_list + wn.tank_name_list + wn.reservoir_name_list link_names = wn.pipe_name_list + wn.head_pump_name_list + wn.power_pump_name_list + wn.valve_name_list for key, value in node_res.items(): node_res[key] = pd.DataFrame(data=np.array([node_res[key][name] for name in node_names]).transpose(), index=results.time, columns=node_names) results.node = node_res for key, value in link_res.items(): link_res[key] = pd.DataFrame(data=np.array([link_res[key][name] for name in link_names]).transpose(), index=results.time, columns=link_names) results.link = link_res
# Add headloss to results.link -- removed for now, this is slow #headloss = pd.DataFrame(data=None, index=results.time, columns=link_names) # for name, link in wn.links(): # start_node = link.start_node_name # end_node = link.end_node_name # start_head = results.node['head'].loc[:,start_node] # end_head = results.node['head'].loc[:,end_node] # if isinstance(link, Pipe): # # Unit headloss for pipes # headloss.loc[:,name] = abs(end_head - start_head)/link.length # elif isinstance(link, Pump): # # Negative of head gain for pumps # head_gain = -(end_head - start_head) # head_gain[head_gain > 0] = 0 # headloss.loc[:,name] = head_gain # else: # # Total head loss for valves # headloss.loc[:,name] = (end_head - start_head) # # Headloss is 0 if the link is closed # headloss.loc[results.link['status'].loc[:,name] == 0,name] = 0 #results.link['headloss'] = headloss
[docs] def store_results_in_network(wn, m): """ Parameters ---------- wn: wntr.network.WaterNetworkModel m: wntr.aml.Model """ mode = wn.options.hydraulic.demand_model for name, link in wn.links(): if link._is_isolated: link._flow = 0 else: link._flow = m.flow[name].value for name, link in wn.valves(): link._setting = m.valve_setting[name].value for name, node in wn.junctions(): if node._is_isolated: node._head = 0 node._demand = 0 node._pressure = 0 node._leak_demand = 0 else: node._head = m.head[name].value node._pressure = m.head[name].value - node.elevation if mode in ['PDD', 'PDA']: node._demand = m.demand[name].value else: node._demand = m.expected_demand[name].value if node.leak_status: node._leak_demand = m.leak_rate[name].value else: node._leak_demand = 0 for name, node in wn.tanks(): if node.leak_status: node._leak_demand = m.leak_rate[name].value else: node._leak_demand = 0 node._demand = (sum(wn.get_link(link_name).flow for link_name in wn.get_links_for_node(name, 'INLET')) - sum(wn.get_link(link_name).flow for link_name in wn.get_links_for_node(name, 'OUTLET')) - node._leak_demand) for name, node in wn.reservoirs(): node._head = node.head_timeseries.at(wn.sim_time) node._leak_demand = 0 node._demand = (sum(wn.get_link(link_name).flow for link_name in wn.get_links_for_node(name, 'INLET')) - sum(wn.get_link(link_name).flow for link_name in wn.get_links_for_node(name, 'OUTLET')))