ras-commander 0.52.0__py3-none-any.whl → 0.54.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ras_commander/RasPlan.py CHANGED
@@ -249,7 +249,6 @@ class RasPlan:
249
249
  def _update_unsteady_in_file(lines, new_unsteady_flow_number):
250
250
  return [f"Unsteady File=u{new_unsteady_flow_number}\n" if line.startswith("Unsteady File=u") else line for line in lines]
251
251
 
252
-
253
252
  @staticmethod
254
253
  @log_call
255
254
  def set_num_cores(plan_number, num_cores, ras_object=None):
@@ -264,6 +263,16 @@ class RasPlan:
264
263
  Returns:
265
264
  None
266
265
 
266
+ Number of cores is controlled by the following parameters in the plan file corresponding to 1D, 2D, Pipe Systems and Pump Stations:
267
+ UNET D1 Cores=
268
+ UNET D2 Cores=
269
+ PS Cores=
270
+
271
+ Where a value of "0" is used for "All Available" cores, and values of 1 or more are used to specify the number of cores to use.
272
+ For complex 1D/2D models with pipe systems, a more complex approach may be needed to optimize performance. (Suggest writing a custom function based on this code).
273
+ This function simply sets the "num_cores" parameter for ALL instances of the above parameters in the plan file.
274
+
275
+
267
276
  Notes on setting num_cores in HEC-RAS:
268
277
  The recommended setting for num_cores is 2 (most efficient) to 8 (most performant)
269
278
  More details in the HEC-Commander Repository Blog "Benchmarking is All You Need"
@@ -290,9 +299,9 @@ class RasPlan:
290
299
  def update_num_cores(lines):
291
300
  updated_lines = []
292
301
  for line in lines:
293
- if "UNET D1 Cores=" in line:
294
- parts = line.split("=")
295
- updated_lines.append(f"{parts[0]}= {num_cores}\n")
302
+ if any(param in line for param in ["UNET D1 Cores=", "UNET D2 Cores=", "PS Cores="]):
303
+ param_name = line.split("=")[0]
304
+ updated_lines.append(f"{param_name}= {num_cores}\n")
296
305
  else:
297
306
  updated_lines.append(line)
298
307
  return updated_lines
@@ -840,7 +849,7 @@ class RasPlan:
840
849
  - 'Plan File' (str): Name of the plan file
841
850
  - 'Plan Title' (str): Title of the simulation plan
842
851
  - 'Program Version' (str): Version number of HEC-RAS
843
- - 'Run HTAB' (int): Flag to run HTab module (-1 or 1)
852
+ - 'Run HTab' (int): Flag to run HTab module (-1 or 1)
844
853
  - 'Run Post Process' (int): Flag to run post-processing (-1 or 1)
845
854
  - 'Run Sediment' (int): Flag to run sediment transport module (0 or 1)
846
855
  - 'Run UNET' (int): Flag to run unsteady network module (-1 or 1)
@@ -848,13 +857,14 @@ class RasPlan:
848
857
  - 'Short Identifier' (str): Short name or ID for the plan
849
858
  - 'Simulation Date' (str): Start and end dates/times for simulation
850
859
  - 'UNET D1 Cores' (int): Number of cores used in 1D calculations
860
+ - 'UNET D2 Cores' (int): Number of cores used in 2D calculations
861
+ - 'PS Cores' (int): Number of cores used in parallel simulation
851
862
  - 'UNET Use Existing IB Tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
852
863
  - 'UNET 1D Methodology' (str): 1D calculation methodology
853
864
  - 'UNET D2 Solver Type' (str): 2D solver type
854
865
  - 'UNET D2 Name' (str): Name of the 2D area
855
866
  - 'Run RASMapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
856
867
 
857
-
858
868
  Note:
859
869
  Writing Multi line keys like 'Description' are not supported by this function.
860
870
 
@@ -868,9 +878,10 @@ class RasPlan:
868
878
  supported_plan_keys = {
869
879
  'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
870
880
  'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
871
- 'Run HTAB', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
872
- 'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET Use Existing IB Tables',
873
- 'UNET 1D Methodology', 'UNET D2 Solver Type', 'UNET D2 Name', 'Run RASMapper'
881
+ 'Run HTab', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
882
+ 'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET D2 Cores', 'PS Cores',
883
+ 'UNET Use Existing IB Tables', 'UNET 1D Methodology', 'UNET D2 Solver Type',
884
+ 'UNET D2 Name', 'Run RASMapper', 'Run HTab', 'Run UNET'
874
885
  }
875
886
 
876
887
  if key not in supported_plan_keys:
@@ -891,7 +902,23 @@ class RasPlan:
891
902
  logger.error(f"Error reading plan file {plan_file_path}: {e}")
892
903
  raise
893
904
 
894
- if key == 'Description':
905
+ # Handle core settings specially to convert to integers
906
+ core_keys = {'UNET D1 Cores', 'UNET D2 Cores', 'PS Cores'}
907
+ if key in core_keys:
908
+ pattern = f"{key}=(.*)"
909
+ match = re.search(pattern, content)
910
+ if match:
911
+ try:
912
+ return int(match.group(1).strip())
913
+ except ValueError:
914
+ logger = logging.getLogger(__name__)
915
+ logger.error(f"Could not convert {key} value to integer")
916
+ return None
917
+ else:
918
+ logger = logging.getLogger(__name__)
919
+ logger.error(f"Key '{key}' not found in the plan file.")
920
+ return None
921
+ elif key == 'Description':
895
922
  match = re.search(r'Begin DESCRIPTION(.*?)END DESCRIPTION', content, re.DOTALL)
896
923
  return match.group(1).strip() if match else None
897
924
  else:
@@ -1060,81 +1087,63 @@ class RasPlan:
1060
1087
  @log_call
1061
1088
  def update_plan_description(plan_number_or_path: Union[str, Path], description: str, ras_object: Optional['RasPrj'] = None) -> None:
1062
1089
  """
1063
- Update the description in the plan file.
1090
+ Update the description block in a HEC-RAS plan file.
1064
1091
 
1065
1092
  Args:
1066
- plan_number_or_path (Union[str, Path]): The plan number or path to the plan file.
1067
- description (str): The new description to be written to the plan file.
1068
- ras_object (Optional[RasPrj]): The RAS project object. If None, uses the global 'ras' object.
1093
+ plan_number_or_path (Union[str, Path]): The plan number or full path to the plan file
1094
+ description (str): The new description text to set
1095
+ ras_object (Optional[RasPrj]): Specific RAS object to use. If None, uses the global ras instance.
1069
1096
 
1070
1097
  Raises:
1071
- ValueError: If the plan file is not found.
1072
- IOError: If there's an error reading from or writing to the plan file.
1098
+ ValueError: If the plan file is not found
1099
+ IOError: If there's an error reading or writing the plan file
1073
1100
  """
1074
- logger = logging.getLogger(__name__)
1101
+ logger = get_logger(__name__)
1102
+ ras_obj = ras_object or ras
1103
+ ras_obj.check_initialized()
1075
1104
 
1076
1105
  plan_file_path = Path(plan_number_or_path)
1077
1106
  if not plan_file_path.is_file():
1078
- plan_file_path = RasPlan.get_plan_path(plan_number_or_path, ras_object)
1079
- if plan_file_path is None or not Path(plan_file_path).exists():
1107
+ plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_object)
1108
+ if not plan_file_path.exists():
1109
+ logger.error(f"Plan file not found: {plan_file_path}")
1080
1110
  raise ValueError(f"Plan file not found: {plan_file_path}")
1081
1111
 
1082
1112
  try:
1083
1113
  with open(plan_file_path, 'r') as file:
