ras-commander 0.70.0__py3-none-any.whl → 0.72.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/RasPrj.py CHANGED
@@ -118,49 +118,79 @@ class RasPrj:
118
118
  @log_call
119
119
  def initialize(self, project_folder, ras_exe_path, suppress_logging=True):
120
120
  """
121
- Initialize a RasPrj instance.
121
+ Initialize a RasPrj instance with project folder and RAS executable path.
122
122
 
123
- This method sets up the RasPrj instance with the given project folder and RAS executable path.
124
- It finds the project file, loads project data, sets the initialization flag, and now also
125
- extracts boundary conditions.
123
+ IMPORTANT: External users should use init_ras_project() function instead of this method.
124
+ This method is intended for internal use only.
126
125
 
127
126
  Args:
128
127
  project_folder (str or Path): Path to the HEC-RAS project folder.
129
128
  ras_exe_path (str or Path): Path to the HEC-RAS executable.
130
- suppress_logging (bool): If True, suppresses initialization logging messages.
129
+ suppress_logging (bool, default=True): If True, suppresses initialization logging messages.
131
130
 
132
131
  Raises:
133
132
  ValueError: If no HEC-RAS project file is found in the specified folder.
134
133
 
135
134
  Note:
136
- This method is intended for internal use. External users should use the init_ras_project function instead.
135
+ This method sets up the RasPrj instance by:
136
+ 1. Finding the project file (.prj)
137
+ 2. Loading project data (plans, geometries, flows)
138
+ 3. Extracting boundary conditions
139
+ 4. Setting the initialization flag
140
+ 5. Loading RASMapper data (.rasmap)
137
141
  """
138
142
  self.suppress_logging = suppress_logging # Store suppress_logging state
139
143
  self.project_folder = Path(project_folder)
140
144
  self.prj_file = self.find_ras_prj(self.project_folder)
141
145
  if self.prj_file is None:
142
146
  logger.error(f"No HEC-RAS project file found in {self.project_folder}")
143
- raise ValueError(f"No HEC-RAS project file found in {self.project_folder}")
147
+ raise ValueError(f"No HEC-RAS project file found in {self.project_folder}. Please check the path and try again.")
144
148
  self.project_name = Path(self.prj_file).stem
145
149
  self.ras_exe_path = ras_exe_path
146
- self._load_project_data()
147
- self.boundaries_df = self.get_boundary_conditions() # Extract boundary conditions
150
+
151
+ # Set initialized to True before loading project data
148
152
  self.initialized = True
149
153
 
154
+ # Now load the project data
155
+ self._load_project_data()
156
+ self.boundaries_df = self.get_boundary_conditions()
157
+
158
+ # Load RASMapper data if available
159
+ try:
160
+ # Import here to avoid circular imports
161
+ from .RasMap import RasMap
162
+ self.rasmap_df = RasMap.initialize_rasmap_df(self)
163
+ except ImportError:
164
+ logger.warning("RasMap module not available. RASMapper data will not be loaded.")
165
+ self.rasmap_df = pd.DataFrame(columns=['projection_path', 'profile_lines_path', 'soil_layer_path',
166
+ 'infiltration_hdf_path', 'landcover_hdf_path', 'terrain_hdf_path',
167
+ 'current_settings'])
168
+ except Exception as e:
169
+ logger.error(f"Error initializing RASMapper data: {e}")
170
+ self.rasmap_df = pd.DataFrame(columns=['projection_path', 'profile_lines_path', 'soil_layer_path',
171
+ 'infiltration_hdf_path', 'landcover_hdf_path', 'terrain_hdf_path',
172
+ 'current_settings'])
173
+
150
174
  if not suppress_logging:
151
175
  logger.info(f"Initialization complete for project: {self.project_name}")
152
176
  logger.info(f"Plan entries: {len(self.plan_df)}, Flow entries: {len(self.flow_df)}, "
153
177
  f"Unsteady entries: {len(self.unsteady_df)}, Geometry entries: {len(self.geom_df)}, "
154
178
  f"Boundary conditions: {len(self.boundaries_df)}")
155
179
  logger.info(f"Geometry HDF files found: {self.plan_df['Geom_File'].notna().sum()}")
180
+ logger.info(f"RASMapper data loaded: {not self.rasmap_df.empty}")
156
181
 
157
182
  @log_call
158
183
  def _load_project_data(self):
159
184
  """
160
185
  Load project data from the HEC-RAS project file.
161
186
 
162
- This method initializes DataFrames for plan, flow, unsteady, and geometry entries
163
- and ensures all required columns are present with appropriate paths.
187
+ This internal method:
188
+ 1. Initializes DataFrames for plan, flow, unsteady, and geometry entries
189
+ 2. Ensures all required columns are present with appropriate default values
190
+ 3. Sets file paths for all components (geometries, flows, plans)
191
+
192
+ Raises:
193
+ Exception: If there's an error loading or processing project data.
164
194
  """
165
195
  try:
166
196
  # Load data frames
@@ -632,10 +662,14 @@ class RasPrj:
632
662
  @log_call
633
663
  def check_initialized(self):
634
664
  """
635
- Ensure that the RasPrj instance has been initialized.
665
+ Ensure that the RasPrj instance has been initialized before operations.
636
666
 
637
667
  Raises:
638
- RuntimeError: If the project has not been initialized.
668
+ RuntimeError: If the project has not been initialized with init_ras_project().
669
+
670
+ Note:
671
+ This method is called by other methods to validate the project state before
672
+ performing operations. Users typically don't need to call this directly.
639
673
  """
640
674
  if not self.initialized:
641
675
  raise RuntimeError("Project not initialized. Call init_ras_project() first.")
@@ -646,11 +680,23 @@ class RasPrj:
646
680
  """
647
681
  Find the appropriate HEC-RAS project file (.prj) in the given folder.
648
682
 
649
- Parameters:
650
- folder_path (str or Path): Path to the folder containing HEC-RAS files.
683
+ This method uses several strategies to locate the correct project file:
684
+ 1. If only one .prj file exists, it is selected
685
+ 2. If multiple .prj files exist, it tries to match with .rasmap file names
686
+ 3. As a last resort, it scans files for "Proj Title=" content
687
+
688
+ Args:
689
+ folder_path (str or Path): Path to the folder containing HEC-RAS files.
651
690
 
652
691
  Returns:
653
- Path: The full path of the selected .prj file or None if no suitable file is found.
692
+ Path: The full path of the selected .prj file or None if no suitable file is found.
693
+
694
+ Example:
695
+ >>> project_file = RasPrj.find_ras_prj("/path/to/ras_project")
696
+ >>> if project_file:
697
+ ... print(f"Found project file: {project_file}")
698
+ ... else:
699
+ ... print("No project file found")
654
700
  """
655
701
  folder_path = Path(folder_path)
656
702
  prj_files = list(folder_path.glob("*.prj"))
@@ -677,13 +723,17 @@ class RasPrj:
677
723
  @log_call
678
724
  def get_project_name(self):
679
725
  """
680
- Get the name of the HEC-RAS project.
726
+ Get the name of the HEC-RAS project (without file extension).
681
727
 
682
728
  Returns:
683
729
  str: The name of the project.
684
730
 
685
731
  Raises:
686
732
  RuntimeError: If the project has not been initialized.
733
+
734
+ Example:
735
+ >>> project_name = ras.get_project_name()
736
+ >>> print(f"Working with project: {project_name}")
687
737
  """
688
738
  self.check_initialized()
689
739
  return self.project_name
