From b0ff2f54420e039e3cabfd07714820dd2333c421 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Feb 2026 21:53:10 -0500 Subject: [PATCH 1/9] cuda support. readme update --- README.md | 103 +++++++++++++++++++--------- documentation/hohlraum.png | Bin 0 -> 85462 bytes run_hohlraum.py | 135 ++++++++++++++++++++++++++++--------- run_lattice.py | 95 +++++++++++++++++++------- src/config_utils.py | 8 ++- src/general_utils.py | 23 +++++++ src/models/hohlraum.py | 16 ++++- src/models/lattice.py | 26 ++++--- src/simulation_utils.py | 26 ++++--- 9 files changed, 327 insertions(+), 105 deletions(-) create mode 100644 documentation/hohlraum.png diff --git a/README.md b/README.md index b230cd8..e20eae2 100644 --- a/README.md +++ b/README.md @@ -48,41 +48,83 @@ Scripts support both HPC (SLURM) and local (no-HPC) execution. ## Running CharmKiT Scripts -CharmKiT provides Python scripts for each test case. These scripts can be run in two modes: +CharmKiT provides test-case drivers: -- **HPC mode**: Submits jobs to a SLURM cluster (using `--hpc` flag). -- **No-HPC (local) mode**: Runs all simulations sequentially on your local machine (default). +- `run_lattice.py` +- `run_hohlraum.py` -### Example: Running the Hohlraum Test Case (Local Mode) +Activate your environment first: -1. Activate your Python environment: - ``` - source venv/bin/activate - ``` -2. Run the script: - ``` - python run_hohlraum.py --no-hpc +```bash +source venv/bin/activate +``` + +Both scripts use the same execution flags: + +- `--use-slurm`: Submit jobs through SLURM. +- `--use-singularity`: Run KiT-RT through the CPU Singularity image. +- `--cuda`: Run KiT-RT through the CUDA Singularity image (`--nv` is added automatically). +- `--load-from-npz`: Load parameter samples from file (script-dependent behavior). + +### Supported run setups + +1. **Local mode, raw (no Singularity)** + + ```bash + python3 run_lattice.py + # or + python3 run_hohlraum.py ``` - This will execute all parameter combinations locally, running KiT-RT inside the Singularity container for each case. Results are saved as `.npz` files in the working directory. -### Example: Running the Lattice Test Case (Local Mode) + Uses local executable: `./KiT-RT/build/KiT-RT`. + +2. **Local mode + Singularity (CPU)** -1. Activate your Python environment: + ```bash + python3 run_lattice.py --use-singularity + # or + python3 run_hohlraum.py --use-singularity ``` - source venv/bin/activate + + Uses image/executable: + `KiT-RT/tools/singularity/kit_rt.sif` and `./KiT-RT/build_singularity/KiT-RT`. + +3. **Local mode + Singularity + GPU** + + ```bash + python3 run_lattice.py --cuda + # or + python3 run_hohlraum.py --cuda ``` -2. Run the script: + + Uses image/executable: + `KiT-RT/tools/singularity/kit_rt_MPI_cuda.sif` and `./KiT-RT/build_singularity_cuda/KiT-RT`. + +4. **SLURM mode, raw (no Singularity)** + + ```bash + python3 run_lattice.py --use-slurm + # or + python3 run_hohlraum.py --use-slurm ``` - python run_lattice.py --no-hpc + + Generated SLURM scripts call: `srun ./KiT-RT/build/KiT-RT ...`. + +5. **SLURM mode + Singularity (CPU)** + + ```bash + python3 run_lattice.py --use-slurm --use-singularity + # or + python3 run_hohlraum.py --use-slurm --use-singularity ``` -### Script Options + Generated SLURM scripts call: + `singularity exec KiT-RT/tools/singularity/kit_rt.sif ./KiT-RT/build_singularity/KiT-RT ...`. -Both scripts support various command-line arguments: +### Not supported -- `--no-hpc` : Run locally (default is HPC SLURM execution - requires SLURM setup). -- `--no-singularity` : Don't use the Singularity container (default: enabled). -- `--load-from-npz` : Loads design and grid parameters from npz file instead of using the presets in the run scripts. +- `--use-slurm --cuda` is intentionally blocked. + GPU mode is currently supported only for local Singularity runs (no SLURM). # Test Case Descriptions @@ -104,20 +146,22 @@ The main design parameters are: - Absorption in blue squares - Scattering in white squares -The script `run_lattice.py` automates parameter sweeps over these variables, generating KiT-RT config files and collecting results. Quantities of interest include outflow and absorption metrics, as well as wall time. The mesh and configuration can be customized via script arguments. +The script `run_lattice.py` automates parameter sweeps over these variables, generating KiT-RT config files and collecting results. Quantities of interest include mass, outflow and absorption metrics, as well as wall time. The mesh and configuration can be customized via script arguments. See `benchmarks/lattice/` for config templates and mesh files. ## 2. Hohlraum Test Case -The hohlraum test case simulates radiative transfer in a symmetric 2D cavity (hohlraum) with specified boundary conditions and material properties. The setup is designed to study energy transport and absorption in a geometry relevant to inertial confinement fusion and high-energy density physics. +The hohlraum test case models linear radiative transfer in a symmetric 2D cavity with mixed inflow and void boundary segments. The geometry affects transport and absorption. +![Hohlraum test case](documentation/hohlraum.png) -Key features: -- Central cavity with reflecting and absorbing boundaries -- Parameter sweep over mesh resolution and quadrature order -- Quantities of interest: outflow, absorption, and time-resolved metrics +The main design parameters are: +- Mesh characteristic length (spatial resolution) +- Quadrature order (velocity space) +- Capsule center location (`x`, `y`) +- Left and right absorber geometry parameters (top, bottom, and horizontal wall positions) -The script `run_hohlraum.py` manages the parameter study, config generation, and result collection. All runs are executed via the Singularity-encapsulated KiT-RT solver. +The script `run_hohlraum.py` automates parameter sweeps over these variables, generating KiT-RT config files and collecting results. Quantities of interest include mass, wall time, cumulative absorption metrics in key regions, and probe-moment summaries. The mesh and configuration can be customized via script arguments. See `benchmarks/hohlraum/` for config templates and mesh files. @@ -143,4 +187,3 @@ If you use CharmKiT or the provided benchmarks in your research, please cite: url={https://arxiv.org/abs/2505.17284}, } ``` - diff --git a/documentation/hohlraum.png b/documentation/hohlraum.png new file mode 100644 index 0000000000000000000000000000000000000000..25b5afef284e9183594fb4d3119d24b5289d3b94 GIT binary patch literal 85462 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 z%0Ayx-Ipxj<&3eS#pvqXiB?F=_KRVc?f^j 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 literal 0 HcmV?d00001 diff --git a/run_hohlraum.py b/run_hohlraum.py index 34414b1..6cc5e20 100644 --- a/run_hohlraum.py +++ b/run_hohlraum.py @@ -1,4 +1,5 @@ import numpy as np +import pandas as pd from src.models.hohlraum import get_qois_col_names, model @@ -17,34 +18,88 @@ def main(): print(f"HPC mode = { args.use_slurm}") print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") + print(f"CUDA mode = {args.cuda}") hpc_operation = args.use_slurm # Flag when using HPC cluster load_from_npz = args.load_from_npz - singularity_hpc = args.use_singularity - - # --- Define parameter ranges --- + use_cuda = args.cuda + if use_cuda and hpc_operation: + raise SystemExit( + "ERROR: --cuda cannot be combined with --use-slurm. " + "GPU mode is supported only with Singularity in non-SLURM runs." + ) + singularity_hpc = args.use_singularity or use_cuda + if use_cuda and not args.use_singularity: + print("CUDA mode requested; enabling Singularity execution.") + + if args.csv: + # --- CSV-driven mode: read design parameters from CSV --- + print(f"Reading design parameters from CSV: {args.csv}") + df = pd.read_csv(args.csv) + + # Allow overwriting spatial and angular resolution + if args.grid_cell_size is not None: + print(f"Overriding grid_cell_size with {args.grid_cell_size}") + df["grid_cell_size"] = args.grid_cell_size + if args.quad_order is not None: + print(f"Overriding quad_order with {args.quad_order}") + df["quad_order"] = args.quad_order + + # Extract columns in the order expected by the hohlraum model: + # left_red_top, left_red_bottom, right_red_top, right_red_bottom, + # horizontal_left, horizontal_right, green_center_x, green_center_y, + # grid_cell_size, quad_order + design_params = df[ + [ + "red_left_top", + "red_left_bottom", + "red_right_top", + "red_right_bottom", + "horizontal_left", + "horizontal_right", + "green_center_x", + "green_center_y", + "grid_cell_size", + "quad_order", + ] + ].to_numpy().T + design_param_names = np.array( + [ + "pos_red_left_top", + "pos_red_left_bottom", + "pos_red_right_top", + "pos_red_right_bottom", + "pos_red_left_horizontal", + "pos_red_right_horizontal", + "pos_green_x", + "pos_green_y", + "grid_cl", + "grid_quad_order", + ] + ) + elif load_from_npz: + design_params, design_param_names = load_hohlraum_samples_from_csv() + else: + # --- Define parameter ranges --- - # characteristic length of the cells: #grid cells = O(1/cell_size^2) - parameter_range_grid_cell_size = [0.0075] + # characteristic length of the cells: #grid cells = O(1/cell_size^2) + parameter_range_grid_cell_size = [0.0075] - # quadrature order (must be an even number): #velocity grid cells = O(order^2) - parameter_range_quad_order = [6] - # balance the two roughly (see Paper Fig 6) + # quadrature order (must be an even number): #velocity grid cells = O(order^2) + parameter_range_quad_order = [6] + # balance the two roughly (see Paper Fig 6) - # Define the geometry settings of the test case + # Define the geometry settings of the test case - parameter_range_green_center_x = [-0.1, 0.0, 0.1] # Default: 0 - parameter_range_green_center_y = [-0.075, 0.0, 0.075] # Default: 0 - parameter_range_red_right_top = [0.3, 0.4, 0.5] # Default: 0.4 - parameter_range_red_right_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 - parameter_range_red_left_top = [0.3, 0.4, 0.5] # Default: 0.4 - parameter_range_red_left_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 - parameter_range_horizontal_left = [-0.63, -0.6, -0.5] # Default: -0.6 - parameter_range_horizontal_right = [0.5, 0.6, 0.63] # Default: 0.6 + parameter_range_green_center_x = [-0.1, 0.0, 0.1] # Default: 0 + parameter_range_green_center_y = [-0.075, 0.0, 0.075] # Default: 0 + parameter_range_red_right_top = [0.3, 0.4, 0.5] # Default: 0.4 + parameter_range_red_right_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 + parameter_range_red_left_top = [0.3, 0.4, 0.5] # Default: 0.4 + parameter_range_red_left_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 + parameter_range_horizontal_left = [-0.63, -0.6, -0.5] # Default: -0.6 + parameter_range_horizontal_right = [0.5, 0.6, 0.63] # Default: 0.6 - if load_from_npz: - design_params, design_param_names = load_hohlraum_samples_from_csv() - else: design_params, design_param_names = create_hohlraum_samples_from_param_range( parameter_range_grid_cell_size, parameter_range_quad_order, @@ -65,7 +120,10 @@ def main(): delete_slurm_scripts(directory) # delete existing slurm files for hohlraum call_models( - design_params, hpc_operation_count=1, singularity_hpc=singularity_hpc + design_params, + hpc_operation_count=1, + singularity_hpc=singularity_hpc, + use_cuda=use_cuda, ) # wait_for_slurm_jobs(user=user, sleep_interval=10) @@ -75,10 +133,31 @@ def main(): wait_for_slurm_jobs(user=user, sleep_interval=10) else: print("Username could not be read from slurm config file.") - qois = call_models(design_params, hpc_operation_count=2) + qois = call_models( + design_params, hpc_operation_count=2, use_cuda=use_cuda + ) else: qois = call_models( - design_params, hpc_operation_count=0, singularity_hpc=singularity_hpc + design_params, + hpc_operation_count=0, + singularity_hpc=singularity_hpc, + use_cuda=use_cuda, + ) + + if args.csv: + # Add all QOI columns to the CSV + qoi_names = get_qois_col_names() + for i in range(qois.shape[1]): + df[qoi_names[i]] = qois[:, i] + df.to_csv(args.csv, index=False) + print(f"Updated CSV with QOI columns: {args.csv}") + else: + np.savez( + "benchmarks/hohlraum/sn_study_hohlraum.npz", + qois=qois, + design_params=design_params, + qoi_column_names=get_qois_col_names(), + design_param_column_names=design_param_names, ) print("design parameter matrix") @@ -87,24 +166,18 @@ def main(): print("quantities of interest:") print(get_qois_col_names()) print(qois) - np.savez( - "benchmarks/hohlraum/sn_study_hohlraum.npz", - qois=qois, - design_params=design_params, - qoi_column_names=get_qois_col_names(), - design_param_column_names=design_param_names, - ) print("======== Finished ===========") return 0 -def call_models(design_params, hpc_operation_count, singularity_hpc=True): +def call_models(design_params, hpc_operation_count, singularity_hpc=True, use_cuda=False): qois = [] for column in design_params.T: input = column.tolist() input.append(hpc_operation_count) input.append(singularity_hpc) + input.append(use_cuda) res = model([input]) qois.append(res[0]) diff --git a/run_lattice.py b/run_lattice.py index 18b44d1..30d00e8 100644 --- a/run_lattice.py +++ b/run_lattice.py @@ -1,5 +1,6 @@ # import umbridge import numpy as np +import pandas as pd from src.config_utils import read_username_from_config from src.models.lattice import get_qois_col_names, model @@ -19,34 +20,62 @@ def main(): print(f"HPC mode = { args.use_slurm}") print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") + print(f"CUDA mode = {args.cuda}") hpc_operation = args.use_slurm # Flag when using HPC cluster load_from_npz = args.load_from_npz - singularity_hpc = args.use_singularity + use_cuda = args.cuda + if use_cuda and hpc_operation: + raise SystemExit( + "ERROR: --cuda cannot be combined with --use-slurm. " + "GPU mode is supported only with Singularity in non-SLURM runs." + ) + singularity_hpc = args.use_singularity or use_cuda + if use_cuda and not args.use_singularity: + print("CUDA mode requested; enabling Singularity execution.") # print(f"Use rectangular_mesh = {args.rectangular_mesh}") rectangular_mesh = False - # --- Define parameter ranges --- - - # characteristic length of the cells: #grid cells = O(1/cell_size^2) - parameter_range_grid_cell_size = [0.015] - - # quadrature order (must be an even number): #velocity grid cells = O(order^2) - parameter_range_quad_order = [6] - - # Prescribed range for LATTICE_DSGN_ABSORPTION_BLUE - parameter_range_abs_blue = [5, 10, 50, 100] # default: 10 - # Prescribed range for LATTICE_DSGN_SCATTER_WHITE - parameter_range_scatter_white = [0.1, 0.5, 1, 5, 10] # default: 1 - - if load_from_npz: # TODO + if args.csv: + # --- CSV-driven mode: read design parameters from CSV --- + print(f"Reading design parameters from CSV: {args.csv}") + df = pd.read_csv(args.csv) + + # Allow overwriting spatial and angular resolution + if args.grid_cell_size is not None: + print(f"Overriding grid_cell_size with {args.grid_cell_size}") + df["grid_cell_size"] = args.grid_cell_size + if args.quad_order is not None: + print(f"Overriding quad_order with {args.quad_order}") + df["quad_order"] = args.quad_order + + design_params = df[ + ["abs_blue", "scatter_white", "grid_cell_size", "quad_order"] + ].to_numpy() + design_param_names = np.array( + ["absorption_blue", "scattering_white", "grid_cl", "grid_quad_order"] + ) + elif load_from_npz: # TODO raise NotImplementedError design_params, design_param_names = load_lattice_samples_from_npz( "sampling/pilot-study-samples-hohlraum-05-29-24.npz" ) exit("TODO") else: + # --- Define parameter ranges --- + + # characteristic length of the cells: #grid cells = O(1/cell_size^2) + parameter_range_grid_cell_size = [0.01] + + # quadrature order (must be an even number): #velocity grid cells = O(order^2) + parameter_range_quad_order = [4] + + # Prescribed range for LATTICE_DSGN_ABSORPTION_BLUE + parameter_range_abs_blue = [10] # default: 10 #10, 50, 100 + # Prescribed range for LATTICE_DSGN_SCATTER_WHITE + parameter_range_scatter_white = [1] # default: 1 0.1, 0.5, 1, 5, 10 + design_params, design_param_names = create_lattice_samples_from_param_range( parameter_range_grid_cell_size, parameter_range_quad_order, @@ -64,6 +93,7 @@ def main(): hpc_operation_count=1, singularity_hpc=singularity_hpc, rectangular_mesh=rectangular_mesh, + use_cuda=use_cuda, ) user = read_username_from_config("./slurm_config.txt") @@ -74,13 +104,32 @@ def main(): else: print("Username could not be read from config file.") - qois = call_models(design_params, hpc_operation_count=2) + qois = call_models( + design_params, hpc_operation_count=2, use_cuda=use_cuda + ) else: qois = call_models( design_params, hpc_operation_count=0, rectangular_mesh=rectangular_mesh, singularity_hpc=singularity_hpc, + use_cuda=use_cuda, + ) + + if args.csv: + # Add all QOI columns to the CSV + qoi_names = get_qois_col_names() + for i in range(qois.shape[1]): + df[qoi_names[i]] = qois[:, i] + df.to_csv(args.csv, index=False) + print(f"Updated CSV with QOI columns: {args.csv}") + else: + np.savez( + "benchmarks/lattice/sn_study_lattice.npz", + qois=qois, + design_params=design_params, + qoi_column_names=get_qois_col_names(), + design_param_column_names=design_param_names, ) print("design parameter matrix") @@ -89,20 +138,17 @@ def main(): print("quantities of interest:") print(get_qois_col_names()) print(qois) - np.savez( - "benchmarks/lattice/sn_study_lattice.npz", - qois=qois, - design_params=design_params, - qoi_column_names=get_qois_col_names(), - design_param_column_names=design_param_names, - ) print("======== Finished ===========") return 0 def call_models( - design_params, hpc_operation_count, singularity_hpc=True, rectangular_mesh=False + design_params, + hpc_operation_count, + singularity_hpc=True, + rectangular_mesh=False, + use_cuda=False, ): qois = [] for column in design_params: @@ -111,6 +157,7 @@ def call_models( input.append(hpc_operation_count) input.append(singularity_hpc) input.append(rectangular_mesh) + input.append(use_cuda) res = model([input]) qois.append(res[0]) diff --git a/src/config_utils.py b/src/config_utils.py index df7cd5c..73db6b4 100644 --- a/src/config_utils.py +++ b/src/config_utils.py @@ -384,7 +384,13 @@ def update_half_lattice_mesh_file(n_cell, filepath, rectangular_mesh=False): return f"half_lattice_p{n_cell}.su2" -def write_slurm_file(output_slurm_dir, unique_name, subfolder, singularity=True): +def write_slurm_file( + output_slurm_dir, unique_name, subfolder, singularity=True, use_cuda=False +): + if use_cuda: + raise ValueError( + "CUDA mode with SLURM is not supported in this workflow." + ) basic_slurm_file = "./slurm_template.sh" # Ensure the output directory exists diff --git a/src/general_utils.py b/src/general_utils.py index 031dccb..82efe59 100644 --- a/src/general_utils.py +++ b/src/general_utils.py @@ -20,6 +20,29 @@ def parse_args(): action="store_true", help="Flag to use Singularity Container for KiT-RT", ) + parser.add_argument( + "--csv", + type=str, + default=None, + help="Path to CSV file with design parameters", + ) + parser.add_argument( + "--grid-cell-size", + type=float, + default=None, + help="Override spatial resolution (grid cell size) for all runs", + ) + parser.add_argument( + "--quad-order", + type=int, + default=None, + help="Override angular resolution (quadrature order) for all runs", + ) + parser.add_argument( + "--cuda", + action="store_true", + help="Run with the CUDA-enabled Singularity image and executable", + ) args = parser.parse_args() return args diff --git a/src/models/hohlraum.py b/src/models/hohlraum.py index b2a7076..35b9d95 100644 --- a/src/models/hohlraum.py +++ b/src/models/hohlraum.py @@ -31,6 +31,13 @@ def model(parameters): quad_order = int(parameters[0][9]) hpc_operation = parameters[0][10] singularity_hpc = parameters[0][11] + use_cuda = bool(parameters[0][12]) if len(parameters[0]) > 12 else False + if use_cuda: + singularity_hpc = 1 + if use_cuda and hpc_operation == 1: + raise ValueError( + "CUDA mode with SLURM is not supported in this workflow." + ) subfolder = "benchmarks/hohlraum/" base_config_file = subfolder + "hohlraum.cfg" @@ -114,7 +121,7 @@ def model(parameters): # Step 5: Run the C++ simulation if singularity_hpc == 1: print("Running simulation with singularity") - run_cpp_simulation_containerized(generated_cfg_file) + run_cpp_simulation_containerized(generated_cfg_file, use_cuda=use_cuda) else: print("Running simulation without singularity") run_cpp_simulation(generated_cfg_file) @@ -125,6 +132,7 @@ def model(parameters): unique_name, subfolder, singularity_hpc, + use_cuda=use_cuda, ) if hpc_operation == 0 or hpc_operation == 2: @@ -140,10 +148,11 @@ def model(parameters): # print(integrated_probe_moments) quantities_of_interest = [ float(log_data["Wall_time_[s]"]), + float(log_data["Mass"]), float(log_data["Cumulated_absorption_center"]), float(log_data["Cumulated_absorption_vertical_wall"]), float(log_data["Cumulated_absorption_horizontal_wall"]), - # float(log_data["Var. absorption green"]), + # float(log_data["Var. absorption green"]), ] for i in range(N): @@ -204,10 +213,11 @@ def get_qois_col_names(): return np.array( [ "Wall_time_[s]", + "Mass", "Cumulated_absorption_center", "Cumulated_absorption_vertical_wall", "Cumulated_absorption_horizontal_wall", - # "Variation_absorption_green", + # "Variation_absorption_green", "Probe0_u0_N1", "Probe0_u0_N2", "Probe0_u0_N3", diff --git a/src/models/lattice.py b/src/models/lattice.py index 69ff64e..503d81f 100644 --- a/src/models/lattice.py +++ b/src/models/lattice.py @@ -30,6 +30,13 @@ def model(parameters): hpc_operation = parameters[0][4] singularity_hpc = parameters[0][5] rectangular_mesh = parameters[0][6] + use_cuda = bool(parameters[0][7]) if len(parameters[0]) > 7 else False + if use_cuda: + singularity_hpc = 1 + if use_cuda and hpc_operation == 1: + raise ValueError( + "CUDA mode with SLURM is not supported in this workflow." + ) subfolder = "benchmarks/lattice/" base_config_file = subfolder + "lattice.cfg" @@ -91,7 +98,7 @@ def model(parameters): # Step 5: Run the C++ simulation if singularity_hpc == 1: print("Running lattice simulation with singularity") - run_cpp_simulation_containerized(generated_cfg_file) + run_cpp_simulation_containerized(generated_cfg_file, use_cuda=use_cuda) else: print("Running lattice simulation without singularity") run_cpp_simulation(generated_cfg_file) @@ -102,6 +109,7 @@ def model(parameters): unique_name, subfolder, singularity_hpc, + use_cuda=use_cuda, ) # Step 6: Read the log file @@ -115,6 +123,7 @@ def model(parameters): log_data["LATTICE_DSGN_SCATTER_WHITE"] = scatter_white_value quantities_of_interest = [ float(log_data["Wall_time_[s]"]), + float(log_data["Mass"]), float(log_data["Cur_absorption"]), float(log_data["Total_absorption"]), float(log_data["Cur_outflow_P1"]), @@ -123,7 +132,7 @@ def model(parameters): float(log_data["Total_outflow_P2"]), ] else: - quantities_of_interest = [0] * 7 + quantities_of_interest = [0] * 8 return [quantities_of_interest] @@ -132,11 +141,12 @@ def get_qois_col_names(): return np.array( [ "Wall_time_[s]", - "Absotption_final_time", - "Cumulated_absorption", - "Outflow_Perimeter1_final_time", - "Cumulated_outflow_Perimeter1", - "Outflow_Perimeter2_final_time", - "Cumulated_outflow_Perimeter2", + "Mass", + "Cur_absorption", + "Total_absorption", + "Cur_outflow_P1", + "Total_outflow_P1", + "Cur_outflow_P2", + "Total_outflow_P2", ] ) diff --git a/src/simulation_utils.py b/src/simulation_utils.py index d44db8f..330ad99 100644 --- a/src/simulation_utils.py +++ b/src/simulation_utils.py @@ -29,15 +29,25 @@ def run_cpp_simulation(config_file): # You can handle the error as needed -def run_cpp_simulation_containerized(config_file): +def run_cpp_simulation_containerized(config_file, use_cuda=False): # Path to the C++ executable - singularity_command = [ - "singularity", - "exec", - "KiT-RT/tools/singularity/kit_rt.sif", - "./KiT-RT/build_singularity/KiT-RT", - config_file, - ] + if use_cuda: + singularity_command = [ + "singularity", + "exec", + "--nv", + "KiT-RT/tools/singularity/kit_rt_MPI_cuda.sif", + "./KiT-RT/build_singularity_cuda/KiT-RT", + config_file, + ] + else: + singularity_command = [ + "singularity", + "exec", + "KiT-RT/tools/singularity/kit_rt.sif", + "./KiT-RT/build_singularity/KiT-RT", + config_file, + ] # Command to run the C++ executable with the provided config file From 10877ca71bc43374a684dbcfc38050a31af70967 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Feb 2026 22:02:58 -0500 Subject: [PATCH 2/9] update installers --- README.md | 5 +++-- install_KiT-RT.sh | 42 +++++++++++++++++++++++++++++------------- update_KiT-RT.sh | 19 ++++++++++++++++++- 3 files changed, 50 insertions(+), 16 deletions(-) mode change 100644 => 100755 install_KiT-RT.sh mode change 100644 => 100755 update_KiT-RT.sh diff --git a/README.md b/README.md index e20eae2..6875bd2 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,12 @@ Preliminaries: 4. Install [KiT-RT](https://github.com/CSMMLab/KiT-RT) as a submodule using the provided installer. (Requires root for container build.) ``` - sh install_KiT-RT.sh + bash install_KiT-RT.sh ``` + The installer always builds the CPU Singularity image + binary. If a CUDA GPU is detected (`nvidia-smi`), it also builds the CUDA Singularity image and `build_singularity_cuda` binary automatically. If updating KiT-RT: ``` - sh update_KiT-RT.sh + bash update_KiT-RT.sh ``` If on a cluster without root, build the container locally and upload it to `CharmKiT/KiT-RT/tools/singularity/`. diff --git a/install_KiT-RT.sh b/install_KiT-RT.sh old mode 100644 new mode 100755 index 7530502..ede25ef --- a/install_KiT-RT.sh +++ b/install_KiT-RT.sh @@ -1,27 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +has_cuda_gpu() { + if ! command -v nvidia-smi >/dev/null 2>&1; then + return 1 + fi + nvidia-smi -L >/dev/null 2>&1 +} + # clone KiT-RT git clone git@github.com:CSMMLab/KiT-RT.git + # go to KiT-RT directory cd KiT-RT -# checkout the branch new_radiation_test_cases +# checkout the default branch and load submodules git fetch origin git checkout master -# load all submodules git submodule update --init --recursive -# create build directory -mkdir build_singularity - -# navigate to directory where the singularity .def script is located +# navigate to directory where the singularity scripts are located cd tools/singularity +chmod +x build_container.sh install_kitrt_singularity.sh install_kitrt_singularity_cuda.sh -# build the singularity container. This requires root privileges -echo "sudo access needed to build the singularity container. This will hopefully not be neccessary in the future." -sudo sh build_container.sh +# build CPU singularity container. This requires root privileges. +echo "Building CPU singularity container (sudo required)." +sudo ./build_container.sh cpu -# compile KiT-RT within the singularity container -chmod +x install_kitrt_singularity.sh +# compile CPU KiT-RT within the singularity container singularity exec kit_rt.sif ./install_kitrt_singularity.sh -# go back to CharmNet repo -cd ../../../ \ No newline at end of file +# optionally build and compile CUDA KiT-RT if a CUDA GPU is present +if has_cuda_gpu; then + echo "CUDA GPU detected. Building CUDA singularity container and CUDA KiT-RT binary." + sudo ./build_container.sh cuda + singularity exec --nv kit_rt_MPI_cuda.sif ./install_kitrt_singularity_cuda.sh +else + echo "No CUDA GPU detected. Skipping CUDA singularity container and CUDA build." +fi + +# go back to CharmKiT repo root +cd ../../../ diff --git a/update_KiT-RT.sh b/update_KiT-RT.sh old mode 100644 new mode 100755 index 82923c6..ea5226d --- a/update_KiT-RT.sh +++ b/update_KiT-RT.sh @@ -1,6 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +has_cuda_gpu() { + if ! command -v nvidia-smi >/dev/null 2>&1; then + return 1 + fi + nvidia-smi -L >/dev/null 2>&1 +} + cd KiT-RT git checkout master git pull origin cd tools/singularity +chmod +x install_kitrt_singularity.sh install_kitrt_singularity_cuda.sh + singularity exec kit_rt.sif ./install_kitrt_singularity.sh -cd ../../../ \ No newline at end of file + +if has_cuda_gpu && [ -f kit_rt_MPI_cuda.sif ]; then + singularity exec --nv kit_rt_MPI_cuda.sif ./install_kitrt_singularity_cuda.sh +fi + +cd ../../../ From 311682c077d5cded405ddb43661f12f656e8234e Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 11 Feb 2026 22:37:40 -0500 Subject: [PATCH 3/9] update installer --- README.md | 2 +- install_KiT-RT.sh | 9 +++++---- update_KiT-RT.sh | 19 +++++++++++++++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6875bd2..a3fd386 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Preliminaries: pip install -r requirements.txt ``` -4. Install [KiT-RT](https://github.com/CSMMLab/KiT-RT) as a submodule using the provided installer. (Requires root for container build.) +4. Install [KiT-RT](https://github.com/KiT-RT/kitrt_code) as a submodule using the provided installer. (Requires root for container build.) ``` bash install_KiT-RT.sh ``` diff --git a/install_KiT-RT.sh b/install_KiT-RT.sh index ede25ef..8c8d56c 100755 --- a/install_KiT-RT.sh +++ b/install_KiT-RT.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +KITRT_REPO_URL="${KITRT_REPO_URL:-git@github.com:KiT-RT/kitrt_code.git}" + has_cuda_gpu() { if ! command -v nvidia-smi >/dev/null 2>&1; then return 1 @@ -9,14 +11,13 @@ has_cuda_gpu() { } # clone KiT-RT -git clone git@github.com:CSMMLab/KiT-RT.git +git clone "${KITRT_REPO_URL}" KiT-RT # go to KiT-RT directory cd KiT-RT -# checkout the default branch and load submodules -git fetch origin -git checkout master +# keep origin synchronized with configured upstream and load submodules +git remote set-url origin "${KITRT_REPO_URL}" git submodule update --init --recursive # navigate to directory where the singularity scripts are located diff --git a/update_KiT-RT.sh b/update_KiT-RT.sh index ea5226d..3c6ee5b 100755 --- a/update_KiT-RT.sh +++ b/update_KiT-RT.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +KITRT_REPO_URL="${KITRT_REPO_URL:-git@github.com:KiT-RT/kitrt_code.git}" + has_cuda_gpu() { if ! command -v nvidia-smi >/dev/null 2>&1; then return 1 @@ -9,8 +11,21 @@ has_cuda_gpu() { } cd KiT-RT -git checkout master -git pull origin +git remote set-url origin "${KITRT_REPO_URL}" +git fetch origin + +default_branch="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || true)" +if [ -z "${default_branch}" ]; then + default_branch="$(git remote show origin | sed -n '/HEAD branch/s/.*: //p' | head -n 1)" +fi +if [ -z "${default_branch}" ]; then + default_branch="$(git rev-parse --abbrev-ref HEAD)" +fi + +git checkout "${default_branch}" +git pull --ff-only origin "${default_branch}" +git submodule update --init --recursive + cd tools/singularity chmod +x install_kitrt_singularity.sh install_kitrt_singularity_cuda.sh From 6b86971209352ea13827ae8f55fc52002c22d655 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Feb 2026 13:01:38 -0500 Subject: [PATCH 4/9] huge set of updates. sry --- README.md | 69 ++++++++++---- requirements.txt | 1 - run_hohlraum.py | 207 ++++++++++++++++++++++++++++++++++++---- run_lattice.py | 104 ++++++++++++++++---- src/general_utils.py | 149 +++++++++++++++++++++++++++-- src/models/hohlraum.py | 9 +- src/models/lattice.py | 17 +++- src/simulation_utils.py | 63 ++++++++---- 8 files changed, 533 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index a3fd386..a486e68 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,10 @@ Preliminaries: git clone git@github.com:ScSteffen/CharmKiT.git ``` -3. Create a local Python environment and install requirements: +3. Install Poetry and create the project environment: ``` - python3 -m venv ./venv - source venv/bin/activate - pip install -r requirements.txt + python3 -m pip install --user poetry + poetry install ``` 4. Install [KiT-RT](https://github.com/KiT-RT/kitrt_code) as a submodule using the provided installer. (Requires root for container build.) @@ -54,18 +53,54 @@ CharmKiT provides test-case drivers: - `run_lattice.py` - `run_hohlraum.py` -Activate your environment first: +Use Poetry to run commands in the project environment. + +Each script has its own CLI parser. They share execution flags, but design-parameter flags are test-case specific. Print all flags with: ```bash -source venv/bin/activate +poetry run python run_lattice.py --help +poetry run python run_hohlraum.py --help ``` -Both scripts use the same execution flags: +Execution and I/O flags: -- `--use-slurm`: Submit jobs through SLURM. -- `--use-singularity`: Run KiT-RT through the CPU Singularity image. +- `--slurm`: Submit jobs through SLURM. +- `--singularity`: Run KiT-RT through the CPU Singularity image. - `--cuda`: Run KiT-RT through the CUDA Singularity image (`--nv` is added automatically). -- `--load-from-npz`: Load parameter samples from file (script-dependent behavior). +- `--load-from-npz`: Load parameter samples from NPZ input (script-dependent behavior). +- `--csv CSV`: Read design parameters from CSV and write QOIs back to that CSV. +- `--config CONFIG`: Path to a TOML hyperparameter file. +- `-q`, `--quiet`: Suppress solver stdout/stderr output. + +Shared parameter override flags: + +- `--grid-cell-size GRID_CELL_SIZE`: Override spatial resolution for all runs. +- `--quad-order QUAD_ORDER`: Override angular quadrature order for all runs (must be even). + +Lattice-specific parameter flags: + +- `--abs-blue ABS_BLUE [ABS_BLUE ...]`: Override absorption coefficient(s) in blue lattice cells. +- `--scatter-white SCATTER_WHITE [SCATTER_WHITE ...]`: Override scattering coefficient(s) in white lattice cells. + +Hohlraum-specific parameter flags: + +- `--green-center-x GREEN_CENTER_X [GREEN_CENTER_X ...]` +- `--green-center-y GREEN_CENTER_Y [GREEN_CENTER_Y ...]` +- `--red-right-top RED_RIGHT_TOP [RED_RIGHT_TOP ...]` +- `--red-right-bottom RED_RIGHT_BOTTOM [RED_RIGHT_BOTTOM ...]` +- `--red-left-top RED_LEFT_TOP [RED_LEFT_TOP ...]` +- `--red-left-bottom RED_LEFT_BOTTOM [RED_LEFT_BOTTOM ...]` +- `--horizontal-left HORIZONTAL_LEFT [HORIZONTAL_LEFT ...]` +- `--horizontal-right HORIZONTAL_RIGHT [HORIZONTAL_RIGHT ...]` + +Default hyperparameter files: +- `benchmarks/lattice/hyperparams.toml` +- `benchmarks/hohlraum/hyperparams.toml` + +Precedence for hyperparameters is: +1. Command-line arguments +2. TOML values +3. Script hardcoded defaults ### Supported run setups @@ -82,9 +117,9 @@ Both scripts use the same execution flags: 2. **Local mode + Singularity (CPU)** ```bash - python3 run_lattice.py --use-singularity + python3 run_lattice.py --singularity # or - python3 run_hohlraum.py --use-singularity + python3 run_hohlraum.py --singularity ``` Uses image/executable: @@ -104,9 +139,9 @@ Both scripts use the same execution flags: 4. **SLURM mode, raw (no Singularity)** ```bash - python3 run_lattice.py --use-slurm + python3 run_lattice.py --slurm # or - python3 run_hohlraum.py --use-slurm + python3 run_hohlraum.py --slurm ``` Generated SLURM scripts call: `srun ./KiT-RT/build/KiT-RT ...`. @@ -114,9 +149,9 @@ Both scripts use the same execution flags: 5. **SLURM mode + Singularity (CPU)** ```bash - python3 run_lattice.py --use-slurm --use-singularity + python3 run_lattice.py --slurm --singularity # or - python3 run_hohlraum.py --use-slurm --use-singularity + python3 run_hohlraum.py --slurm --singularity ``` Generated SLURM scripts call: @@ -124,7 +159,7 @@ Both scripts use the same execution flags: ### Not supported -- `--use-slurm --cuda` is intentionally blocked. +- `--slurm --cuda` is intentionally blocked. GPU mode is currently supported only for local Singularity runs (no SLURM). diff --git a/requirements.txt b/requirements.txt index 2252986..4bf9135 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -umbridge gmsh numpy pandas \ No newline at end of file diff --git a/run_hohlraum.py b/run_hohlraum.py index 6cc5e20..5b99e14 100644 --- a/run_hohlraum.py +++ b/run_hohlraum.py @@ -9,41 +9,136 @@ create_hohlraum_samples_from_param_range, load_hohlraum_samples_from_csv, delete_slurm_scripts, + load_toml_hyperparameters, ) -from src.general_utils import parse_args +from src.general_utils import parse_hohlraum_args def main(): - args = parse_args() + args = parse_hohlraum_args() print(f"HPC mode = { args.use_slurm}") print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") print(f"CUDA mode = {args.cuda}") + print(f"Quiet mode = {args.quiet}") hpc_operation = args.use_slurm # Flag when using HPC cluster load_from_npz = args.load_from_npz use_cuda = args.cuda if use_cuda and hpc_operation: raise SystemExit( - "ERROR: --cuda cannot be combined with --use-slurm. " + "ERROR: --cuda cannot be combined with --slurm. " "GPU mode is supported only with Singularity in non-SLURM runs." ) singularity_hpc = args.use_singularity or use_cuda if use_cuda and not args.use_singularity: print("CUDA mode requested; enabling Singularity execution.") + config_path = args.config or "benchmarks/hohlraum/hyperparams.toml" + hyper = load_toml_hyperparameters(config_path) + print(f"Hohlraum hyperparameter config = {config_path}") + + def as_list_or_none(value): + if value is None: + return None + if isinstance(value, list): + return value + return [value] + + def override_csv_column(df, values, column_name, arg_name): + values = as_list_or_none(values) + if values is None: + return + print(f"Overriding {column_name} with {values}") + if len(values) == 1: + df[column_name] = values[0] + elif len(values) == len(df): + df[column_name] = values + else: + raise SystemExit( + f"ERROR: In --csv mode, {arg_name} must provide either one value " + "or exactly one value per CSV row." + ) if args.csv: # --- CSV-driven mode: read design parameters from CSV --- print(f"Reading design parameters from CSV: {args.csv}") df = pd.read_csv(args.csv) + override_csv_column( + df, + args.green_center_x + if args.green_center_x is not None + else hyper.get("green_center_x"), + "green_center_x", + "--green-center-x", + ) + override_csv_column( + df, + args.green_center_y + if args.green_center_y is not None + else hyper.get("green_center_y"), + "green_center_y", + "--green-center-y", + ) + override_csv_column( + df, + args.red_right_top if args.red_right_top is not None else hyper.get("red_right_top"), + "red_right_top", + "--red-right-top", + ) + override_csv_column( + df, + args.red_right_bottom + if args.red_right_bottom is not None + else hyper.get("red_right_bottom"), + "red_right_bottom", + "--red-right-bottom", + ) + override_csv_column( + df, + args.red_left_top if args.red_left_top is not None else hyper.get("red_left_top"), + "red_left_top", + "--red-left-top", + ) + override_csv_column( + df, + args.red_left_bottom + if args.red_left_bottom is not None + else hyper.get("red_left_bottom"), + "red_left_bottom", + "--red-left-bottom", + ) + override_csv_column( + df, + args.horizontal_left + if args.horizontal_left is not None + else hyper.get("horizontal_left"), + "horizontal_left", + "--horizontal-left", + ) + override_csv_column( + df, + args.horizontal_right + if args.horizontal_right is not None + else hyper.get("horizontal_right"), + "horizontal_right", + "--horizontal-right", + ) # Allow overwriting spatial and angular resolution - if args.grid_cell_size is not None: - print(f"Overriding grid_cell_size with {args.grid_cell_size}") - df["grid_cell_size"] = args.grid_cell_size - if args.quad_order is not None: - print(f"Overriding quad_order with {args.quad_order}") - df["quad_order"] = args.quad_order + override_csv_column( + df, + args.grid_cell_size + if args.grid_cell_size is not None + else hyper.get("grid_cell_size"), + "grid_cell_size", + "--grid-cell-size", + ) + override_csv_column( + df, + args.quad_order if args.quad_order is not None else hyper.get("quad_order"), + "quad_order", + "--quad-order", + ) # Extract columns in the order expected by the hohlraum model: # left_red_top, left_red_bottom, right_red_top, right_red_bottom, @@ -83,22 +178,83 @@ def main(): # --- Define parameter ranges --- # characteristic length of the cells: #grid cells = O(1/cell_size^2) - parameter_range_grid_cell_size = [0.0075] + parameter_range_grid_cell_size = as_list_or_none( + args.grid_cell_size + if args.grid_cell_size is not None + else hyper.get("grid_cell_size") + ) or [0.0075] # quadrature order (must be an even number): #velocity grid cells = O(order^2) - parameter_range_quad_order = [6] + parameter_range_quad_order = as_list_or_none( + args.quad_order if args.quad_order is not None else hyper.get("quad_order") + ) or [6] + if any(int(q) % 2 != 0 for q in parameter_range_quad_order): + raise SystemExit("ERROR: --quad-order must be an even number.") # balance the two roughly (see Paper Fig 6) # Define the geometry settings of the test case - parameter_range_green_center_x = [-0.1, 0.0, 0.1] # Default: 0 - parameter_range_green_center_y = [-0.075, 0.0, 0.075] # Default: 0 - parameter_range_red_right_top = [0.3, 0.4, 0.5] # Default: 0.4 - parameter_range_red_right_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 - parameter_range_red_left_top = [0.3, 0.4, 0.5] # Default: 0.4 - parameter_range_red_left_bottom = [-0.5, -0.4, -0.3] # Default: -0.4 - parameter_range_horizontal_left = [-0.63, -0.6, -0.5] # Default: -0.6 - parameter_range_horizontal_right = [0.5, 0.6, 0.63] # Default: 0.6 + parameter_range_green_center_x = as_list_or_none( + args.green_center_x + if args.green_center_x is not None + else hyper.get("green_center_x") + ) or [-0.1, 0.0, 0.1] # Default: 0 + parameter_range_green_center_y = ( + as_list_or_none( + args.green_center_y + if args.green_center_y is not None + else hyper.get("green_center_y") + ) + or [-0.075, 0.0, 0.075] + ) # Default: 0 + parameter_range_red_right_top = ( + as_list_or_none( + args.red_right_top + if args.red_right_top is not None + else hyper.get("red_right_top") + ) + or [0.3, 0.4, 0.5] + ) # Default: 0.4 + parameter_range_red_right_bottom = ( + as_list_or_none( + args.red_right_bottom + if args.red_right_bottom is not None + else hyper.get("red_right_bottom") + ) + or [-0.5, -0.4, -0.3] + ) # Default: -0.4 + parameter_range_red_left_top = ( + as_list_or_none( + args.red_left_top + if args.red_left_top is not None + else hyper.get("red_left_top") + ) + or [0.3, 0.4, 0.5] + ) # Default: 0.4 + parameter_range_red_left_bottom = ( + as_list_or_none( + args.red_left_bottom + if args.red_left_bottom is not None + else hyper.get("red_left_bottom") + ) + or [-0.5, -0.4, -0.3] + ) # Default: -0.4 + parameter_range_horizontal_left = ( + as_list_or_none( + args.horizontal_left + if args.horizontal_left is not None + else hyper.get("horizontal_left") + ) + or [-0.63, -0.6, -0.5] + ) # Default: -0.6 + parameter_range_horizontal_right = ( + as_list_or_none( + args.horizontal_right + if args.horizontal_right is not None + else hyper.get("horizontal_right") + ) + or [0.5, 0.6, 0.63] + ) # Default: 0.6 design_params, design_param_names = create_hohlraum_samples_from_param_range( parameter_range_grid_cell_size, @@ -124,6 +280,7 @@ def main(): hpc_operation_count=1, singularity_hpc=singularity_hpc, use_cuda=use_cuda, + quiet=args.quiet, ) # wait_for_slurm_jobs(user=user, sleep_interval=10) @@ -134,7 +291,7 @@ def main(): else: print("Username could not be read from slurm config file.") qois = call_models( - design_params, hpc_operation_count=2, use_cuda=use_cuda + design_params, hpc_operation_count=2, use_cuda=use_cuda, quiet=args.quiet ) else: qois = call_models( @@ -142,6 +299,7 @@ def main(): hpc_operation_count=0, singularity_hpc=singularity_hpc, use_cuda=use_cuda, + quiet=args.quiet, ) if args.csv: @@ -171,13 +329,20 @@ def main(): return 0 -def call_models(design_params, hpc_operation_count, singularity_hpc=True, use_cuda=False): +def call_models( + design_params, + hpc_operation_count, + singularity_hpc=True, + use_cuda=False, + quiet=False, +): qois = [] for column in design_params.T: input = column.tolist() input.append(hpc_operation_count) input.append(singularity_hpc) input.append(use_cuda) + input.append(quiet) res = model([input]) qois.append(res[0]) diff --git a/run_lattice.py b/run_lattice.py index 30d00e8..6edfaa1 100644 --- a/run_lattice.py +++ b/run_lattice.py @@ -10,24 +10,26 @@ create_lattice_samples_from_param_range, load_lattice_samples_from_npz, delete_slurm_scripts, + load_toml_hyperparameters, ) -from src.general_utils import parse_args +from src.general_utils import parse_lattice_args def main(): - args = parse_args() + args = parse_lattice_args() print(f"HPC mode = { args.use_slurm}") print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") print(f"CUDA mode = {args.cuda}") + print(f"Quiet mode = {args.quiet}") hpc_operation = args.use_slurm # Flag when using HPC cluster load_from_npz = args.load_from_npz use_cuda = args.cuda if use_cuda and hpc_operation: raise SystemExit( - "ERROR: --cuda cannot be combined with --use-slurm. " + "ERROR: --cuda cannot be combined with --slurm. " "GPU mode is supported only with Singularity in non-SLURM runs." ) singularity_hpc = args.use_singularity or use_cuda @@ -36,19 +38,65 @@ def main(): # print(f"Use rectangular_mesh = {args.rectangular_mesh}") rectangular_mesh = False + config_path = args.config or "benchmarks/lattice/hyperparams.toml" + hyper = load_toml_hyperparameters(config_path) + print(f"Lattice hyperparameter config = {config_path}") + + def as_list_or_none(value): + if value is None: + return None + if isinstance(value, list): + return value + return [value] + + def override_csv_column(df, values, column_name, arg_name): + if values is None: + return + values = as_list_or_none(values) + print(f"Overriding {column_name} with {values}") + if len(values) == 1: + df[column_name] = values[0] + elif len(values) == len(df): + df[column_name] = values + else: + raise SystemExit( + f"ERROR: In --csv mode, {arg_name} must provide either one value " + "or exactly one value per CSV row." + ) if args.csv: # --- CSV-driven mode: read design parameters from CSV --- print(f"Reading design parameters from CSV: {args.csv}") df = pd.read_csv(args.csv) + abs_blue_override = ( + args.abs_blue if args.abs_blue is not None else hyper.get("abs_blue") + ) + scatter_white_override = ( + args.scatter_white + if args.scatter_white is not None + else hyper.get("scatter_white") + ) + override_csv_column(df, abs_blue_override, "abs_blue", "--abs-blue") + override_csv_column( + df, scatter_white_override, "scatter_white", "--scatter-white" + ) # Allow overwriting spatial and angular resolution - if args.grid_cell_size is not None: - print(f"Overriding grid_cell_size with {args.grid_cell_size}") - df["grid_cell_size"] = args.grid_cell_size - if args.quad_order is not None: - print(f"Overriding quad_order with {args.quad_order}") - df["quad_order"] = args.quad_order + grid_cell_size_override = ( + args.grid_cell_size + if args.grid_cell_size is not None + else hyper.get("grid_cell_size") + ) + quad_order_override = ( + args.quad_order if args.quad_order is not None else hyper.get("quad_order") + ) + override_csv_column( + df, + grid_cell_size_override, + "grid_cell_size", + "--grid-cell-size", + ) + override_csv_column(df, quad_order_override, "quad_order", "--quad-order") design_params = df[ ["abs_blue", "scatter_white", "grid_cell_size", "quad_order"] @@ -66,15 +114,27 @@ def main(): # --- Define parameter ranges --- # characteristic length of the cells: #grid cells = O(1/cell_size^2) - parameter_range_grid_cell_size = [0.01] + parameter_range_grid_cell_size = as_list_or_none( + args.grid_cell_size if args.grid_cell_size is not None else hyper.get("grid_cell_size") + ) or [0.01] # quadrature order (must be an even number): #velocity grid cells = O(order^2) - parameter_range_quad_order = [4] + parameter_range_quad_order = as_list_or_none( + args.quad_order if args.quad_order is not None else hyper.get("quad_order") + ) or [4] + if any(int(q) % 2 != 0 for q in parameter_range_quad_order): + raise SystemExit("ERROR: --quad-order must be an even number.") # Prescribed range for LATTICE_DSGN_ABSORPTION_BLUE - parameter_range_abs_blue = [10] # default: 10 #10, 50, 100 + parameter_range_abs_blue = as_list_or_none( + args.abs_blue if args.abs_blue is not None else hyper.get("abs_blue") + ) or [10] # Prescribed range for LATTICE_DSGN_SCATTER_WHITE - parameter_range_scatter_white = [1] # default: 1 0.1, 0.5, 1, 5, 10 + parameter_range_scatter_white = as_list_or_none( + args.scatter_white + if args.scatter_white is not None + else hyper.get("scatter_white") + ) or [1] design_params, design_param_names = create_lattice_samples_from_param_range( parameter_range_grid_cell_size, @@ -83,17 +143,24 @@ def main(): parameter_range_scatter_white, ) + def safe_call_models(*model_args, **model_kwargs): + try: + return call_models(*model_args, **model_kwargs) + except (RuntimeError, FileNotFoundError) as e: + raise SystemExit(f"ERROR: {e}") from e + if hpc_operation: print("==== Execute HPC version ====") directory = "./benchmarks/lattice/slurm_scripts/" delete_slurm_scripts(directory) # delete existing slurm files for hohlraum - call_models( + safe_call_models( design_params, hpc_operation_count=1, singularity_hpc=singularity_hpc, rectangular_mesh=rectangular_mesh, use_cuda=use_cuda, + quiet=args.quiet, ) user = read_username_from_config("./slurm_config.txt") @@ -104,16 +171,17 @@ def main(): else: print("Username could not be read from config file.") - qois = call_models( - design_params, hpc_operation_count=2, use_cuda=use_cuda + qois = safe_call_models( + design_params, hpc_operation_count=2, use_cuda=use_cuda, quiet=args.quiet ) else: - qois = call_models( + qois = safe_call_models( design_params, hpc_operation_count=0, rectangular_mesh=rectangular_mesh, singularity_hpc=singularity_hpc, use_cuda=use_cuda, + quiet=args.quiet, ) if args.csv: @@ -149,6 +217,7 @@ def call_models( singularity_hpc=True, rectangular_mesh=False, use_cuda=False, + quiet=False, ): qois = [] for column in design_params: @@ -158,6 +227,7 @@ def call_models( input.append(singularity_hpc) input.append(rectangular_mesh) input.append(use_cuda) + input.append(quiet) res = model([input]) qois.append(res[0]) diff --git a/src/general_utils.py b/src/general_utils.py index 82efe59..2da3eec 100644 --- a/src/general_utils.py +++ b/src/general_utils.py @@ -3,22 +3,43 @@ import os import argparse +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover + import tomli as tomllib -def parse_args(): - parser = argparse.ArgumentParser( - description="Process some flags for HPC and mesh operations." + +def _add_common_execution_args(parser): + parser.add_argument( + "--slurm", + dest="use_slurm", + action="store_true", + help="Submit jobs through SLURM", ) - # Add arguments + # Backward-compatible alias (hidden from --help) parser.add_argument( - "--use-slurm", action="store_true", help="Flag when using HPC cluster" + "--use-slurm", + dest="use_slurm", + action="store_true", + help=argparse.SUPPRESS, ) parser.add_argument( - "--load-from-npz", action="store_true", help="Flag to load from NPZ file" + "--load-from-npz", + action="store_true", + help="Load parameter samples from NPZ input", + ) + parser.add_argument( + "--singularity", + dest="use_singularity", + action="store_true", + help="Run KiT-RT through the CPU Singularity image", ) + # Backward-compatible alias (hidden from --help) parser.add_argument( "--use-singularity", + dest="use_singularity", action="store_true", - help="Flag to use Singularity Container for KiT-RT", + help=argparse.SUPPRESS, ) parser.add_argument( "--csv", @@ -38,12 +59,118 @@ def parse_args(): default=None, help="Override angular resolution (quadrature order) for all runs", ) + + parser.add_argument( + "--config", + type=str, + default=None, + help="Path to TOML hyperparameter config file", + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="Suppress solver stdout/stderr output", + ) parser.add_argument( "--cuda", action="store_true", help="Run with the CUDA-enabled Singularity image and executable", ) + +def _add_lattice_design_args(parser): + parser.add_argument( + "--abs-blue", + type=float, + nargs="+", + default=None, + help="Override absorption coefficient(s) in blue lattice cells", + ) + parser.add_argument( + "--scatter-white", + type=float, + nargs="+", + default=None, + help="Override scattering coefficient(s) in white lattice cells", + ) + + +def _add_hohlraum_design_args(parser): + parser.add_argument( + "--green-center-x", + type=float, + nargs="+", + default=None, + help="Override green-center x value(s) for hohlraum runs", + ) + parser.add_argument( + "--green-center-y", + type=float, + nargs="+", + default=None, + help="Override green-center y value(s) for hohlraum runs", + ) + parser.add_argument( + "--red-right-top", + type=float, + nargs="+", + default=None, + help="Override right-top red boundary value(s) for hohlraum runs", + ) + parser.add_argument( + "--red-right-bottom", + type=float, + nargs="+", + default=None, + help="Override right-bottom red boundary value(s) for hohlraum runs", + ) + parser.add_argument( + "--red-left-top", + type=float, + nargs="+", + default=None, + help="Override left-top red boundary value(s) for hohlraum runs", + ) + parser.add_argument( + "--red-left-bottom", + type=float, + nargs="+", + default=None, + help="Override left-bottom red boundary value(s) for hohlraum runs", + ) + parser.add_argument( + "--horizontal-left", + type=float, + nargs="+", + default=None, + help="Override left horizontal wall value(s) for hohlraum runs", + ) + parser.add_argument( + "--horizontal-right", + type=float, + nargs="+", + default=None, + help="Override right horizontal wall value(s) for hohlraum runs", + ) + + +def parse_lattice_args(): + parser = argparse.ArgumentParser( + description="Run lattice benchmark with lattice-specific design parameters." + ) + _add_common_execution_args(parser) + _add_lattice_design_args(parser) + args = parser.parse_args() + return args + + +def parse_hohlraum_args(): + parser = argparse.ArgumentParser( + description="Run hohlraum benchmark with hohlraum-specific design parameters." + ) + _add_common_execution_args(parser) + _add_hohlraum_design_args(parser) args = parser.parse_args() return args @@ -61,6 +188,14 @@ def replace_next_line(input_file, custom_line, output_file): f.writelines(lines) +def load_toml_hyperparameters(config_path): + if config_path is None or not os.path.exists(config_path): + return {} + with open(config_path, "rb") as f: + data = tomllib.load(f) + return data.get("hyperparameters", {}) + + def get_user_job_count(user): """ Get the number of jobs in the SLURM queue for the specified user. diff --git a/src/models/hohlraum.py b/src/models/hohlraum.py index 35b9d95..d858123 100644 --- a/src/models/hohlraum.py +++ b/src/models/hohlraum.py @@ -32,6 +32,7 @@ def model(parameters): hpc_operation = parameters[0][10] singularity_hpc = parameters[0][11] use_cuda = bool(parameters[0][12]) if len(parameters[0]) > 12 else False + quiet = bool(parameters[0][13]) if len(parameters[0]) > 13 else False if use_cuda: singularity_hpc = 1 if use_cuda and hpc_operation == 1: @@ -121,10 +122,14 @@ def model(parameters): # Step 5: Run the C++ simulation if singularity_hpc == 1: print("Running simulation with singularity") - run_cpp_simulation_containerized(generated_cfg_file, use_cuda=use_cuda) + run_cpp_simulation_containerized( + generated_cfg_file, + use_cuda=use_cuda, + quiet=quiet, + ) else: print("Running simulation without singularity") - run_cpp_simulation(generated_cfg_file) + run_cpp_simulation(generated_cfg_file, quiet=quiet) elif hpc_operation == 1: # Write slurm file write_slurm_file( diff --git a/src/models/lattice.py b/src/models/lattice.py index 503d81f..14ab797 100644 --- a/src/models/lattice.py +++ b/src/models/lattice.py @@ -31,6 +31,7 @@ def model(parameters): singularity_hpc = parameters[0][5] rectangular_mesh = parameters[0][6] use_cuda = bool(parameters[0][7]) if len(parameters[0]) > 7 else False + quiet = bool(parameters[0][8]) if len(parameters[0]) > 8 else False if use_cuda: singularity_hpc = 1 if use_cuda and hpc_operation == 1: @@ -98,10 +99,14 @@ def model(parameters): # Step 5: Run the C++ simulation if singularity_hpc == 1: print("Running lattice simulation with singularity") - run_cpp_simulation_containerized(generated_cfg_file, use_cuda=use_cuda) + run_cpp_simulation_containerized( + generated_cfg_file, + use_cuda=use_cuda, + quiet=quiet, + ) else: print("Running lattice simulation without singularity") - run_cpp_simulation(generated_cfg_file) + run_cpp_simulation(generated_cfg_file, quiet=quiet) elif hpc_operation == 1: # Write slurm file write_slurm_file( @@ -117,8 +122,14 @@ def model(parameters): log_filename = generate_log_filename(kitrt_parameters) if log_filename: + csv_path = subfolder + log_filename + ".csv" + if not os.path.exists(csv_path): + raise FileNotFoundError( + "Expected lattice log CSV was not generated: " + f"{csv_path}. The KiT-RT solver likely failed before writing output." + ) # Step 7: Read and convert the data from the CSV log file to a DataFrame - log_data = read_csv_file(subfolder + log_filename + ".csv") + log_data = read_csv_file(csv_path) log_data["LATTICE_DSGN_ABSORPTION_BLUE"] = absorption_blue_value log_data["LATTICE_DSGN_SCATTER_WHITE"] = scatter_white_value quantities_of_interest = [ diff --git a/src/simulation_utils.py b/src/simulation_utils.py index 330ad99..1633d0b 100644 --- a/src/simulation_utils.py +++ b/src/simulation_utils.py @@ -4,7 +4,46 @@ from src.general_utils import get_user_job_count -def run_cpp_simulation(config_file): +def _run_and_raise(command, mode_label, quiet=False): + try: + if quiet: + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + else: + result = subprocess.run(command) + except FileNotFoundError as e: + raise RuntimeError( + f"{mode_label} run failed: executable not found: {command[0]}" + ) from e + + if result.returncode != 0: + stderr = "" + if quiet: + stderr = (result.stderr or "").strip() + hint = "" + if "error while loading shared libraries" in stderr: + hint = ( + " Hint: missing system libraries in local mode. " + "Use --singularity (or --cuda) to run inside the container." + ) + if not stderr: + stderr = ( + "See solver output above." + if not quiet + else "No stderr captured." + ) + raise RuntimeError( + f"{mode_label} run failed with return code {result.returncode}. " + f"Command: {' '.join(command)}. " + f"Stderr: {stderr}{hint}" + ) + + +def run_cpp_simulation(config_file, quiet=False): # Path to the C++ executable print("here") current_path = os.getcwd() @@ -18,18 +57,11 @@ def run_cpp_simulation(config_file): command = [cpp_executable_path, config_file] print(command) - try: - # Run the C++ executable - subprocess.run(command, check=True) - print("C++ simulation completed successfully.") - except subprocess.CalledProcessError as e: - print(f"Error running C++ simulation. Return code: {e.returncode}") - # You can handle the error as needed - - # You can handle the error as needed + _run_and_raise(command, "Local KiT-RT", quiet=quiet) + print("C++ simulation completed successfully.") -def run_cpp_simulation_containerized(config_file, use_cuda=False): +def run_cpp_simulation_containerized(config_file, use_cuda=False, quiet=False): # Path to the C++ executable if use_cuda: singularity_command = [ @@ -51,13 +83,8 @@ def run_cpp_simulation_containerized(config_file, use_cuda=False): # Command to run the C++ executable with the provided config file - try: - # Run the C++ executable - subprocess.run(singularity_command, check=True) - print("C++ simulation completed successfully.") - except subprocess.CalledProcessError as e: - print(f"Error running C++ simulation. Return code: {e.returncode}") - # You can handle the error as needed + _run_and_raise(singularity_command, "Containerized KiT-RT", quiet=quiet) + print("C++ simulation completed successfully.") def execute_slurm_scripts(directory, user, max_jobs=60, sleep_time=30): From 9a96bb3bc37e23171e9a798f94e242f72a0ff636 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 12 Feb 2026 13:09:47 -0500 Subject: [PATCH 5/9] update repo name --- .gitignore | 2 +- README.md | 31 +-- explore_exploit_hohlraum.py | 2 +- install_KiT-RT.sh | 6 +- poetry.lock | 409 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 17 ++ src/config_utils.py | 4 +- src/simulation_utils.py | 10 +- update_KiT-RT.sh | 2 +- 9 files changed, 455 insertions(+), 28 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore index 3a9d995..09c4dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ *.sh -./KiT-RT/ +./kitrt_code/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index a486e68..c80fa6b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# CharmKiT +# charm_kit -CharmKiT is a benchmarking suite for the CharmNet project, providing automated parameter studies and test case management for the [KiT-RT PDE simulator](https://kit-rt.readthedocs.io/en/develop/index.html). It enables reproducible runs of radiative transfer test cases such as the lattice and hohlraum setups, using Python scripts to manage parameter sweeps, configuration, and result collection. CharmKiT supports both high-performance computing (HPC) and local (no-HPC) execution modes, leveraging Singularity containers for reproducibility. +charm_kit is a benchmarking suite for the CharmNet project, providing automated parameter studies and test case management for the [KiT-RT PDE simulator](https://kit-rt.readthedocs.io/en/develop/index.html). It enables reproducible runs of radiative transfer test cases such as the lattice and hohlraum setups, using Python scripts to manage parameter sweeps, configuration, and result collection. charm_kit supports both high-performance computing (HPC) and local (no-HPC) execution modes, leveraging Singularity containers for reproducibility. @@ -11,9 +11,9 @@ Preliminaries: 1. Install [Singularity](https://docs.sylabs.io/guides/latest/user-guide/quick_start.html) on your system. -2. Clone the `CharmKiT` Github repository: +2. Clone the `charm_kit` Github repository: ``` - git clone git@github.com:ScSteffen/CharmKiT.git + git clone git@github.com:ScSteffen/charm_kit.git ``` 3. Install Poetry and create the project environment: @@ -26,17 +26,18 @@ Preliminaries: ``` bash install_KiT-RT.sh ``` + The repository is installed into `./kitrt_code/`. The installer always builds the CPU Singularity image + binary. If a CUDA GPU is detected (`nvidia-smi`), it also builds the CUDA Singularity image and `build_singularity_cuda` binary automatically. If updating KiT-RT: ``` bash update_KiT-RT.sh ``` - If on a cluster without root, build the container locally and upload it to `CharmKiT/KiT-RT/tools/singularity/`. + If on a cluster without root, build the container locally and upload it to `charm_kit/kitrt_code/tools/singularity/`. -## How CharmKiT Works +## How charm_kit Works -CharmKiT automates the setup, execution, and result collection for radiative transfer test cases using the KiT-RT solver. The workflow is managed by Python scripts (e.g., `run_hohlraum.py`, `run_lattice.py`) that: +charm_kit automates the setup, execution, and result collection for radiative transfer test cases using the KiT-RT solver. The workflow is managed by Python scripts (e.g., `run_hohlraum.py`, `run_lattice.py`) that: - Define parameter sweeps for each test case (e.g., mesh size, quadrature order, absorption/scattering coefficients). - Generate the necessary configuration files for KiT-RT. @@ -46,9 +47,9 @@ CharmKiT automates the setup, execution, and result collection for radiative tra Scripts support both HPC (SLURM) and local (no-HPC) execution. -## Running CharmKiT Scripts +## Running charm_kit Scripts -CharmKiT provides test-case drivers: +charm_kit provides test-case drivers: - `run_lattice.py` - `run_hohlraum.py` @@ -112,7 +113,7 @@ Precedence for hyperparameters is: python3 run_hohlraum.py ``` - Uses local executable: `./KiT-RT/build/KiT-RT`. + Uses local executable: `./kitrt_code/build/KiT-RT`. 2. **Local mode + Singularity (CPU)** @@ -123,7 +124,7 @@ Precedence for hyperparameters is: ``` Uses image/executable: - `KiT-RT/tools/singularity/kit_rt.sif` and `./KiT-RT/build_singularity/KiT-RT`. + `kitrt_code/tools/singularity/kit_rt.sif` and `./kitrt_code/build_singularity/KiT-RT`. 3. **Local mode + Singularity + GPU** @@ -134,7 +135,7 @@ Precedence for hyperparameters is: ``` Uses image/executable: - `KiT-RT/tools/singularity/kit_rt_MPI_cuda.sif` and `./KiT-RT/build_singularity_cuda/KiT-RT`. + `kitrt_code/tools/singularity/kit_rt_MPI_cuda.sif` and `./kitrt_code/build_singularity_cuda/KiT-RT`. 4. **SLURM mode, raw (no Singularity)** @@ -144,7 +145,7 @@ Precedence for hyperparameters is: python3 run_hohlraum.py --slurm ``` - Generated SLURM scripts call: `srun ./KiT-RT/build/KiT-RT ...`. + Generated SLURM scripts call: `srun ./kitrt_code/build/KiT-RT ...`. 5. **SLURM mode + Singularity (CPU)** @@ -155,7 +156,7 @@ Precedence for hyperparameters is: ``` Generated SLURM scripts call: - `singularity exec KiT-RT/tools/singularity/kit_rt.sif ./KiT-RT/build_singularity/KiT-RT ...`. + `singularity exec kitrt_code/tools/singularity/kit_rt.sif ./kitrt_code/build_singularity/KiT-RT ...`. ### Not supported @@ -210,7 +211,7 @@ For more details on the scientific background and test case motivation, see the ## Citation -If you use CharmKiT or the provided benchmarks in your research, please cite: +If you use charm_kit or the provided benchmarks in your research, please cite: ```bibtex @misc{schotthöfer2025referencesolutionslinearradiation, diff --git a/explore_exploit_hohlraum.py b/explore_exploit_hohlraum.py index fb2833d..ae222c5 100644 --- a/explore_exploit_hohlraum.py +++ b/explore_exploit_hohlraum.py @@ -131,7 +131,7 @@ def main(): ] ) - # Update the ranges of the variables to match what CharmKiT needs + # Update the ranges of the variables to match what charm_kit needs # marginals = [stats.uniform(0.1, 0.2)]*4 + [stats.uniform(0.025, 0.05)]*2 marginals = [ # first entry: lower bound, second entry: length of intervall stats.uniform(0.3, 0.2), # left red top diff --git a/install_KiT-RT.sh b/install_KiT-RT.sh index 8c8d56c..aad79e6 100755 --- a/install_KiT-RT.sh +++ b/install_KiT-RT.sh @@ -11,10 +11,10 @@ has_cuda_gpu() { } # clone KiT-RT -git clone "${KITRT_REPO_URL}" KiT-RT +git clone "${KITRT_REPO_URL}" kitrt_code -# go to KiT-RT directory -cd KiT-RT +# go to kitrt_code directory +cd kitrt_code # keep origin synchronized with configured upstream and load submodules git remote set-url origin "${KITRT_REPO_URL}" diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..802d3c6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,409 @@ +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. + +[[package]] +name = "gmsh" +version = "4.15.0" +description = "Gmsh is a three-dimensional finite element mesh generator with built-in pre- and post-processing facilities." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "gmsh-4.15.0-py2.py3-none-macosx_10_15_x86_64.whl", hash = "sha256:1a7951836982f97e21e8b2de106fe1e8455cde53719191a3950784b56e2606af"}, + {file = "gmsh-4.15.0-py2.py3-none-macosx_12_0_arm64.whl", hash = "sha256:042f76562244dfbad46edfeacaa092c8c95a9df8fb4fd7611771477f0031be0d"}, + {file = "gmsh-4.15.0-py2.py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:998527cb172f2529c6beca7334a6ca788fa54fbb3d4f817deb0903005d49344d"}, + {file = "gmsh-4.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:a5b9867f88af59aff269a93abbd009551fbc263eb94a165a25a0c8072bae13db"}, +] + +[[package]] +name = "numpy" +version = "2.2.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +] + +[[package]] +name = "numpy" +version = "2.4.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7"}, + {file = "numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73"}, + {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1"}, + {file = "numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32"}, + {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390"}, + {file = "numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413"}, + {file = "numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda"}, + {file = "numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695"}, + {file = "numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e"}, + {file = "numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27"}, + {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548"}, + {file = "numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f"}, + {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460"}, + {file = "numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba"}, + {file = "numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f"}, + {file = "numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85"}, + {file = "numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98"}, + {file = "numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef"}, + {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7"}, + {file = "numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499"}, + {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb"}, + {file = "numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7"}, + {file = "numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110"}, + {file = "numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622"}, + {file = "numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913"}, + {file = "numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab"}, + {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82"}, + {file = "numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f"}, + {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554"}, + {file = "numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257"}, + {file = "numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657"}, + {file = "numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b"}, + {file = "numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1"}, + {file = "numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74"}, + {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a"}, + {file = "numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325"}, + {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909"}, + {file = "numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a"}, + {file = "numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a"}, + {file = "numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75"}, + {file = "numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef"}, + {file = "numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d"}, + {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8"}, + {file = "numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5"}, + {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e"}, + {file = "numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a"}, + {file = "numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443"}, + {file = "numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236"}, + {file = "numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0"}, + {file = "numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0"}, + {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, +] + +[[package]] +name = "pandas" +version = "2.3.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c"}, + {file = "pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1"}, + {file = "pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250"}, + {file = "pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4"}, + {file = "pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523"}, + {file = "pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66"}, + {file = "pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791"}, + {file = "pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151"}, + {file = "pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53"}, + {file = "pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908"}, + {file = "pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98"}, + {file = "pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084"}, + {file = "pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713"}, + {file = "pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d"}, + {file = "pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c"}, + {file = "pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493"}, + {file = "pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5"}, + {file = "pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78"}, + {file = "pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86"}, + {file = "pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0"}, + {file = "pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c"}, + {file = "pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6"}, + {file = "pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3"}, + {file = "pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec"}, + {file = "pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450"}, + {file = "pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788"}, + {file = "pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2"}, + {file = "pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff"}, + {file = "pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73"}, + {file = "pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9"}, + {file = "pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa"}, + {file = "pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b"}, +] + +[package.dependencies] +numpy = {version = ">=1.22.4", markers = "python_version < \"3.11\""} +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "pandas" +version = "3.0.0" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850"}, + {file = "pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2"}, + {file = "pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5"}, + {file = "pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae"}, + {file = "pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9"}, + {file = "pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d"}, + {file = "pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd"}, + {file = "pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b"}, + {file = "pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd"}, + {file = "pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740"}, + {file = "pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801"}, + {file = "pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a"}, + {file = "pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb"}, + {file = "pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f"}, + {file = "pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1"}, + {file = "pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0"}, + {file = "pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6"}, + {file = "pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f"}, + {file = "pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70"}, + {file = "pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e"}, + {file = "pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3"}, + {file = "pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e"}, + {file = "pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e"}, + {file = "pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be"}, + {file = "pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98"}, + {file = "pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327"}, + {file = "pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb"}, + {file = "pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812"}, + {file = "pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08"}, + {file = "pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c"}, + {file = "pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa"}, + {file = "pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b"}, + {file = "pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe"}, + {file = "pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70"}, + {file = "pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d"}, + {file = "pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986"}, + {file = "pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49"}, + {file = "pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7"}, + {file = "pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8"}, + {file = "pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73"}, + {file = "pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2"}, + {file = "pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a"}, + {file = "pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084"}, + {file = "pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721"}, + {file = "pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac"}, + {file = "pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb"}, + {file = "pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa"}, + {file = "pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f"}, +] + +[package.dependencies] +numpy = {version = ">=1.26.0", markers = "python_version < \"3.14\""} +python-dateutil = ">=2.8.2" +tzdata = {version = "*", markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\""} + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "adbc-driver-sqlite (>=1.2.0)", "beautifulsoup4 (>=4.12.3)", "bottleneck (>=1.4.2)", "fastparquet (>=2024.11.0)", "fsspec (>=2024.10.0)", "gcsfs (>=2024.10.0)", "html5lib (>=1.1)", "hypothesis (>=6.116.0)", "jinja2 (>=3.1.5)", "lxml (>=5.3.0)", "matplotlib (>=3.9.3)", "numba (>=0.60.0)", "numexpr (>=2.10.2)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.5)", "psycopg2 (>=2.9.10)", "pyarrow (>=13.0.0)", "pyiceberg (>=0.8.1)", "pymysql (>=1.1.1)", "pyreadstat (>=1.2.8)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)", "python-calamine (>=0.3.0)", "pytz (>=2024.2)", "pyxlsb (>=1.0.10)", "qtpy (>=2.4.2)", "s3fs (>=2024.10.0)", "scipy (>=1.14.1)", "tables (>=3.10.1)", "tabulate (>=0.9.0)", "xarray (>=2024.10.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.2.0)", "zstandard (>=0.23.0)"] +aws = ["s3fs (>=2024.10.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.4.2)"] +compression = ["zstandard (>=0.23.0)"] +computation = ["scipy (>=1.14.1)", "xarray (>=2024.10.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.5)", "python-calamine (>=0.3.0)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.2.0)"] +feather = ["pyarrow (>=13.0.0)"] +fss = ["fsspec (>=2024.10.0)"] +gcp = ["gcsfs (>=2024.10.0)"] +hdf5 = ["tables (>=3.10.1)"] +html = ["beautifulsoup4 (>=4.12.3)", "html5lib (>=1.1)", "lxml (>=5.3.0)"] +iceberg = ["pyiceberg (>=0.8.1)"] +mysql = ["SQLAlchemy (>=2.0.36)", "pymysql (>=1.1.1)"] +output-formatting = ["jinja2 (>=3.1.5)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=13.0.0)"] +performance = ["bottleneck (>=1.4.2)", "numba (>=0.60.0)", "numexpr (>=2.10.2)"] +plot = ["matplotlib (>=3.9.3)"] +postgresql = ["SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "psycopg2 (>=2.9.10)"] +pyarrow = ["pyarrow (>=13.0.0)"] +spss = ["pyreadstat (>=1.2.8)"] +sql-other = ["SQLAlchemy (>=2.0.36)", "adbc-driver-postgresql (>=1.2.0)", "adbc-driver-sqlite (>=1.2.0)"] +test = ["hypothesis (>=6.116.0)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)"] +timezone = ["pytz (>=2024.2)"] +xml = ["lxml (>=5.3.0)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "tzdata" +version = "2025.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\" or sys_platform == \"emscripten\" or python_version == \"3.10\"" +files = [ + {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"}, + {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"}, +] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.10,<3.13" +content-hash = "29584e446de63325ccc1f34d02224ec9ef7b54f8c179bbad63e0f39471df94af" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3ec4fe5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "charm_kit" +version = "0.1.0" +description = "Benchmarking workflows for KiT-RT lattice and hohlraum test cases." +authors = ["charm_kit Developers"] +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.10,<3.13" +gmsh = "*" +numpy = "*" +pandas = "*" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/src/config_utils.py b/src/config_utils.py index 73db6b4..0df603d 100644 --- a/src/config_utils.py +++ b/src/config_utils.py @@ -405,14 +405,14 @@ def write_slurm_file( if lines: if singularity: lines[-1] = ( - "singularity exec KiT-RT/tools/singularity/kit_rt.sif ./KiT-RT/build_singularity/KiT-RT " + "singularity exec kitrt_code/tools/singularity/kit_rt.sif ./kitrt_code/build_singularity/KiT-RT " + subfolder + unique_name + ".cfg\n" ) else: - lines[-1] = "srun ./KiT-RT/build/KiT-RT " + subfolder + unique_name + ".cfg\n" + lines[-1] = "srun ./kitrt_code/build/KiT-RT " + subfolder + unique_name + ".cfg\n" # Write the modified lines to the output file with open(output_slurm_dir + unique_name + ".sh", "w") as file: diff --git a/src/simulation_utils.py b/src/simulation_utils.py index 1633d0b..6b76c34 100644 --- a/src/simulation_utils.py +++ b/src/simulation_utils.py @@ -51,7 +51,7 @@ def run_cpp_simulation(config_file, quiet=False): # Print the current path print(f"The current working directory is: {current_path}") print(config_file) - cpp_executable_path = "./KiT-RT/build/KiT-RT" # mpirun -np 4 + cpp_executable_path = "./kitrt_code/build/KiT-RT" # mpirun -np 4 # Command to run the C++ executable with the provided config file command = [cpp_executable_path, config_file] @@ -68,16 +68,16 @@ def run_cpp_simulation_containerized(config_file, use_cuda=False, quiet=False): "singularity", "exec", "--nv", - "KiT-RT/tools/singularity/kit_rt_MPI_cuda.sif", - "./KiT-RT/build_singularity_cuda/KiT-RT", + "kitrt_code/tools/singularity/kit_rt_MPI_cuda.sif", + "./kitrt_code/build_singularity_cuda/KiT-RT", config_file, ] else: singularity_command = [ "singularity", "exec", - "KiT-RT/tools/singularity/kit_rt.sif", - "./KiT-RT/build_singularity/KiT-RT", + "kitrt_code/tools/singularity/kit_rt.sif", + "./kitrt_code/build_singularity/KiT-RT", config_file, ] diff --git a/update_KiT-RT.sh b/update_KiT-RT.sh index 3c6ee5b..b1735ad 100755 --- a/update_KiT-RT.sh +++ b/update_KiT-RT.sh @@ -10,7 +10,7 @@ has_cuda_gpu() { nvidia-smi -L >/dev/null 2>&1 } -cd KiT-RT +cd kitrt_code git remote set-url origin "${KITRT_REPO_URL}" git fetch origin From 871cc7fadb127642b855bd0a3947fffaa5bbc87d Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Feb 2026 09:57:21 -0500 Subject: [PATCH 6/9] added toml files with hyperparams --- benchmarks/hohlraum/hyperparams.toml | 14 ++++++++++++++ benchmarks/lattice/hyperparams.toml | 10 ++++++++++ 2 files changed, 24 insertions(+) create mode 100644 benchmarks/hohlraum/hyperparams.toml create mode 100644 benchmarks/lattice/hyperparams.toml diff --git a/benchmarks/hohlraum/hyperparams.toml b/benchmarks/hohlraum/hyperparams.toml new file mode 100644 index 0000000..c8e2208 --- /dev/null +++ b/benchmarks/hohlraum/hyperparams.toml @@ -0,0 +1,14 @@ +[hyperparameters] +# Spatial and angular resolution ranges +grid_cell_size = [0.0075] +quad_order = [6] + +# Geometry ranges +green_center_x = [-0.1, 0.0, 0.1] +green_center_y = [-0.075, 0.0, 0.075] +red_right_top = [0.3, 0.4, 0.5] +red_right_bottom = [-0.5, -0.4, -0.3] +red_left_top = [0.3, 0.4, 0.5] +red_left_bottom = [-0.5, -0.4, -0.3] +horizontal_left = [-0.63, -0.6, -0.5] +horizontal_right = [0.5, 0.6, 0.63] diff --git a/benchmarks/lattice/hyperparams.toml b/benchmarks/lattice/hyperparams.toml new file mode 100644 index 0000000..52c9931 --- /dev/null +++ b/benchmarks/lattice/hyperparams.toml @@ -0,0 +1,10 @@ +[hyperparameters] +# Spatial resolution range (characteristic mesh cell size) +grid_cell_size = [0.01] + +# Angular quadrature order range (must be even) +quad_order = [4] + +# Material parameter ranges +abs_blue = [10.0] +scatter_white = [1.0] From 74b8ed886b8a0da4cf7e7060c142d6721524eb0b Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Feb 2026 10:03:26 -0500 Subject: [PATCH 7/9] added tests --- .github/workflows/tests.yml | 40 ++ README.md | 4 +- benchmarks/half_lattice/.DS_Store | Bin 6148 -> 0 bytes benchmarks/half_lattice/cfg_files/.gitkeep | 0 benchmarks/half_lattice/half_lattice.cfg | 54 --- benchmarks/half_lattice/mesh/.DS_Store | Bin 6148 -> 0 bytes .../mesh/half_lattice_homogeneous.geo | 214 --------- .../mesh/half_lattice_rectangular.geo | 27 -- .../half_lattice/slurm_scripts/.gitkeep | 0 .../quarter_hohlraum/cfg_files/.gitkeep | 0 .../mesh/quarter_hohlraum.geo | 144 ------ .../mesh/quarter_hohlraum_rectangular.geo | 29 -- .../mesh/quarter_hohlraum_variable.geo | 116 ----- .../quarter_hohlraum/quarter_hohlraum.cfg | 61 --- .../quarter_hohlraum/slurm_scripts/.gitkeep | 0 pyproject.toml | 3 + requirements.txt | 3 +- run_hohlraum.py | 5 - run_lattice.py | 9 - src/general_utils.py | 5 - tests/test_cli_and_runner_flags.py | 436 ++++++++++++++++++ tests/test_model_operations.py | 208 +++++++++ update_KiT-RT.sh | 38 -- 23 files changed, 692 insertions(+), 704 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 benchmarks/half_lattice/.DS_Store delete mode 100644 benchmarks/half_lattice/cfg_files/.gitkeep delete mode 100644 benchmarks/half_lattice/half_lattice.cfg delete mode 100644 benchmarks/half_lattice/mesh/.DS_Store delete mode 100644 benchmarks/half_lattice/mesh/half_lattice_homogeneous.geo delete mode 100644 benchmarks/half_lattice/mesh/half_lattice_rectangular.geo delete mode 100644 benchmarks/half_lattice/slurm_scripts/.gitkeep delete mode 100644 benchmarks/quarter_hohlraum/cfg_files/.gitkeep delete mode 100644 benchmarks/quarter_hohlraum/mesh/quarter_hohlraum.geo delete mode 100644 benchmarks/quarter_hohlraum/mesh/quarter_hohlraum_rectangular.geo delete mode 100644 benchmarks/quarter_hohlraum/mesh/quarter_hohlraum_variable.geo delete mode 100644 benchmarks/quarter_hohlraum/quarter_hohlraum.cfg delete mode 100644 benchmarks/quarter_hohlraum/slurm_scripts/.gitkeep create mode 100644 tests/test_cli_and_runner_flags.py create mode 100644 tests/test_model_operations.py delete mode 100755 update_KiT-RT.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a23ac1d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: + +jobs: + pytest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.10" + - "3.11" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: | + requirements.txt + pyproject.toml + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run pytest + run: pytest -q diff --git a/README.md b/README.md index c80fa6b..8b67ace 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # charm_kit +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![GitHub Stars](https://img.shields.io/github/stars/KiT-RT/CharmKiT)](https://github.com/KiT-RT/CharmKiT/stargazers) +[![Tests](https://github.com/KiT-RT/CharmKiT/actions/workflows/tests.yml/badge.svg)](https://github.com/KiT-RT/CharmKiT/actions/workflows/tests.yml) + charm_kit is a benchmarking suite for the CharmNet project, providing automated parameter studies and test case management for the [KiT-RT PDE simulator](https://kit-rt.readthedocs.io/en/develop/index.html). It enables reproducible runs of radiative transfer test cases such as the lattice and hohlraum setups, using Python scripts to manage parameter sweeps, configuration, and result collection. charm_kit supports both high-performance computing (HPC) and local (no-HPC) execution modes, leveraging Singularity containers for reproducibility. @@ -68,7 +71,6 @@ Execution and I/O flags: - `--slurm`: Submit jobs through SLURM. - `--singularity`: Run KiT-RT through the CPU Singularity image. - `--cuda`: Run KiT-RT through the CUDA Singularity image (`--nv` is added automatically). -- `--load-from-npz`: Load parameter samples from NPZ input (script-dependent behavior). - `--csv CSV`: Read design parameters from CSV and write QOIs back to that CSV. - `--config CONFIG`: Path to a TOML hyperparameter file. - `-q`, `--quiet`: Suppress solver stdout/stderr output. diff --git a/benchmarks/half_lattice/.DS_Store b/benchmarks/half_lattice/.DS_Store deleted file mode 100644 index 8fbd4fff7db5bf469e0dc83396499e37979b27ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~JqiLr422WjLa^D=avBfd4F=H@cmeSTEEEf|pQHQogWzf{A}^49lgxx=U$L_h z5nbL73z1$#CUB$7EeuSNcXE-V+~jn)?a#aG^_*u$Njn4YWw4*y1QnnHRDcRl0V?oA z3fR33n@od@RDcRlfu{oYeJF5aO}3zaIuLvW06Uc3u=ZI3ELH$(vIS9rX|#gTsy>ET z-P^&E=W4PAqg^zI56vg5O))TycF}?arq#hf1*pJ4fpO%$-T!O&r}=--!juY7fj?6~ zyVYj3#7pJf`u2KO-)7a;4G#KogqNQHBz6_g;BMG2wg79g1yO % -% Date 08.01.2024 % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% -% ----IO specification ---- -% -OUTPUT_DIR = result -OUTPUT_FILE = half_lattice -LOG_DIR = result/logs -LOG_FILE = half_lattice -MESH_FILE = mesh/half_lattice_p0.02.su2 -FORCE_CONNECTIVITY_RECOMPUTE = YES -% -% --- Problem definition --- -% -PROBLEM = HALF_LATTICE -TIME_FINAL = 3.2 -SPATIAL_DIM = 2 -SOURCE_MAGNITUDE = 1.0 -LATTICE_DSGN_ABSORPTION_BLUE = 10 -LATTICE_DSGN_SCATTER_WHITE = 1 -% -% ---- Solver specifications ---- -% -% Solver type -HPC_SOLVER = YES -SOLVER = SN_SOLVER -% CFL number -CFL_NUMBER = 0.4 -% Space Integration order -RECONS_ORDER = 1 -% Time integration order -TIME_INTEGRATION_ORDER = 1 -% -% ---- Boundary Conditions ---- -% -BC_NEUMANN = ( reflecting, void ) -% -% ----- Quadrature Specification --- -% -QUAD_TYPE = GAUSS_LEGENDRE_TENSORIZED_2D -QUAD_ORDER = 10 -% -% ----- Output ---- -% -VOLUME_OUTPUT = (MINIMAL) -VOLUME_OUTPUT_FREQUENCY = 0 -SCREEN_OUTPUT = (ITER, MASS, WALL_TIME, RMS_FLUX, CUR_PARTICLE_ABSORPTION, TOTAL_PARTICLE_ABSORPTION, CUR_OUTFLOW_P1, TOTAL_OUTFLOW_P1, CUR_OUTFLOW_P2, TOTAL_OUTFLOW_P2 ) -SCREEN_OUTPUT_FREQUENCY = 100 -HISTORY_OUTPUT = (ITER, MASS, WALL_TIME, RMS_FLUX, CUR_PARTICLE_ABSORPTION, TOTAL_PARTICLE_ABSORPTION, CUR_OUTFLOW_P1, TOTAL_OUTFLOW_P1, CUR_OUTFLOW_P2, TOTAL_OUTFLOW_P2 ) -HISTORY_OUTPUT_FREQUENCY = 1 diff --git a/benchmarks/half_lattice/mesh/.DS_Store b/benchmarks/half_lattice/mesh/.DS_Store deleted file mode 100644 index fcdcf67ee1cba3ec23ca7a19586890e920199b76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mVfW*>~F$)La1`&c2Z~+92AdrAPN9Xxxp)jKgJxlf%JGI)rp{Yeg z*U!^Rq!*DH+$c*615@OkJmey`)BSipT(4&b zkk95LdL}*!6`%s+P{6(q1#Ya#7W7XCf{y^;6lFK8eU<==6~LNoK~!KGtzfjOk0Dm~ zcCh4iHQ9pEE}FxK=AG517??)8Xh8zg>R_M(RA8V$7kO{z{}%pj{vWh3r2 % -% Date 27.02.2024 % -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -% ---- File specifications ---- -% -OUTPUT_DIR = result -% Output file -OUTPUT_FILE = quarter_hohlraum -% Log directory -LOG_DIR = result/logs -% Log file -LOG_FILE = quarter_hohlraum -% Mesh File -MESH_FILE = mesh/quarter_hohlraum.su2 -FORCE_CONNECTIVITY_RECOMPUTE = YES -% -% ---- Problem specifications ---- -% -PROBLEM = QUARTER_HOHLRAUM -SPATIAL_DIM = 2 -TIME_FINAL = 2.6 -% -% ---- Design Parameters --- -N_SAMPLING_PTS_LINE_GREEN = 100 -POS_RED_RIGHT_TOP = 0.4 -POS_BORDER_RED_RIGHT = 0.6 -% -% ---- Solver specifications ---- -% - -% Solver type -HPC_SOLVER = YES -SOLVER = SN_SOLVER -% CFL number -CFL_NUMBER = 0.5 -% Space Integration order -RECONS_ORDER =2 -% Time integration order -TIME_INTEGRATION_ORDER = 1 -% -% ---- Boundary Conditions ---- -% -BC_NEUMANN = ( void, inflow ) -% -% ---- Quadrature ---- -% -QUAD_TYPE = GAUSS_LEGENDRE_TENSORIZED_2D -QUAD_ORDER = 10 -% -% ----- Output ---- -% -VOLUME_OUTPUT = (MINIMAL, MOMENTS) -VOLUME_OUTPUT_FREQUENCY = 0 -SCREEN_OUTPUT = ( ITER, WALL_TIME, MASS, RMS_FLUX, VTK_OUTPUT, CSV_OUTPUT, CUR_OUTFLOW, TOTAL_OUTFLOW, MAX_OUTFLOW, TOTAL_PARTICLE_ABSORPTION_CENTER, TOTAL_PARTICLE_ABSORPTION_VERTICAL, TOTAL_PARTICLE_ABSORPTION_HORIZONTAL, PROBE_MOMENT_TIME_TRACE, VAR_ABSORPTION_GREEN) -SCREEN_OUTPUT_FREQUENCY = 100 -HISTORY_OUTPUT = ( ITER, WALL_TIME, MASS, RMS_FLUX, VTK_OUTPUT, CSV_OUTPUT, CUR_OUTFLOW, TOTAL_OUTFLOW, MAX_OUTFLOW, TOTAL_PARTICLE_ABSORPTION_CENTER, TOTAL_PARTICLE_ABSORPTION_VERTICAL, TOTAL_PARTICLE_ABSORPTION_HORIZONTAL, PROBE_MOMENT_TIME_TRACE, VAR_ABSORPTION_GREEN) -HISTORY_OUTPUT_FREQUENCY = 1 - diff --git a/benchmarks/quarter_hohlraum/slurm_scripts/.gitkeep b/benchmarks/quarter_hohlraum/slurm_scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml index 3ec4fe5..f670cf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,9 @@ gmsh = "*" numpy = "*" pandas = "*" +[tool.poetry.group.dev.dependencies] +pytest = "*" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 4bf9135..87b15df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ gmsh numpy -pandas \ No newline at end of file +pandas +pytest diff --git a/run_hohlraum.py b/run_hohlraum.py index 5b99e14..2ad0071 100644 --- a/run_hohlraum.py +++ b/run_hohlraum.py @@ -7,7 +7,6 @@ from src.simulation_utils import execute_slurm_scripts, wait_for_slurm_jobs from src.general_utils import ( create_hohlraum_samples_from_param_range, - load_hohlraum_samples_from_csv, delete_slurm_scripts, load_toml_hyperparameters, ) @@ -17,13 +16,11 @@ def main(): args = parse_hohlraum_args() print(f"HPC mode = { args.use_slurm}") - print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") print(f"CUDA mode = {args.cuda}") print(f"Quiet mode = {args.quiet}") hpc_operation = args.use_slurm # Flag when using HPC cluster - load_from_npz = args.load_from_npz use_cuda = args.cuda if use_cuda and hpc_operation: raise SystemExit( @@ -172,8 +169,6 @@ def override_csv_column(df, values, column_name, arg_name): "grid_quad_order", ] ) - elif load_from_npz: - design_params, design_param_names = load_hohlraum_samples_from_csv() else: # --- Define parameter ranges --- diff --git a/run_lattice.py b/run_lattice.py index 6edfaa1..4c2ec55 100644 --- a/run_lattice.py +++ b/run_lattice.py @@ -8,7 +8,6 @@ from src.simulation_utils import execute_slurm_scripts, wait_for_slurm_jobs from src.general_utils import ( create_lattice_samples_from_param_range, - load_lattice_samples_from_npz, delete_slurm_scripts, load_toml_hyperparameters, ) @@ -19,13 +18,11 @@ def main(): args = parse_lattice_args() print(f"HPC mode = { args.use_slurm}") - print(f"Load from npz = {args.load_from_npz}") print(f"HPC with singularity = { args.use_singularity}") print(f"CUDA mode = {args.cuda}") print(f"Quiet mode = {args.quiet}") hpc_operation = args.use_slurm # Flag when using HPC cluster - load_from_npz = args.load_from_npz use_cuda = args.cuda if use_cuda and hpc_operation: raise SystemExit( @@ -104,12 +101,6 @@ def override_csv_column(df, values, column_name, arg_name): design_param_names = np.array( ["absorption_blue", "scattering_white", "grid_cl", "grid_quad_order"] ) - elif load_from_npz: # TODO - raise NotImplementedError - design_params, design_param_names = load_lattice_samples_from_npz( - "sampling/pilot-study-samples-hohlraum-05-29-24.npz" - ) - exit("TODO") else: # --- Define parameter ranges --- diff --git a/src/general_utils.py b/src/general_utils.py index 2da3eec..5a265f1 100644 --- a/src/general_utils.py +++ b/src/general_utils.py @@ -23,11 +23,6 @@ def _add_common_execution_args(parser): action="store_true", help=argparse.SUPPRESS, ) - parser.add_argument( - "--load-from-npz", - action="store_true", - help="Load parameter samples from NPZ input", - ) parser.add_argument( "--singularity", dest="use_singularity", diff --git a/tests/test_cli_and_runner_flags.py b/tests/test_cli_and_runner_flags.py new file mode 100644 index 0000000..52cfe4b --- /dev/null +++ b/tests/test_cli_and_runner_flags.py @@ -0,0 +1,436 @@ +import argparse +import sys + +import numpy as np +import pandas as pd +import pytest + +import run_hohlraum +import run_lattice +from src import general_utils + + +def lattice_args(**overrides): + values = { + "use_slurm": False, + "use_singularity": False, + "cuda": False, + "quiet": False, + "csv": None, + "config": None, + "grid_cell_size": None, + "quad_order": None, + "abs_blue": None, + "scatter_white": None, + } + values.update(overrides) + return argparse.Namespace(**values) + + +def hohlraum_args(**overrides): + values = { + "use_slurm": False, + "use_singularity": False, + "cuda": False, + "quiet": False, + "csv": None, + "config": None, + "grid_cell_size": None, + "quad_order": None, + "green_center_x": None, + "green_center_y": None, + "red_right_top": None, + "red_right_bottom": None, + "red_left_top": None, + "red_left_bottom": None, + "horizontal_left": None, + "horizontal_right": None, + } + values.update(overrides) + return argparse.Namespace(**values) + + +def test_parse_lattice_args_supports_current_flags(monkeypatch): + monkeypatch.setattr( + sys, + "argv", + [ + "run_lattice.py", + "--slurm", + "--singularity", + "--cuda", + "--quiet", + "--csv", + "in.csv", + "--grid-cell-size", + "0.02", + "--quad-order", + "6", + "--config", + "cfg.toml", + "--abs-blue", + "10.0", + "11.0", + "--scatter-white", + "1.0", + "2.0", + ], + ) + args = general_utils.parse_lattice_args() + assert args.use_slurm is True + assert args.use_singularity is True + assert args.cuda is True + assert args.quiet is True + assert args.csv == "in.csv" + assert args.grid_cell_size == 0.02 + assert args.quad_order == 6 + assert args.config == "cfg.toml" + assert args.abs_blue == [10.0, 11.0] + assert args.scatter_white == [1.0, 2.0] + assert not hasattr(args, "load_from_npz") + + +def test_parse_hohlraum_args_supports_current_flags(monkeypatch): + monkeypatch.setattr( + sys, + "argv", + [ + "run_hohlraum.py", + "--slurm", + "--singularity", + "--cuda", + "--quiet", + "--csv", + "in.csv", + "--grid-cell-size", + "0.01", + "--quad-order", + "8", + "--config", + "cfg.toml", + "--green-center-x", + "-0.1", + "0.1", + "--green-center-y", + "-0.2", + "0.2", + "--red-right-top", + "0.5", + "--red-right-bottom", + "-0.5", + "--red-left-top", + "0.5", + "--red-left-bottom", + "-0.5", + "--horizontal-left", + "-0.6", + "--horizontal-right", + "0.6", + ], + ) + args = general_utils.parse_hohlraum_args() + assert args.use_slurm is True + assert args.use_singularity is True + assert args.cuda is True + assert args.quiet is True + assert args.csv == "in.csv" + assert args.grid_cell_size == 0.01 + assert args.quad_order == 8 + assert args.config == "cfg.toml" + assert args.green_center_x == [-0.1, 0.1] + assert args.green_center_y == [-0.2, 0.2] + assert args.red_right_top == [0.5] + assert args.red_right_bottom == [-0.5] + assert args.red_left_top == [0.5] + assert args.red_left_bottom == [-0.5] + assert args.horizontal_left == [-0.6] + assert args.horizontal_right == [0.6] + assert not hasattr(args, "load_from_npz") + + +def test_lattice_main_rejects_cuda_with_slurm(monkeypatch): + monkeypatch.setattr( + run_lattice, "parse_lattice_args", lambda: lattice_args(use_slurm=True, cuda=True) + ) + with pytest.raises(SystemExit, match="--cuda cannot be combined with --slurm"): + run_lattice.main() + + +def test_hohlraum_main_rejects_cuda_with_slurm(monkeypatch): + monkeypatch.setattr( + run_hohlraum, + "parse_hohlraum_args", + lambda: hohlraum_args(use_slurm=True, cuda=True), + ) + with pytest.raises(SystemExit, match="--cuda cannot be combined with --slurm"): + run_hohlraum.main() + + +def test_lattice_main_local_cuda_forces_containerized_mode(monkeypatch): + monkeypatch.setattr( + run_lattice, "parse_lattice_args", lambda: lattice_args(cuda=True, use_singularity=False) + ) + monkeypatch.setattr(run_lattice, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr( + run_lattice, + "create_lattice_samples_from_param_range", + lambda *_: ( + np.array([[10.0, 1.0, 0.01, 4]]), + np.array(["absorption_blue", "scattering_white", "grid_cl", "grid_quad_order"]), + ), + ) + calls = [] + + def fake_call_models(*args, **kwargs): + calls.append(kwargs) + return np.array([[1.0] * 8]) + + monkeypatch.setattr(run_lattice, "call_models", fake_call_models) + monkeypatch.setattr(run_lattice.np, "savez", lambda *_, **__: None) + + assert run_lattice.main() == 0 + assert len(calls) == 1 + assert calls[0]["hpc_operation_count"] == 0 + assert calls[0]["singularity_hpc"] is True + assert calls[0]["use_cuda"] is True + + +def test_hohlraum_main_local_cuda_forces_containerized_mode(monkeypatch): + monkeypatch.setattr( + run_hohlraum, + "parse_hohlraum_args", + lambda: hohlraum_args(cuda=True, use_singularity=False), + ) + monkeypatch.setattr(run_hohlraum, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr( + run_hohlraum, + "create_hohlraum_samples_from_param_range", + lambda *_: (np.zeros((10, 1)), np.array(["p"] * 10)), + ) + calls = [] + + def fake_call_models(*args, **kwargs): + calls.append(kwargs) + return np.array([[1.0] * len(run_hohlraum.get_qois_col_names())]) + + monkeypatch.setattr(run_hohlraum, "call_models", fake_call_models) + monkeypatch.setattr(run_hohlraum.np, "savez", lambda *_, **__: None) + + assert run_hohlraum.main() == 0 + assert len(calls) == 1 + assert calls[0]["hpc_operation_count"] == 0 + assert calls[0]["singularity_hpc"] is True + assert calls[0]["use_cuda"] is True + + +def test_lattice_main_slurm_runs_stage_1_and_2(monkeypatch): + monkeypatch.setattr( + run_lattice, + "parse_lattice_args", + lambda: lattice_args(use_slurm=True, use_singularity=True), + ) + monkeypatch.setattr(run_lattice, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr( + run_lattice, + "create_lattice_samples_from_param_range", + lambda *_: (np.array([[10.0, 1.0, 0.01, 4]]), np.array(["a", "b", "c", "d"])), + ) + + calls = [] + + def fake_call_models(*args, **kwargs): + calls.append(kwargs["hpc_operation_count"]) + return np.array([[1.0] * 8]) + + monkeypatch.setattr(run_lattice, "call_models", fake_call_models) + monkeypatch.setattr(run_lattice, "delete_slurm_scripts", lambda *_: None) + monkeypatch.setattr(run_lattice, "read_username_from_config", lambda *_: "alice") + monkeypatch.setattr(run_lattice, "execute_slurm_scripts", lambda *_: None) + monkeypatch.setattr(run_lattice, "wait_for_slurm_jobs", lambda **_: None) + monkeypatch.setattr(run_lattice.np, "savez", lambda *_, **__: None) + + assert run_lattice.main() == 0 + assert calls == [1, 2] + + +def test_hohlraum_main_slurm_runs_stage_1_and_2(monkeypatch): + monkeypatch.setattr( + run_hohlraum, + "parse_hohlraum_args", + lambda: hohlraum_args(use_slurm=True, use_singularity=True), + ) + monkeypatch.setattr(run_hohlraum, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr( + run_hohlraum, + "create_hohlraum_samples_from_param_range", + lambda *_: (np.zeros((10, 1)), np.array(["p"] * 10)), + ) + + calls = [] + + def fake_call_models(*args, **kwargs): + calls.append(kwargs["hpc_operation_count"]) + return np.array([[1.0] * len(run_hohlraum.get_qois_col_names())]) + + monkeypatch.setattr(run_hohlraum, "call_models", fake_call_models) + monkeypatch.setattr(run_hohlraum, "delete_slurm_scripts", lambda *_: None) + monkeypatch.setattr(run_hohlraum, "read_username_from_config", lambda *_: "alice") + monkeypatch.setattr(run_hohlraum, "execute_slurm_scripts", lambda *_: None) + monkeypatch.setattr(run_hohlraum, "wait_for_slurm_jobs", lambda **_: None) + monkeypatch.setattr(run_hohlraum.np, "savez", lambda *_, **__: None) + + assert run_hohlraum.main() == 0 + assert calls == [1, 2] + + +def test_lattice_main_csv_mode_updates_columns_and_qois(monkeypatch, tmp_path): + csv_path = tmp_path / "lattice.csv" + pd.DataFrame( + [ + { + "abs_blue": 10.0, + "scatter_white": 1.0, + "grid_cell_size": 0.01, + "quad_order": 4, + } + ] + ).to_csv(csv_path, index=False) + + monkeypatch.setattr( + run_lattice, + "parse_lattice_args", + lambda: lattice_args( + csv=str(csv_path), + abs_blue=[12.0], + scatter_white=[2.0], + grid_cell_size=0.02, + quad_order=6, + ), + ) + monkeypatch.setattr(run_lattice, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr(run_lattice, "call_models", lambda *_, **__: np.array([[1.0] * 8])) + monkeypatch.setattr( + run_lattice.np, + "savez", + lambda *_, **__: (_ for _ in ()).throw(AssertionError("np.savez should not run in --csv mode")), + ) + + assert run_lattice.main() == 0 + output = pd.read_csv(csv_path) + assert output.loc[0, "abs_blue"] == 12.0 + assert output.loc[0, "scatter_white"] == 2.0 + assert output.loc[0, "grid_cell_size"] == 0.02 + assert output.loc[0, "quad_order"] == 6 + for qoi_name in run_lattice.get_qois_col_names(): + assert qoi_name in output.columns + + +def test_hohlraum_main_csv_mode_updates_columns_and_qois(monkeypatch, tmp_path): + csv_path = tmp_path / "hohlraum.csv" + pd.DataFrame( + [ + { + "red_left_top": 0.4, + "red_left_bottom": -0.4, + "red_right_top": 0.4, + "red_right_bottom": -0.4, + "horizontal_left": -0.6, + "horizontal_right": 0.6, + "green_center_x": 0.0, + "green_center_y": 0.0, + "grid_cell_size": 0.01, + "quad_order": 6, + } + ] + ).to_csv(csv_path, index=False) + + monkeypatch.setattr( + run_hohlraum, + "parse_hohlraum_args", + lambda: hohlraum_args( + csv=str(csv_path), + green_center_x=[0.1], + horizontal_right=[0.55], + grid_cell_size=0.02, + quad_order=8, + ), + ) + monkeypatch.setattr(run_hohlraum, "load_toml_hyperparameters", lambda _: {}) + monkeypatch.setattr( + run_hohlraum, + "call_models", + lambda *_, **__: np.array([[1.0] * len(run_hohlraum.get_qois_col_names())]), + ) + monkeypatch.setattr( + run_hohlraum.np, + "savez", + lambda *_, **__: (_ for _ in ()).throw(AssertionError("np.savez should not run in --csv mode")), + ) + + assert run_hohlraum.main() == 0 + output = pd.read_csv(csv_path) + assert output.loc[0, "green_center_x"] == 0.1 + assert output.loc[0, "horizontal_right"] == 0.55 + assert output.loc[0, "grid_cell_size"] == 0.02 + assert output.loc[0, "quad_order"] == 8 + for qoi_name in run_hohlraum.get_qois_col_names(): + assert qoi_name in output.columns + + +def test_lattice_main_rejects_odd_quad_order(monkeypatch): + monkeypatch.setattr( + run_lattice, "parse_lattice_args", lambda: lattice_args(quad_order=3) + ) + monkeypatch.setattr(run_lattice, "load_toml_hyperparameters", lambda _: {}) + with pytest.raises(SystemExit, match="--quad-order must be an even number"): + run_lattice.main() + + +def test_hohlraum_main_rejects_odd_quad_order(monkeypatch): + monkeypatch.setattr( + run_hohlraum, "parse_hohlraum_args", lambda: hohlraum_args(quad_order=5) + ) + monkeypatch.setattr(run_hohlraum, "load_toml_hyperparameters", lambda _: {}) + with pytest.raises(SystemExit, match="--quad-order must be an even number"): + run_hohlraum.main() + + +def test_lattice_call_models_appends_operation_and_execution_flags(monkeypatch): + captured = [] + + def fake_model(payload): + captured.append(payload[0]) + return [[42.0] * 8] + + monkeypatch.setattr(run_lattice, "model", fake_model) + design_params = np.array([[10.0, 1.0, 0.01, 4]]) + qois = run_lattice.call_models( + design_params, + hpc_operation_count=2, + singularity_hpc=True, + rectangular_mesh=False, + use_cuda=True, + quiet=True, + ) + assert qois.shape == (1, 8) + assert captured[0][-5:] == [2, True, False, True, True] + + +def test_hohlraum_call_models_uses_transposed_design_matrix(monkeypatch): + captured = [] + + def fake_model(payload): + captured.append(payload[0]) + return [[7.0] * len(run_hohlraum.get_qois_col_names())] + + monkeypatch.setattr(run_hohlraum, "model", fake_model) + design_params = np.arange(10).reshape(10, 1) + qois = run_hohlraum.call_models( + design_params, + hpc_operation_count=1, + singularity_hpc=False, + use_cuda=False, + quiet=True, + ) + assert qois.shape == (1, len(run_hohlraum.get_qois_col_names())) + assert captured[0][-4:] == [1, False, False, True] diff --git a/tests/test_model_operations.py b/tests/test_model_operations.py new file mode 100644 index 0000000..fe92976 --- /dev/null +++ b/tests/test_model_operations.py @@ -0,0 +1,208 @@ +import numpy as np +import pytest + +from src.models import hohlraum as hohlraum_model +from src.models import lattice as lattice_model + + +@pytest.fixture +def lattice_mocks(monkeypatch): + calls = {"raw": [], "container": [], "slurm": []} + base_config = {"LOG_DIR": "result/logs", "OUTPUT_DIR": "result"} + + def update_parameter(values, key, new_value): + updated = values.copy() + updated[key] = new_value + return updated + + monkeypatch.setattr(lattice_model, "read_config_file", lambda _: base_config.copy()) + monkeypatch.setattr(lattice_model, "update_lattice_mesh_file", lambda *_, **__: "mesh.su2") + monkeypatch.setattr(lattice_model, "update_parameter", update_parameter) + monkeypatch.setattr(lattice_model, "write_config_file", lambda **_: None) + monkeypatch.setattr(lattice_model, "remove_files", lambda *_: None) + monkeypatch.setattr( + lattice_model, + "write_slurm_file", + lambda *args, **kwargs: calls["slurm"].append((args, kwargs)), + ) + monkeypatch.setattr(lattice_model, "generate_log_filename", lambda _: "result/logs/lattice_run") + monkeypatch.setattr( + lattice_model, + "read_csv_file", + lambda _: { + "Wall_time_[s]": 1.0, + "Mass": 2.0, + "Cur_absorption": 3.0, + "Total_absorption": 4.0, + "Cur_outflow_P1": 5.0, + "Total_outflow_P1": 6.0, + "Cur_outflow_P2": 7.0, + "Total_outflow_P2": 8.0, + }, + ) + monkeypatch.setattr( + lattice_model, + "run_cpp_simulation", + lambda config, quiet=False: calls["raw"].append((config, quiet)), + ) + monkeypatch.setattr( + lattice_model, + "run_cpp_simulation_containerized", + lambda config, use_cuda=False, quiet=False: calls["container"].append( + (config, use_cuda, quiet) + ), + ) + monkeypatch.setattr( + lattice_model.os.path, "exists", lambda path: str(path).endswith(".csv") + ) + monkeypatch.setattr(lattice_model.os, "remove", lambda *_: None) + return calls + + +@pytest.fixture +def hohlraum_mocks(monkeypatch): + calls = {"raw": [], "container": [], "slurm": []} + base_config = {"LOG_DIR": "result/logs", "OUTPUT_DIR": "result", "TIME_FINAL": 1.0} + + def update_parameter(values, key, new_value): + updated = values.copy() + updated[key] = new_value + return updated + + def integrated_probes(*_, **__): + result = {} + for probe in range(4): + for moment in range(3): + result[f"Probe {probe} u_{moment}"] = [float(i) for i in range(10)] + return result + + monkeypatch.setattr(hohlraum_model, "read_config_file", lambda _: base_config.copy()) + monkeypatch.setattr( + hohlraum_model, "update_var_hohlraum_mesh_file", lambda **_: "mesh.su2" + ) + monkeypatch.setattr(hohlraum_model, "update_parameter", update_parameter) + monkeypatch.setattr(hohlraum_model, "write_config_file", lambda **_: None) + monkeypatch.setattr(hohlraum_model, "remove_files", lambda *_: None) + monkeypatch.setattr( + hohlraum_model, + "write_slurm_file", + lambda *args, **kwargs: calls["slurm"].append((args, kwargs)), + ) + monkeypatch.setattr(hohlraum_model, "generate_log_filename", lambda _: "result/logs/hohlraum_run") + monkeypatch.setattr( + hohlraum_model, + "read_csv_file", + lambda _: { + "Wall_time_[s]": 1.0, + "Mass": 2.0, + "Cumulated_absorption_center": 3.0, + "Cumulated_absorption_vertical_wall": 4.0, + "Cumulated_absorption_horizontal_wall": 5.0, + }, + ) + monkeypatch.setattr( + hohlraum_model, + "get_integrated_hohlraum_probe_moments", + integrated_probes, + ) + monkeypatch.setattr( + hohlraum_model, + "run_cpp_simulation", + lambda config, quiet=False: calls["raw"].append((config, quiet)), + ) + monkeypatch.setattr( + hohlraum_model, + "run_cpp_simulation_containerized", + lambda config, use_cuda=False, quiet=False: calls["container"].append( + (config, use_cuda, quiet) + ), + ) + monkeypatch.setattr( + hohlraum_model.os.path, "exists", lambda path: str(path).endswith(".csv") + ) + monkeypatch.setattr(hohlraum_model.os, "remove", lambda *_: None) + return calls + + +def test_lattice_model_local_raw_operation(lattice_mocks): + params = [[10.0, 1.0, 0.01, 4, 0, 0, False, False, True]] + qois = lattice_model.model(params)[0] + assert len(qois) == 8 + assert len(lattice_mocks["raw"]) == 1 + assert lattice_mocks["raw"][0][1] is True + assert lattice_mocks["container"] == [] + + +def test_lattice_model_local_cuda_uses_container(lattice_mocks): + params = [[10.0, 1.0, 0.01, 4, 0, 0, False, True, False]] + qois = lattice_model.model(params)[0] + assert len(qois) == 8 + assert len(lattice_mocks["container"]) == 1 + assert lattice_mocks["container"][0][1] is True + assert lattice_mocks["raw"] == [] + + +def test_lattice_model_slurm_operation_writes_script(lattice_mocks): + params = [[10.0, 1.0, 0.01, 4, 1, 1, False, False, False]] + qois = lattice_model.model(params)[0] + assert qois == [0] * 8 + assert len(lattice_mocks["slurm"]) == 1 + assert lattice_mocks["raw"] == [] + assert lattice_mocks["container"] == [] + + +def test_lattice_model_postprocess_operation_reads_qois(lattice_mocks): + params = [[10.0, 1.0, 0.01, 4, 2, 1, False, False, False]] + qois = lattice_model.model(params)[0] + assert len(qois) == 8 + assert qois[0] == 1.0 + assert lattice_mocks["raw"] == [] + assert lattice_mocks["container"] == [] + + +def test_lattice_model_rejects_cuda_with_slurm(): + params = [[10.0, 1.0, 0.01, 4, 1, 1, False, True, False]] + with pytest.raises(ValueError, match="CUDA mode with SLURM is not supported"): + lattice_model.model(params) + + +def test_hohlraum_model_local_raw_operation(hohlraum_mocks): + params = [[0.4, -0.4, 0.4, -0.4, -0.6, 0.6, 0.0, 0.0, 0.01, 6, 0, 0, False, True]] + qois = hohlraum_model.model(params)[0] + assert len(qois) == 125 + assert len(hohlraum_mocks["raw"]) == 1 + assert hohlraum_mocks["raw"][0][1] is True + assert hohlraum_mocks["container"] == [] + + +def test_hohlraum_model_local_cuda_uses_container(hohlraum_mocks): + params = [[0.4, -0.4, 0.4, -0.4, -0.6, 0.6, 0.0, 0.0, 0.01, 6, 0, 0, True, False]] + qois = hohlraum_model.model(params)[0] + assert len(qois) == 125 + assert len(hohlraum_mocks["container"]) == 1 + assert hohlraum_mocks["container"][0][1] is True + assert hohlraum_mocks["raw"] == [] + + +def test_hohlraum_model_slurm_operation_writes_script(hohlraum_mocks): + params = [[0.4, -0.4, 0.4, -0.4, -0.6, 0.6, 0.0, 0.0, 0.01, 6, 1, 1, False, False]] + qois = hohlraum_model.model(params)[0] + assert qois == [0] * 125 + assert len(hohlraum_mocks["slurm"]) == 1 + assert hohlraum_mocks["raw"] == [] + assert hohlraum_mocks["container"] == [] + + +def test_hohlraum_model_postprocess_operation_reads_qois(hohlraum_mocks): + params = [[0.4, -0.4, 0.4, -0.4, -0.6, 0.6, 0.0, 0.0, 0.01, 6, 2, 1, False, False]] + qois = hohlraum_model.model(params)[0] + assert len(qois) == 125 + assert qois[0] == 1.0 + assert hohlraum_mocks["raw"] == [] + assert hohlraum_mocks["container"] == [] + + +def test_hohlraum_model_rejects_cuda_with_slurm(): + params = [[0.4, -0.4, 0.4, -0.4, -0.6, 0.6, 0.0, 0.0, 0.01, 6, 1, 1, True, False]] + with pytest.raises(ValueError, match="CUDA mode with SLURM is not supported"): + hohlraum_model.model(params) diff --git a/update_KiT-RT.sh b/update_KiT-RT.sh deleted file mode 100755 index b1735ad..0000000 --- a/update_KiT-RT.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -KITRT_REPO_URL="${KITRT_REPO_URL:-git@github.com:KiT-RT/kitrt_code.git}" - -has_cuda_gpu() { - if ! command -v nvidia-smi >/dev/null 2>&1; then - return 1 - fi - nvidia-smi -L >/dev/null 2>&1 -} - -cd kitrt_code -git remote set-url origin "${KITRT_REPO_URL}" -git fetch origin - -default_branch="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##' || true)" -if [ -z "${default_branch}" ]; then - default_branch="$(git remote show origin | sed -n '/HEAD branch/s/.*: //p' | head -n 1)" -fi -if [ -z "${default_branch}" ]; then - default_branch="$(git rev-parse --abbrev-ref HEAD)" -fi - -git checkout "${default_branch}" -git pull --ff-only origin "${default_branch}" -git submodule update --init --recursive - -cd tools/singularity -chmod +x install_kitrt_singularity.sh install_kitrt_singularity_cuda.sh - -singularity exec kit_rt.sif ./install_kitrt_singularity.sh - -if has_cuda_gpu && [ -f kit_rt_MPI_cuda.sif ]; then - singularity exec --nv kit_rt_MPI_cuda.sif ./install_kitrt_singularity_cuda.sh -fi - -cd ../../../ From 2b9b0cc93888b5a6efdb3e9f0dcf13c80f33a572 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Feb 2026 10:08:46 -0500 Subject: [PATCH 8/9] readme update --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b67ace..ba69908 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# charm_kit - [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![GitHub Stars](https://img.shields.io/github/stars/KiT-RT/CharmKiT)](https://github.com/KiT-RT/CharmKiT/stargazers) [![Tests](https://github.com/KiT-RT/CharmKiT/actions/workflows/tests.yml/badge.svg)](https://github.com/KiT-RT/CharmKiT/actions/workflows/tests.yml) +# charm_kit: A wrapper for KiT-RT to conduct simulation sweeps fast + + charm_kit is a benchmarking suite for the CharmNet project, providing automated parameter studies and test case management for the [KiT-RT PDE simulator](https://kit-rt.readthedocs.io/en/develop/index.html). It enables reproducible runs of radiative transfer test cases such as the lattice and hohlraum setups, using Python scripts to manage parameter sweeps, configuration, and result collection. charm_kit supports both high-performance computing (HPC) and local (no-HPC) execution modes, leveraging Singularity containers for reproducibility. From d0c615af303d29c7ffaf85955b167dc89dc79bf1 Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 13 Feb 2026 10:25:26 -0500 Subject: [PATCH 9/9] ci runs --- README.md | 29 ++++-- poetry.lock | 184 +++++++++++++++++++++++++++++++++++- pyproject.toml | 3 + run_hohlraum.py | 219 ++++++++++++++++++++++--------------------- src/general_utils.py | 20 ---- 5 files changed, 317 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index ba69908..a4ac26f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,15 @@ Preliminaries: ``` If on a cluster without root, build the container locally and upload it to `charm_kit/kitrt_code/tools/singularity/`. +## Testing + +Run unit tests from the repo root: + +```bash +poetry install --with dev +poetry run pytest -q +``` + ## How charm_kit Works @@ -111,9 +120,9 @@ Precedence for hyperparameters is: 1. **Local mode, raw (no Singularity)** ```bash - python3 run_lattice.py + poetry run python run_lattice.py # or - python3 run_hohlraum.py + poetry run python run_hohlraum.py ``` Uses local executable: `./kitrt_code/build/KiT-RT`. @@ -121,9 +130,9 @@ Precedence for hyperparameters is: 2. **Local mode + Singularity (CPU)** ```bash - python3 run_lattice.py --singularity + poetry run python run_lattice.py --singularity # or - python3 run_hohlraum.py --singularity + poetry run python run_hohlraum.py --singularity ``` Uses image/executable: @@ -132,9 +141,9 @@ Precedence for hyperparameters is: 3. **Local mode + Singularity + GPU** ```bash - python3 run_lattice.py --cuda + poetry run python run_lattice.py --cuda # or - python3 run_hohlraum.py --cuda + poetry run python run_hohlraum.py --cuda ``` Uses image/executable: @@ -143,9 +152,9 @@ Precedence for hyperparameters is: 4. **SLURM mode, raw (no Singularity)** ```bash - python3 run_lattice.py --slurm + poetry run python run_lattice.py --slurm # or - python3 run_hohlraum.py --slurm + poetry run python run_hohlraum.py --slurm ``` Generated SLURM scripts call: `srun ./kitrt_code/build/KiT-RT ...`. @@ -153,9 +162,9 @@ Precedence for hyperparameters is: 5. **SLURM mode + Singularity (CPU)** ```bash - python3 run_lattice.py --slurm --singularity + poetry run python run_lattice.py --slurm --singularity # or - python3 run_hohlraum.py --slurm --singularity + poetry run python run_hohlraum.py --slurm --singularity ``` Generated SLURM scripts call: diff --git a/poetry.lock b/poetry.lock index 802d3c6..39e80bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,37 @@ # This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, + {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "gmsh" version = "4.15.0" @@ -14,6 +46,18 @@ files = [ {file = "gmsh-4.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:a5b9867f88af59aff269a93abbd009551fbc263eb94a165a25a0c8072bae13db"}, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + [[package]] name = "numpy" version = "2.2.6" @@ -163,6 +207,18 @@ files = [ {file = "numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae"}, ] +[[package]] +name = "packaging" +version = "26.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, +] + [[package]] name = "pandas" version = "2.3.3" @@ -350,6 +406,61 @@ test = ["hypothesis (>=6.116.0)", "pytest (>=8.3.4)", "pytest-xdist (>=3.6.1)"] timezone = ["pytz (>=2024.2)"] xml = ["lxml (>=5.3.0)"] +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "9.0.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -390,6 +501,77 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "tomli" +version = "2.4.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + [[package]] name = "tzdata" version = "2025.3" @@ -406,4 +588,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "29584e446de63325ccc1f34d02224ec9ef7b54f8c179bbad63e0f39471df94af" +content-hash = "ab4ff4d38bc164bbcedda8a6e341606b3f91d29bbd820fe198a805982f747a75" diff --git a/pyproject.toml b/pyproject.toml index f670cf5..f737746 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,9 @@ pandas = "*" [tool.poetry.group.dev.dependencies] pytest = "*" +[tool.pytest.ini_options] +pythonpath = ["."] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/run_hohlraum.py b/run_hohlraum.py index 53ee484..edd2112 100644 --- a/run_hohlraum.py +++ b/run_hohlraum.py @@ -63,70 +63,92 @@ def override_csv_column(df, values, column_name, arg_name): override_csv_column( df, - args.green_center_x - if args.green_center_x is not None - else hyper.get("green_center_x"), + ( + args.green_center_x + if args.green_center_x is not None + else hyper.get("green_center_x") + ), "green_center_x", "--green-center-x", ) override_csv_column( df, - args.green_center_y - if args.green_center_y is not None - else hyper.get("green_center_y"), + ( + args.green_center_y + if args.green_center_y is not None + else hyper.get("green_center_y") + ), "green_center_y", "--green-center-y", ) override_csv_column( df, - args.red_right_top if args.red_right_top is not None else hyper.get("red_right_top"), + ( + args.red_right_top + if args.red_right_top is not None + else hyper.get("red_right_top") + ), "red_right_top", "--red-right-top", ) override_csv_column( df, - args.red_right_bottom - if args.red_right_bottom is not None - else hyper.get("red_right_bottom"), + ( + args.red_right_bottom + if args.red_right_bottom is not None + else hyper.get("red_right_bottom") + ), "red_right_bottom", "--red-right-bottom", ) override_csv_column( df, - args.red_left_top if args.red_left_top is not None else hyper.get("red_left_top"), + ( + args.red_left_top + if args.red_left_top is not None + else hyper.get("red_left_top") + ), "red_left_top", "--red-left-top", ) override_csv_column( df, - args.red_left_bottom - if args.red_left_bottom is not None - else hyper.get("red_left_bottom"), + ( + args.red_left_bottom + if args.red_left_bottom is not None + else hyper.get("red_left_bottom") + ), "red_left_bottom", "--red-left-bottom", ) override_csv_column( df, - args.horizontal_left - if args.horizontal_left is not None - else hyper.get("horizontal_left"), + ( + args.horizontal_left + if args.horizontal_left is not None + else hyper.get("horizontal_left") + ), "horizontal_left", "--horizontal-left", ) override_csv_column( df, - args.horizontal_right - if args.horizontal_right is not None - else hyper.get("horizontal_right"), + ( + args.horizontal_right + if args.horizontal_right is not None + else hyper.get("horizontal_right") + ), "horizontal_right", "--horizontal-right", ) # Allow overwriting spatial and angular resolution override_csv_column( df, - args.grid_cell_size - if args.grid_cell_size is not None - else hyper.get("grid_cell_size"), + ( + args.grid_cell_size + if args.grid_cell_size is not None + else hyper.get("grid_cell_size") + ), "grid_cell_size", "--grid-cell-size", ) @@ -141,20 +163,24 @@ def override_csv_column(df, values, column_name, arg_name): # left_red_top, left_red_bottom, right_red_top, right_red_bottom, # horizontal_left, horizontal_right, green_center_x, green_center_y, # grid_cell_size, quad_order - design_params = df[ - [ - "red_left_top", - "red_left_bottom", - "red_right_top", - "red_right_bottom", - "horizontal_left", - "horizontal_right", - "green_center_x", - "green_center_y", - "grid_cell_size", - "quad_order", + design_params = ( + df[ + [ + "red_left_top", + "red_left_bottom", + "red_right_top", + "red_right_bottom", + "horizontal_left", + "horizontal_right", + "green_center_x", + "green_center_y", + "grid_cell_size", + "quad_order", + ] ] - ].to_numpy().T + .to_numpy() + .T + ) design_param_names = np.array( [ "pos_red_left_top", @@ -169,87 +195,59 @@ def override_csv_column(df, values, column_name, arg_name): "grid_quad_order", ] ) -<<<<<<< HEAD -======= - elif load_from_npz: - design_params, design_param_names = load_hohlraum_samples_from_csv() + else: + # --- Define parameter ranges --- parameter_range_grid_cell_size = as_list_or_none( args.grid_cell_size if args.grid_cell_size is not None else hyper.get("grid_cell_size") ) or [0.0075] - - # quadrature order (must be an even number): #velocity grid cells = O(order^2) parameter_range_quad_order = as_list_or_none( args.quad_order if args.quad_order is not None else hyper.get("quad_order") ) or [6] if any(int(q) % 2 != 0 for q in parameter_range_quad_order): raise SystemExit("ERROR: --quad-order must be an even number.") - # balance the two roughly (see Paper Fig 6) - - # Define the geometry settings of the test case parameter_range_green_center_x = as_list_or_none( args.green_center_x if args.green_center_x is not None else hyper.get("green_center_x") - ) or [-0.1, 0.0, 0.1] # Default: 0 - parameter_range_green_center_y = ( - as_list_or_none( - args.green_center_y - if args.green_center_y is not None - else hyper.get("green_center_y") - ) - or [-0.075, 0.0, 0.075] - ) # Default: 0 - parameter_range_red_right_top = ( - as_list_or_none( - args.red_right_top - if args.red_right_top is not None - else hyper.get("red_right_top") - ) - or [0.3, 0.4, 0.5] - ) # Default: 0.4 - parameter_range_red_right_bottom = ( - as_list_or_none( - args.red_right_bottom - if args.red_right_bottom is not None - else hyper.get("red_right_bottom") - ) - or [-0.5, -0.4, -0.3] - ) # Default: -0.4 - parameter_range_red_left_top = ( - as_list_or_none( - args.red_left_top - if args.red_left_top is not None - else hyper.get("red_left_top") - ) - or [0.3, 0.4, 0.5] - ) # Default: 0.4 - parameter_range_red_left_bottom = ( - as_list_or_none( - args.red_left_bottom - if args.red_left_bottom is not None - else hyper.get("red_left_bottom") - ) - or [-0.5, -0.4, -0.3] - ) # Default: -0.4 - parameter_range_horizontal_left = ( - as_list_or_none( - args.horizontal_left - if args.horizontal_left is not None - else hyper.get("horizontal_left") - ) - or [-0.63, -0.6, -0.5] - ) # Default: -0.6 - parameter_range_horizontal_right = ( - as_list_or_none( - args.horizontal_right - if args.horizontal_right is not None - else hyper.get("horizontal_right") - ) - or [0.5, 0.6, 0.63] - ) # Default: 0.6 + ) or [0.0] + parameter_range_green_center_y = as_list_or_none( + args.green_center_y + if args.green_center_y is not None + else hyper.get("green_center_y") + ) or [0.0] + parameter_range_red_right_top = as_list_or_none( + args.red_right_top + if args.red_right_top is not None + else hyper.get("red_right_top") + ) or [0.4] + parameter_range_red_right_bottom = as_list_or_none( + args.red_right_bottom + if args.red_right_bottom is not None + else hyper.get("red_right_bottom") + ) or [-0.4] + parameter_range_red_left_top = as_list_or_none( + args.red_left_top + if args.red_left_top is not None + else hyper.get("red_left_top") + ) or [0.4] + parameter_range_red_left_bottom = as_list_or_none( + args.red_left_bottom + if args.red_left_bottom is not None + else hyper.get("red_left_bottom") + ) or [-0.4] + parameter_range_horizontal_left = as_list_or_none( + args.horizontal_left + if args.horizontal_left is not None + else hyper.get("horizontal_left") + ) or [-0.6] + parameter_range_horizontal_right = as_list_or_none( + args.horizontal_right + if args.horizontal_right is not None + else hyper.get("horizontal_right") + ) or [0.6] design_params, design_param_names = create_hohlraum_samples_from_param_range( parameter_range_grid_cell_size, @@ -264,13 +262,19 @@ def override_csv_column(df, values, column_name, arg_name): parameter_range_horizontal_right, ) + def safe_call_models(*model_args, **model_kwargs): + try: + return call_models(*model_args, **model_kwargs) + except (RuntimeError, FileNotFoundError) as e: + raise SystemExit(f"ERROR: {e}") from e + if hpc_operation: print("==== Execute HPC version ====") directory = "./benchmarks/hohlraum/slurm_scripts/" user = read_username_from_config("./slurm_config.txt") delete_slurm_scripts(directory) # delete existing slurm files for hohlraum - call_models( + safe_call_models( design_params, hpc_operation_count=1, singularity_hpc=singularity_hpc, @@ -285,11 +289,11 @@ def override_csv_column(df, values, column_name, arg_name): wait_for_slurm_jobs(user=user, sleep_interval=10) else: print("Username could not be read from slurm config file.") - qois = call_models( + qois = safe_call_models( design_params, hpc_operation_count=2, use_cuda=use_cuda, quiet=args.quiet ) else: - qois = call_models( + qois = safe_call_models( design_params, hpc_operation_count=0, singularity_hpc=singularity_hpc, @@ -300,8 +304,9 @@ def override_csv_column(df, values, column_name, arg_name): if args.csv: # Add all QOI columns to the CSV qoi_names = get_qois_col_names() - for i in range(qois.shape[1]): - df[qoi_names[i]] = qois[:, i] + qoi_df = pd.DataFrame(qois, columns=qoi_names, index=df.index) + # Replace existing QOI columns in one concat to avoid DataFrame fragmentation. + df = pd.concat([df.drop(columns=qoi_names, errors="ignore"), qoi_df], axis=1) df.to_csv(args.csv, index=False) print(f"Updated CSV with QOI columns: {args.csv}") else: diff --git a/src/general_utils.py b/src/general_utils.py index cfe8aea..0c69f60 100644 --- a/src/general_utils.py +++ b/src/general_utils.py @@ -24,30 +24,10 @@ def _add_common_execution_args(parser): help=argparse.SUPPRESS, ) parser.add_argument( -<<<<<<< HEAD "--singularity", dest="use_singularity", action="store_true", help="Run KiT-RT through the CPU Singularity image", -======= - "--load-from-npz", - action="store_true", - help="Load parameter samples from NPZ input", ->>>>>>> ca586e4bdf84b4cb5fc66bf6e34578e45736e9b3 - ) - # Backward-compatible alias (hidden from --help) - parser.add_argument( - "--singularity", - dest="use_singularity", - action="store_true", - help="Run KiT-RT through the CPU Singularity image", - ) - # Backward-compatible alias (hidden from --help) - parser.add_argument( - "--use-singularity", - dest="use_singularity", - action="store_true", - help=argparse.SUPPRESS, ) parser.add_argument( "--csv",