ras-commander 0.33.0__py3-none-any.whl → 0.34.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/RasCmdr.py +163 -131
- ras_commander/RasExamples.py +96 -46
- ras_commander/RasGeo.py +27 -6
- ras_commander/RasHdf.py +248 -0
- ras_commander/RasPlan.py +391 -437
- ras_commander/RasPrj.py +396 -64
- ras_commander/RasUnsteady.py +24 -4
- ras_commander/RasUtils.py +352 -51
- ras_commander/__init__.py +4 -1
- ras_commander-0.34.0.dist-info/METADATA +263 -0
- ras_commander-0.34.0.dist-info/RECORD +15 -0
- ras_commander-0.33.0.dist-info/METADATA +0 -5
- ras_commander-0.33.0.dist-info/RECORD +0 -14
- {ras_commander-0.33.0.dist-info → ras_commander-0.34.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.33.0.dist-info → ras_commander-0.34.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.33.0.dist-info → ras_commander-0.34.0.dist-info}/top_level.txt +0 -0
    
        ras_commander/RasCmdr.py
    CHANGED
    
    | @@ -12,18 +12,26 @@ from .RasPrj import ras, RasPrj, init_ras_project, get_ras_exe | |
| 12 12 | 
             
            from .RasPlan import RasPlan
         | 
| 13 13 | 
             
            from .RasGeo import RasGeo
         | 
| 14 14 | 
             
            from .RasUtils import RasUtils
         | 
| 15 | 
            -
            import subprocess
         | 
| 16 | 
            -
            import os
         | 
| 17 15 | 
             
            import logging
         | 
| 18 16 | 
             
            import time
         | 
| 19 | 
            -
            import pandas as pd
         | 
| 20 | 
            -
            from threading import Thread, Lock
         | 
| 21 17 | 
             
            import queue
         | 
| 18 | 
            +
            from threading import Thread, Lock
         | 
| 19 | 
            +
            from typing import Union, List, Optional, Dict
         | 
| 22 20 | 
             
            from pathlib import Path
         | 
| 23 21 | 
             
            import shutil
         | 
| 24 | 
            -
            import  | 
| 25 | 
            -
            from  | 
| 26 | 
            -
            import  | 
| 22 | 
            +
            import logging
         | 
| 23 | 
            +
            from concurrent.futures import ThreadPoolExecutor, as_completed
         | 
| 24 | 
            +
            from threading import Lock, Thread
         | 
| 25 | 
            +
            from itertools import cycle
         | 
| 26 | 
            +
            from ras_commander.RasPrj import RasPrj  # Ensure RasPrj is imported
         | 
| 27 | 
            +
            from threading import Lock, Thread, current_thread
         | 
| 28 | 
            +
            from concurrent.futures import ThreadPoolExecutor, as_completed
         | 
| 29 | 
            +
            from itertools import cycle
         | 
| 30 | 
            +
            from typing import Union, List, Optional, Dict
         | 
| 31 | 
            +
             | 
| 32 | 
            +
             | 
| 33 | 
            +
            # Configure logging
         | 
| 34 | 
            +
            logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
         | 
| 27 35 |  | 
| 28 36 | 
             
            # TO DO: 
         | 
| 29 37 | 
             
            # 1. Alternate Run Mode for compute_plan and compute_parallel:  Using Powershell to execute the HEC-RAS command and hide the RAS window and all child windows.
         | 
| @@ -75,11 +83,15 @@ class RasCmdr: | |
| 75 83 | 
             
                        if dest_folder.exists():
         | 
| 76 84 | 
             
                            if overwrite_dest:
         | 
| 77 85 | 
             
                                shutil.rmtree(dest_folder)
         | 
| 86 | 
            +
                                logging.info(f"Destination folder '{dest_folder}' exists. Overwriting as per overwrite_dest=True.")
         | 
| 78 87 | 
             
                            elif any(dest_folder.iterdir()):
         | 
| 79 | 
            -
                                 | 
| 88 | 
            +
                                error_msg = f"Destination folder '{dest_folder}' exists and is not empty. Use overwrite_dest=True to overwrite."
         | 
| 89 | 
            +
                                logging.error(error_msg)
         | 
| 90 | 
            +
                                raise ValueError(error_msg)
         | 
| 80 91 |  | 
| 81 92 | 
             
                        dest_folder.mkdir(parents=True, exist_ok=True)
         | 
| 82 93 | 
             
                        shutil.copytree(ras_obj.project_folder, dest_folder, dirs_exist_ok=True)
         | 