1084
- lines = file.readlines()
1085
- except IOError as e:
1086
- logger.error(f"Error reading plan file {plan_file_path}: {e}")
1087
- raise
1114
+ content = file.read()
1088
1115
 
1089
- start_index = None
1090
- end_index = None
1091
- comp_interval_index = None
1092
- for i, line in enumerate(lines):
1093
- if line.strip() == "BEGIN DESCRIPTION:":
1094
- start_index = i
1095
- elif line.strip() == "END DESCRIPTION:":
1096
- end_index = i
1097
- elif line.strip().startswith("Computation Interval="):
1098
- comp_interval_index = i
1099
-
1100
- if start_index is not None and end_index is not None:
1101
- # Description exists, update it
1102
- new_lines = lines[:start_index + 1]
1103
- if description:
1104
- new_lines.extend(description.split('\n'))
1105
- else:
1106
- new_lines.append('\n')
1107
- new_lines.extend(lines[end_index:])
1108
- else:
1109
- # Description doesn't exist, insert before Computation Interval
1110
- if comp_interval_index is None:
1111
- logger.warning("Neither description tags nor Computation Interval found in plan file. Appending to end of file.")
1112
- comp_interval_index = len(lines)
1113
-
1114
- new_lines = lines[:comp_interval_index]
1115
- new_lines.append("BEGIN DESCRIPTION:\n")
1116
- if description:
1117
- new_lines.extend(f"{line}\n" for line in description.split('\n'))
1116
+ # Find the description block
1117
+ desc_pattern = r'Begin DESCRIPTION.*?END DESCRIPTION'
1118
+ new_desc_block = f'Begin DESCRIPTION\n{description}\nEND DESCRIPTION'
1119
+
1120
+ if re.search(desc_pattern, content, re.DOTALL):
1121
+ # Replace existing description block
1122
+ new_content = re.sub(desc_pattern, new_desc_block, content, flags=re.DOTALL)
1118
1123
  else:
1119
- new_lines.append('\n')
1120
- new_lines.append("END DESCRIPTION:\n")
1121
- new_lines.extend(lines[comp_interval_index:])
1124
+ # Add new description block at the start of the file
1125
+ new_content = new_desc_block + '\n' + content
1122
1126
 
1123
- try:
1127
+ # Write the updated content back to the file
1124
1128
  with open(plan_file_path, 'w') as file:
1125
- file.writelines(new_lines)
1129
+ file.write(new_content)
1130
+
1126
1131
  logger.info(f"Updated description in plan file: {plan_file_path}")
1132
+
1133
+ # Update the dataframes in the RAS object to reflect changes
1134
+ if ras_object:
1135
+ ras_object.plan_df = ras_object.get_plan_entries()
1136
+ ras_object.geom_df = ras_object.get_geom_entries()
1137
+ ras_object.flow_df = ras_object.get_flow_entries()
1138
+ ras_object.unsteady_df = ras_object.get_unsteady_entries()
1139
+
1127
1140
  except IOError as e:
1128
- logger.error(f"Error writing to plan file {plan_file_path}: {e}")
1141
+ logger.error(f"Error updating plan description in {plan_file_path}: {e}")
1142
+ raise
1143
+ except Exception as e:
1144
+ logger.error(f"Unexpected error updating plan description: {e}")
1129
1145
  raise
1130
1146
 
1131
- # Refresh RasPrj dataframes
1132
- if ras_object:
1133
- ras_object.plan_df = ras_object.get_plan_entries()
1134
- ras_object.geom_df = ras_object.get_geom_entries()
1135
- ras_object.flow_df = ras_object.get_flow_entries()
1136
- ras_object.unsteady_df = ras_object.get_unsteady_entries()
1137
-
1138
1147
  @staticmethod
1139
1148
  @log_call
1140
1149
  def read_plan_description(plan_number_or_path: Union[str, Path], ras_object: Optional['RasPrj'] = None) -> str: