class TestNetwork(object): @classmethod def setup_class(self): self.network = Network() self.network.solve() @classmethod def teardown(self): self.network.ep.ENclose() def test01_multiple_pumps(self): network = self.network network.add_reservoir("R1", 0, 0, 0) network.add_junction("J1", 1, 0) network.add_junction("J2", 2, 0) network.add_junction("J3", 3, 0) network.add_junction("J4", 4, 0) network.add_reservoir("R2", 5, 0, 0) network.add_pipe("P1", "R1", "J1", 200, 100) network.add_pipe("P2", "J2", "J4", 200, 100) network.add_pipe("P3", "J3", "J4", 200, 100) network.add_pipe("P4", "J4", "R2", 200, 100) P1 = network.add_pump("PU1", "J1", "J2", 1) P2 = network.add_pump("PU2", "J1", "J3", 1) C1 = network.add_curve("1", [[100, 30], [200, 20], [300, 10]]) C2 = network.add_curve("2", [[100, 40], [200, 25], [300, 10]]) P1.curve = C1 P2.curve = C2 network.solve() assert_almost_equal(P1.flow, 225.88, 2) assert_almost_equal(P2.flow, 248.14, 2)
class NetworkAgent: def __init__(self, filename='anytown_master.inp'): # load network self.network = Network(filename) self.network.solve() # for control self.speed_limit = (0.7, 1.2) # danger zone self.junction_threshold = (15, 120) self.state = None # function to get the effeciency of the pump(s) self.eff = None # speed increase/decrease resolution self.stepsize = 0.1 self.network.solve() self._calc_effeciency('E1') def _calc_effeciency(self, curve_id): curve = None for x in self.network.curves: if curve_id in x.uid: flow = [ value[0] for value in x.values] y = [value[1] for value in x.values] curve = np.poly1d(np.polyfit(flow,y,4)) if not curve: logging.info('no curve found with id{}'.format(curve_id)) self.eff = curve def reset(self): # epynet has its own reset function # self._calc_effeciency('E1') #optimize from scipy.optimize import minimize # initial guess is 0.8 thats the speed self.res = minimize(self.find_opt,1.2, method='Nelder-Mead', options={'disp':True}, bounds = self.speed_limit) print(self.res.x) self.network.reset() def find_opt(self, x): """ x : pump's speed returns: effeciency """ # change speed self.network.pumps['78'].speed = x self.network.solve() eff = self.calc_eff() # - sign because we need the maximum value return - eff def step(self): x = self.network.solve() print('solve function returns: {}'.format(x)) def calc_eff(self): # get only one pump with id 78 # complicated networks will be harder than this flow = self.network.pumps['78'].flow print('flow is: {}'.format(flow)) speed = self.network.pumps['78'].speed true_flow = float(flow/speed) return self.eff(true_flow) def junction_head(self): return list(self.network.junctions.head) def junction_demand(self): return list(self.network.junctions.demand) def junction_pressure(self): return list(self.network.junctions.pressure) def _action_increase_speed(self): # check if it's within the limits if self.network.pumps['78'].speed + self.stepsize > self.speed_limit[1]: return self.network.pumps['78'].speed += self.stepsize def _action_decrease_speed(self): if self.network.pumps['78'].speed - self.stepsize < self.speed_limit[1]: return self.network.pumps['78'].speed -= self.stepsize
class wds(): """Gym-like environment for water distribution systems.""" def __init__(self, wds_name='anytown_master', speed_increment=.05, episode_len=10, pump_groups=[['78', '79']], total_demand_lo=.3, total_demand_hi=1.1, reset_orig_pump_speeds=False, reset_orig_demands=False, seed=None): self.seedNum = seed if self.seedNum: np.random.seed(self.seedNum) else: np.random.seed() pathToRoot = os.path.dirname(os.path.realpath(__file__)) pathToWDS = os.path.join(pathToRoot, 'water_networks', wds_name + '.inp') self.wds = Network(pathToWDS) self.demandDict = self.build_demand_dict() self.pumpGroups = pump_groups self.pump_speeds = np.ones(shape=(len(self.pumpGroups)), dtype=np.float32) self.pumpEffs = np.empty(shape=(len(self.pumpGroups)), dtype=np.float32) nomHCurvePtsDict, nomECurvePtsDict = self.get_performance_curve_points( ) nomHCurvePoliDict = self.fit_polinomials(nomHCurvePtsDict, degree=2, encapsulated=True) self.nomECurvePoliDict = self.fit_polinomials(nomECurvePtsDict, degree=4, encapsulated=True) self.sumOfDemands = sum( [demand for demand in self.wds.junctions.basedemand]) self.demandRandomizer = self.build_truncnorm_randomizer(lo=.7, hi=1.3, mu=1.0, sigma=1.0) # Theoretical bounds of {head, efficiency} peak_heads = [] for key in nomHCurvePoliDict.keys(): max_q = np.max(nomHCurvePtsDict[key][:, 0]) opti_result = minimize(-nomHCurvePoliDict[key], x0=1, bounds=[(0, max_q)]) peak_heads.append(nomHCurvePoliDict[key](opti_result.x[0])) peak_effs = [] for key in nomHCurvePoliDict.keys(): max_q = np.max(nomHCurvePtsDict[key][:, 0]) q_list = np.linspace(0, max_q, 10) head_poli = nomHCurvePoliDict[key] eff_poli = self.nomECurvePoliDict[key] opti_result = minimize(-eff_poli, x0=1, bounds=[(0, max_q)]) peak_effs.append(eff_poli(opti_result.x[0])) self.peakTotEff = np.prod(peak_effs) # Reward control self.dimensions = len(self.pumpGroups) self.episodeLength = episode_len self.headLimitLo = 15 self.headLimitHi = 120 self.maxHead = np.max(peak_heads) self.rewScale = [5, 8, 3] # eff, head, pump self.baseReward = +1 self.bumpPenalty = -1 self.distanceRange = .5 self.wrongMovePenalty = -1 self.lazinessPenalty = -1 # ----- ----- ----- ----- ----- # Tweaking reward # ----- ----- ----- ----- ----- #maxReward = 5 # ----- ----- ----- ----- ----- self.maxReward = +1 self.minReward = -1 # Inner variables self.spec = None self.metadata = None self.totalDemandLo = total_demand_lo self.totalDemandHi = total_demand_hi self.speedIncrement = speed_increment self.speedLimitLo = .7 self.speedLimitHi = 1.2 self.validSpeeds = np.arange(self.speedLimitLo, self.speedLimitHi + .001, self.speedIncrement, dtype=np.float32) self.resetOrigPumpSpeeds = reset_orig_pump_speeds self.resetOrigDemands = reset_orig_demands self.optimized_speeds = np.empty(shape=(len(self.pumpGroups)), dtype=np.float32) self.optimized_speeds.fill(np.nan) self.optimized_value = np.nan self.previous_distance = np.nan # initialization of {observation, steps, done} observation = self.reset(training=False) self.action_space = gym.spaces.Discrete(2 * self.dimensions + 1) self.observation_space = gym.spaces.Box( low=-1, high=+1, shape=(len(self.wds.junctions) + len(self.pumpGroups), ), dtype=np.float32) # for one-shot tests self.one_shot = rs.rs(target=self.reward_to_deap, dims=self.dimensions, limit_lo=self.speedLimitLo, limit_hi=self.speedLimitHi, step_size=self.speedIncrement, maxfev=1) def step(self, action, training=True): """ Reward computed from the Euclidean distance between the speed of the pumps and the optimized speeds.""" self.steps += 1 self.done = (self.steps == self.episodeLength) group_id = action // 2 command = action % 2 if training: if group_id != self.dimensions: self.n_siesta = 0 first_pump_in_grp = self.wds.pumps[self.pumpGroups[group_id] [0]] if command == 0: if first_pump_in_grp.speed < self.speedLimitHi: for pump in self.pumpGroups[group_id]: self.wds.pumps[pump].speed += self.speedIncrement self.update_pump_speeds() distance = np.linalg.norm(self.optimized_speeds - self.pump_speeds) if distance < self.previous_distance: # ----- ----- ----- ----- ----- # Tweaking reward # ----- ----- ----- ----- ----- #reward = distance * self.baseReward / self.distanceRange reward = distance * self.baseReward / self.distanceRange / self.maxReward # ----- ----- ----- ----- ----- else: reward = self.wrongMovePenalty self.previous_distance = distance else: self.n_bump += 1 reward = self.bumpPenalty else: if first_pump_in_grp.speed > self.speedLimitLo: for pump in self.pumpGroups[group_id]: self.wds.pumps[pump].speed -= self.speedIncrement self.update_pump_speeds() distance = np.linalg.norm(self.optimized_speeds - self.pump_speeds) if distance < self.previous_distance: # ----- ----- ----- ----- ----- # Tweaking reward # ----- ----- ----- ----- ----- #reward = distance * self.baseReward / self.distanceRange reward = distance * self.baseReward / self.distanceRange / self.maxReward # ----- ----- ----- ----- ----- else: reward = self.wrongMovePenalty self.previous_distance = distance else: self.n_bump += 1 reward = self.bumpPenalty else: self.n_siesta += 1 value = self.get_state_value() if self.n_siesta == 3: self.done = True if value / self.optimized_value > .98: # ----- ----- ----- ----- ----- # Tweaking reward # ----- ----- ----- ----- ----- #reward = 5 reward = 5 / self.maxReward # ----- ----- ----- ----- ----- else: reward = self.lazinessPenalty else: if value / self.optimized_value > .98: reward = self.n_siesta * self.baseReward else: reward = self.lazinessPenalty self.wds.solve() else: if group_id != self.dimensions: self.n_siesta = 0 first_pump_in_grp = self.wds.pumps[self.pumpGroups[group_id] [0]] if command == 0: if first_pump_in_grp.speed < self.speedLimitHi: for pump in self.pumpGroups[group_id]: self.wds.pumps[pump].speed += self.speedIncrement else: self.n_bump += 1 else: if first_pump_in_grp.speed > self.speedLimitLo: for pump in self.pumpGroups[group_id]: self.wds.pumps[pump].speed -= self.speedIncrement else: self.n_bump += 1 else: self.n_siesta += 1 if self.n_siesta == 3: self.done = True self.wds.solve() reward = self.get_state_value() observation = self.get_observation() return observation, reward, self.done, {} def reset(self, training=True): if training: if self.resetOrigDemands: self.restore_original_demands() else: self.randomize_demands() self.optimize_state() ## One-shot begins # self.optimize_state_with_one_shot() # if self.optimized_value == 0: # self.optimized_value = .01 ## One-shot ends if self.resetOrigPumpSpeeds: initial_speed = 1. for pump in self.wds.pumps: pump.speed = initial_speed else: for pump_grp in self.pumpGroups: initial_speed = np.random.choice(self.validSpeeds) for pump in pump_grp: self.wds.pumps[pump].speed = initial_speed else: if self.resetOrigPumpSpeeds: initial_speed = 1. for pump in self.wds.pumps: pump.speed = initial_speed else: for pump_grp in self.pumpGroups: initial_speed = np.random.choice(self.validSpeeds) for pump in pump_grp: self.wds.pumps[pump].speed = initial_speed self.wds.solve() observation = self.get_observation() self.done = False self.steps = 0 self.n_bump = 0 self.n_siesta = 0 return observation def seed(self, seed=None): """Collecting seeds.""" return [seed] def optimize_state(self): speeds, target_val, _ = nm.minimize(self.reward_to_scipy, self.dimensions) self.optimized_speeds = speeds self.optimized_value = -target_val def optimize_state_with_one_shot(self): speeds, target_val, _ = self.one_shot.maximize() self.optimized_speeds = speeds self.optimized_value = target_val def fit_polinomials(self, pts_dict, degree, encapsulated=False): """Fitting polinomials to points stored in dict.""" polinomials = dict() if encapsulated: for curve in pts_dict: polinomials[curve] = np.poly1d( np.polyfit(pts_dict[curve][:, 0], pts_dict[curve][:, 1], degree)) else: for curve in pts_dict: polinomials[curve] = np.polyfit(pts_dict[curve][:, 0], pts_dict[curve][:, 1], degree) return polinomials def get_performance_curve_points(self): """Reader for H(Q) and P(Q) curves.""" head_curves = dict() eff_curves = dict() # Loading data to dictionary for curve in self.wds.curves: if curve.uid[0] == 'H': # this is an H(Q) curve head_curves[curve.uid[1:]] = np.empty([len(curve.values), 2], dtype=np.float32) for i, op_pnt in enumerate(curve.values): head_curves[curve.uid[1:]][i, 0] = op_pnt[0] head_curves[curve.uid[1:]][i, 1] = op_pnt[1] for curve in self.wds.curves: if curve.uid[0] == 'E': # this is an E(Q) curve eff_curves[curve.uid[1:]] = np.empty([len(curve.values), 2], dtype=np.float32) for i, op_pnt in enumerate(curve.values): eff_curves[curve.uid[1:]][i, 0] = op_pnt[0] eff_curves[curve.uid[1:]][i, 1] = op_pnt[1] # Checking consistency for head_key in head_curves.keys(): if all(head_key != eff_key for eff_key in eff_curves.keys()): print('\nInconsistency in H(Q) and P(Q) curves.\n') raise IndexError return head_curves, eff_curves def get_junction_heads(self): junc_heads = np.empty(shape=(len(self.wds.junctions), ), dtype=np.float32) for junc_id, junction in enumerate(self.wds.junctions): junc_heads[junc_id] = junction.head return junc_heads def get_observation(self): heads = (2 * self.get_junction_heads() / self.maxHead) - 1 self.update_pump_speeds() speeds = self.pump_speeds / self.speedLimitHi return np.concatenate([heads, speeds]) def restore_original_demands(self): for junction in self.wds.junctions: junction.basedemand = self.demandDict[junction.uid] def build_truncnorm_randomizer(self, lo, hi, mu, sigma): randomizer = stats.truncnorm((lo - mu) / sigma, (hi - mu) / sigma, loc=mu, scale=sigma) return randomizer def randomize_demands(self): target_sum_of_demands = self.sumOfDemands * ( self.totalDemandLo + np.random.rand() * (self.totalDemandHi - self.totalDemandLo)) sum_of_random_demands = 0 if self.seedNum: for junction in self.wds.junctions: junction.basedemand = ( self.demandDict[junction.uid] * self.demandRandomizer.rvs( random_state=self.seedNum * int(np.abs(np.floor(junction.coordinates[0]))))) sum_of_random_demands += junction.basedemand else: for junction in self.wds.junctions: junction.basedemand = (self.demandDict[junction.uid] * self.demandRandomizer.rvs()) sum_of_random_demands += junction.basedemand for junction in self.wds.junctions: junction.basedemand *= target_sum_of_demands / sum_of_random_demands def calculate_pump_efficiencies(self): for i, group in enumerate(self.pumpGroups): pump = self.wds.pumps[group[0]] curve_id = pump.curve.uid[1:] pump_head = pump.downstream_node.head - pump.upstream_node.head eff_poli = self.nomECurvePoliDict[curve_id] self.pumpEffs[i] = eff_poli(pump.flow / pump.speed) def build_demand_dict(self): demand_dict = dict() for junction in self.wds.junctions: demand_dict[junction.uid] = junction.basedemand return demand_dict def get_state_value_separated(self): self.calculate_pump_efficiencies() pump_ok = (self.pumpEffs < 1).all() and (self.pumpEffs > 0).all() if pump_ok: heads = np.array([head for head in self.wds.junctions.head]) invalid_heads_count = (np.count_nonzero(heads < self.headLimitLo) + np.count_nonzero(heads > self.headLimitHi)) valid_heads_ratio = 1 - (invalid_heads_count / len(heads)) total_demand = sum( [junction.basedemand for junction in self.wds.junctions]) total_tank_flow = sum( [tank.inflow + tank.outflow for tank in self.wds.tanks]) demand_to_total = total_demand / (total_demand + total_tank_flow) total_efficiency = np.prod(self.pumpEffs) eff_ratio = total_efficiency / self.peakTotEff else: eff_ratio = 0 valid_heads_ratio = 0 demand_to_total = 0 return eff_ratio, valid_heads_ratio, demand_to_total def get_state_value(self): self.calculate_pump_efficiencies() pump_ok = (self.pumpEffs < 1).all() and (self.pumpEffs > 0).all() if pump_ok: heads = np.array([head for head in self.wds.junctions.head]) invalid_heads_count = (np.count_nonzero(heads < self.headLimitLo) + np.count_nonzero(heads > self.headLimitHi)) valid_heads_ratio = 1 - (invalid_heads_count / len(heads)) total_demand = sum( [junction.basedemand for junction in self.wds.junctions]) total_tank_flow = sum( [tank.inflow + tank.outflow for tank in self.wds.tanks]) demand_to_total = total_demand / (total_demand + total_tank_flow) total_efficiency = np.prod(self.pumpEffs) reward = (self.rewScale[0] * total_efficiency / self.peakTotEff + self.rewScale[1] * valid_heads_ratio + self.rewScale[2] * demand_to_total) / sum(self.rewScale) else: reward = 0 return reward def get_state_value_to_opti(self, pump_speeds): np.clip(a=pump_speeds, a_min=self.speedLimitLo, a_max=self.speedLimitHi, out=pump_speeds) for group_id, pump_group in enumerate(self.pumpGroups): for pump in pump_group: self.wds.pumps[pump].speed = pump_speeds[group_id] self.wds.solve() return self.get_state_value() def reward_to_scipy(self, pump_speeds): """Only minimization allowed.""" return -self.get_state_value_to_opti(pump_speeds) def reward_to_deap(self, pump_speeds): """Return should be tuple.""" return self.get_state_value_to_opti(np.asarray(pump_speeds)), def update_pump_speeds(self): for i, pump_group in enumerate(self.pumpGroups): self.pump_speeds[i] = self.wds.pumps[pump_group[0]].speed return self.pump_speeds def get_pump_speeds(self): self.update_pump_speeds() return self.pump_speeds
class TestNetwork(object): @classmethod def setup_class(self): self.network = Network(inputfile="tests/testnetwork.inp") self.network.solve() @classmethod def teadown(self): self.network.ep.ENclose() def test01_network(self): # test0 node count assert_equal(len(self.network.nodes), 11) # test0 link count assert_equal(len(self.network.links), 12) # test0 reservoir count assert_equal(len(self.network.reservoirs), 1) # test0 valve count assert_equal(len(self.network.valves), 1) # test0 pump count assert_equal(len(self.network.pumps), 1) # test0 tank count assert_equal(len(self.network.tanks), 1) def test02_link(self): # test0 the properties of a single link link = self.network.links["11"] # pipe index and uid assert_equal(link.index, 9) assert_equal(link.uid, "11") # from/to node assert_equal(link.from_node.uid, "4") assert_equal(link.to_node.uid, "9") def test03_pipe(self): # test0 the properties of a single pipe pipe = self.network.links["11"] # check type assert_equal(pipe.link_type, "pipe") assert_almost_equal(pipe.length, 100, 2) assert_almost_equal(pipe.diameter, 150, 2) assert_almost_equal(pipe.roughness, 0.1, 2) assert_almost_equal(pipe.minorloss, 0.1, 2) # flow assert_almost_equal(pipe.flow, 87.92, 2) # direction assert_almost_equal(pipe.velocity, 1.38, 2) # status assert_equal(pipe.status, 1) # headloss assert_almost_equal(pipe.headloss, 1.29, 2) # upstream/downstream node assert_equal(pipe.upstream_node.uid, "4") assert_equal(pipe.downstream_node.uid, "9") def test04_pump(self): pump = self.network.pumps["2"] # check type assert_equal(pump.link_type, "pump") assert_equal(pump.speed, 1.0) assert_almost_equal(pump.flow, 109.67, 2) # change speed pump.speed = 1.5 assert_equal(pump.speed, 1.5) # resolve network self.network.solve() assert_almost_equal(pump.flow, 164.5, 2) # revert speed pump.speed = 1.0 self.network.solve() def test05_valve(self): valve = self.network.valves["9"] # check type assert_equal(valve.link_type, "valve") # check valve type assert_equal(valve.valve_type, "PRV") # valve settings assert_equal(valve.setting, 5) assert_almost_equal(valve.downstream_node.pressure, 5, 2) # change setting valve.setting = 10 assert_equal(valve.setting, 10) self.network.solve() assert_almost_equal(valve.downstream_node.pressure, 10, 2) def test06_node(self): node = self.network.nodes["4"] # uid assert_equal(node.uid, "4") # coordinates coordinates = node.coordinates assert_almost_equal(coordinates[0], 2103.02, 2) assert_almost_equal(coordinates[1], 5747.69, 2) # links assert_equal(len(node.links), 3) # up and downstream links assert_equal(len(node.downstream_links), 2) assert_equal(len(node.upstream_links), 1) # inflow assert_equal(round(node.inflow, 2), 109.67) # outflow assert_equal(round(node.outflow, 2), round(node.inflow, 2) - node.demand) # elevation assert_equal(node.elevation, 5) # head assert_equal(round(node.head, 2), 25.13) def test07_junction(self): junction = self.network.junctions["4"] assert_equal(round(junction.basedemand, 2), 1) assert_equal(round(junction.demand, 2), 1) def test08_tank(self): tank = self.network.tanks["11"] assert_equal(round(tank.diameter, 2), 50) assert_equal(round(tank.initvolume, 2), 19634.95) assert_equal(tank.minvolume, 0) assert_equal(tank.minlevel, 0) assert_equal(tank.maxlevel, 20) assert_equal(round(tank.volume, 2), 19634.95) assert_equal(round(tank.maxvolume), 2 * round(tank.volume)) def test09_time(self): junction = self.network.junctions["4"] self.network.solve(3600) assert_equal(round(junction.demand, 2), 2) self.network.solve(7200) assert_equal(round(junction.demand, 2), 3) def test10_collections(self): # collection attributes as pandas Series assert_almost_equal(self.network.pipes.flow.mean(), 46.78, 2) assert_almost_equal(self.network.pipes.diameter.max(), 150, 2) assert_almost_equal(self.network.pipes.velocity.min(), 0.105, 2) assert_equal(self.network.valves.setting.mean(), 10) assert_almost_equal(self.network.junctions.demand.mean(), 2.33, 2) # filtering and slicing collections assert_equal(len(self.network.pipes[self.network.pipes.velocity > 3]), 3) assert_equal(len(self.network.nodes[self.network.nodes.pressure < 20]), 5) #increase the size of all pipes self.network.pipes.diameter += 500 assert_almost_equal(self.network.pipes.diameter.mean(), 605, 2) self.network.pipes.diameter -= 500 self.network.solve() # resize pipes, and recalculate velocity self.network.pipes[self.network.pipes.velocity > 3].diameter += 100 self.network.solve() assert_equal(len(self.network.pipes[self.network.pipes.velocity > 3]), 0) def test11_timeseries(self): # run network self.network.run() # check return types # should return Series assert (isinstance(self.network.pipes["1"].velocity, pd.Series)) # should return Dataframe assert (isinstance(self.network.pipes.velocity, pd.DataFrame)) # timeseries operations # pipe 1 max velocity assert_almost_equal(self.network.pipes["1"].velocity.mean(), 1.66, 2) # all day mean velocity assert_almost_equal(self.network.pipes.velocity.mean().mean(), 1.14, 2) # test revert to steady state calculation self.network.solve() assert (isinstance(self.network.pipes["1"].velocity, float)) assert (isinstance(self.network.pipes.velocity, pd.Series)) def test12_comments(self): # test reading comments assert_equal(self.network.links['1'].comment, "testcommentpipe") assert_equal(self.network.reservoirs['in'].comment, "testcommentreservoir") assert_equal(self.network.tanks['11'].comment, "testcommenttank") assert_equal(self.network.junctions['2'].comment, "testcommentjunction") # test writing comments self.network.links['1'].comment = 'testwrite' assert_equal(self.network.links['1'].comment, 'testwrite')
class TestGeneratedNetwork(object): @classmethod def setup_class(self): self.network = Network() @classmethod def teadown(self): self.network.ep.ENclose() def test00_build_network(self): network = self.network network.ep.ENsettimeparam(epanet2.EN_DURATION, 10 * 3600) # add nodes reservoir = network.add_reservoir('in', 0, 30) reservoir.elevation = 10 pattern_values = [1, 2, 3, 4, 5, 4, 3, 2, 1, 1] pattern = network.add_pattern('1', pattern_values) junctions = { '2': (10, 30, 0), '3': (20, 30, 0), '4': (30, 30, 1), '5': (30, 20, 1), '6': (30, 10, 1), '7': (40, 10, 1), '8': (40, 20, 1), '9': (40, 30, 1), '10': (50, 30, 1) } links = { '1': ('in', '2'), '3': ('3', '4'), '4': ('4', '5'), '5': ('5', '6'), '6': ('6', '7'), '7': ('7', '8'), '8': ('8', '9'), '10': ('5', '8'), '11': ('4', '9'), '12': ('9', '11') } for uid, coord in junctions.items(): node = network.add_junction(uid, coord[0], coord[1], elevation=0, basedemand=coord[2]) node.pattern = pattern tank = network.add_tank('11', 40, 40, diameter=50, maxlevel=20, minlevel=0, tanklevel=10) for uid, coord in links.items(): link = network.add_pipe(uid, coord[0], coord[1], diameter=100, length=100, roughness=0.1) valve = network.add_valve('9', 'prv', '9', '10', diameter=100, setting=5) pump = network.add_pump('2', '2', '3', speed=1) curve = network.add_curve('1', [(100, 50)]) pump.curve = curve network.nodes['4'].elevation = 5 network.links['11'].diameter = 150 network.links['11'].minorloss = 0.1 network.solve() def test01_network(self): # test0 node count assert_equal(len(self.network.nodes), 11) # test0 link count assert_equal(len(self.network.links), 12) # test0 reservoir count assert_equal(len(self.network.reservoirs), 1) # test0 valve count assert_equal(len(self.network.valves), 1) # test0 pump count assert_equal(len(self.network.pumps), 1) # test0 tank count assert_equal(len(self.network.tanks), 1) def test02_link(self): # test0 the properties of a single link link = self.network.links['11'] # pipe index and uid assert_equal(link.uid, '11') # from/to node assert_equal(link.from_node.uid, '4') assert_equal(link.to_node.uid, '9') def test03_pipe(self): # test0 the properties of a single pipe pipe = self.network.links['11'] # check type assert_equal(pipe.link_type, 'pipe') assert_almost_equal(pipe.length, 100, 2) assert_almost_equal(pipe.diameter, 150, 2) assert_almost_equal(pipe.roughness, 0.1, 2) assert_almost_equal(pipe.minorloss, 0.1, 2) # flow assert_almost_equal(pipe.flow, 87.92, 2) # direction assert_almost_equal(pipe.velocity, 1.38, 2) # status assert_equal(pipe.status, 1) # headloss assert_almost_equal(pipe.headloss, 1.29, 2) # upstream/downstream node assert_equal(pipe.upstream_node.uid, '4') assert_equal(pipe.downstream_node.uid, '9') def test04_pump(self): pump = self.network.pumps['2'] # check type assert_equal(pump.link_type, 'pump') assert_equal(pump.speed, 1.0) assert_almost_equal(pump.flow, 109.67, 2) # change speed pump.speed = 1.5 assert_equal(pump.speed, 1.5) # resolve network self.network.solve() assert_almost_equal(pump.flow, 164.5, 2) # revert speed pump.speed = 1.0 self.network.solve() def test05_valve(self): valve = self.network.valves['9'] # check type assert_equal(valve.link_type, 'valve') # check valve type assert_equal(valve.valve_type, 'PRV') # valve settings assert_equal(valve.setting, 5) assert_almost_equal(valve.downstream_node.pressure, 5, 2) # change setting valve.setting = 10 assert_equal(valve.setting, 10) self.network.solve() assert_almost_equal(valve.downstream_node.pressure, 10, 2) def test06_node(self): node = self.network.nodes['4'] # uid assert_equal(node.uid, '4') # coordinates #coordinates = node.coordinates # dont test these for created networks #assert_almost_equal(coordinates[0],2103.02,2) #assert_almost_equal(coordinates[1],5747.69,2) # links assert_equal(len(node.links), 3) # up and downstream links assert_equal(len(node.downstream_links), 2) assert_equal(len(node.upstream_links), 1) # inflow assert_equal(round(node.inflow, 2), 109.67) # outflow assert_equal(round(node.outflow, 2), round(node.inflow, 2) - node.demand) # elevation assert_equal(node.elevation, 5) # head assert_equal(round(node.head, 2), 25.13) def test07_junction(self): junction = self.network.junctions['4'] assert_equal(round(junction.basedemand, 2), 1) assert_equal(round(junction.demand, 2), 1) def test08_tank(self): tank = self.network.tanks['11'] assert_equal(tank.diameter, 50) assert_equal(round(tank.initvolume, 2), 19634.95) assert_equal(tank.minvolume, 0) assert_equal(tank.minlevel, 0) assert_equal(tank.maxlevel, 20) assert_equal(round(tank.volume, 2), 19634.95) assert_equal(round(tank.maxvolume), 2 * round(tank.volume)) def test09_time(self): junction = self.network.junctions['4'] self.network.solve(3600) assert_equal(round(junction.demand, 2), 2) self.network.solve(7200) assert_equal(round(junction.demand, 2), 3) def test10_collections(self): # collection attributes as pandas Series assert_almost_equal(self.network.pipes.flow.mean(), 46.77, 2) assert_almost_equal(self.network.pipes.diameter.max(), 150, 2) assert_almost_equal(self.network.pipes.velocity.min(), 0.105, 2) assert_equal(self.network.valves.setting.mean(), 10) assert_almost_equal(self.network.junctions.demand.mean(), 2.33, 2) # filtering and slicing collections assert_equal(len(self.network.pipes[self.network.pipes.velocity > 3]), 3) assert_equal(len(self.network.nodes[self.network.nodes.pressure < 20]), 5) #increase the size of all pipes self.network.pipes.diameter += 500 assert_almost_equal(self.network.pipes.diameter.mean(), 605, 2) self.network.pipes.diameter -= 500 self.network.solve() # resize pipes, and recalculate velocity self.network.pipes[self.network.pipes.velocity > 3].diameter += 100 self.network.solve() assert_equal(len(self.network.pipes[self.network.pipes.velocity > 3]), 0) def test11_timeseries(self): # run network self.network.run() # check return types # should return Series assert (isinstance(self.network.pipes['1'].velocity, pd.Series)) # should return Dataframe assert (isinstance(self.network.pipes.velocity, pd.DataFrame)) # timeseries operations # pipe 1 max velocity assert_almost_equal(self.network.pipes['1'].velocity.mean(), 1.66, 2) # all day mean velocity assert_almost_equal(self.network.pipes.velocity.mean().mean(), 1.14, 2) # test revert to steady state calculation self.network.solve() print(type(self.network.pipes['1'].velocity)) assert (isinstance(self.network.pipes['1'].velocity, float)) assert (isinstance(self.network.pipes.velocity, pd.Series))
class Test_line_network(unittest.TestCase): # Test simple network consisting of 4 nodes in a line def setUp(self): # self.net = Network(inputfile="tests/Case1.inp") self.net = Network(inputfile="Case1.inp") self.pp = PhreeqPython() self.vic = Victoria(self.net, self.pp) self.solutions = {} sol0 = self.pp.add_solution({'Ca': 0, 'units': 'mmol/l'}) sol1 = self.pp.add_solution({'Ca': 1, 'units': 'mmol/l'}) self.solutions[0] = sol0 self.solutions[self.net.reservoirs['1'].uid] = sol1 def test1(self): models = self.vic.solver.models # Test node count self.assertEqual(len(self.net.nodes), len(models.nodes)) # Test junction count self.assertEqual(len(self.net.junctions), len(models.junctions)) # Test reservoir count self.assertEqual(len(self.net.reservoirs), len(models.reservoirs)) # Test tank count self.assertEqual(len(self.net.tanks), len(models.tanks)) # Test link count self.assertEqual(len(self.net.links), len(models.links)) # Test pipe count self.assertEqual(len(self.net.pipes), len(models.pipes)) # Test pump count self.assertEqual(len(self.net.pumps), len(models.pumps)) # Test valve count self.assertEqual(len(self.net.valves), len(models.valves)) def test2(self): # Fill the network with a standard solution self.net.solve() self.vic.fill_network(self.solutions, from_reservoir=False) # Test the initial concentrations of Ca self.assertEqual( self.vic.get_conc_pipe_avg(self.net.links['1'], 'Ca', 'mmol'), 0) self.assertEqual( self.vic.get_conc_pipe_avg(self.net.links['2'], 'Ca', 'mmol'), 0) self.assertEqual( self.vic.get_conc_pipe_avg(self.net.links['3'], 'Ca', 'mmol'), 0) self.assertEqual( self.vic.get_conc_pipe_avg(self.net.links['6'], 'Ca', 'mmol'), 0) def test3(self): # Fill the network with the initial reservoir solution self.net.solve() self.vic.fill_network(self.solutions, from_reservoir=True) # Test the initial concentrations of Ca self.assertAlmostEqual( self.vic.get_conc_pipe_avg(self.net.links['1'], 'Ca', 'mmol'), 1, 4) self.assertAlmostEqual( self.vic.get_conc_pipe_avg(self.net.links['2'], 'Ca', 'mmol'), 1, 4) self.assertAlmostEqual( self.vic.get_conc_pipe_avg(self.net.links['3'], 'Ca', 'mmol'), 1, 4) self.assertAlmostEqual( self.vic.get_conc_pipe_avg(self.net.links['6'], 'Ca', 'mmol'), 1, 4) def test4(self): # Test the residence time in the pipes. Each pipe has a residence time of 1000 seconds # Time parameters Victoria time_end = 2 # hours timestep_network = 60 # minutes timestep_victoria = 1 # second # Convert units to seconds time_end *= 3600 timestep_network *= 60 time_count = 0 # Fill the network self.net.solve() # Need to run Epynet for a single timestep self.vic.fill_network(self.solutions, from_reservoir=False) for t1 in range(0, time_end, timestep_network): self.net.solve(simtime=t1) self.vic.check_flow_direction() for t2 in range(0, timestep_network, timestep_victoria): self.vic.step(timestep_victoria, self.solutions) time_count += t2 if time_count == 999: node = self.vic.get_conc_node(self.net.nodes['2'], 'Ca', 'mmol') self.assertAlmostEqual(node, 0, 4) elif time_count == 1000: node = self.vic.get_conc_node(self.net.nodes['2'], 'Ca', 'mmol') self.assertAlmostEqual(node, 1, 4) elif time_count == 1999: node = self.vic.get_conc_node(self.net.nodes['3'], 'Ca', 'mmol') self.assertAlmostEqual(node, 0, 4) elif time_count == 2000: node = self.vic.get_conc_node(self.net.nodes['3'], 'Ca', 'mmol') self.assertAlmostEqual(node, 1, 4) elif time_count == 2999: node = self.vic.get_conc_node(self.net.nodes['4'], 'Ca', 'mmol') self.assertAlmostEqual(node, 0, 4) elif time_count == 3000: node = self.vic.get_conc_node(self.net.nodes['4'], 'Ca', 'mmol') self.assertAlmostEqual(node, 1, 4) elif time_count == 3999: node = self.vic.get_conc_node(self.net.nodes['6'], 'Ca', 'mmol') self.assertAlmostEqual(node, 0, 4) elif time_count == 4000: node = self.vic.get_conc_node(self.net.nodes['6'], 'Ca', 'mmol') self.assertAlmostEqual(node, 1, 4)