@@ -693,14 +743,26 @@ class RasPrj:
693
743
  """
694
744
  Get entries of a specific type from the HEC-RAS project.
695
745
 
746
+ This method extracts files of the specified type from the project file,
747
+ parses their content, and returns a structured DataFrame.
748
+
696
749
  Args:
697
- entry_type (str): The type of entry to retrieve (e.g., 'Plan', 'Flow', 'Unsteady', 'Geom').
750
+ entry_type (str): The type of entry to retrieve ('Plan', 'Flow', 'Unsteady', or 'Geom').
698
751
 
699
752
  Returns:
700
- pd.DataFrame: A DataFrame containing the requested entries.
753
+ pd.DataFrame: A DataFrame containing the requested entries with appropriate columns.
701
754
 
702
755
  Raises:
703
756
  RuntimeError: If the project has not been initialized.
757
+
758
+ Example:
759
+ >>> # Get all geometry files in the project
760
+ >>> geom_entries = ras.get_prj_entries('Geom')
761
+ >>> print(f"Project contains {len(geom_entries)} geometry files")
762
+
763
+ Note:
764
+ This is a generic method. For specific file types, use the dedicated methods:
765
+ get_plan_entries(), get_flow_entries(), get_unsteady_entries(), get_geom_entries()
704
766
  """
705
767
  self.check_initialized()
706
768
  return self._get_prj_entries(entry_type)
@@ -709,12 +771,23 @@ class RasPrj:
709
771
  def get_plan_entries(self):
710
772
  """
711
773
  Get all plan entries from the HEC-RAS project.
774
+
775
+ Returns a DataFrame containing all plan files (.p*) in the project
776
+ with their associated properties, paths and settings.
712
777
 
713
778
  Returns:
714
- pd.DataFrame: A DataFrame containing all plan entries.
779
+ pd.DataFrame: A DataFrame with columns including 'plan_number', 'full_path',
780
+ 'unsteady_number', 'geometry_number', etc.
715
781
 
716
782
  Raises:
717
783
  RuntimeError: If the project has not been initialized.
784
+
785
+ Example:
786
+ >>> plan_entries = ras.get_plan_entries()
787
+ >>> print(f"Project contains {len(plan_entries)} plan files")
788
+ >>> # Display the first plan's properties
789
+ >>> if not plan_entries.empty:
790
+ ... print(plan_entries.iloc[0])
718
791
  """
719
792
  self.check_initialized()
720
793
  return self._get_prj_entries('Plan')
@@ -723,12 +796,22 @@ class RasPrj:
723
796
  def get_flow_entries(self):
724
797
  """
725
798
  Get all flow entries from the HEC-RAS project.
799
+
800
+ Returns a DataFrame containing all flow files (.f*) in the project
801
+ with their associated properties and paths.
726
802
 
727
803
  Returns:
728
- pd.DataFrame: A DataFrame containing all flow entries.
804
+ pd.DataFrame: A DataFrame with columns including 'flow_number', 'full_path', etc.
729
805
 
730
806
  Raises:
731
807
  RuntimeError: If the project has not been initialized.
808
+
809
+ Example:
810
+ >>> flow_entries = ras.get_flow_entries()
811
+ >>> print(f"Project contains {len(flow_entries)} flow files")
812
+ >>> # Display the first flow file's properties
813
+ >>> if not flow_entries.empty:
814
+ ... print(flow_entries.iloc[0])
732
815
  """
733
816
  self.check_initialized()
734
817
  return self._get_prj_entries('Flow')
@@ -737,12 +820,22 @@ class RasPrj:
737
820
  def get_unsteady_entries(self):
738
821
  """
739
822
  Get all unsteady flow entries from the HEC-RAS project.
823
+
824
+ Returns a DataFrame containing all unsteady flow files (.u*) in the project
825
+ with their associated properties and paths.
740
826
 
741
827
  Returns:
742
- pd.DataFrame: A DataFrame containing all unsteady flow entries.
828
+ pd.DataFrame: A DataFrame with columns including 'unsteady_number', 'full_path', etc.
743
829
 
744
830
  Raises:
745
831
  RuntimeError: If the project has not been initialized.
832
+
833
+ Example:
834
+ >>> unsteady_entries = ras.get_unsteady_entries()
835
+ >>> print(f"Project contains {len(unsteady_entries)} unsteady flow files")
836
+ >>> # Display the first unsteady file's properties
837
+ >>> if not unsteady_entries.empty:
838
+ ... print(unsteady_entries.iloc[0])
746
839
  """
747
840
  self.check_initialized()
748
841
  return self._get_prj_entries('Unsteady')
@@ -750,11 +843,26 @@ class RasPrj:
750
843
  @log_call
751
844
  def get_geom_entries(self):
752
845
  """
753
- Get geometry entries from the project file.
846
+ Get all geometry entries from the HEC-RAS project.
847
+
848
+ Returns a DataFrame containing all geometry files (.g*) in the project
849
+ with their associated properties, paths and HDF links.
754
850
 
755
851
  Returns:
756
- pd.DataFrame: DataFrame containing geometry entries.
852
+ pd.DataFrame: A DataFrame with columns including 'geom_number', 'full_path',
853
+ 'hdf_path', etc.
854
+
855
+ Raises:
856
+ RuntimeError: If the project has not been initialized.
857
+
858
+ Example:
859
+ >>> geom_entries = ras.get_geom_entries()
860
+ >>> print(f"Project contains {len(geom_entries)} geometry files")
861
+ >>> # Display the first geometry file's properties
862
+ >>> if not geom_entries.empty:
863
+ ... print(geom_entries.iloc[0])
757
864
  """
865
+ self.check_initialized()
758
866
  geom_pattern = re.compile(r'Geom File=(\w+)')
759
867
  geom_entries = []
760
868
 
@@ -780,11 +888,28 @@ class RasPrj:
780
888
  @log_call
781
889
  def get_hdf_entries(self):
782
890
  """
783
- Get HDF entries for plans that have results.
891
+ Get all plan entries that have associated HDF results files.
892
+
893
+ This method identifies which plans have been successfully computed
894
+ and have HDF results available for further analysis.
784
895
 
785
896
  Returns:
786
897
  pd.DataFrame: A DataFrame containing plan entries with HDF results.
787
- Returns an empty DataFrame if no HDF entries are found.
898
+ Returns an empty DataFrame if no results are found.
899
+
900
+ Raises:
901
+ RuntimeError: If the project has not been initialized.
902
+
903
+ Example:
904
+ >>> hdf_entries = ras.get_hdf_entries()
905
+ >>> if hdf_entries.empty:
906
+ ... print("No computed results found. Run simulations first.")
907
+ ... else:
908
+ ... print(f"Found results for {len(hdf_entries)} plans")
909
+
910
+ Note:
911
+ This is useful for identifying which plans have been successfully computed
912
+ and can be used for further results analysis.
788
913
  """
789
914
  self.check_initialized()
790
915
 
@@ -798,7 +923,23 @@ class RasPrj:
798
923
 
799
924
  @log_call
800
925
  def print_data(self):
801
- """Print all RAS Object data for this instance."""
926
+ """
927
+ Print a comprehensive summary of all RAS Object data for this instance.
928
+
929
+ This method outputs:
930
+ - Project information (name, folder, file paths)
931
+ - Summary of plans, flows, geometries, and unsteady files
932
+ - HDF results availability
933
+ - Boundary conditions
934
+
935
+ Useful for debugging, validation, and exploring project structure.
936
+
937
+ Raises:
938
+ RuntimeError: If the project has not been initialized.
939
+
940
+ Example:
941
+ >>> ras.print_data() # Displays complete project overview
942
+ """
802
943
  self.check_initialized()
803
944
  logger.info(f"--- Data for {self.project_name} ---")
804
945
  logger.info(f"Project folder: {self.project_folder}")
@@ -821,33 +962,37 @@ class RasPrj:
821
962
  @log_call
822
963
  def get_boundary_conditions(self) -> pd.DataFrame:
823
964
  """
824
- Extract boundary conditions from unsteady flow files and create a DataFrame.
965
+ Extract boundary conditions from unsteady flow files into a structured DataFrame.
825
966
 
826
- This method parses unsteady flow files to extract boundary condition information.
827
- It creates a DataFrame with structured data for known boundary condition types
828
- and parameters, and associates this information with the corresponding unsteady flow file.
967
+ This method:
968
+ 1. Parses all unsteady flow files to extract boundary condition information
969
+ 2. Creates a structured DataFrame with boundary locations, types and parameters
970
+ 3. Links boundary conditions to their respective unsteady flow files
829
971
 
830
- Note:
831
- Any lines in the boundary condition blocks that are not explicitly parsed and
832
- incorporated into the DataFrame are captured in a multi-line string. This string
833
- is logged at the DEBUG level for each boundary condition. This feature is crucial
834
- for developers incorporating new boundary condition types or parameters, as it
835
- allows them to see what information might be missing from the current parsing logic.
836
- If no unsteady flow files are present, it returns an empty DataFrame.
972
+ Supported boundary condition types include:
973
+ - Flow Hydrograph
974
+ - Stage Hydrograph
975
+ - Normal Depth
976
+ - Lateral Inflow Hydrograph
977
+ - Uniform Lateral Inflow Hydrograph
978
+ - Gate Opening
837
979
 
838
980
  Returns:
839
- pd.DataFrame: A DataFrame containing detailed boundary condition information,
840
- linked to the unsteady flow files.
841
-
842
- Usage:
843
- To see the unparsed lines, set the logging level to DEBUG before calling this method:
844
-
981
+ pd.DataFrame: A DataFrame containing detailed boundary condition information.
982
+ Returns an empty DataFrame if no unsteady flow files are present.
983
+
984
+ Example:
985
+ >>> boundaries = ras.get_boundary_conditions()
986
+ >>> if not boundaries.empty:
987
+ ... print(f"Found {len(boundaries)} boundary conditions")
988
+ ... # Show flow hydrographs only
989
+ ... flow_hydrographs = boundaries[boundaries['bc_type'] == 'Flow Hydrograph']
990
+ ... print(f"Project has {len(flow_hydrographs)} flow hydrographs")
991
+
992
+ Note:
993
+ To see unparsed boundary condition lines for debugging, set logging to DEBUG:
845
994
  import logging
846
- getLogger().setLevel(logging.DEBUG)
847
-
848
- boundaries_df = ras_project.get_boundary_conditions()
849
- linked to the unsteady flow files. Returns an empty DataFrame if
850
- no unsteady flow files are present.
995
+ logging.getLogger().setLevel(logging.DEBUG)
851
996
  """
852
997
  boundary_data = []
853
998
 
@@ -1117,27 +1262,38 @@ ras = RasPrj()
1117
1262
  @log_call
1118
1263
  def init_ras_project(ras_project_folder, ras_version=None, ras_object=None):
1119
1264
  """
1120
- Initialize a RAS project.
1121
-
1122
- USE THIS FUNCTION TO INITIALIZE A RAS PROJECT, NOT THE INITIALIZE METHOD OF THE RasPrj CLASS.
1123
-
1124
- Parameters:
1125
- -----------
1126
- ras_project_folder : str
1127
- The path to the RAS project folder.
1128
- ras_version : str, optional
1129
- The version of RAS to use (e.g., "6.6").
1130
- The version can also be a full path to the Ras.exe file.
1131
- If None, the function will attempt to use the version from the global 'ras' object or a default path.
1132
- ras_object : RasPrj or str, optional
1133
- If None, the global 'ras' object is updated.
1134
- If a RasPrj instance, that instance is updated.
1135
- If any other value is provided, a new RasPrj instance is created and returned.
1265
+ Initialize a RAS project for use with the ras-commander library.
1266
+
1267
+ This is the primary function for setting up a HEC-RAS project. It:
1268
+ 1. Finds the project file (.prj) in the specified folder
1269
+ 2. Identifies the appropriate HEC-RAS executable
1270
+ 3. Loads project data (plans, geometries, flows)
1271
+ 4. Creates dataframes containing project components
1272
+
1273
+ Args:
1274
+ ras_project_folder (str or Path): The path to the RAS project folder.
1275
+ ras_version (str, optional): The version of RAS to use (e.g., "6.6").
1276
+ Can also be a full path to the Ras.exe file.
1277
+ If None, will attempt to use a default path.
1278
+ ras_object (RasPrj, optional): If None, updates the global 'ras' object.
1279
+ If a RasPrj instance, updates that instance.
1280
+ If any other value, creates and returns a new RasPrj instance.
1136
1281
 
1137
1282
  Returns:
1138
- --------
1139
- RasPrj
1140
- An initialized RasPrj instance.
1283
+ RasPrj: An initialized RasPrj instance.
1284
+
1285
+ Raises:
1286
+ FileNotFoundError: If the specified project folder doesn't exist.
1287
+ ValueError: If no HEC-RAS project file is found in the folder.
1288
+
1289
+ Example:
1290
+ >>> # Initialize using the global 'ras' object (most common)
1291
+ >>> init_ras_project("/path/to/project", "6.6")
1292
+ >>> print(f"Initialized project: {ras.project_name}")
1293
+ >>>
1294
+ >>> # Create a new RasPrj instance
1295
+ >>> my_project = init_ras_project("/path/to/project", "6.6", "new")
1296
+ >>> print(f"Created project instance: {my_project.project_name}")
1141
1297
  """
1142
1298
  if not Path(ras_project_folder).exists():
1143
1299
  logger.error(f"The specified RAS project folder does not exist: {ras_project_folder}")
@@ -1171,23 +1327,31 @@ def get_ras_exe(ras_version=None):
1171
1327
  """
1172
1328
  Determine the HEC-RAS executable path based on the input.
1173
1329
 
1330
+ This function attempts to find the HEC-RAS executable in the following order:
1331
+ 1. If ras_version is a valid file path to an .exe file, use that path
1332
+ 2. If ras_version is a known version number, use default installation path
1333
+ 3. If global 'ras' object has ras_exe_path, use that
1334
+ 4. As a fallback, return a default path (which may not exist)
1335
+
1174
1336
  Args:
1175
1337
  ras_version (str, optional): Either a version number or a full path to the HEC-RAS executable.
1176
- If None, the function will first check the global 'ras' object for a path.
1177
- or a default path.
1178
1338
 
1179
1339
  Returns:
1180
1340
  str: The full path to the HEC-RAS executable.
1181
1341
 
1182
- Raises:
1183
- ValueError: If the input is neither a valid version number nor a valid file path.
1184
-
1185
- Notes:
1186
- - If ras_version is not provided, the function will first check the global 'ras' object for a path.
1187
- - If the global 'ras' object is not initialized or doesn't have a path, a default path will be used.
1188
- - The default path allows the library to function in environments without HEC-RAS installed.
1189
- - This enables the HEC-Commander GPT to operate without stopping, even if HEC-RAS is not present.
1190
- - End users MUST use logging to check for operational errors, as full code stops prevent the GPT from running.
1342
+ Note:
1343
+ - HEC-RAS version numbers include: "6.6", "6.5", "6.4.1", "6.3", etc.
1344
+ - The default installation path follows: C:/Program Files (x86)/HEC/HEC-RAS/{version}/Ras.exe
1345
+ - Returns a default path ("Ras.exe") if no valid path is found
1346
+ - This allows the library to function even without HEC-RAS installed
1347
+
1348
+ Example:
1349
+ >>> # Get path for specific version
1350
+ >>> ras_path = get_ras_exe("6.6")
1351
+ >>> print(f"HEC-RAS 6.6 executable: {ras_path}")
1352
+ >>>
1353
+ >>> # Provide direct path to executable
1354
+ >>> custom_path = get_ras_exe("C:/My_Programs/HEC-RAS/Ras.exe")
1191
1355
  """
1192
1356
  if ras_version is None:
1193
1357
  if hasattr(ras, 'ras_exe_path') and ras.ras_exe_path: