Source code for wntr.metrics.economic

"""
The wntr.metrics.economic module contains economic metrics.
"""
from wntr.network import Tank, Pipe, Pump, Valve
import numpy as np 
import pandas as pd 
import logging
import scipy

logger = logging.getLogger(__name__)

[docs] def annual_network_cost(wn, tank_cost=None, pipe_cost=None, prv_cost=None, pump_cost=None): """ Compute annual network cost :cite:p:`sokz12`. Use the closest value from the lookup tables to compute annual cost for each component in the network. Parameters ---------- wn : wntr WaterNetworkModel Water network model. The water network model is needed to define tank volume, pipe and valve diameter, and pump power conditions. tank_cost : pandas Series, optional Annual tank cost indexed by volume (default values below, from :cite:p:`sokz12`). ============= ================================ Volume (m3) Annual Cost ($/yr) ============= ================================ 500 14020 1000 30640 2000 61210 3750 87460 5000 122420 10000 174930 ============= ================================ pipe_cost : pandas Series, optional Annual pipe cost per pipe length indexed by diameter (default values below, from :cite:p:`sokz12`). ============= ============= ================================ Diameter (in) Diameter (m) Annual Cost ($/m/yr) ============= ============= ================================ 4 0.102 8.31 6 0.152 10.10 8 0.203 12.10 10 0.254 12.96 12 0.305 15.22 14 0.356 16.62 16 0.406 19.41 18 0.457 22.20 20 0.508 24.66 24 0.610 35.69 28 0.711 40.08 30 0.762 42.60 ============= ============= ================================ prv_cost : pandas Series, optional Annual PRV valve cost indexed by diameter (default values below, from :cite:p:`sokz12`). ============= ============= ================================ Diameter (in) Diameter (m) Annual Cost ($/m/yr) ============= ============= ================================ 4 0.102 323 6 0.152 529 8 0.203 779 10 0.254 1113 12 0.305 1892 14 0.356 2282 16 0.406 4063 18 0.457 4452 20 0.508 4564 24 0.610 5287 28 0.711 6122 30 0.762 6790 ============= ============= ================================ pump_cost : pd.Series, optional Annual pump cost indexed by maximum power input to pump (default values below, from :cite:p:`sokz12`). Maximum Power for a HeadPump is computed from the pump curve as follows: .. math:: Pmp = g*rho/eff*exp(ln(A/(B*(C+1)))/C)*(A - B*(exp(ln(A/(B*(C+1)))/C))^C) where :math:`Pmp` is the maximum power (W), :math:`g` is acceleration due to gravity (9.81 m/s^2), :math:`rho` is the density of water (1000 kg/m^3), :math:`eff` is the global efficiency (0.75 default), :math:`A`, :math:`B`, and :math:`C` are the pump curve coefficients. ================== ================================ Maximum power (W) Annual Cost ($/yr) ================== ================================ 11310 2850 22620 3225 24880 3307 31670 3563 38000 3820 45240 4133 49760 4339 54280 4554 59710 4823 ================== ================================ Returns ---------- Annual network cost in dollars (float) """ # Initialize network construction cost network_cost = 0 # Set defaults if tank_cost is None: volume = [500, 1000, 2000, 3750, 5000, 10000] cost = [14020, 30640, 61210, 87460, 122420, 174930] tank_cost = pd.Series(cost, volume) if pipe_cost is None: diameter = [4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30] # inch diameter = np.array(diameter)*0.0254 # m cost = [8.31, 10.1, 12.1, 12.96, 15.22, 16.62, 19.41, 22.2, 24.66, 35.69, 40.08, 42.6] pipe_cost = pd.Series(cost, diameter) if prv_cost is None: diameter = [4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30] # inch diameter = np.array(diameter)*0.0254 # m cost = [323, 529, 779, 1113, 1892, 2282, 4063, 4452, 4564, 5287, 6122, 6790] prv_cost = pd.Series(cost, diameter) if pump_cost is None: Pmp = [11310, 22620, 24880, 31670, 38000, 45240, 49760, 54280, 59710] cost = [2850, 3225, 3307, 3563, 3820, 4133, 4339, 4554, 4823] pump_cost = pd.Series(cost, Pmp) # Tank construction cost - not only looking at direct volume capacity but also # but a rough estimate of the support structure size # below the tank. for node_name, node in wn.nodes(Tank): if node.vol_curve is None: tank_construction_volume = np.pi*(node.diameter/2)**2 *node.max_level else: vcurve = np.array(node.vol_curve.points) tank_volume = np.interp(node.max_level,vcurve[:,0],vcurve[:,1]) avg_area = tank_volume / (node.max_level - node.min_level) tank_base_volume = node.min_level * avg_area tank_construction_volume = tank_volume + tank_base_volume # choose the entry that is closest (keep the cost structure discreet) idx = np.argmin([np.abs(tank_cost.index - tank_construction_volume)]) #print(node_name, tank_cost.iloc[idx]) network_cost = network_cost + tank_cost.iloc[idx] # Pipe construction cost for link_name, link in wn.links(Pipe): idx = np.argmin([np.abs(pipe_cost.index - link.diameter)]) #print(link_name, pipe_cost.iloc[idx], link.length) network_cost = network_cost + pipe_cost.iloc[idx]*link.length # Pump construction cost for link_name, link in wn.head_pumps(): coeff = link.get_head_curve_coefficients() A = coeff[0] B = coeff[1] C = coeff[2] Pmax = 9.81*1000*np.exp(np.log(A/(B*(C+1)))/C)*(A - B*(np.exp(np.log(A/(B*(C+1)))/C))**C) Pmax = Pmax / wn.options.energy.global_efficiency idx = np.argmin([np.abs(pump_cost.index - Pmax)]) #print(link_name, Pmax, pump_cost.iloc[idx]) network_cost = network_cost + pump_cost.iloc[idx] for link_name, link in wn.power_pumps(): Pmax = link.power Pmax = Pmax / wn.options.energy.global_efficiency idx = np.argmin([np.abs(pump_cost.index - Pmax)]) #print(link_name, Pmax, pump_cost.iloc[idx]) network_cost = network_cost + pump_cost.iloc[idx] # PRV valve construction cost for link_name, link in wn.links(Valve): if link.valve_type == 'PRV': idx = np.argmin([np.abs(prv_cost.index - link.diameter)]) #print(link_name, link.diameter, prv_cost.iloc[idx]) network_cost = network_cost + prv_cost.iloc[idx] return network_cost
[docs] def annual_ghg_emissions(wn, pipe_ghg=None): """ Compute annual greenhouse gas emissions :cite:p:`sokz12`. Use the closest value in the lookup table to compute annual GHG emissions for each pipe in the network. Parameters ---------- wn : wntr WaterNetworkModel Water network model. The water network model is needed to define pipe diameter. pipe_ghg : pandas Series, optional Annual GHG emissions indexed by pipe diameter (default values below, from :cite:p:`sokz12`). ============= ================================ Diameter (mm) Annualized EE (kg-CO2-e/m/yr) ============= ================================ 102 5.90 152 9.71 203 13.94 254 18.43 305 23.16 356 28.09 406 33.09 457 38.35 508 43.76 610 54.99 711 66.57 762 72.58 ============= ================================ Returns ---------- Annual greenhouse gas emissions (float) """ # Initialize network GHG emissions network_ghg = 0 # Set defaults if pipe_ghg is None: diameter = [4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30] # inches diameter = np.array(diameter)*0.0254 # m cost = [5.9, 9.71, 13.94, 18.43, 23.16, 28.09, 33.09, 38.35, 43.76, 54.99, 66.57, 72.58] pipe_ghg = pd.Series(cost, diameter) # GHG emissions from pipes for link_name, link in wn.links(Pipe): idx = np.argmin([np.abs(pipe_ghg.index - link.diameter)]) #print(link_name, link.diameter, pipe_ghg.iloc[idx],link.length) network_ghg = network_ghg + pipe_ghg.iloc[idx]*link.length return network_ghg
[docs] def pump_power(flowrate, head, wn): """ Compute pump power. The computation uses pump flow rate, node head (used to compute headloss at each pump), and pump efficiency. Pump efficiency is defined in ``wn.options.energy.global_efficiency``. Pump efficiency curves are currently not supported. wn.options.energy.global_efficiency = 75 # This means 75% or 0.75 Parameters ---------- flowrate : pandas DataFrame A pandas DataFrame containing pump flowrates (index = times, columns = pump names). head : pandas DataFrame A pandas DataFrame containing node head (index = times, columns = node names). wn: wntr WaterNetworkModel Water network model. The water network model is needed to define energy efficiency. Returns ------- A DataFrame that contains pump power in W (index = times, columns = pump names). """ pumps = wn.pump_name_list time = flowrate.index headloss = pd.DataFrame(data=None, index=time, columns=pumps) for pump_name, pump in wn.pumps(): start_node = pump.start_node_name end_node = pump.end_node_name start_head = head.loc[:,start_node] end_head = head.loc[:,end_node] headloss.loc[:,pump_name] = end_head - start_head efficiency_dict = {} for pump_name, pump in wn.pumps(): if pump.efficiency is None: efficiency_dict[pump_name] = [wn.options.energy.global_efficiency/100.0 for i in time] else: raise NotImplementedError('WNTR does not support pump efficiency curves yet.') # TODO: WNTR does not support pump efficiency curves yet # curve = wn.get_curve(pump.efficiency) # x = [point[0] for point in curve.points] # y = [point[1]/100.0 for point in curve.points] # interp = scipy.interpolate.interp1d(x, y, kind='linear') # efficiency_dict[pump_name] = interp(np.array(flowrate.loc[:, pump_name])) efficiency = pd.DataFrame(data=efficiency_dict, index=time, columns=pumps) power = 1000.0 * 9.81 * headloss * flowrate / efficiency # Watts = J/s return power
[docs] def pump_energy(flowrate, head, wn): """ Compute the pump energy over time. The computation uses pump flow rate, node head (used to compute headloss at each pump), and pump efficiency. Pump efficiency is defined in ``wn.options.energy.global_efficiency``. Pump efficiency curves are currently not supported. wn.options.energy.global_efficiency = 75 # This means 75% or 0.75 Parameters ---------- flowrate : pandas DataFrame A pandas DataFrame containing pump flowrates (index = times, columns = pump names). head : pandas DataFrame A pandas DataFrame containing node head (index = times, columns = node names). wn: wntr WaterNetworkModel Water network model. The water network model is needed to define energy efficiency. Returns ------- A DataFrame that contains pump energy in J (index = times, columns = pump names). """ power = pump_power(flowrate, head, wn) # Watts = J/s energy = power * wn.options.time.report_timestep # J = Ws return energy
[docs] def pump_cost(energy, wn): """ Compute the pump cost over time. Energy cost is defined in ``wn.options.energy.global_price``. Pump energy price and price patterns are currently not supported. wn.options.energy.global_price = 3.61e-8 # $/J; equal to $0.13/kW-h Parameters ---------- energy : pandas DataFrame A pandas DataFrame containing pump energy (J), computed from ``wntr.metrics.pump_energy`` (index = times, columns = pump names). wn: wntr WaterNetworkModel Water network model. The water network model is needed to define pump enery prices and patterns. Returns ----------- A DataFrame that contains pump cost in $ (index = times, columns = pump names). """ time = energy.index pumps = wn.pump_name_list # TODO: Need to get this unit tested and not just functionally tested if wn.options.energy.demand_charge is not None and wn.options.energy.demand_charge != 0: # Additional energy charge per maximum kilowatt usage raise ValueError('WNTR does not support demand charge yet.') price_dict = {} for pump_name, pump in wn.pumps(): if pump.energy_price is None and pump.energy_pattern is None: if wn.options.energy.global_pattern is None: price_dict[pump_name] = [wn.options.energy.global_price for i in time] else: raise NotImplementedError('WNTR does not support price patterns yet.') elif pump.energy_pattern is None: if wn.options.energy.global_pattern is None: price_dict[pump_name] = [pump.energy_price for i in time] else: raise NotImplementedError('WNTR does not support price patterns yet.') else: raise NotImplementedError('WNTR does not support price patterns yet.') price = pd.DataFrame(data=price_dict, index=time, columns=pumps) pump_cost = energy * price return pump_cost