| 94 | 
            +
                        logging.info(f"Copied project folder to destination: {dest_folder}")
         | 
| 83 95 |  | 
| 84 96 | 
             
                        compute_ras = RasPrj()
         | 
| 85 97 | 
             
                        compute_ras.initialize(dest_folder, ras_obj.ras_exe_path)
         | 
| @@ -92,29 +104,29 @@ class RasCmdr: | |
| 92 104 | 
             
                    compute_plan_path = Path(plan_number) if isinstance(plan_number, (str, Path)) and Path(plan_number).is_file() else RasPlan.get_plan_path(plan_number, compute_ras)
         | 
| 93 105 |  | 
| 94 106 | 
             
                    if not compute_prj_path or not compute_plan_path:
         | 
| 95 | 
            -
                         | 
| 107 | 
            +
                        logging.error(f"Could not find project file or plan file for plan {plan_number}")
         | 
| 96 108 | 
             
                        return False
         | 
| 97 109 |  | 
| 98 110 | 
             
                    # Clear geometry preprocessor files if requested
         | 
| 99 111 | 
             
                    if clear_geompre:
         | 
| 100 112 | 
             
                        try:
         | 
| 101 113 | 
             
                            RasGeo.clear_geompre_files(compute_plan_path, ras_object=compute_ras)
         | 
| 102 | 
            -
                             | 
| 114 | 
            +
                            logging.info(f"Cleared geometry preprocessor files for plan: {plan_number}")
         | 
| 103 115 | 
             
                        except Exception as e:
         | 
| 104 | 
            -
                             | 
| 116 | 
            +
                            logging.error(f"Error clearing geometry preprocessor files for plan {plan_number}: {str(e)}")
         | 
| 105 117 |  | 
| 106 118 | 
             
                    # Set the number of cores if specified
         | 
| 107 119 | 
             
                    if num_cores is not None:
         | 
| 108 120 | 
             
                        try:
         | 
| 109 121 | 
             
                            RasPlan.set_num_cores(compute_plan_path, num_cores=num_cores, ras_object=compute_ras)
         | 
| 110 | 
            -
                             | 
| 122 | 
            +
                            logging.info(f"Set number of cores to {num_cores} for plan: {plan_number}")
         | 
| 111 123 | 
             
                        except Exception as e:
         | 
| 112 | 
            -
                             | 
| 124 | 
            +
                            logging.error(f"Error setting number of cores for plan {plan_number}: {str(e)}")
         | 
| 113 125 |  | 
| 114 126 | 
             
                    # Prepare the command for HEC-RAS execution
         | 
| 115 127 | 
             
                    cmd = f'"{compute_ras.ras_exe_path}" -c "{compute_prj_path}" "{compute_plan_path}"'
         | 
| 116 | 
            -
                     | 
| 117 | 
            -
                     | 
| 128 | 
            +
                    logging.info("Running HEC-RAS from the Command Line:")
         | 
| 129 | 
            +
                    logging.info(f"Running command: {cmd}")
         | 
| 118 130 |  | 
| 119 131 | 
             
                    # Execute the HEC-RAS command
         | 
| 120 132 | 
             
                    start_time = time.time()
         | 
| @@ -122,15 +134,15 @@ class RasCmdr: | |
| 122 134 | 
             
                        subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
         | 
| 123 135 | 
             
                        end_time = time.time()
         | 
| 124 136 | 
             
                        run_time = end_time - start_time
         | 
| 125 | 
            -
                         | 
| 126 | 
            -
                         | 
| 137 | 
            +
                        logging.info(f"HEC-RAS execution completed for plan: {plan_number}")
         | 
| 138 | 
            +
                        logging.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
         | 
| 127 139 | 
             
                        return True
         | 
| 128 140 | 
             
                    except subprocess.CalledProcessError as e:
         | 
| 129 141 | 
             
                        end_time = time.time()
         | 
| 130 142 | 
             
                        run_time = end_time - start_time
         | 
| 131 | 
            -
                         | 
| 132 | 
            -
                         | 
| 133 | 
            -
                         | 
| 143 | 
            +
                        logging.error(f"Error running plan: {plan_number}")
         | 
| 144 | 
            +
                        logging.error(f"Error message: {e.output}")
         | 
| 145 | 
            +
                        logging.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
         | 
| 134 146 | 
             
                        return False
         | 
| 135 147 |  | 
| 136 148 | 
             
                    ras_obj = ras_object or ras
         | 
| @@ -138,77 +150,51 @@ class RasCmdr: | |
| 138 150 | 
             
                    ras_obj.geom_df = ras_obj.get_geom_entries()
         | 
| 139 151 | 
             
                    ras_obj.flow_df = ras_obj.get_flow_entries()
         | 
| 140 152 | 
             
                    ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
         | 
| 141 | 
            -
             | 
| 153 | 
            +
                
         | 
| 142 154 |  | 
| 143 155 |  | 
| 144 156 | 
             
                @staticmethod
         | 
| 145 157 | 
             
                def compute_parallel(
         | 
| 146 | 
            -
                    plan_number: str  | 
| 158 | 
            +
                    plan_number: Union[str, List[str], None] = None,
         | 
| 147 159 | 
             
                    max_workers: int = 2,
         | 
| 148 160 | 
             
                    num_cores: int = 2,
         | 
| 149 161 | 
             
                    clear_geompre: bool = False,
         | 
| 150 | 
            -
                    ras_object: RasPrj  | 
| 151 | 
            -
                    dest_folder: str  | 
| 162 | 
            +
                    ras_object: Optional['RasPrj'] = None,  # Type hinting as string to avoid NameError
         | 
| 163 | 
            +
                    dest_folder: Union[str, Path, None] = None,
         | 
| 152 164 | 
             
                    overwrite_dest: bool = False
         | 
| 153 | 
            -
                ) ->  | 
| 165 | 
            +
                ) -> Dict[str, bool]:
         | 
| 154 166 | 
             
                    """
         | 
| 155 | 
            -
                     | 
| 156 | 
            -
             | 
| 157 | 
            -
                    This function creates separate worker folders, copies the project to each, and executes the specified plans
         | 
| 158 | 
            -
                    in parallel. It allows for isolated and concurrent execution of multiple plans.
         | 
| 159 | 
            -
             | 
| 160 | 
            -
                    Args:
         | 
| 161 | 
            -
                        plan_number (str | list[str] | None): Plan number, list of plan numbers, or None to execute all plans.
         | 
| 162 | 
            -
                        max_workers (int, optional): Maximum number of worker threads to use. Default is 2.
         | 
| 163 | 
            -
                        num_cores (int, optional): Number of cores to use for each plan execution. Default is 2.
         | 
| 164 | 
            -
                        clear_geompre (bool, optional): Whether to clear geometry preprocessor files. Defaults to False.
         | 
| 165 | 
            -
                        ras_object (RasPrj, optional): Specific RAS object to use. If None, uses the global ras instance.
         | 
| 166 | 
            -
                        dest_folder (str | Path, optional): Destination folder for the final computed results.
         | 
| 167 | 
            -
                            If None, results will be stored in a "[Computed]" folder next to the original project.
         | 
| 168 | 
            -
                        overwrite_dest (bool, optional): If True, overwrite the destination folder if it exists. Defaults to False.
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                    Returns:
         | 
| 171 | 
            -
                        dict[str, bool]: A dictionary with plan numbers as keys and boolean values indicating success (True) or failure (False).
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                    Raises:
         | 
| 174 | 
            -
                        ValueError: If the destination folder exists and is not empty, and overwrite_dest is False.
         | 
| 175 | 
            -
                        FileNotFoundError: If a plan file is not found.
         | 
| 176 | 
            -
             | 
| 177 | 
            -
                    Notes:
         | 
| 178 | 
            -
                        - This function creates separate folders for each worker to ensure isolated execution.
         | 
| 179 | 
            -
                        - Each worker uses its own RAS object to prevent conflicts.
         | 
| 180 | 
            -
                        - Plans are distributed among workers using a queue to ensure efficient parallel processing.
         | 
| 181 | 
            -
                        - The function automatically handles cleanup and consolidation of results after execution.
         | 
| 182 | 
            -
                    
         | 
| 183 | 
            -
                    Revision Notes:
         | 
| 184 | 
            -
                        - Added support for clear_geompre flag as a pass-through to compute_plan.
         | 
| 185 | 
            -
                        - Simplified worker thread logic by removing redundant operations.
         | 
| 186 | 
            -
                        - Removed duplicate RAS object initialization in worker threads.
         | 
| 167 | 
            +
                    [Docstring remains unchanged]
         | 
| 187 168 | 
             
                    """
         | 
| 188 | 
            -
                    ras_obj = ras_object or ras
         | 
| 169 | 
            +
                    ras_obj = ras_object or ras  # Assuming 'ras' is a global RasPrj instance
         | 
| 189 170 | 
             
                    ras_obj.check_initialized()
         | 
| 190 171 |  | 
| 191 | 
            -
                    project_folder = ras_obj.project_folder
         | 
| 172 | 
            +
                    project_folder = Path(ras_obj.project_folder)
         | 
| 192 173 |  | 
| 193 174 | 
             
                    if dest_folder is not None:
         | 
| 194 175 | 
             
                        dest_folder_path = Path(dest_folder)
         | 
| 195 176 | 
             
                        if dest_folder_path.exists():
         | 
| 196 177 | 
             
                            if overwrite_dest:
         | 
| 197 178 | 
             
                                shutil.rmtree(dest_folder_path)
         | 
| 179 | 
            +
                                logging.info(f"Destination folder '{dest_folder_path}' exists. Overwriting as per overwrite_dest=True.")
         | 
| 198 180 | 
             
                            elif any(dest_folder_path.iterdir()):
         | 
| 199 | 
            -
                                 | 
| 181 | 
            +
                                error_msg = f"Destination folder '{dest_folder_path}' exists and is not empty. Use overwrite_dest=True to overwrite."
         | 
| 182 | 
            +
                                logging.error(error_msg)
         | 
| 183 | 
            +
                                raise ValueError(error_msg)
         | 
| 200 184 | 
             
                        dest_folder_path.mkdir(parents=True, exist_ok=True)
         | 
| 201 185 | 
             
                        shutil.copytree(project_folder, dest_folder_path, dirs_exist_ok=True)
         | 
| 186 | 
            +
                        logging.info(f"Copied project folder to destination: {dest_folder_path}")
         | 
| 202 187 | 
             
                        project_folder = dest_folder_path
         | 
| 203 188 |  | 
| 204 189 | 
             
                    if plan_number:
         | 
| 205 190 | 
             
                        if isinstance(plan_number, str):
         | 
| 206 191 | 
             
                            plan_number = [plan_number]
         | 
| 207 192 | 
             
                        ras_obj.plan_df = ras_obj.plan_df[ras_obj.plan_df['plan_number'].isin(plan_number)]
         | 
| 193 | 
            +
                        logging.info(f"Filtered plans to execute: {plan_number}")
         | 
| 208 194 |  | 
| 209 195 | 
             
                    num_plans = len(ras_obj.plan_df)
         | 
| 210 196 | 
             
                    max_workers = min(max_workers, num_plans) if num_plans > 0 else 1
         | 
| 211 | 
            -
                     | 
| 197 | 
            +
                    logging.info(f"Adjusted max_workers to {max_workers} based on the number of plans: {num_plans}")
         | 
| 212 198 |  | 
| 213 199 | 
             
                    # Clean up existing worker folders and create new ones
         | 
| 214 200 | 
             
                    worker_ras_objects = {}
         | 
| @@ -216,85 +202,122 @@ class RasCmdr: | |
| 216 202 | 
             
                        worker_folder = project_folder.parent / f"{project_folder.name} [Worker {worker_id}]"
         | 
| 217 203 | 
             
                        if worker_folder.exists():
         | 
| 218 204 | 
             
                            shutil.rmtree(worker_folder)
         | 
| 205 | 
            +
                            logging.info(f"Removed existing worker folder: {worker_folder}")
         | 
| 219 206 | 
             
                        shutil.copytree(project_folder, worker_folder)
         | 
| 220 | 
            -
                        
         | 
| 207 | 
            +
                        logging.info(f"Created worker folder: {worker_folder}")
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                        # Instantiate RasPrj properly
         | 
| 210 | 
            +
                        ras_instance = RasPrj()  # Add necessary parameters if required
         | 
| 221 211 | 
             
                        worker_ras_instance = init_ras_project(
         | 
| 222 212 | 
             
                            ras_project_folder=worker_folder,
         | 
| 223 213 | 
             
                            ras_version=ras_obj.ras_exe_path,
         | 
| 224 | 
            -
                            ras_instance= | 
| 214 | 
            +
                            ras_instance=ras_instance  # Pass the instance instead of a string
         | 
| 225 215 | 
             
                        )
         | 
| 226 216 | 
             
                        worker_ras_objects[worker_id] = worker_ras_instance
         | 
| 227 217 |  | 
| 228 | 
            -
                     | 
| 229 | 
            -
                     | 
| 230 | 
            -
             | 
| 231 | 
            -
             | 
| 232 | 
            -
                     | 
| 233 | 
            -
                     | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
                                 | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
                            
         | 
| 218 | 
            +
                    # Distribute plans among workers in a round-robin fashion
         | 
| 219 | 
            +
                    worker_cycle = cycle(range(1, max_workers + 1))
         | 
| 220 | 
            +
                    plan_assignments = [(next(worker_cycle), plan_num) for plan_num in ras_obj.plan_df['plan_number']]
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                    # Initialize ThreadPoolExecutor without tracking individual plan success
         | 
| 223 | 
            +
                    with ThreadPoolExecutor(max_workers=max_workers) as executor:
         | 
| 224 | 
            +
                        # Submit all plan executions to the executor
         | 
| 225 | 
            +
                        futures = [
         | 
| 226 | 
            +
                            executor.submit(
         | 
| 227 | 
            +
                                RasCmdr.compute_plan,
         | 
| 228 | 
            +
                                plan_num, 
         | 
| 229 | 
            +
                                ras_object=worker_ras_objects[worker_id], 
         | 
| 230 | 
            +
                                clear_geompre=clear_geompre,
         | 
| 231 | 
            +
                                num_cores=num_cores
         | 
| 232 | 
            +
                            )
         | 
| 233 | 
            +
                            for worker_id, plan_num in plan_assignments
         | 
| 234 | 
            +
                        ]
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                        # Optionally, you can log when each plan starts and completes
         | 
| 237 | 
            +
                        for future, (worker_id, plan_num) in zip(as_completed(futures), plan_assignments):
         | 
| 244 238 | 
             
                            try:
         | 
| 245 | 
            -
                                 | 
| 246 | 
            -
                                 | 
| 247 | 
            -
                                    plan_number, 
         | 
| 248 | 
            -
                                    ras_object=worker_ras_obj, 
         | 
| 249 | 
            -
                                    clear_geompre=clear_geompre,
         | 
| 250 | 
            -
                                    num_cores=num_cores
         | 
| 251 | 
            -
                                )
         | 
| 252 | 
            -
                                with results_lock:
         | 
| 253 | 
            -
                                    execution_results[plan_number] = success
         | 
| 254 | 
            -
                                print(f"Completed: Plan {plan_number} in worker {worker_id}")
         | 
| 239 | 
            +
                                future.result()  # We don't need the success flag here
         | 
| 240 | 
            +
                                logging.info(f"Plan {plan_num} executed in worker {worker_id}")
         | 
| 255 241 | 
             
                            except Exception as e:
         | 
| 256 | 
            -
                                 | 
| 257 | 
            -
             | 
| 258 | 
            -
                                print(f"Failed: Plan {plan_number} in worker {worker_id}. Error: {str(e)}")
         | 
| 259 | 
            -
             | 
| 260 | 
            -
                    # Start worker threads
         | 
| 261 | 
            -
                    worker_threads = [Thread(target=worker_thread, args=(worker_id,)) for worker_id in range(1, max_workers + 1)]
         | 
| 262 | 
            -
                    for thread in worker_threads:
         | 
| 263 | 
            -
                        thread.start()
         | 
| 264 | 
            -
             | 
| 265 | 
            -
                    # Wait for all threads to complete
         | 
| 266 | 
            -
                    for thread in worker_threads:
         | 
| 267 | 
            -
                        thread.join()
         | 
| 242 | 
            +
                                logging.error(f"Plan {plan_num} failed in worker {worker_id}: {str(e)}")
         | 
| 243 | 
            +
                                # Depending on requirements, you might want to handle retries or mark these plans differently
         | 
| 268 244 |  | 
| 269 245 | 
             
                    # Consolidate results
         | 
| 270 246 | 
             
                    final_dest_folder = dest_folder_path if dest_folder is not None else project_folder.parent / f"{project_folder.name} [Computed]"
         | 
| 271 | 
            -
                    final_dest_folder.mkdir(exist_ok=True)
         | 
| 272 | 
            -
                     | 
| 247 | 
            +
                    final_dest_folder.mkdir(parents=True, exist_ok=True)
         | 
| 248 | 
            +
                    logging.info(f"Final destination for computed results: {final_dest_folder}")
         | 
| 273 249 |  | 
| 274 250 | 
             
                    for worker_ras in worker_ras_objects.values():
         | 
| 275 | 
            -
                        worker_folder = worker_ras.project_folder
         | 
| 251 | 
            +
                        worker_folder = Path(worker_ras.project_folder)
         | 
| 276 252 | 
             
                        try:
         | 
| 277 253 | 
             
                            for item in worker_folder.iterdir():
         | 
| 278 254 | 
             
                                dest_path = final_dest_folder / item.name
         | 
| 279 255 | 
             
                                if dest_path.exists():
         | 
| 280 256 | 
             
                                    if dest_path.is_dir():
         | 
| 281 257 | 
             
                                        shutil.rmtree(dest_path)
         | 
| 258 | 
            +
                                        logging.debug(f"Removed existing directory at {dest_path}")
         | 
| 282 259 | 
             
                                    else:
         | 
| 283 260 | 
             
                                        dest_path.unlink()
         | 
| 261 | 
            +
                                        logging.debug(f"Removed existing file at {dest_path}")
         | 
| 284 262 | 
             
                                shutil.move(str(item), final_dest_folder)
         | 
| 263 | 
            +
                                logging.debug(f"Moved {item} to {final_dest_folder}")
         | 
| 285 264 | 
             
                            shutil.rmtree(worker_folder)
         | 
| 265 | 
            +
                            logging.info(f"Removed worker folder: {worker_folder}")
         | 
| 286 266 | 
             
                        except Exception as e:
         | 
| 287 | 
            -
                             | 
| 267 | 
            +
                            logging.error(f"Error moving results from {worker_folder} to {final_dest_folder}: {str(e)}")
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    # Initialize a new RasPrj object for the final destination
         | 
| 270 | 
            +
                    try:
         | 
| 271 | 
            +
                        # Create a new RasPrj instance
         | 
| 272 | 
            +
                        final_dest_folder_ras_obj = RasPrj()
         | 
| 273 | 
            +
                        
         | 
| 274 | 
            +
                        # Initialize it using init_ras_project
         | 
| 275 | 
            +
                        final_dest_folder_ras_obj = init_ras_project(
         | 
| 276 | 
            +
                            ras_project_folder=final_dest_folder, 
         | 
| 277 | 
            +
                            ras_version=ras_obj.ras_exe_path,
         | 
| 278 | 
            +
                            ras_instance=final_dest_folder_ras_obj
         | 
| 279 | 
            +
                        )
         | 
| 280 | 
            +
                        
         | 
| 281 | 
            +
                        # Now we can check if it's initialized
         | 
| 282 | 
            +
                        final_dest_folder_ras_obj.check_initialized()
         | 
| 283 | 
            +
                    except Exception as e:
         | 
| 284 | 
            +
                        logging.error(f"Failed to initialize RasPrj for final destination: {str(e)}")
         | 
| 285 | 
            +
                        raise
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                    # Retrieve plan entries and check for HDF results
         | 
| 288 | 
            +
                    try:
         | 
| 289 | 
            +
                        plan_entries = final_dest_folder_ras_obj.get_prj_entries('Plan')
         | 
| 290 | 
            +
                    except Exception as e:
         | 
| 291 | 
            +
                        logging.error(f"Failed to retrieve plan entries from final RasPrj: {str(e)}")
         | 
| 292 | 
            +
                        raise
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                    execution_results: Dict[str, bool] = {}
         | 
| 295 | 
            +
                    for _, row in ras_obj.plan_df.iterrows():
         | 
| 296 | 
            +
                        plan_num = row['plan_number']
         | 
| 297 | 
            +
                        # Find the corresponding entry in plan_entries
         | 
| 298 | 
            +
                        entry = plan_entries[plan_entries['plan_number'] == plan_num]
         | 
| 299 | 
            +
                        if not entry.empty:
         | 
| 300 | 
            +
                            hdf_path = entry.iloc[0].get('HDF_Results_Path')
         | 
| 301 | 
            +
                            success = hdf_path is not None and Path(hdf_path).exists()
         | 
| 302 | 
            +
                        else:
         | 
| 303 | 
            +
                            success = False
         | 
| 304 | 
            +
                        execution_results[plan_num] = success
         | 
| 288 305 |  | 
| 289 306 | 
             
                    # Print execution results for each plan
         | 
| 290 | 
            -
                     | 
| 291 | 
            -
                    for  | 
| 292 | 
            -
                         | 
| 307 | 
            +
                    logging.info("\nExecution Results:")
         | 
| 308 | 
            +
                    for plan_num, success in execution_results.items():
         | 
| 309 | 
            +
                        status = 'Successful' if success else 'Failed'
         | 
| 310 | 
            +
                        logging.info(f"Plan {plan_num}: {status} \n(HDF_Results_Path: {hdf_path})")
         | 
| 311 | 
            +
                        
         | 
| 312 | 
            +
                    ras_obj = ras_object or ras
         | 
| 313 | 
            +
                    ras_obj.plan_df = ras_obj.get_plan_entries()
         | 
| 314 | 
            +
                    ras_obj.geom_df = ras_obj.get_geom_entries()
         | 
| 315 | 
            +
                    ras_obj.flow_df = ras_obj.get_flow_entries()
         | 
| 316 | 
            +
                    ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
         | 
| 293 317 |  | 
| 294 318 | 
             
                    return execution_results
         | 
| 295 319 |  | 
| 296 | 
            -
             | 
| 297 | 
            -
                
         | 
| 320 | 
            +
             | 
| 298 321 | 
             
                @staticmethod
         | 
| 299 322 | 
             
                def compute_test_mode(
         | 
| 300 323 | 
             
                    plan_number=None, 
         | 
| @@ -355,40 +378,44 @@ class RasCmdr: | |
| 355 378 | 
             
                    # This line of code is used to check if the RasPrj object is initialized.
         | 
| 356 379 | 
             
                    ras_obj.check_initialized()
         | 
| 357 380 |  | 
| 358 | 
            -
                     | 
| 381 | 
            +
                    logging.info("Starting the compute_test_mode...")
         | 
| 359 382 |  | 
| 360 383 | 
             
                    # Use the project folder from the ras object
         | 
| 361 384 | 
             
                    project_folder = ras_obj.project_folder
         | 
| 362 385 |  | 
| 363 386 | 
             
                    # Check if the project folder exists
         | 
| 364 387 | 
             
                    if not project_folder.exists():
         | 
| 365 | 
            -
                         | 
| 388 | 
            +
                        logging.error(f"Project folder '{project_folder}' does not exist.")
         | 
| 366 389 | 
             
                        return
         | 
| 367 390 |  | 
| 368 391 | 
             
                    # Create test folder with the specified suffix in the same directory as the project folder
         | 
| 369 392 | 
             
                    compute_folder = project_folder.parent / f"{project_folder.name} {dest_folder_suffix}"
         | 
| 370 | 
            -
                     | 
| 393 | 
            +
                    logging.info(f"Creating the test folder: {compute_folder}...")
         | 
| 371 394 |  | 
| 372 395 | 
             
                    # Check if the compute folder exists and is empty
         | 
| 373 396 | 
             
                    if compute_folder.exists():
         | 
| 374 397 | 
             
                        if overwrite_dest:
         | 
| 375 398 | 
             
                            shutil.rmtree(compute_folder)
         | 
| 399 | 
            +
                            logging.info(f"Compute folder '{compute_folder}' exists. Overwriting as per overwrite_dest=True.")
         | 
| 376 400 | 
             
                        elif any(compute_folder.iterdir()):
         | 
| 377 | 
            -
                             | 
| 401 | 
            +
                            error_msg = (
         | 
| 378 402 | 
             
                                f"Compute folder '{compute_folder}' exists and is not empty. "
         | 
| 379 403 | 
             
                                "Use overwrite_dest=True to overwrite."
         | 
| 380 404 | 
             
                            )
         | 
| 405 | 
            +
                            logging.error(error_msg)
         | 
| 406 | 
            +
                            raise ValueError(error_msg)
         | 
| 381 407 | 
             
                    else:
         | 
| 382 408 | 
             
                        try:
         | 
| 383 409 | 
             
                            shutil.copytree(project_folder, compute_folder)
         | 
| 410 | 
            +
                            logging.info(f"Copied project folder to compute folder: {compute_folder}")
         | 
| 384 411 | 
             
                        except FileNotFoundError:
         | 
| 385 | 
            -
                             | 
| 412 | 
            +
                            logging.error(f"Unable to copy project folder. Source folder '{project_folder}' not found.")
         | 
| 386 413 | 
             
                            return
         | 
| 387 414 | 
             
                        except PermissionError:
         | 
| 388 | 
            -
                             | 
| 415 | 
            +
                            logging.error(f"Permission denied when trying to create or copy to '{compute_folder}'.")
         | 
| 389 416 | 
             
                            return
         | 
| 390 417 | 
             
                        except Exception as e:
         | 
| 391 | 
            -
                             | 
| 418 | 
            +
                            logging.error(f"Error occurred while copying project folder: {str(e)}")
         | 
| 392 419 | 
             
                            return
         | 
| 393 420 |  | 
| 394 421 | 
             
                    # Initialize a new RAS project in the compute folder
         | 
| @@ -396,22 +423,22 @@ class RasCmdr: | |
| 396 423 | 
             
                        compute_ras = RasPrj()
         | 
| 397 424 | 
             
                        compute_ras.initialize(compute_folder, ras_obj.ras_exe_path)
         | 
| 398 425 | 
             
                        compute_prj_path = compute_ras.prj_file
         | 
| 426 | 
            +
                        logging.info(f"Initialized RAS project in compute folder: {compute_prj_path}")
         | 
| 399 427 | 
             
                    except Exception as e:
         | 
| 400 | 
            -
                         | 
| 428 | 
            +
                        logging.error(f"Error initializing RAS project in compute folder: {str(e)}")
         | 
| 401 429 | 
             
                        return
         | 
| 402 430 |  | 
| 403 431 | 
             
                    if not compute_prj_path:
         | 
| 404 | 
            -
                         | 
| 432 | 
            +
                        logging.error("Project file not found.")
         | 
| 405 433 | 
             
                        return
         | 
| 406 434 |  | 
| 407 | 
            -
             | 
| 408 435 | 
             
                    # Get plan entries
         | 
| 409 | 
            -
                     | 
| 436 | 
            +
                    logging.info("Getting plan entries...")
         | 
| 410 437 | 
             
                    try:
         | 
| 411 438 | 
             
                        ras_compute_plan_entries = compute_ras.plan_df
         | 
| 412 | 
            -
                         | 
| 439 | 
            +
                        logging.info("Retrieved plan entries successfully.")
         | 
| 413 440 | 
             
                    except Exception as e:
         | 
| 414 | 
            -
                         | 
| 441 | 
            +
                        logging.error(f"Error retrieving plan entries: {str(e)}")
         | 
| 415 442 | 
             
                        return
         | 
| 416 443 |  | 
| 417 444 | 
             
                    if plan_number:
         | 
| @@ -420,30 +447,35 @@ class RasCmdr: | |
| 420 447 | 
             
                        ras_compute_plan_entries = ras_compute_plan_entries[
         | 
| 421 448 | 
             
                            ras_compute_plan_entries['plan_number'].isin(plan_number)
         | 
| 422 449 | 
             
                        ]
         | 
| 423 | 
            -
                         | 
| 450 | 
            +
                        logging.info(f"Filtered plans to execute: {plan_number}")
         | 
| 424 451 |  | 
| 425 | 
            -
                     | 
| 452 | 
            +
                    logging.info("Running selected plans sequentially...")
         | 
| 426 453 | 
             
                    for _, plan in ras_compute_plan_entries.iterrows():
         | 
| 427 454 | 
             
                        plan_number = plan["plan_number"]
         | 
| 428 455 | 
             
                        start_time = time.time()
         | 
| 429 456 | 
             
                        try:
         | 
| 430 | 
            -
                             | 
| 457 | 
            +
                            success = RasCmdr.compute_plan(
         | 
| 431 458 | 
             
                                plan_number,
         | 
| 432 459 | 
             
                                ras_object=compute_ras,
         | 
| 433 460 | 
             
                                clear_geompre=clear_geompre,
         | 
| 434 461 | 
             
                                num_cores=num_cores
         | 
| 435 462 | 
             
                            )
         | 
| 463 | 
            +
                            if success:
         | 
| 464 | 
            +
                                logging.info(f"Successfully computed plan {plan_number}")
         | 
| 465 | 
            +
                            else:
         | 
| 466 | 
            +
                                logging.error(f"Failed to compute plan {plan_number}")
         | 
| 436 467 | 
             
                        except Exception as e:
         | 
| 437 | 
            -
                             | 
| 468 | 
            +
                            logging.error(f"Error computing plan {plan_number}: {str(e)}")
         | 
| 438 469 | 
             
                        end_time = time.time()
         | 
| 439 470 | 
             
                        run_time = end_time - start_time
         | 
| 440 | 
            -
                         | 
| 471 | 
            +
                        logging.info(f"Total run time for plan {plan_number}: {run_time:.2f} seconds")
         | 
| 441 472 |  | 
| 442 | 
            -
                     | 
| 443 | 
            -
                     | 
| 473 | 
            +
                    logging.info("All selected plans have been executed.")
         | 
| 474 | 
            +
                    logging.info("compute_test_mode completed.")
         | 
| 444 475 |  | 
| 445 476 | 
             
                    ras_obj = ras_object or ras
         | 
| 446 477 | 
             
                    ras_obj.plan_df = ras_obj.get_plan_entries()
         | 
| 447 478 | 
             
                    ras_obj.geom_df = ras_obj.get_geom_entries()
         | 
| 448 479 | 
             
                    ras_obj.flow_df = ras_obj.get_flow_entries()
         | 
| 449 | 
            -
                    ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
         | 
| 480 | 
            +
                    ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
         | 
| 481 | 
            +
                
         |