diff --git a/modules/Workflow/whale/main.py b/modules/Workflow/whale/main.py index 732702807..f51a60435 100644 --- a/modules/Workflow/whale/main.py +++ b/modules/Workflow/whale/main.py @@ -1507,17 +1507,23 @@ def augment_asset_files(self): # noqa: C901 return assetFilesList # noqa: DOC201, RUF100 def perform_system_performance_assessment(self, asset_type): - """For an asset type run the system level performance assessment application + """Run the system level performance assessment application. - Longer description + For an asset type run the system level performance assessment + application. Parameters ---------- asset_type: string Asset type to run perform system assessment of - """ # noqa: D400 - if 'SystemPerformance' in self.workflow_apps.keys(): # noqa: SIM118 + """ + # Make sure that we are in the run directory before we run REWET + # Every other path will be relative to the Run Directory (result dir) + os.chdir(self.run_dir) + + # Check if system performance is requested + if 'SystemPerformance' in self.workflow_apps: performance_app = self.workflow_apps['SystemPerformance'][asset_type] else: log_msg( @@ -1525,9 +1531,9 @@ def perform_system_performance_assessment(self, asset_type): prepend_timestamp=False, ) log_div() - return False # noqa: DOC201, RUF100 + return False - if performance_app.rel_path == None: # noqa: E711 + if performance_app.rel_path is None: log_msg( f'No Performance application to run for asset type: {asset_type}.', prepend_timestamp=False, @@ -1536,8 +1542,7 @@ def perform_system_performance_assessment(self, asset_type): return False log_msg( - 'Performing System Performance Application for asset type: ' - + asset_type, + f'Performing System Performance Application for asset type: {asset_type}', prepend_timestamp=False, ) log_div() @@ -1550,18 +1555,13 @@ def perform_system_performance_assessment(self, asset_type): # defaults added to a system performance app are asset_type, input_dir and running_parallel (default False) # - # app_command_list.append('--asset_type') - # app_command_list.append(asset_type) app_command_list.append('--input') app_command_list.append(self.input_file) - # app_command_list.append('--working_dir') - # app_command_list.append(self.working_dir) # Sina added this part for parallel run in REWET if self.parType == 'parSETUP': log_msg( - '\nParallel settings for System Performance for asset type:' - + asset_type, + f'\nParallel settings for System Performance for asset type:{asset_type}', prepend_timestamp=False, ) app_command_list.append('--par') @@ -1583,34 +1583,10 @@ def perform_system_performance_assessment(self, asset_type): ) log_msg( - 'System Performance Application Completed for asset type: ' + asset_type, + f'System Performance Application Completed for asset type: {asset_type}', prepend_timestamp=False, ) - # end of Sina's odifications for parallel run - - # if (self.parType == 'parSETUP'): - - # log_msg('\nWriting System Performance application for asset type:' + asset_type, prepend_timestamp=False) - # self.parCommandFile.write("\n# Writing System Performance application for asset type:" + asset_type +"\n") - - # if performance_app.runsParallel == False: - # self.parCommandFile.write(command + "\n") - # else: - # self.parCommandFile.write(self.mpiExec + " -n " + str(self.numProc) + " " + command + " --running_parallel True\n") - - # else: - - # log_msg('\n{}\n'.format(command), prepend_timestamp=False, - # prepend_blank_space=False) - - # result, returncode = run_command(command) - - # log_msg('Output: ', prepend_timestamp=False, prepend_blank_space=False) - # log_msg('\n{}\n'.format(result), prepend_timestamp=False, prepend_blank_space=False) - - # log_msg('System Performance Application Completed for asset type: ' + asset_type, prepend_timestamp=False) - log_div() return True @@ -2870,7 +2846,7 @@ def aggregate_results( # noqa: C901, PLR0912, PLR0915 bldg_dir = Path(os.path.dirname(asst_data[a_i]['file'])).resolve() # noqa: PTH120 main_dir = bldg_dir assetTypeHierarchy = [bldg_dir.name] # noqa: N806 - while main_dir.parent.name != self.run_dir.name: + while main_dir.parent.name != 'Results': main_dir = bldg_dir.parent assetTypeHierarchy = [main_dir.name] + assetTypeHierarchy # noqa: N806, RUF005 diff --git a/modules/performREC/pyrecodes/PyReCode-REWET-preprocessor.py b/modules/performREC/pyrecodes/PyReCode-REWET-preprocessor.py new file mode 100644 index 000000000..60cd9aadd --- /dev/null +++ b/modules/performREC/pyrecodes/PyReCode-REWET-preprocessor.py @@ -0,0 +1,782 @@ +# Copyright (c) 2024 The Regents of the University of California # noqa: INP001 +# Copyright (c) 2024 Leland Stanford Junior University +# +# This file is part of whale. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# You should have received a copy of the BSD 3-Clause License along with +# whale. If not, see . +# +# Contributors: +# Sina Naeimi + +"""Provide Interface between REWET and PYReCoDes.""" + +import copy +import json +import random +from pathlib import Path + +import pandas as pd +import rewet +import wntrfr +from rewet.api import API +from sklearn.cluster import KMeans + +TEMP_DIR = './' +RESULT_DIR = './rewet_result' +INPUT_FILE_DIR = './' + + +class REWETPyReCoDes: + """Provide the wrapper for REWET API.""" + + def __init__(self, inp_file): + self.wn = None + self._clean_wn = None + self.inp_file_path = None + self.asset_information = {} + self.building_coordinates = [] + self.demand_node_to_building = {} + self.buildings = {} + self.nodes = {} + self.pipe_damage = None + self.node_damage = None + self.pump_damage = None + self.tank_damage = None + self.rewet = None + self.damage_file_list = {} + self.damage_state = {} + self.hydraulic_time_step = 3600 + self.inp_file_path = inp_file + + def system_state(self, state, damage, damage_time=0): + """ + Set the WDN system for PyReCoDes Interface. + + Parameters + ---------- + state : dict + The State of system which is defined in the R2DTool's system_det + style. + damage : dict + The damage state in 2DTool's system_i style. + Damage_time : int + When initil damages happen in seconds. + + Returns + ------- + None. + + """ + # read the inp file + self.read_inp_file(self.inp_file_path) + + self.set_asset_data(state) + + # sets the damage state based on the initial damage (from pelicun) + self.update_state_with_damages(damage, damage_time, state) + + # couple building to the demand nodes + self.couple_buildings_to_demand_nodes(state) + + def system_performance(self, state, current_time, next_time): + """ + Assess the system functionality. + + Parameters + ---------- + state : dict. + The _det file content. + current_time : int + Current time in seconds. + next_time : int + Next time in seconds. + + Returns + ------- + building_satisfaction : dict + The ratio of satiesfied water for each building. + + """ + self.wn = copy.deepcopy(self._clean_wn) + # sets the damage state based on the current (change of the network) + # and saves the current time + self.save_damage(state, current_time) + + # prepare rewet inputs + self.make_rewet_inputs(current_time, next_time) + + # load REWET API interface + self.rewet = API(self.input_file_path) + + # sets the new demand absed on the new percentage + self.set_new_demand(state) + + # apply_damages + # self.apply_damage() + + # run WDN performance evaluation + self.run_performance(current_time, next_time) + + # Save REWET Result + self.rewet.save_result() + + # Get result + building_satisfaction = self.get_building_data_satisfaction(method='mean') + self.building_satisfaction = building_satisfaction + + return building_satisfaction + + def read_inp_file(self, inp_file): + """ + Read the inp file. + + Parameters + ---------- + inp_file : str + The path to the inp file. + + Returns + ------- + None. + + """ + self.inp_file_path = Path(inp_file) + self.inp_file_path = self.inp_file_path.resolve() + + if not self.inp_file_path.exists(): + raise ValueError(f'There inp file does not exists: {inp_file}') # noqa: EM102, TRY003 + + self.inp_file_path = str(self.inp_file_path) + + # Read the inp file and create the WDN object file + self.wn = wntrfr.network.model.WaterNetworkModel(self.inp_file_path) + self._clean_wn = copy.deepcopy(self.wn) + + for node_name, node in self.wn.junctions(): + node_demand_base_value = node.demand_timeseries_list[0].base_value + if node_demand_base_value > 0: + if node_name not in self.nodes: + self.nodes[node_name] = {} + + self.nodes[node_name]['initial_demand'] = node_demand_base_value + + self.nodes[node_name]['coordinates'] = node.coordinates + + def set_asset_data(self, state): + """ + Set the asset information from state file. + + Parameters + ---------- + state : dict + _det file. + + Raises + ------ + ValueError + Unexpecyed values exists in state file. + + Returns + ------- + None. + + """ + wdn_state = state['WaterDistributionNetwork'] + wdn_state = wdn_state.get('Pipe', []) + + for asset_type, asset_type_data in state.items(): + if asset_type not in self.asset_information: + # check if asset_type exists in self.asset_information + self.asset_information[asset_type] = {} + for sub_asset_type, sub_asset_type_data in asset_type_data.items(): + # check if sub_asset_type exists in + # self.asset_information[asset_type] + if sub_asset_type not in self.asset_information[asset_type]: + self.asset_information[asset_type][sub_asset_type] = {} + + for element_key, element_data in sub_asset_type_data.items(): + asset_id = element_data['GeneralInformation']['AIM_id'] + if asset_id != element_key: + raise ValueError( # noqa: TRY003 + 'The rationality behidd the workdflow' # noqa: EM101 + 'is that oth aim-id and keys be the' + 'same' + ) + + self.asset_information[asset_type][sub_asset_type][asset_id] = ( + element_data['GeneralInformation'] + ) + + building_state = state['Buildings']['Building'] + + for building_id, each_building in building_state.items(): + population = each_building['GeneralInformation']['Population'] + population_ratio = each_building.get('Population_Ratio', None) + + if population_ratio is not None: + ratio = population_ratio + else: + ratio = 1 + + cur_building = {} + cur_building['initial_population_ratio'] = ratio + cur_building['population_ratio'] = ratio + cur_building['initial_population'] = population + cur_building['population'] = population + + self.buildings[building_id] = cur_building + + def update_state_with_damages(self, damage, damage_time, state): # noqa: ARG002 + """ + Update the state dic with damages. + + Parameters + ---------- + damage : dict + _i file in dict form.. + damage_time : int + Damaeg time. + state : dict + _det file. + + Raises + ------ + ValueError + Unexpected damage state in damage data. + + Returns + ------- + None. + + """ + damage = damage['WaterDistributionNetwork'] + pipe_damage = damage.get('Pipe', []) + + for asset_id, damage_location in pipe_damage.items(): + damage_location_info = damage_location['Damage'] + + aggregate_keys = [ + key for key in damage_location_info if 'aggregate-' in key + ] + + aggregate_keys.sort() + + aggregate_results = [damage_location_info[key] for key in aggregate_keys] + + segment_sizes = len(aggregate_results) + segment_step = 1 / segment_sizes + c = 0 + + cur_pipe_damage_location_list = [] + cur_pipe_damage_location_type = [] + for damage_val in aggregate_results: + if damage_val > 0: + if damage_val == 1: + damage_type = 'leak' + elif damage_val == 2: # noqa: PLR2004 + damage_type = 'break' + else: + raise ValueError('The damage type must be either 1 or 2') # noqa: EM101, TRY003 + else: + continue + + cur_loc = c * segment_step + segment_step / 2 + cur_pipe_damage_location_list.append(cur_loc) + cur_pipe_damage_location_type.append(damage_type) + + wdn_state = state['WaterDistributionNetwork'] + pipe_state = wdn_state.get('Pipe') + + if 'Damage' in pipe_state[asset_id]: + raise ValueError(f'Damage is already exist for Pipe ' f'{asset_id}') # noqa: EM102, TRY003 + + pipe_state[asset_id]['Damage'] = dict() # noqa: C408 + pipe_state[asset_id]['Damage']['Location'] = ( + cur_pipe_damage_location_list + ) + + pipe_state[asset_id]['Damage']['Type'] = cur_pipe_damage_location_type + + def set_rewet_damage_from_state(self, state, damage_time): + """ + Set REWET damafe data from state at each time step. + + Parameters + ---------- + state : dict + _det file in dict format. + damage_time : int + Current damage time. + + Raises + ------ + ValueError + Unexpected or abnormal data in state. + + Returns + ------- + None. + + """ + state = state['WaterDistributionNetwork'] + pipe_damage = state.get('Pipe', []) + + damage_list = [] + for asset_id, pipe_info in pipe_damage.items(): + damage_location_info = pipe_info['Damage'] + damage_location_list = damage_location_info['Location'] + damage_type_list = damage_location_info['Type'] + + if len(damage_location_list) != len(damage_type_list): + raise ValueError('The size of types and locationis not the same.') # noqa: EM101, TRY003 + + segment_sizes = len(damage_location_list) + + pipe_id = self.asset_information['WaterDistributionNetwork']['Pipe'][ + asset_id + ]['InpID'] + + for c in range(segment_sizes): + cur_loc = damage_location_list[c] + damage_type = damage_type_list[c] + + damage_list.append( + { + 'pipe_id': pipe_id, + 'damage_loc': cur_loc, + 'type': damage_type, + 'Material': 'CI', + } + ) + + damage_list.reverse() + self.pipe_damage = pd.Series( + data=damage_list, + index=[damage_time for val in damage_list], + dtype='O', + ) + + self.node_damage = pd.Series(dtype='O') + + self.pump_damage = pd.Series(dtype='O') + + self.tank_damage = pd.Series(dtype='O') + + if damage_time in self.damage_state: + raise ValueError(f'Time {damage_time} still exists in damage state.') # noqa: EM102, TRY003 + + def couple_buildings_to_demand_nodes(self, state): # noqa: C901 + """ + Couple building to the demand nodes based on their coordinates. + + Parameters + ---------- + state :dict + State file content. + + Returns + ------- + None. + + """ + building_state = state['Buildings']['Building'] + + building_id_list = [] + for building_id, each_building in building_state.items(): + location = each_building['GeneralInformation']['location'] + coordinate = (location['latitude'], location['longitude']) + building_id_list.append(building_id) + + self.building_coordinates.append(coordinate) + + demand_node_coordinate_list = [ + val['coordinates'] for key, val in self.nodes.items() + ] + + demand_node_name_list = [key for key, val in self.nodes.items()] + + kmeans = KMeans( + n_clusters=len(demand_node_coordinate_list), + init=demand_node_coordinate_list, + n_init=1, + random_state=0, + ) + + kmeans.fit(self.building_coordinates) + + labels = kmeans.labels_ + labels = labels.tolist() + + for group_i in range(len(demand_node_coordinate_list)): + node_name = demand_node_name_list[group_i] + for building_l in range(len(labels)): + cur_node_l = labels[building_l] + if group_i == cur_node_l: + if node_name not in self.demand_node_to_building: + self.demand_node_to_building[node_name] = [] + + building_id = building_id_list[building_l] + self.demand_node_to_building[node_name].append(building_id) + + for node_name in self.demand_node_to_building: + building_name_list = self.demand_node_to_building[node_name] + population_list = [ + self.buildings[bldg_id]['initial_population'] + for bldg_id in building_name_list + ] + + total_initial_population = sum(population_list) + + cur_node = self.nodes[node_name] + initial_node_demand = cur_node['initial_demand'] + + if initial_node_demand == 0 and total_initial_population > 0: + Warning( # noqa: PLW0133 + f"Initial demand for node {node_name} is 0." + f" Thus, the demand ratio in buildidng(s)" + f" {repr(building_name_list).strip('[').strip(']')}" + f" is naturally ineffective and their demand" + f" is not met." + ) + + if total_initial_population == 0: + Warning( # noqa: PLW0133 + f"The population assigned to {node_name} is 0." + f" Thus, the demand ratio in buildidng(s)" + f" {repr(building_name_list).strip('[').strip(']')}" + f" is ignored." + ) + + # We assume that population in the State does not change in the + # course of recovery. Thus, the population is initial population. + # For more clarity, we name the variable "initial_population". + # It does not mean that there will be ffdifferent population in + # the course of recovery + self.nodes[node_name]['initial_population'] = total_initial_population + + self.nodes[node_name]['initial_node_demand'] = initial_node_demand + + for bldg_id in building_name_list: + pop = self.buildings[bldg_id]['initial_population'] + + if total_initial_population != 0: + cur_bldg_initial_demand = ( + pop / total_initial_population * initial_node_demand + ) + else: + cur_bldg_initial_demand = None + + self.buildings[bldg_id]['initial_demand'] = cur_bldg_initial_demand + + def save_damage(self, state, current_time): + """ + Convert and save the dmaages that are set before. + + Parameters + ---------- + damage : dict + damage dict. + current_time : int + Current time. + + Returns + ------- + None. + + """ + pipe_damage_file_name = 'temp_pipe_damage_file.pkl' + node_damage_file_name = 'node_pipe_damage_file.pkl' + tank_damage_file_name = 'tank_pipe_damage_file.pkl' + pump_damage_file_name = 'pump_pipe_damage_file.pkl' + + pipe_path = Path(TEMP_DIR) / pipe_damage_file_name + node_path = Path(TEMP_DIR) / node_damage_file_name + tank_path = Path(TEMP_DIR) / tank_damage_file_name + pump_path = Path(TEMP_DIR) / pump_damage_file_name + list_path = Path(TEMP_DIR) / 'list.xlsx' + + pipe_path = str(pipe_path) + node_path = str(node_path) + tank_path = str(tank_path) + pump_path = str(pump_path) + + # self.damage_state[current_time] = damage + self.set_rewet_damage_from_state(state, current_time) + + if current_time in self.damage_state: + raise ValueError( # noqa: TRY003 + f'The time {current_time} is already in ' f' damage state.' # noqa: EM102 + ) + + self.damage_state[current_time] = { + 'Pipe': self.pipe_damage, + 'Node': self.node_damage, + 'Pump': self.pump_damage, + 'Tank': self.tank_damage, + } + + self.pipe_damage.to_pickle(pipe_path) + self.node_damage.to_pickle(node_path) + self.pump_damage.to_pickle(pump_path) + self.tank_damage.to_pickle(tank_path) + + scn_postfix_list = random.choices( + ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'L', 'M'], k=0 + ) + + scn_postfix = '' + for p in scn_postfix_list: + scn_postfix += p + + self.damage_file_list['Scenario Name'] = 'SCN_' + scn_postfix + self.damage_file_list['Pipe Damage'] = pipe_damage_file_name + self.damage_file_list['Nodal Damage'] = node_damage_file_name + self.damage_file_list['Pump Damage'] = tank_damage_file_name + self.damage_file_list['Tank Damage'] = pump_damage_file_name + self.damage_file_list['Probability'] = 1 + + damage_file_list = pd.DataFrame.from_dict([self.damage_file_list]) + damage_file_list.to_excel(list_path) + self.list_path = list_path + + def run_performance(self, current_time, next_time): + """ + Run the performance model using a hydraic solver (REWET). + + Parameters + ---------- + current_time : int + Current time in day. + next_time : int + Mext time in day. + + Raises + ------ + ValueError + When the current or next time is given wrong. + + Returns + ------- + building_demand : dict + the result specifying the percentage of demand satisfied by each + building. + + """ + if current_time > next_time: + raise ValueError('Current tiime cannot be bigger than the next' 'time') # noqa: EM101, TRY003 + if abs(next_time - current_time) % self.hydraulic_time_step != 0: + raise ValueError( # noqa: TRY003 + 'next_time - current_time must be a factor of ' # noqa: EM101 + 'the hydraulci time step.' + ) + + status = self.rewet.initiate(current_time=current_time, debug=True) + if status != 0: + raise ValueError(f'There is an error: {status}') # noqa: EM102, TRY003 + + self.rewet.apply_damage(current_time, 0.001) + + time_step = next_time - current_time + status = self.rewet.run_hydraulic_simulation(time_step) + if status != 0: + raise ValueError(f'There is an error: {status}') # noqa: EM102, TRY003 + + return dict + + def make_rewet_inputs(self, current_time, next_time): + """ + Create setting input for REWET. + + Parameters + ---------- + current_time : int + Current time in seconds. + next_time : TYPE + Next stop time in seconds. + + Raises + ------ + ValueError + Path are not available. + + Returns + ------- + None. + + """ + settings = get_rewet_hydraulic_basic_setting() + run_time = next_time - current_time + list_file_path = Path(self.list_path) + list_file_path = list_file_path.resolve() + if not list_file_path.exists(): + raise ValueError( # noqa: TRY003 + f'The list file does not exists: ' f'{list_file_path!s}' # noqa: EM102 + ) + list_file_path = str(list_file_path) + + temp_dir = Path(TEMP_DIR).resolve() + if not temp_dir.exists(): + raise ValueError(f'The temp directory does not exists: ' f'{temp_dir!s}') # noqa: EM102, TRY003 + temp_dir = str(temp_dir) + + settings['RUN_TIME'] = run_time + settings['minimum_simulation_time'] = run_time + settings['result_directory'] = RESULT_DIR + settings['temp_directory'] = temp_dir + settings['WN_INP'] = self.inp_file_path + settings['pipe_damage_file_list'] = list_file_path + settings['pipe_damage_file_directory'] = temp_dir + settings['Restoration_on'] = False + settings['Pipe_damage_input_method'] = 'pickle' + + input_file_path = Path(INPUT_FILE_DIR) / 'rewet_input.json' + input_file_path = input_file_path.resolve() + with open(input_file_path, 'w') as f: # noqa: PTH123 + json.dump(settings, f, indent=4) + + self.input_file_path = str(input_file_path) + + def get_building_data_satisfaction(self, method): + """ + Get building water satiesfaction data. + + Parameters + ---------- + method : str + MEAB, MAX, or MIN. + + Raises + ------ + ValueError + Unrecognizable method is given. + + Returns + ------- + building_demand_satisfaction_ratio : dict + Building satiesfied ratio. + + """ + demand_sat = self.rewet.get_satisfied_demand_ratio() + + if method.upper() == 'MEAN': + demand_sat = demand_sat.mean() + elif method.upper() == 'MAX': + demand_sat = demand_sat.max() + elif method.upper() == 'MIN': + demand_sat = demand_sat.min() + else: + raise ValueError(f'The method is not recognizable: {method}') # noqa: EM102, TRY003 + + building_demand_satisfaction_ratio = {} + + for node_name in self.demand_node_to_building: + node_demand_ratio = demand_sat[node_name] + building_names = self.demand_node_to_building[node_name] + cur_satisified_demand_building = dict( + zip(building_names, [node_demand_ratio] * len(building_names)) + ) + + building_demand_satisfaction_ratio.update(cur_satisified_demand_building) + + return building_demand_satisfaction_ratio + + def set_new_demand(self, state): + """ + Set new demand from state. + + Parameters + ---------- + state : dict + _det file in dict format. + + Returns + ------- + None. + + """ + for node_name in self.demand_node_to_building: + # cur_node = self.nodes[node_name] + total_initial_population = self.nodes[node_name]['initial_population'] + + if not total_initial_population > 0: + continue + + building_name_list = self.demand_node_to_building[node_name] + + building = state['Buildings']['Building'] + + node_new_demand = 0 + + for bldg_id in building_name_list: + cur_bldg_initial_demand = self.buildings[bldg_id]['initial_demand'] + + cur_bldg_deamnd_ratio = building[bldg_id]['GeneralInformation'][ + 'Population_Ratio' + ] + + cur_bldg_new_deamnd = cur_bldg_deamnd_ratio * cur_bldg_initial_demand + + self.buildings[bldg_id]['current_demand'] = cur_bldg_new_deamnd + + node_new_demand += cur_bldg_new_deamnd + + self.nodes[node_name]['current_demand'] = node_new_demand + node = self.wn.get_node(node_name) + node.demand_timeseries_list[0].base_value = node_new_demand + + +def get_rewet_hydraulic_basic_setting(): + """ + Create basic settings for rewet's input. + + Returns + ------- + settings_dict : dict + REWET input. + + """ + settings = rewet.Input.Settings.Settings() + settings_dict = settings.process.settings + + return settings_dict # noqa: RET504 + + +if __name__ == '__main__': + with open('Results_det.json') as f: # noqa: PTH123 + state = json.load(f) + + with open('Results_0.json') as f: # noqa: PTH123 + damage = json.load(f) + + inp_file = 'waterNetwork.inp' + + interface = REWETPyReCoDes(inp_file) + interface.system_state(state, damage) + result = interface.system_performance(state, 0, 24 * 3600) diff --git a/modules/performREC/pyrecodes/damage_convertor.py b/modules/performREC/pyrecodes/damage_convertor.py new file mode 100644 index 000000000..ea4f2f013 --- /dev/null +++ b/modules/performREC/pyrecodes/damage_convertor.py @@ -0,0 +1,470 @@ +# Copyright (c) 2024 The Regents of the University of California # noqa: INP001 +# Copyright (c) 2024 Leland Stanford Junior University +# +# This file is part of whale. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# You should have received a copy of the BSD 3-Clause License along with +# whale. If not, see . +# +# Contributors: +# Sina Naeimi + +"""Converts damages from Pelicun to REWET format.""" + +import os +from pathlib import Path + +import pandas as pd +import preprocessorIO + +CBIG_int = int(1e9) + + +def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geojson): # noqa: N802 + """ + Create REWET-style piep damage file. + + Parameters + ---------- + pipe_damage_data : dict + Pipe damage data from PELICUN. + run_dir : path + Run dierctory. + event_time : int + Damage time. + sc_geojson : dict + Backend sc_geojson. + + Raises + ------ + ValueError + If damage type is not what it should be. + + Returns + ------- + pipe_damage_list : Pandas Series + REWET-style pipe damage file. + + """ + pipe_id_list = [key for key in pipe_damage_data] # noqa: C416 + + damage_list = [] + damage_time = event_time + sc_geojson_file = preprocessorIO.readJSONFile(sc_geojson) + pipe_data = [ + ss + for ss in sc_geojson_file['features'] + if ss['properties']['type'] == 'Pipe' + ] + pipe_index = [str(ss['id']) for ss in pipe_data] + pipe_id = [ss['properties']['InpID'] for ss in pipe_data] + pipe_index_to_id = dict(zip(pipe_index, pipe_id)) + + for pipe_id in pipe_id_list: + cur_data = pipe_damage_data[pipe_id] + + cur_damage = cur_data['Damage'] + # cur_demand = cur_data["Demand"] + + aim_data = findAndReadAIMFile( + pipe_id, + os.path.join('Results', 'WaterDistributionNetwork', 'Pipe'), # noqa: PTH118 + run_dir, + ) + + material = aim_data['GeneralInformation'].get('Material', None) + + if material is None: + # raise ValueError("Material is none") + material = 'CI' + + aggregates_list = [ + cur_agg for cur_agg in list(cur_damage.keys()) if 'aggregate' in cur_agg + ] + segment_sizes = len(aggregates_list) + segment_step = 1 / segment_sizes + c = 0 + + for cur_agg in aggregates_list: # cur_damage["aggregate"]: + damage_val = cur_damage[cur_agg] + if damage_val > 0: + if damage_val == 1: + damage_type = 'leak' + elif damage_val == 2: # noqa: PLR2004 + damage_type = 'break' + else: + raise ValueError('The damage type must be eother 1 or 2') # noqa: EM101, TRY003 + else: + continue + + cur_loc = c * segment_step + segment_step / 2 + # print(cur_loc) + c += 1 + damage_list.append( + { + 'pipe_id': pipe_index_to_id[pipe_id], + 'damage_loc': cur_loc, + 'type': damage_type, + 'Material': material, + } + ) + damage_list.reverse() + pipe_damage_list = pd.Series( + data=damage_list, index=[damage_time for val in damage_list], dtype='O' + ) + + # REWET_input_data["Pipe_damage_list"] = pipe_damage_list + # REWET_input_data["AIM"] = aim_data + + return pipe_damage_list # noqa: RET504 + + +def createNodeDamageInputForREWET(node_damage_data, run_dir, event_time): # noqa: N802 + """ + Create REWET-style node damage file. + + Parameters + ---------- + node_damage_data : dict + Node damage data from PELICUN. + run_dir : path + Run dierctory. + event_time : int + Damage time. + + Returns + ------- + node_damage_list : Pandas Series + REWET-style node damage file. + + """ + node_id_list = [key for key in node_damage_data] # noqa: C416 + + damage_list = [] + damage_time = event_time + + for node_id in node_id_list: + cur_damage = node_damage_data[node_id] + aggregates_list = [ + cur_agg for cur_agg in list(cur_damage.keys()) if 'aggregate' in cur_agg + ] + + if len(aggregates_list) == 0: + continue + + cur_data = node_damage_data[node_id] + + cur_damage = cur_data['Damage'] + # cur_demand = cur_data["Demand"] + + aim_data = findAndReadAIMFile( + node_id, + os.path.join('Results', 'WaterDistributionNetwork', 'Node'), # noqa: PTH118 + run_dir, + ) + + total_length = aim_data['GeneralInformation'].get('Total_length', None) + total_number_of_damages = cur_damage['aggregate'] + + damage_list.append( + { + 'node_name': node_id, + 'number_of_damages': total_number_of_damages, + 'node_Pipe_Length': total_length, + } + ) + + node_damage_list = pd.Series( + data=damage_list, index=[damage_time for val in damage_list], dtype='O' + ) + + return node_damage_list # noqa: RET504 + + +def createPumpDamageInputForREWET(pump_damage_data, REWET_input_data): # noqa: N802, N803 + """ + Create REWET-style pump damage file. + + Parameters + ---------- + pump_damage_data : dict + Pump damage data from PELICUN. + REWET_input_data : dict + REWET input data. + + Returns + ------- + pump_damage_list : Pandas Series + REWET-style pump damage file. + + """ + pump_id_list = [key for key in pump_damage_data] # noqa: C416 + + damage_list = [] + damage_time = REWET_input_data['event_time'] + + for pump_id in pump_id_list: + cur_data = pump_damage_data[pump_id] + + cur_damage = cur_data['Damage'] + cur_repair_time = cur_data['Repair'] + + if cur_damage == 0: + continue # cur_damage_state = 0 means undamaged pump + + # I'm not sure if we need any data about the pump at this point + + # aim_data = findAndReadAIMFile(tank_id, os.path.join( + # "Results", "WaterDistributionNetwork", "Pump"), + # REWET_input_data["run_dir"]) + + # We are getting this data from PELICUN + # restore_time = getPumpRetsoreTime(cur_damage) + damage_list.append( + { + 'pump_id': pump_id, + 'time': damage_time, + 'Restore_time': cur_repair_time, + } + ) + pump_damage_list = pd.Series( + index=[damage_time for val in damage_list], data=damage_list + ) + + return pump_damage_list # noqa: RET504 + + +def createTankDamageInputForREWET(tank_damage_data, REWET_input_data): # noqa: N802, N803 + """ + Create REWET-style Tank damage file. + + Parameters + ---------- + tank_damage_data : dict + Tank damage data from PELICUN. + REWET_input_data : dict + REWET input data. + + Returns + ------- + tank_damage_list : Pandas Series + REWET-style tank damage file. + """ + tank_id_list = [key for key in tank_damage_data] # noqa: C416 + + damage_list = [] + damage_time = REWET_input_data['event_time'] + + for tank_id in tank_id_list: + cur_data = tank_damage_data[tank_id] + + cur_damage = cur_data['Damage'] + cur_repair_time = cur_data['Repair'] + + if cur_damage == 0: + continue # cur_damage_state = 0 meeans undamged tank + + damage_list.append( + { + 'tank_id': tank_id, + 'time': damage_time, + 'Restore_time': cur_repair_time, + } + ) + + tank_damage_list = pd.Series( + index=[damage_time for val in damage_list], data=damage_list + ) + + return tank_damage_list # noqa: RET504 + + +def findAndReadAIMFile(asset_id, asset_type, run_dir): # noqa: N802 + """ + Find and read the AIM file for an asset. + + Parameters + ---------- + asset_id : int + The asset ID. + asset_type : str + Asset Type (e.g., Building, WaterDistributionNetwork). + run_dir : path + The directory where data is stored (aka the R2dTool directory) + + Returns + ------- + aim_file_data : dict + AIM file data as a dict. + + """ + file_path = Path( + run_dir, + asset_type, + str(asset_id), + 'templatedir', + f'{asset_id}-AIM.json', + ) + aim_file_data = preprocessorIO.readJSONFile(str(file_path)) + return aim_file_data # noqa: RET504 + + +# Not UsedWE WILL GET IT FROM PELICUN +def getPumpRetsoreTime(damage_state): # noqa: N802 + """ + Provide the restore time. + + Based on HAZUS repair time or any other + approach available in the future. If damage state is slight, the restore + time is 3 days (in seconds). If damage state is 2, the restore time is 7 + days (in seconds). If damage state is 3 or 4, the restore time is + indefinite (a big number). + + Parameters + ---------- + damage_state : Int + Specifies the damage state (1 for slightly damages, 2 for moderate, + 3 etensive, and 4 complete. + + Returns + ------- + Retstor time : int + + + """ + if damage_state == 1: + restore_time = int(3 * 24 * 3600) + elif damage_state == 2: # noqa: PLR2004 + restore_time = int(7 * 24 * 3600) + else: + restore_time = CBIG_int + + return restore_time + + +# NOT USED! WE WILL GET IT FROM PELICUN +def getTankRetsoreTime(tank_type, damage_state): # noqa: ARG001, N802 + """ + Provide the restore time. + + Based on HAZUS repair time or any other + approach available in the future. if damage state is slight, the restore + time is 3 days (in seconds). If damage state is 2, the restore time is 7 + days (in seconds). If damage state is 3 or 4, the restore time is + indefinite (a big number). + + Parameters + ---------- + tank_type : STR + Tank type based on the data schema. The parameter is not used for now. + damage_state : Int + Specifies the damage state (1 for slightly damages, 2 for moderate, + 3 etensive, and 4 complete. + + Returns + ------- + Retstor time : int + + + """ + if damage_state == 1: + restore_time = int(3 * 24 * 3600) + elif damage_state == 2: # noqa: PLR2004 + restore_time = int(7 * 24 * 3600) + else: + restore_time = CBIG_int + + return restore_time + + +def readDamagefile(file_addr, run_dir, event_time, sc_geojson): # noqa: N802 + """ + Read PELICUN damage files. and create REWET-Style damage. + + Reads PELICUN damage files. and create REWET-Style damage for all + WaterDistributionNetwork elements + + Parameters + ---------- + file_addr : path + PELICUN damage file in JSON format. + REWET_input_data : dict + REWET input data, which is updated in the function. + scn_number : dict + JSON FILE. + + Returns + ------- + damage_data : dict + Damage data in PELICUN dict format. + + """ + # TODO(Sina): Make reading once for each scenario + + # wn = wntrfr.network.WaterNetworkModel(REWET_input_data["inp_file"] ) + + damage_data = preprocessorIO.readJSONFile(file_addr) + + wn_damage_data = damage_data['WaterDistributionNetwork'] + + if 'Pipe' in wn_damage_data: + pipe_damage_data = createPipeDamageInputForREWET( + wn_damage_data['Pipe'], run_dir, event_time, sc_geojson + ) + else: + pipe_damage_data = pd.Series(dtype='O') + + if 'Tank' in wn_damage_data: + tank_damage_data = createTankDamageInputForREWET( + wn_damage_data['Tank'], run_dir, event_time + ) + else: + tank_damage_data = pd.Series(dtype='O') + + if 'Pump' in wn_damage_data: + pump_damage_data = createPumpDamageInputForREWET( + wn_damage_data['Pump'], run_dir, event_time + ) + else: + pump_damage_data = pd.Series(dtype='O') + + if 'Junction' in wn_damage_data: + node_damage_data = createNodeDamageInputForREWET( + wn_damage_data['Junction'], run_dir, event_time + ) + else: + node_damage_data = pd.Series(dtype='O') + + damage_data = {} + damage_data['Pipe'] = pipe_damage_data + damage_data['Tank'] = tank_damage_data + damage_data['Pump'] = pump_damage_data + damage_data['Node'] = node_damage_data + + return damage_data diff --git a/modules/systemPerformance/REWET/REWET_Wrapper.py b/modules/systemPerformance/REWET/REWET_Wrapper.py index cbb99b361..177d9a722 100644 --- a/modules/systemPerformance/REWET/REWET_Wrapper.py +++ b/modules/systemPerformance/REWET/REWET_Wrapper.py @@ -37,7 +37,7 @@ # Sina Naeimi # Jinyan Zhao -import argparse +import argparse # noqa: I001 import importlib import json import os @@ -45,63 +45,73 @@ import string import sys from pathlib import Path +import warnings import damage_convertor import pandas as pd import preprocessorIO from shapely import geometry -# try: -# import REWET -# print("Imported") -# except: -# This is only for now -# print("HERE") +# TODO (SINA): please resolve this one later this_dir = Path(os.path.dirname(os.path.abspath(__file__))).resolve() # noqa: PTH100, PTH120 -# main_dir = this_dir.parent - +# TODO (SINA): Import REWET instead and check these sys.path.insert(0, str(this_dir / 'REWET')) from initial import Starter # noqa: E402 from Result_Project import Project_Result # noqa: E402 +PAR_CERITERIA = 2 -def createScnearioList(run_directory, scn_number): # noqa: N802, D103 - damage_input_dir = os.path.join( # noqa: PTH118 - run_directory, 'Results', 'WaterDistributionNetwork', 'damage_input' - ) - - if not os.path.exists(damage_input_dir): # noqa: PTH110 - os.makedirs(damage_input_dir) # noqa: PTH103 - - # REWET_input_data["damage_input_dir"] = damage_input_dir - - prefix = chooseARandomPreefix(damage_input_dir) - - scenario_name = f'{prefix}_scn_{scn_number}' - pipe_file_name = f'{prefix}_pipe_{scn_number}' - node_file_name = f'{prefix}_node_{scn_number}' - pump_file_name = f'{prefix}_pump_{scn_number}' - tank_file_name = f'{prefix}_tank_{scn_number}' - - scenario = { - 'Scenario Name': scenario_name, - 'Pipe Damage': pipe_file_name, - 'Nodal Damage': node_file_name, - 'Pump Damage': pump_file_name, - 'Tank Damage': tank_file_name, - 'Probability': 1, - } - - scenario_list = scenario # pd.DataFrame([scenario]).set_index("Scenario Name") - - # REWET_input_data["scenario_list"] = scenario_list - - return scenario_list, prefix +# ============================================================================= +# def createScnearioList(run_dir, scn_number): +# """ +# Create scenario list. +# +# Parameters +# ---------- +# run_dir : path +# Run Directory. +# scn_number : int +# Scenario number. +# +# Returns +# ------- +# scenario : dict +# scenario. +# prefix : str +# Scenario name prefix. +# +# """ +# damage_input_dir = run_dir / 'WaterDistributionNetwork' / 'damage_input' +# damage_input_dir = damage_input_dir.resolve() +# +# if not damage_input_dir.exists(): +# damage_input_dir.mkdir(parents=True) +# +# prefix = choose_random_preefix(damage_input_dir) +# +# scenario_name = f'{prefix}_scn_{scn_number}' +# pipe_file_name = f'{prefix}_pipe_{scn_number}' +# node_file_name = f'{prefix}_node_{scn_number}' +# pump_file_name = f'{prefix}_pump_{scn_number}' +# tank_file_name = f'{prefix}_tank_{scn_number}' +# +# scenario = { +# 'Scenario Name': scenario_name, +# 'Pipe Damage': pipe_file_name, +# 'Nodal Damage': node_file_name, +# 'Pump Damage': pump_file_name, +# 'Tank Damage': tank_file_name, +# 'Probability': 1, +# } +# +# return scenario, prefix +# ============================================================================= +def choose_random_preefix(damage_input_dir): + """Choose a random prefix. -def chooseARandomPreefix(damage_input_dir): # noqa: N802 - """Choses a random prefix for sceranio and pipe, node, pump and tank damage + Choose a random prefix for sceranio and pipe, node, pump and tank damage file. The is important to find and unused prefix so if this script is being ran in parallel, then files are not being overwritten. @@ -115,7 +125,7 @@ def chooseARandomPreefix(damage_input_dir): # noqa: N802 random_prefix : str The Chosen random prefix string. - """ # noqa: D205 + """ number_of_prefix = 4 dir_list = os.listdir(damage_input_dir) @@ -137,37 +147,49 @@ def chooseARandomPreefix(damage_input_dir): # noqa: N802 return random_prefix - # def setSettingsData(rwhale_data, REWET_input_data): - """ - Sets the settings (future project file) for REWET. REWET input data - dictionary is both used as a source and destination for settinsg data. The - data is stored in REWET_input_data object with key='settings'. The value - for 'settinsg' is an object of REWET's 'Settings' class. + +def get_dl_file_name(run_dir, dl_file_path, scn_number): + """Get damage and Loss fole name. + + If dl_file_path is not given, the path is acquired from rwhale input data. Parameters ---------- - rwhale_data : json dict - rwhale input file. - REWET_input_data : dict - REWET input data. + rewet_input_data : dict + REWET-style settings input file. + damage_file_path : path + Path to the damage file. + scn_number : int + Damage scenario number. Returns ------- - None. + file_path : path + Path to the damage file. + file_dir : path + Path to the directory of the damage file. - """ # noqa: RET503, W291 + """ + if dl_file_path is None: + file_name = f'WaterDistributionNetwork_{scn_number}.json' + file_dir = run_dir / 'WaterDistributionNetwork' + file_path = file_dir / file_name + else: + file_path = dl_file_path + file_dir = Path(dl_file_path).parent + return file_path, file_dir -def getDLFileName(run_dir, dl_file_path, scn_number): # noqa: N802 - """If dl_file_path is not given, the path is acquired from rwhale input data. + +def set_settings_data(input_json, rewet_input_data): + """ + Set REWET-style input settings data. Parameters ---------- - REWET_input_data : TYPE + input_json : TYPE DESCRIPTION. - damage_file_pa : TYPE - DESCRIPTION. - scn_number : TYPE + rewet_input_data : TYPE DESCRIPTION. Returns @@ -175,68 +197,54 @@ def getDLFileName(run_dir, dl_file_path, scn_number): # noqa: N802 None. """ - if dl_file_path == None: # noqa: E711 - file_name = f'WaterDistributionNetwork_{scn_number}.json' - run_dir = run_dir # noqa: PLW0127 - file_dir = os.path.join(run_dir, 'Results', 'WaterDistributionNetwork') # noqa: PTH118 - file_path = os.path.join(file_dir, file_name) # noqa: PTH118 - else: - file_path = dl_file_path - file_dir = Path(dl_file_path).parent - - return file_path, file_dir + policy_file_name = input_json['SystemPerformance']['WaterDistributionNetwork'][ + 'Policy Definition' + ] + policy_file_path = input_json['SystemPerformance']['WaterDistributionNetwork'][ + 'Policy DefinitionPath' + ] + policy_config_file = Path(policy_file_path) / Path(policy_file_name) -def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, D103 - policy_file_name = rwhale_input_Data['SystemPerformance'][ - 'WaterDistributionNetwork' - ]['Policy Definition'] - policy_file_path = rwhale_input_Data['SystemPerformance'][ + rewet_input_data['settings']['RUN_TIME'] = input_json['SystemPerformance'][ 'WaterDistributionNetwork' - ]['Policy DefinitionPath'] - - policy_config_file = os.path.join(Path(policy_file_path), Path(policy_file_name)) # noqa: PTH118 - - REWET_input_data['settings']['RUN_TIME'] = rwhale_input_Data[ - 'SystemPerformance' - ]['WaterDistributionNetwork']['simulationTime'] - REWET_input_data['settings']['simulation_time_step'] = rwhale_input_Data[ + ]['simulationTime'] + rewet_input_data['settings']['simulation_time_step'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['simulationTimeStep'] - REWET_input_data['settings']['last_sequence_termination'] = rwhale_input_Data[ + rewet_input_data['settings']['last_sequence_termination'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['last_sequence_termination'] - REWET_input_data['settings']['node_demand_temination'] = rwhale_input_Data[ + rewet_input_data['settings']['node_demand_temination'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['node_demand_temination'] - REWET_input_data['settings']['node_demand_termination_time'] = rwhale_input_Data[ + rewet_input_data['settings']['node_demand_termination_time'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['node_demand_termination_time'] - REWET_input_data['settings']['node_demand_termination_ratio'] = ( - rwhale_input_Data[ - 'SystemPerformance' - ]['WaterDistributionNetwork']['node_demand_termination_ratio'] - ) - REWET_input_data['settings']['solver'] = rwhale_input_Data['SystemPerformance'][ + rewet_input_data['settings']['node_demand_termination_ratio'] = input_json[ + 'SystemPerformance' + ]['WaterDistributionNetwork']['node_demand_termination_ratio'] + rewet_input_data['settings']['solver'] = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['Solver'] - REWET_input_data['settings']['Restoration_on'] = rwhale_input_Data[ - 'SystemPerformance' - ]['WaterDistributionNetwork']['Restoration_on'] - REWET_input_data['settings']['minimum_job_time'] = rwhale_input_Data[ + rewet_input_data['settings']['Restoration_on'] = input_json['SystemPerformance'][ + 'WaterDistributionNetwork' + ]['Restoration_on'] + rewet_input_data['settings']['minimum_job_time'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['minimum_job_time'] - REWET_input_data['settings']['Restortion_config_file'] = ( - policy_config_file # TODO: SINA unmark it # noqa: TD002 - ) - p = rwhale_input_Data['SystemPerformance']['WaterDistributionNetwork'][ + rewet_input_data['settings']['Restortion_config_file'] = str( + policy_config_file + ) # TODO (Sina): unmark it + + p = input_json['SystemPerformance']['WaterDistributionNetwork'][ 'pipe_damage_model' ] - REWET_input_data['settings']['pipe_damage_model'] = {} + rewet_input_data['settings']['pipe_damage_model'] = {} for mat_data in p: - REWET_input_data['settings']['pipe_damage_model'][mat_data[0]] = { + rewet_input_data['settings']['pipe_damage_model'][mat_data[0]] = { 'alpha': mat_data[1], 'beta': mat_data[2], 'gamma': mat_data[3], @@ -244,11 +252,11 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'b': mat_data[5], } - n = rwhale_input_Data['SystemPerformance']['WaterDistributionNetwork'][ + n = input_json['SystemPerformance']['WaterDistributionNetwork'][ 'node_damage_model' ] n = n[0] - REWET_input_data['settings']['node_damage_model'] = { + rewet_input_data['settings']['node_damage_model'] = { 'x': 0.9012, 'a': n[0], 'aa': n[1], @@ -267,22 +275,22 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'damage_node_model': 'equal_diameter_emitter', } - if rwhale_input_Data['SystemPerformance']['WaterDistributionNetwork'][ + if input_json['SystemPerformance']['WaterDistributionNetwork'][ 'Pipe_Leak_Based' ]: - pipe_leak_amount = rwhale_input_Data['SystemPerformance'][ + pipe_leak_amount = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['pipe_leak_amount'] - pipe_leak_time = rwhale_input_Data['SystemPerformance'][ - 'WaterDistributionNetwork' - ]['pipe_leak_time'] + pipe_leak_time = input_json['SystemPerformance']['WaterDistributionNetwork'][ + 'pipe_leak_time' + ] pipe_damage_discovery_mode = { 'method': 'leak_based', 'leak_amount': pipe_leak_amount, 'leak_time': pipe_leak_time, } else: - pipe_time_discovery_ratio = rwhale_input_Data['SystemPerformance'][ + pipe_time_discovery_ratio = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['pipe_time_discovery_ratio'] pipe_damage_discovery_mode = { @@ -290,22 +298,22 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'time_discovery_ratio': pipe_time_discovery_ratio, } # pd.Series([line[0] for line in pipe_time_discovery_ratio], index = [line[1] for line in pipe_time_discovery_ratio])} - if rwhale_input_Data['SystemPerformance']['WaterDistributionNetwork'][ + if input_json['SystemPerformance']['WaterDistributionNetwork'][ 'Node_Leak_Based' ]: - node_leak_amount = rwhale_input_Data['SystemPerformance'][ + node_leak_amount = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['node_leak_amount'] - node_leak_time = rwhale_input_Data['SystemPerformance'][ - 'WaterDistributionNetwork' - ]['node_leak_time'] + node_leak_time = input_json['SystemPerformance']['WaterDistributionNetwork'][ + 'node_leak_time' + ] node_damage_discovery_mode = { 'method': 'leak_based', 'leak_amount': node_leak_amount, 'leak_time': node_leak_time, } else: - node_time_discovery_ratio = rwhale_input_Data['SystemPerformance'][ + node_time_discovery_ratio = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['node_time_discovery_ratio'] node_damage_discovery_mode = { @@ -313,10 +321,10 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'time_discovery_ratio': node_time_discovery_ratio, } # pd.Series([line[0] for line in node_time_discovery_ratio], index = [line[1] for line in node_time_discovery_ratio])} - pump_time_discovery_ratio = rwhale_input_Data['SystemPerformance'][ + pump_time_discovery_ratio = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['pump_time_discovery_ratio'] - tank_time_discovery_ratio = rwhale_input_Data['SystemPerformance'][ + tank_time_discovery_ratio = input_json['SystemPerformance'][ 'WaterDistributionNetwork' ]['tank_time_discovery_ratio'] pump_damage_discovery_model = { @@ -328,50 +336,51 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'time_discovery_ratio': tank_time_discovery_ratio, } # pd.Series([line[0] for line in tank_time_discovery_ratio], index = [line[1] for line in tank_time_discovery_ratio])} - REWET_input_data['settings']['pipe_damage_discovery_model'] = ( + rewet_input_data['settings']['pipe_damage_discovery_model'] = ( pipe_damage_discovery_mode ) - REWET_input_data['settings']['node_damage_discovery_model'] = ( + rewet_input_data['settings']['node_damage_discovery_model'] = ( node_damage_discovery_mode ) - REWET_input_data['settings']['pump_damage_discovery_model'] = ( + rewet_input_data['settings']['pump_damage_discovery_model'] = ( pump_damage_discovery_model ) - REWET_input_data['settings']['tank_damage_discovery_model'] = ( + rewet_input_data['settings']['tank_damage_discovery_model'] = ( tank_damage_discovery_model ) - REWET_input_data['settings']['minimum_pressure'] = rwhale_input_Data[ + rewet_input_data['settings']['minimum_pressure'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['minimum_pressure'] - REWET_input_data['settings']['required_pressure'] = rwhale_input_Data[ + + rewet_input_data['settings']['required_pressure'] = input_json[ 'SystemPerformance' ]['WaterDistributionNetwork']['required_pressure'] # Not Supposed to be in R2DTool GUI ############ - REWET_input_data['settings']['minimum_simulation_time'] = ( - 0 # TODO : HERE #REWET_input_data["event_time"] + REWET_input_data["settings"]["simulation_time_step"] # noqa: TD002 - ) - REWET_input_data['settings']['save_time_step'] = True - REWET_input_data['settings']['record_restoration_agent_logs'] = True - REWET_input_data['settings']['record_damage_table_logs'] = True - REWET_input_data['settings']['simulation_time_step'] = 3600 - REWET_input_data['settings']['number_of_proccessor'] = 1 - REWET_input_data['settings']['demand_ratio'] = 1 - REWET_input_data['settings']['dmg_rst_data_save'] = True - REWET_input_data['settings']['Parameter_override'] = True - REWET_input_data['settings']['mpi_resume'] = ( + # TODO (Sina): HERE + # rewet_input_data["event_time"] + rewet_input_data["settings"]["simulation_time_step"] + rewet_input_data['settings']['minimum_simulation_time'] = 0 + rewet_input_data['settings']['save_time_step'] = True + rewet_input_data['settings']['record_restoration_agent_logs'] = True + rewet_input_data['settings']['record_damage_table_logs'] = True + rewet_input_data['settings']['simulation_time_step'] = 3600 + rewet_input_data['settings']['number_of_proccessor'] = 1 + rewet_input_data['settings']['demand_ratio'] = 1 + rewet_input_data['settings']['dmg_rst_data_save'] = True + rewet_input_data['settings']['Parameter_override'] = True + rewet_input_data['settings']['mpi_resume'] = ( True # ignores the scenarios that are done ) - REWET_input_data['settings']['ignore_empty_damage'] = False - REWET_input_data['settings']['result_details'] = 'extended' - REWET_input_data['settings']['negative_node_elmination'] = True - REWET_input_data['settings']['nne_flow_limit'] = 0.5 - REWET_input_data['settings']['nne_pressure_limit'] = -5 - REWET_input_data['settings']['Virtual_node'] = True - REWET_input_data['settings']['damage_node_model'] = ( + rewet_input_data['settings']['ignore_empty_damage'] = False + rewet_input_data['settings']['result_details'] = 'extended' + rewet_input_data['settings']['negative_node_elmination'] = True + rewet_input_data['settings']['nne_flow_limit'] = 0.5 + rewet_input_data['settings']['nne_pressure_limit'] = -5 + rewet_input_data['settings']['Virtual_node'] = True + rewet_input_data['settings']['damage_node_model'] = ( 'equal_diameter_emitter' # "equal_diameter_reservoir" ) - REWET_input_data['settings']['default_pipe_damage_model'] = { + rewet_input_data['settings']['default_pipe_damage_model'] = { 'alpha': -0.0038, 'beta': 0.1096, 'gamma': 0.0196, @@ -379,17 +388,31 @@ def setSettingsData(input_json, REWET_input_data): # noqa: ARG001, N802, N803, 'b': 1, } - REWET_input_data['settings'][ + rewet_input_data['settings'][ 'limit_result_file_size' ] = -1 # in Mb. 0 means no limit - REWET_input_data['settings']['Pipe_damage_input_method'] = 'pickle' + rewet_input_data['settings']['Pipe_damage_input_method'] = 'pickle' + + +def create_path(path): + """ + Create a path. + Parameters + ---------- + path : path + Path. -def create_path(path): # noqa: D103 + Returns + ------- + None. + + """ if isinstance(path, str): path = Path(path) not_existing_hir = [] - while os.path.exists(path) == False: # noqa: PTH110, E712 + path = path.resolve() + while path.exists() is False: not_existing_hir.append(path.name) path = path.parent @@ -402,29 +425,27 @@ def create_path(path): # noqa: D103 if __name__ == '__main__': # Setting arg parser - argParser = argparse.ArgumentParser('Preprocess rwhale workflow to REWET input.') # noqa: N816 + arg_parser = argparse.ArgumentParser( + 'Preprocess rwhale workflow to REWET input.' + ) - argParser.add_argument( + arg_parser.add_argument( '--input', '-i', default='inputRWHALE.json', help='rwhale input file json file', ) - # argParser.add_argument("--damage", "-d", - # default="water_damage_input_structure.json", - # help="water damage input json file. If provided, number of realization is ignored if prvided and number of realization is set to 1.") + arg_parser.add_argument('--dir', '-d', help='WDN damage result directory') - argParser.add_argument('--dir', '-d', help='WDN damage result directory') - - argParser.add_argument( + arg_parser.add_argument( '--number', '-n', default=None, help='If specified, indicates realization number, otherwise, all scenarios are run on all CPUS.', ) - argParser.add_argument( + arg_parser.add_argument( '--par', '-p', default=False, @@ -432,101 +453,101 @@ def create_path(path): # noqa: D103 help='if specified, uses all CPUS. 2 or more CPUs are not available, it will revert back to serial run.', ) - parser_data = argParser.parse_args() + parser_data = arg_parser.parse_args() - # learning about parallel or serial settings + # Setting parallel or serial settings - numP = 1 # noqa: N816 - procID = 0 # noqa: N816 - doParallel = False # noqa: N816 + proc_size = 1 + proc_id = 0 + do_parallel = False mpi_spec = importlib.util.find_spec('mpi4py') found = mpi_spec is not None - if found and argParser.par: + if found and arg_parser.par: from mpi4py import MPI comm = MPI.COMM_WORLD - numP = comm.Get_size() # noqa: N816 - procID = comm.Get_rank() # noqa: N816 - if numP < 2: # noqa: PLR2004 - doParallel = False # noqa: N816 - numP = 1 # noqa: N816 - procID = 0 # noqa: N816 - print( # noqa: T201 - 'Parallel running is not possible. Number of CPUS are are not enough.' + proc_size = comm.Get_size() + proc_id = comm.Get_rank() + if proc_size < PAR_CERITERIA: + do_parallel = False + proc_size = 1 + proc_id = 0 + warnings.warn( + 'Parallel running is not possible. Number ' + 'of CPUS are are not enough.', + stacklevel=2, ) else: - doParallel = True # noqa: N816 + do_parallel = True + # get the current directory, assuming the current directory + # is the run directory (Result Directory) + cur_dir = Path.cwd() # Setting up run settings + rewet_input_data = {} + rewet_input_data['settings'] = {} - REWET_input_data = {} - REWET_input_data['settings'] = {} - - # print(parser_data.input) - rwhale_input_Data = preprocessorIO.readJSONFile(parser_data.input) # noqa: N816 - setSettingsData(rwhale_input_Data, REWET_input_data) - event_time = rwhale_input_Data['SystemPerformance']['WaterDistributionNetwork'][ + rwhale_input_data = preprocessorIO.read_json_file(parser_data.input) + set_settings_data(rwhale_input_data, rewet_input_data) + event_time = rwhale_input_data['SystemPerformance']['WaterDistributionNetwork'][ 'eventTime' ] # Set R2D environment and parameters - water_asset_data = rwhale_input_Data['Applications']['Assets'][ + water_asset_data = rwhale_input_data['Applications']['Assets'][ 'WaterDistributionNetwork' ] inp_file_addr = water_asset_data['ApplicationData']['inpFile'] if '{Current_Dir}' in inp_file_addr: inp_file_addr = inp_file_addr.replace('{Current_Dir}', '.') - sc_geojson = rwhale_input_Data['Applications']['Assets'][ + sc_geojson = rwhale_input_data['Applications']['Assets'][ 'WaterDistributionNetwork' ]['ApplicationData']['assetSourceFile'] - run_directory = rwhale_input_Data['runDir'] - number_of_realization = rwhale_input_Data['Applications']['DL'][ + run_dir = cur_dir + number_of_realization = rwhale_input_data['Applications']['DL'][ 'WaterDistributionNetwork' ]['ApplicationData']['Realizations'] - REWET_input_data['settings']['result_directory'] = os.path.join( # noqa: PTH118 - run_directory, 'Results', 'WaterDistributionNetwork', 'REWET_Result' - ) + rewet_res_dir = run_dir / 'WaterDistributionNetwork' / 'REWET_Result' + rewet_input_data['settings']['result_directory'] = str(rewet_res_dir) - REWET_input_data['settings']['temp_directory'] = os.path.join( # noqa: PTH118 - run_directory, 'Results', 'WaterDistributionNetwork', 'REWET_RunFiles' - ) + temp_dir = run_dir / 'WaterDistributionNetwork' / 'REWET_RunFiles' + rewet_input_data['settings']['temp_directory'] = str(temp_dir) - REWET_input_data['settings']['WN_INP'] = inp_file_addr + rewet_input_data['settings']['WN_INP'] = str(inp_file_addr) + + damage_save_path = run_dir / 'WaterDistributionNetwork' / 'damage_input' - damage_save_path = ( - Path(run_directory) / 'Results' / 'WaterDistributionNetwork' / 'damage_input' - ) damage_save_path_hir = damage_save_path create_path(damage_save_path_hir) - if parser_data.number == None: # noqa: E711 + if parser_data.number is None: scneario_list_path = damage_save_path / 'scenario_table.xlsx' else: scneario_list_path = ( damage_save_path / f'scenario_table_{parser_data.number}.xlsx' ) - REWET_input_data['settings']['pipe_damage_file_list'] = str(scneario_list_path) - REWET_input_data['settings']['pipe_damage_file_directory'] = str( + rewet_input_data['settings']['pipe_damage_file_list'] = str(scneario_list_path) + rewet_input_data['settings']['pipe_damage_file_directory'] = str( damage_save_path ) # Add Single Scenario or multiple scenario Damage_file_name = [] - if doParallel and procID > 0: + if do_parallel and proc_id > 0: pass else: - settings_json_file_path = preprocessorIO.saveSettingsFile( - REWET_input_data, damage_save_path, parser_data.number + settings_json_file_path = preprocessorIO.save_settings_file( + rewet_input_data, damage_save_path, parser_data.number ) scenario_table = preprocessorIO.create_scneario_table() - if parser_data.number == None: # noqa: E711 + if parser_data.number is None: Damage_file_name = list(range(number_of_realization)) else: @@ -535,14 +556,13 @@ def create_path(path): # noqa: D103 damage_save_path = scneario_list_path.parent for scn_number in Damage_file_name: - dl_file_path, dl_file_dir = getDLFileName( - run_directory, parser_data.dir, scn_number + dl_file_path, dl_file_dir = get_dl_file_name( + run_dir, parser_data.dir, scn_number ) - damage_data = damage_convertor.readDamagefile( - dl_file_path, run_directory, event_time, sc_geojson + damage_data = damage_convertor.read_damage_file( + dl_file_path, run_dir, event_time, sc_geojson ) - # damage_save_path = Path(run_directory) / "Results" / "WaterDistributionNetwork" / "damage_input" cur_damage_file_name_list = preprocessorIO.save_damage_data( damage_save_path, damage_data, scn_number @@ -553,14 +573,14 @@ def create_path(path): # noqa: D103 ) preprocessorIO.save_scenario_table( - scenario_table, REWET_input_data['settings']['pipe_damage_file_list'] + scenario_table, rewet_input_data['settings']['pipe_damage_file_list'] ) command = ( - 'python ' # noqa: ISC003 - + 'C:\\Users\\naeim\\Desktop\\REWET\\main.py -j ' - + str(settings_json_file_path) + 'python ' + f'C:\\Users\\naeim\\Desktop\\REWET\\main.py -j {settings_json_file_path}' ) + # try: # result = subprocess.check_output(command, shell=True, text=True) # returncode = 0 @@ -573,39 +593,34 @@ def create_path(path): # noqa: D103 # if returncode == 0: # print("REWET ran Successfully") - create_path(REWET_input_data['settings']['result_directory']) - create_path(REWET_input_data['settings']['temp_directory']) + create_path(rewet_input_data['settings']['result_directory']) + create_path(rewet_input_data['settings']['temp_directory']) - rewet_log_path = ( - Path(run_directory) - / 'Results' - / 'WaterDistributionNetwork' - / 'rewet_log.txt' - ) + rewet_log_path = run_dir / 'WaterDistributionNetwork' / 'rewet_log.txt' system_std_out = sys.stdout - with open(rewet_log_path, 'w') as log_file: # noqa: PTH123 + with rewet_log_path.open('wt') as log_file: sys.stdout = log_file REWET_starter = Starter() REWET_starter.run(settings_json_file_path) p = Project_Result( - Path(REWET_input_data['settings']['result_directory']) / 'project.prj' + Path(rewet_input_data['settings']['result_directory']) / 'project.prj' ) # these are the input for result section. They are not include in the requested_result = ['DL', 'QN'] substitute_ft = {'DL': 'Delivery', 'QN': 'Quantity'} consistency_time_window = 0 # 7200 - iConsider_leak = False # True # noqa: N816 - # the following does not matter if iConsider_leak is false + iConsider_leak = False # noqa: N816 + # the following does not matter if consider_leak is false leak_ratio = {'DL': 0.75, 'QN': 0} sub_asset_list = ['Junction', 'Pipe', 'Reservoir'] - sub_asset_name_to_id = dict() # noqa: C408 - sub_asset_id_to_name = dict() # noqa: C408 + sub_asset_name_to_id = {} + sub_asset_id_to_name = {} for sub_asset in sub_asset_list: - sc_geojson_file = preprocessorIO.readJSONFile(sc_geojson) + sc_geojson_file = preprocessorIO.read_json_file(sc_geojson) sub_asset_data = [ ss for ss in sc_geojson_file['features'] @@ -635,13 +650,10 @@ def create_path(path): # noqa: D103 ) ) - for scn_name, row in p.project.scenario_list.iterrows(): # noqa: B007 + for scn_name, _row in p.project.scenario_list.iterrows(): realization_number = int(scn_name.strip('SCN_')) for single_requested_result in requested_result: - if ( - single_requested_result == 'DL' # noqa: PLR1714 - or single_requested_result == 'QN' - ): + if single_requested_result in {'DL', 'QN'}: # Running Output module's method to get DL time series status time_series_result[single_requested_result][ realization_number @@ -662,7 +674,8 @@ def create_path(path): # noqa: D103 / 3600 ) - # Running Output module's method to get BSC data for each junction (sum of outage) + # Run Output module's method to get BSC data for + # each junction (sum of outage) res[single_requested_result] = p.getOutageTimeGeoPandas_5( scn_name, bsc=single_requested_result, @@ -671,16 +684,19 @@ def create_path(path): # noqa: D103 consistency_time_window=consistency_time_window, sum_time=True, ) + if res_agg.get(single_requested_result) is None: res_agg[single_requested_result] = res[ single_requested_result ].to_dict() - for key in res_agg[single_requested_result].keys(): # noqa: SIM118 + + for key in res_agg[single_requested_result]: res_agg[single_requested_result][key] = [ res_agg[single_requested_result][key] ] + else: - for key in res_agg[single_requested_result].keys(): # noqa: SIM118 + for key in res_agg[single_requested_result]: res_agg[single_requested_result][key].append( res[single_requested_result][key] ) @@ -689,13 +705,10 @@ def create_path(path): # noqa: D103 f'WaterDistributionNetwork_{realization_number}.json' ) cur_json_file_path = ( - Path(run_directory) - / 'Results' - / 'WaterDistributionNetwork' - / cur_json_file_name + run_dir / 'WaterDistributionNetwork' / cur_json_file_name ) - with open(cur_json_file_path) as f: # noqa: PTH123 + with cur_json_file_path.open('rt') as f: json_data = json.load(f) for single_requested_result in requested_result: @@ -707,24 +720,24 @@ def create_path(path): # noqa: D103 'Junction', {} ) - for junction_name in req_result.keys(): # noqa: SIM118 + for junction_name in req_result.index: junction_id = sub_asset_name_to_id['Junction'][junction_name] cur_junction = junction_json_data.get(junction_id, {}) - cur_junction_SP = cur_junction.get('SystemPerformance', {}) # noqa: N816 - cur_junction_SP[result_key] = float(req_result[junction_name]) + cur_junction_sp = cur_junction.get('SystemPerformance', {}) + cur_junction_sp[result_key] = float(req_result[junction_name]) - cur_junction['SystemPerformance'] = cur_junction_SP + cur_junction['SystemPerformance'] = cur_junction_sp junction_json_data[junction_id] = cur_junction json_data['WaterDistributionNetwork']['Junction'] = ( junction_json_data ) - with open(cur_json_file_path, 'w') as f: # noqa: PTH123 + with cur_json_file_path.open('wt') as f: json_data = json.dump(json_data, f, indent=2) - res_agg_mean = dict() # noqa: C408 - res_agg_std = dict() # noqa: C408 + res_agg_mean = {} + res_agg_std = {} for single_requested_result in requested_result: res_agg[single_requested_result] = pd.DataFrame( res_agg[single_requested_result] @@ -739,34 +752,32 @@ def create_path(path): # noqa: D103 # Append junction and reservior general information to WaterDistributionNetwork_det det_json_path = cur_json_file_path = ( - Path(run_directory) - / 'Results' - / 'WaterDistributionNetwork' - / 'WaterDistributionNetwork_det.json' + run_dir / 'WaterDistributionNetwork' / 'WaterDistributionNetwork_det.json' ) - det_json = preprocessorIO.readJSONFile(det_json_path) - inp_json = preprocessorIO.readJSONFile(sc_geojson) + + det_json = preprocessorIO.read_json_file(det_json_path) + inp_json = preprocessorIO.read_json_file(sc_geojson) inp_json = inp_json['features'] for WDNtype in ['Reservoir', 'Junction']: - json_to_attach = dict() # noqa: C408 + json_to_attach = {} for ft in inp_json: prop = ft['properties'] if prop['type'] == WDNtype: - id = str(ft['id']) # noqa: A001 - generalInfo = dict() # noqa: C408, N816 + asset_id = str(ft['id']) + general_info = {} json_geometry = ft['geometry'] shapely_geometry = geometry.shape(json_geometry) wkt_geometry = shapely_geometry.wkt - generalInfo.update({'geometry': wkt_geometry}) - asset_name = sub_asset_id_to_name[WDNtype][id] - generalInfo.update({'REWET_id': asset_name}) - generalInfo.update({'AIM_id': id}) + general_info.update({'geometry': wkt_geometry}) + asset_name = sub_asset_id_to_name[WDNtype][asset_id] + general_info.update({'REWET_id': asset_name}) + general_info.update({'AIM_id': asset_id}) for key, item in prop.items(): if key == 'id': continue - generalInfo.update({key: item}) - R2Dres = dict() # noqa: C408 - asset_name = sub_asset_id_to_name[WDNtype][id] + general_info.update({key: item}) + R2Dres = {} + asset_name = sub_asset_id_to_name[WDNtype][asset_id] for single_requested_result in requested_result: if asset_name not in res_agg_mean[single_requested_result].index: continue @@ -782,23 +793,25 @@ def create_path(path): # noqa: D103 ], } ) - # location = dict() - # location.update({'latitude':ft['geometry']['coordinates'][1],\ - # 'longitude':ft['geometry']['coordinates'][0]}) - # generalInfo.update({'location':location}) + json_to_attach.update( - {id: {'GeneralInformation': generalInfo, 'R2Dres': R2Dres}} + { + asset_id: { + 'general_information': general_info, + 'R2Dres': R2Dres, + } + } ) det_json['WaterDistributionNetwork'].update({WDNtype: json_to_attach}) - with open(det_json_path, 'w') as f: # noqa: PTH123 + with det_json_path.open('wt') as f: json.dump(det_json, f, indent=2) ts_result_json_path = cur_json_file_path = ( - Path(run_directory) - / 'Results' + run_dir / 'WaterDistributionNetwork' / 'WaterDistributionNetwork_timeseries.json' ) + time_series_result_struc = { 'Type': 'TimeSeries', 'Asset': 'WaterDistributionNetwork', @@ -814,6 +827,5 @@ def create_path(path): # noqa: D103 i ] = time_series_result[single_requested_result][i].to_dict() - with open(ts_result_json_path, 'w') as f: # noqa: PTH123 + with ts_result_json_path.open('wt') as f: json.dump(time_series_result_struc, f, indent=2) - print('here') # noqa: T201 diff --git a/modules/systemPerformance/REWET/damage_convertor.py b/modules/systemPerformance/REWET/damage_convertor.py index 9b70d66e6..3518e1721 100644 --- a/modules/systemPerformance/REWET/damage_convertor.py +++ b/modules/systemPerformance/REWET/damage_convertor.py @@ -36,23 +36,24 @@ # Contributors: # Sina Naeimi -import os -from pathlib import Path - import pandas as pd import preprocessorIO CBIG_int = int(1e9) +LEAK_VALUE = 1 +BREAK_VALUE = 2 -def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geojson): # noqa: N802 - """Creates REWET-style piep damage file. +def create_pipe_damage_input_for_rewet( + pipe_damage_data, run_dir, event_time, sc_geojson +): + """Create REWET-style pipe damage file. Parameters ---------- pipe_damage_data : dict Pipe damage data from PELICUN. - REWET_input_data : dict + rewet_input_data : dict REWET input data. Raises @@ -65,12 +66,12 @@ def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geoj pipe_damage_list : Pandas Series REWET-style pipe damage file. - """ # noqa: D401 - pipe_id_list = [key for key in pipe_damage_data] # noqa: C416 + """ + pipe_id_list = list(pipe_damage_data.keys()) damage_list = [] damage_time = event_time - sc_geojson_file = preprocessorIO.readJSONFile(sc_geojson) + sc_geojson_file = preprocessorIO.read_json_file(sc_geojson) pipe_data = [ ss for ss in sc_geojson_file['features'] @@ -84,18 +85,18 @@ def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geoj cur_data = pipe_damage_data[pipe_id] cur_damage = cur_data['Damage'] - cur_demand = cur_data['Demand'] # noqa: F841 - aim_data = findAndReadAIMFile( + aim_data = find_read_aim_file( pipe_id, - os.path.join('Results', 'WaterDistributionNetwork', 'Pipe'), # noqa: PTH118 + 'WaterDistributionNetwork', + 'Pipe', run_dir, ) material = aim_data['GeneralInformation'].get('Material', None) - if material == None: # noqa: E711 - # raise ValueError("Material is none") + # If material is not ptovided, then the material is CI as the default + if material is None: material = 'CI' aggregates_list = [ @@ -105,20 +106,19 @@ def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geoj segment_step = 1 / segment_sizes c = 0 - for cur_agg in aggregates_list: # cur_damage["aggregate"]: + for cur_agg in aggregates_list: damage_val = cur_damage[cur_agg] if damage_val > 0: - if damage_val == 1: + if damage_val == LEAK_VALUE: damage_type = 'leak' - elif damage_val == 2: # noqa: PLR2004 + elif damage_val == BREAK_VALUE: damage_type = 'break' else: - raise ValueError('The damage type must be eother 1 or 2') # noqa: EM101, TRY003 + raise ValueError('The damage type must be either 1 or 2') # noqa: EM101, TRY003 else: continue cur_loc = c * segment_step + segment_step / 2 - # print(cur_loc) c += 1 damage_list.append( { @@ -133,20 +133,17 @@ def createPipeDamageInputForREWET(pipe_damage_data, run_dir, event_time, sc_geoj data=damage_list, index=[damage_time for val in damage_list], dtype='O' ) - # REWET_input_data["Pipe_damage_list"] = pipe_damage_list - # REWET_input_data["AIM"] = aim_data - return pipe_damage_list # noqa: RET504 -def createNodeDamageInputForREWET(node_damage_data, run_dir, event_time): # noqa: N802 - """Creates REWET-style node damage file. +def create_node_damage_input_for_rewet(node_damage_data, run_dir, event_time): + """Create REWET-style node damage file. Parameters ---------- node_damage_data : dict Node damage data from PELICUN. - REWET_input_data : dict + rewet_input_data : dict REWET input data. Returns @@ -154,8 +151,8 @@ def createNodeDamageInputForREWET(node_damage_data, run_dir, event_time): # noq node_damage_list : Pandas Series REWET-style node damage file. - """ # noqa: D401 - node_id_list = [key for key in node_damage_data] # noqa: C416 + """ + node_id_list = node_damage_data.keys() damage_list = [] damage_time = event_time @@ -172,11 +169,11 @@ def createNodeDamageInputForREWET(node_damage_data, run_dir, event_time): # noq cur_data = node_damage_data[node_id] cur_damage = cur_data['Damage'] - cur_demand = cur_data['Demand'] # noqa: F841 - aim_data = findAndReadAIMFile( + aim_data = find_read_aim_file( node_id, - os.path.join('Results', 'WaterDistributionNetwork', 'Node'), # noqa: PTH118 + 'WaterDistributionNetwork', + 'Node', run_dir, ) @@ -198,14 +195,14 @@ def createNodeDamageInputForREWET(node_damage_data, run_dir, event_time): # noq return node_damage_list # noqa: RET504 -def createPumpDamageInputForREWET(pump_damage_data, REWET_input_data): # noqa: N802, N803 - """Creates REWET-style pump damage file. +def create_pump_damage_input_for_rewet(pump_damage_data, rewet_input_data): + """Create REWET-style pump damage file. Parameters ---------- pump_damage_data : dict Pump damage data from PELICUN. - REWET_input_data : dict + rewet_input_data : dict REWET input data. Returns @@ -213,11 +210,11 @@ def createPumpDamageInputForREWET(pump_damage_data, REWET_input_data): # noqa: pump_damage_list : Pandas Series REWET-style pump damage file. - """ # noqa: D401 - pump_id_list = [key for key in pump_damage_data] # noqa: C416 + """ + pump_id_list = list(pump_damage_data.keys()) damage_list = [] - damage_time = REWET_input_data['event_time'] + damage_time = rewet_input_data['event_time'] for pump_id in pump_id_list: cur_data = pump_damage_data[pump_id] @@ -228,11 +225,11 @@ def createPumpDamageInputForREWET(pump_damage_data, REWET_input_data): # noqa: if cur_damage == 0: continue # cur_damage_state = 0 means undamaged pump - # I'm not sure if we need any data about the pump at this point + # (SINA) I'm not sure if we need any data about the pump at this point # aim_data = findAndReadAIMFile(tank_id, os.path.join( # "Results", "WaterDistributionNetwork", "Pump"), - # REWET_input_data["run_dir"]) + # rewet_input_data["run_dir"]) # We are getting this data from PELICUN # restore_time = getPumpRetsoreTime(cur_damage) @@ -250,14 +247,14 @@ def createPumpDamageInputForREWET(pump_damage_data, REWET_input_data): # noqa: return pump_damage_list # noqa: RET504 -def createTankDamageInputForREWET(tank_damage_data, REWET_input_data): # noqa: N802, N803 - """Creates REWET-style Tank damage file. +def create_tank_damage_input_for_rewet(tank_damage_data, rewet_input_data): + """Create REWET-style Tank damage file. Parameters ---------- tank_damage_data : dict Tank damage data from PELICUN. - REWET_input_data : dict + rewet_input_data : dict REWET input data. Returns @@ -265,11 +262,11 @@ def createTankDamageInputForREWET(tank_damage_data, REWET_input_data): # noqa: tank_damage_list : Pandas Series REWET-style tank damage file. - """ # noqa: D401 - tank_id_list = [key for key in tank_damage_data] # noqa: C416 + """ + tank_id_list = tank_damage_data.keys() damage_list = [] - damage_time = REWET_input_data['event_time'] + damage_time = rewet_input_data['event_time'] for tank_id in tank_id_list: cur_data = tank_damage_data[tank_id] @@ -285,7 +282,7 @@ def createTankDamageInputForREWET(tank_damage_data, REWET_input_data): # noqa: # # aim_data = findAndReadAIMFile(tank_id, os.path.join( # "Results", "WaterDistributionNetwork", "Tank"), - # REWET_input_data["run_dir"]) + # rewet_input_data["run_dir"]) # tank_type = aim_data["GeneralInformation"].get("Type", None) # restore_time = getTankRetsoreTime(tank_type, cur_damage) # ============================================================================= @@ -305,8 +302,8 @@ def createTankDamageInputForREWET(tank_damage_data, REWET_input_data): # noqa: return tank_damage_list # noqa: RET504 -def findAndReadAIMFile(asset_id, asset_type, run_dir): # noqa: N802 - """Finds and read the AIM file for an asset. +def find_read_aim_file(asset_id, asset_type, asset_sub_type, run_dir): + """Find and read the AIM file for an asset. Parameters ---------- @@ -322,87 +319,30 @@ def findAndReadAIMFile(asset_id, asset_type, run_dir): # noqa: N802 aim_file_data : dict AIM file data as a dict. - """ # noqa: D401 - file_path = Path( - run_dir, asset_type, str(asset_id), 'templatedir', f'{asset_id}-AIM.json' + """ + file_path = ( + run_dir + / asset_type + / asset_sub_type + / str(asset_id) + / 'templatedir' + / f'{asset_id}-AIM.json' ) - aim_file_data = preprocessorIO.readJSONFile(str(file_path)) + aim_file_data = preprocessorIO.read_json_file(str(file_path)) return aim_file_data # noqa: RET504 -def getPumpRetsoreTime(damage_state): # noqa: N802 - """NOT USED! WE WILL GET IT FROM PELICUN - - Provides the restore time based on HAZUS repair time or any other - approach available in the future. If damage state is slight, the restore - time is 3 days (in seconds). If damage state is 2, the restore time is 7 - days (in seconds). If damage state is 3 or 4, the restore time is - indefinite (a big number). - - Parameters - ---------- - damage_state : Int - Specifies the damage state (1 for slightly damages, 2 for moderate, - 3 etensive, and 4 complete. - - Returns - ------- - Retstor time : int - - - """ # noqa: D400 - if damage_state == 1: - restore_time = int(3 * 24 * 3600) - elif damage_state == 2: # noqa: PLR2004 - restore_time = int(7 * 24 * 3600) - else: - restore_time = CBIG_int - - return restore_time - +def read_damage_file(file_addr, run_dir, event_time, sc_geojson): + """Read PELICUN damage files. -def getTankRetsoreTime(tank_type, damage_state): # noqa: ARG001, N802 - """NOT USED! WE WILL GET IT FROM PELICUN - - Provides the restore time based on HAZUS repair time or any other - approach available in the future. if damage state is slight, the restore - time is 3 days (in seconds). If damage state is 2, the restore time is 7 - days (in seconds). If damage state is 3 or 4, the restore time is - indefinite (a big number). - - Parameters - ---------- - tank_type : STR - Tank type based on the data schema. The parameter is not used for now. - damage_state : Int - Specifies the damage state (1 for slightly damages, 2 for moderate, - 3 etensive, and 4 complete. - - Returns - ------- - Retstor time : int - - - """ # noqa: D400 - if damage_state == 1: - restore_time = int(3 * 24 * 3600) - elif damage_state == 2: # noqa: PLR2004 - restore_time = int(7 * 24 * 3600) - else: - restore_time = CBIG_int - - return restore_time - - -def readDamagefile(file_addr, run_dir, event_time, sc_geojson): # noqa: N802 - """Reads PELICUN damage files and create REWET-Style damage for all - WaterDistributionNetwork elements + Read PELICUN damage files and create REWET-Style damage for all + WaterDistributionNetwork elements. Parameters ---------- file_addr : path PELICUN damage file in JSON format. - REWET_input_data : dict + rewet_input_data : dict REWET input data, which is updated in the function. scn_number : dict JSON FILE. @@ -412,38 +352,36 @@ def readDamagefile(file_addr, run_dir, event_time, sc_geojson): # noqa: N802 damage_data : dict Damage data in PELICUN dict format. - """ # noqa: D205, D400, D401 - # TODO: Make reading once for each scenario # noqa: TD002 - - # wn = wntrfr.network.WaterNetworkModel(REWET_input_data["inp_file"] ) + """ + # TODO(SINA): Make reading once for each scenario - damage_data = preprocessorIO.readJSONFile(file_addr) + damage_data = preprocessorIO.read_json_file(file_addr) wn_damage_data = damage_data['WaterDistributionNetwork'] if 'Pipe' in wn_damage_data: - pipe_damage_data = createPipeDamageInputForREWET( + pipe_damage_data = create_pipe_damage_input_for_rewet( wn_damage_data['Pipe'], run_dir, event_time, sc_geojson ) else: pipe_damage_data = pd.Series(dtype='O') if 'Tank' in wn_damage_data: - tank_damage_data = createTankDamageInputForREWET( + tank_damage_data = create_tank_damage_input_for_rewet( wn_damage_data['Tank'], run_dir, event_time ) else: tank_damage_data = pd.Series(dtype='O') if 'Pump' in wn_damage_data: - pump_damage_data = createPumpDamageInputForREWET( + pump_damage_data = create_pump_damage_input_for_rewet( wn_damage_data['Pump'], run_dir, event_time ) else: pump_damage_data = pd.Series(dtype='O') if 'Junction' in wn_damage_data: - node_damage_data = createNodeDamageInputForREWET( + node_damage_data = create_node_damage_input_for_rewet( wn_damage_data['Junction'], run_dir, event_time ) else: diff --git a/modules/systemPerformance/REWET/preprocessorIO.py b/modules/systemPerformance/REWET/preprocessorIO.py index 10f291d48..8ed6d464d 100644 --- a/modules/systemPerformance/REWET/preprocessorIO.py +++ b/modules/systemPerformance/REWET/preprocessorIO.py @@ -37,13 +37,13 @@ # Sina Naeimi import json -import os +from pathlib import Path import pandas as pd -def readJSONFile(file_addr): # noqa: N802 - """Reads a json file. +def read_json_file(file_addr): + """Read a JSON file. Parameters ---------- @@ -60,33 +60,34 @@ def readJSONFile(file_addr): # noqa: N802 data : dict JSON File data as a dict. - """ # noqa: D401 - if not os.path.exists(file_addr): # noqa: PTH110 - raise ValueError('INPUT WHALE FILE is not found.', repr(file_addr)) # noqa: EM101, TRY003 + """ + file_addr = Path(file_addr).resolve() + if not file_addr.exists(): + raise ValueError('INPUT WHALE FILE is not found.', file_addr) # noqa: EM101, TRY003 - with open(file_addr) as f: # noqa: PTH123 + with file_addr.open('rt') as f: data = json.load(f) return data # noqa: RET504 # ============================================================================= -# def readRWHALEFileForREWET(file_addr, REWET_input_data): +# def readRWHALEFileForREWET(file_addr, rewet_input_data): # """ -# Reads rwhile input file and returns the data as a dict and updates REWET +# Reads rWhale input file and returns the data as a dict and updates REWET # input file. # # Parameters # ---------- # file_addr : Path -# rwhale input file path. -# REWET_input_data : dict +# rWhale input file path. +# rewet_input_data : dict # REWET input data. # # Returns # ------- # rwhale_data : dict -# rwhale inoput data as a dict. +# rWhale input data as a dict. # # """ # @@ -98,15 +99,33 @@ def readJSONFile(file_addr): # noqa: N802 # number_of_realization = rwhale_data["Applications"]\ # ["DL"]["WaterDistributionNetwork"]["ApplicationData"]["Realizations"] # -# REWET_input_data["inp_file" ] = inp_file_addr -# REWET_input_data["run_dir"] = run_directory -# REWET_input_data["number_of_realizations"] = number_of_realization +# rewet_input_data["inp_file" ] = inp_file_addr +# rewet_input_data["run_dir"] = run_directory +# rewet_input_data["number_of_realizations"] = number_of_realization # # return rwhale_data # ============================================================================= -def save_damage_data(damage_save_path, damage_data, scn_number): # noqa: D103 +def save_damage_data(damage_save_path, damage_data, scn_number): + """ + Save REWET-style damage data. + + Parameters + ---------- + damage_save_path : path + path to the damage directory. + damage_data : dict + REWET-style damage data. + scn_number : int + Scenario name. + + Returns + ------- + dict + Names of damaged files saved. + + """ pipe_damage_data = damage_data['Pipe'] node_damage_data = damage_data['Node'] pump_damage_data = damage_data['Pump'] @@ -117,10 +136,10 @@ def save_damage_data(damage_save_path, damage_data, scn_number): # noqa: D103 pump_damage_file_name = f'pump_damage_{scn_number}' tank_damage_file_name = f'tank_damage_{scn_number}' - pipe_damage_file_path = os.path.join(damage_save_path, pipe_damage_file_name) # noqa: PTH118 - node_damage_file_path = os.path.join(damage_save_path, node_damage_file_name) # noqa: PTH118 - pump_damage_file_path = os.path.join(damage_save_path, pump_damage_file_name) # noqa: PTH118 - tank_damage_file_path = os.path.join(damage_save_path, tank_damage_file_name) # noqa: PTH118 + pipe_damage_file_path = damage_save_path / pipe_damage_file_name + node_damage_file_path = damage_save_path / node_damage_file_name + pump_damage_file_path = damage_save_path / pump_damage_file_name + tank_damage_file_path = damage_save_path / tank_damage_file_name pipe_damage_data.to_pickle(pipe_damage_file_path) node_damage_data.to_pickle(node_damage_file_path) @@ -137,7 +156,16 @@ def save_damage_data(damage_save_path, damage_data, scn_number): # noqa: D103 return damage_file_name_list # noqa: RET504 -def create_scneario_table(): # noqa: D103 +def create_scneario_table(): + """ + Create a REWET-style scenario table. + + Returns + ------- + Pandas DataFrame + Scenario table. + + """ scenario_table = pd.DataFrame( dtype='O', columns=[ @@ -149,16 +177,40 @@ def create_scneario_table(): # noqa: D103 'Probability', ], ) + return scenario_table # noqa: RET504 -def update_scenario_table(scenario_table, cur_damage_file_name_list, scn_number): # noqa: D103 +def update_scenario_table(scenario_table, cur_damage_file_name_list, scn_number): + """ + Update the scenario table. + + Parameters + ---------- + scenario_table : Pandas DataFrame + Scenario table. + cur_damage_file_name_list : Dict + Damage file name. + scn_number : int + Scenario number. + + Raises + ------ + ValueError + Unknown type. + + Returns + ------- + scenario_table : List + Scenario table in the records format. + + """ if isinstance(scenario_table, pd.core.frame.DataFrame): scenario_table = scenario_table.to_dict('records') elif isinstance(scenario_table, list): pass else: - raise ValueError('This is an unknown behavior.') # noqa: EM101, TRY003, TRY004 + raise TypeError('unknown scenario table value.') # noqa: EM101, TRY003 new_row = { 'Scenario Name': f'SCN_{scn_number}', @@ -175,53 +227,54 @@ def update_scenario_table(scenario_table, cur_damage_file_name_list, scn_number) def save_scenario_table(scenario_table, scenario_table_file_path): - """Saves the scenario data including scenario table and damage data according - to the table data + """Save the scenario data. + + Save the scenario data including scenario table and damage data according + to the table data. Parameters ---------- - REWET_input_data : Dict + rewet_input_data : Dict REWET input data. Returns ------- None. - """ # noqa: D205, D400, D401, DOC202, RUF100 + """ if isinstance(scenario_table, pd.core.frame.DataFrame): pass elif isinstance(scenario_table, list): scenario_table = pd.DataFrame(scenario_table) else: - raise ValueError('This is an unknown behavior.') # noqa: EM101, TRY003, TRY004 + raise TypeError('Unknown scenario table type.') # noqa: EM101, TRY003 scenario_table = scenario_table.set_index('Scenario Name') - # scenario_list_file_path = os.path.join(damage_save_path, scenario_list_file_name) - scenario_table.to_excel(scenario_table_file_path) -def saveSettingsFile(REWET_input_data, save_directory, prefix): # noqa: N802, N803 - """Saves settings data that REWET NEEDs. +def save_settings_file(rewet_input_data, save_directory, prefix): + """Save settings data that REWET needs. Parameters ---------- - REWET_input_data : Dict + rewet_input_data : Dict REWET input data. Returns ------- - None. + setting_save_path : path + Path to the settings-file location. - """ # noqa: D401 - settings = REWET_input_data['settings'] - if prefix == None: # noqa: E711 + """ + settings = rewet_input_data['settings'] + if prefix is None: settings_file_name = 'settings.json' else: settings_file_name = prefix + '_' + 'settings.json' - damage_save_path = save_directory / settings_file_name - with open(damage_save_path, 'w') as f: # noqa: PTH123 + setting_save_path = save_directory / settings_file_name + with setting_save_path.open('wt') as f: json.dump(settings, f, indent=4) - return damage_save_path + return setting_save_path