diff --git a/compass/landice/tests/ensemble_generator/__init__.py b/compass/landice/tests/ensemble_generator/__init__.py index 4a391e0e20..444a6eef64 100644 --- a/compass/landice/tests/ensemble_generator/__init__.py +++ b/compass/landice/tests/ensemble_generator/__init__.py @@ -7,10 +7,10 @@ from compass.landice.tests.ensemble_generator.branch_ensemble import ( BranchEnsemble, ) -from compass.landice.tests.ensemble_generator.sgh_analysis import ( +from compass.landice.tests.ensemble_generator.sgh_ensemble_analysis import ( AnalysisEnsemble, ) -from compass.landice.tests.ensemble_generator.sgh_restart import ( +from compass.landice.tests.ensemble_generator.sgh_restart_ensemble import ( RestartEnsemble, ) from compass.landice.tests.ensemble_generator.spinup_ensemble import ( diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/analysis_step.py b/compass/landice/tests/ensemble_generator/sgh_analysis/analysis_step.py deleted file mode 100644 index 06a0e1a4fa..0000000000 --- a/compass/landice/tests/ensemble_generator/sgh_analysis/analysis_step.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Analysis ensemble test case for SGH template. - -Analyzes a completed ensemble run (spinup or restart) and produces -summary statistics and visualizations. - -Usage: - compass setup -t landice/ensemble_generator/sgh_ensemble_analysis \\ - -w /work/analysis -f analysis_ensemble.cfg - compass run -w /work/analysis -""" - -import os - -from compass.testcase import TestCase - -from .analysis_step import AnalysisStep - - -class AnalysisEnsemble(TestCase): - """ - A test case for analyzing completed ensemble runs. - - This test case: - 1. Reads a completed ensemble directory - 2. Analyzes each run for steady-state and data compatibility - 3. Generates analysis_summary.json with results - """ - - def __init__(self, test_group): - """ - Create the analysis ensemble test case. - - Parameters - ---------- - test_group : compass test group - The test group that this test case belongs to - """ - name = 'sgh_ensemble_analysis' - super().__init__(test_group=test_group, name=name) - - def configure(self): - """ - Configure analysis by reading ensemble directory to analyze. - """ - config = self.config - section = config.get('analysis_ensemble', {}) - - ensemble_dir = section.get('ensemble_work_dir') - - if not ensemble_dir: - raise ValueError( - "analysis_ensemble config must specify " - "ensemble_work_dir\n" - "Add to config file:\n" - "[analysis_ensemble]\n" - "ensemble_work_dir = /path/to/completed/ensemble" - ) - - if not os.path.exists(ensemble_dir): - raise ValueError( - f"ensemble_work_dir not found: {ensemble_dir}" - ) - - # Add single analysis step (config file will be auto-detected) - self.add_step(AnalysisStep( - test_case=self, - ensemble_dir=ensemble_dir - )) diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/README.md b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/README.md similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_analysis/README.md rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/README.md diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/__init__.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/__init__.py similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_analysis/__init__.py rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/__init__.py diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/analysis_ensemble.cfg b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_ensemble.cfg similarity index 85% rename from compass/landice/tests/ensemble_generator/sgh_analysis/analysis_ensemble.cfg rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_ensemble.cfg index c103e13fa0..57be4f104b 100644 --- a/compass/landice/tests/ensemble_generator/sgh_analysis/analysis_ensemble.cfg +++ b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_ensemble.cfg @@ -13,7 +13,7 @@ ensemble_template = sgh_ensemble # REQUIRED: Path to the ensemble work directory to analyze # This should be a completed spinup_ensemble or restart_ensemble directory -ensemble_work_dir = /pscratch/sd/a/ahager/AIS_GHF_basalMelt_testing/sgh_ensemble_generator_test/KQ_initial_uniform/landice/ensemble_generator/spinup_ensemble +ensemble_work_dir = /pscratch/sd/a/ahager/AIS_GHF_basalMelt_testing/sgh_stage1_32member_ensemble/landice/ensemble_generator/spinup_ensemble/ [steady_state] @@ -36,7 +36,7 @@ imbalance_threshold = 0.05 # - subglacial_water_mass_balance.png # - water_mass_balance_residual.png # - subglacial_hydrology_timeseries.png -plot_results = False +plot_results = True [validation] @@ -48,8 +48,8 @@ balanced_accuracy_threshold = 0.65 # Path to specularity content TIFF file # Set to None or comment out if not available # If not provided here, will check ensemble_generator.cfg as fallback -spec_tiff_file = /global/cfs/cdirs/fanssie/users/ahager/maliSpecValidation/antarctica_radar_specularity_content_young2016.tiff +spec_tiff_file = /global/cfs/cdirs/fanssie/standard_datasets/AIS_datasets/antarctica_radar_specularity_content_young2016.tiff # Whether to generate validation plots # Plot saved as: spec_subglacialHydro_validation.png -plot_validation = False +plot_validation = True diff --git a/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_step.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_step.py new file mode 100644 index 0000000000..af109755cc --- /dev/null +++ b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analysis_step.py @@ -0,0 +1,424 @@ +""" +Analysis step that performs the actual ensemble analysis. +""" + +import configparser +import glob +import json +import os +import subprocess +import tempfile +from datetime import datetime + +from compass.step import Step + + +class AnalysisStep(Step): + """ + A step that analyzes a completed ensemble. + """ + + def __init__(self, test_case, ensemble_dir): + """ + Create an analysis step. + + Parameters + ---------- + test_case : compass.TestCase + The test case this step belongs to + + ensemble_dir : str + Directory containing completed ensemble runs + + config_file : str + Path to configuration file for analysis + """ + self.ensemble_dir = ensemble_dir + + super().__init__(test_case=test_case, name='analyze_ensemble') + + def setup(self): + """Setup phase - prepare for analysis.""" + # Get path to analysis scripts in this package + self.script_dir = os.path.dirname(os.path.abspath(__file__)) + + def run(self): + """Run the analysis.""" + logger = self.logger + + logger.info(f"Analyzing ensemble: {self.ensemble_dir}") + + if self.config_file is None: + raise FileNotFoundError( + f"Could not find ensemble config file for " + f"{self.ensemble_dir}" + ) + + logger.info(f"Using config file: {self.config_file}") + + # Load configurations + config_dict = self._load_config(self.config_file) + + # Get analysis configs with defaults + analysis_config = { + 'steady_state': self._merge_config( + config_dict.get('steady_state', {}), + self._get_default_steady_state_config() + ), + 'validation': self._merge_config( + config_dict.get('validation', {}), + self._get_default_validation_config() + ), + } + + logger.info(f"Loaded steady_state config: \ + {analysis_config['steady_state']}") + logger.info(f"Loaded validation config: \ + {analysis_config['validation']}") + + # Initialize results + summary = { + 'timestamp': datetime.now().isoformat(), + 'ensemble_dir': self.ensemble_dir, + 'total_runs': 0, + 'completed_runs': 0, + 'incomplete_runs': 0, + 'steady_state_runs': [], + 'not_steady_state_runs': [], + 'data_compatible_runs': [], + 'not_data_compatible_runs': [], + 'both_criteria_runs': [], + 'restart_needed_runs': [], + 'individual_results': {}, + 'analysis_parameters': { + 'steady_state': analysis_config.get( + 'steady_state', {}), + 'validation': analysis_config.get( + 'validation', {}), + } + } + + # Get all runs with output + all_runs = self._get_all_runs() + summary['total_runs'] = len(all_runs) + + logger.info(f"Found {len(all_runs)} total runs") + logger.info("Checking for output files...") + + runs_with_output = [] + runs_without_output = [] + + for run_dir in all_runs: + run_name = os.path.basename(run_dir) + run_num = int(run_name.replace('run', '')) + + if self._is_run_complete(run_dir): + runs_with_output.append((run_num, run_dir, run_name)) + else: + runs_without_output.append(run_num) + + summary['completed_runs'] = len(runs_with_output) + summary['incomplete_runs'] = len(runs_without_output) + + logger.info(f" {len(runs_with_output)} with output, " + f"{len(runs_without_output)} without output") + logger.info("Analyzing runs with output...") + + # Analyze each run with output + for run_num, run_dir, run_name in runs_with_output: + results = self._run_analysis_on_run( + run_dir, run_name, analysis_config) + summary['individual_results'][run_num] = results + + # Categorize based on steady state + ss_info = results.get('steady_state') + val_info = results.get('validation') + + if ss_info and ss_info.get('is_steady_state'): + summary['steady_state_runs'].append(run_num) + # If steady state, also check validation + if val_info and val_info.get('is_data_compatible'): + summary['data_compatible_runs'].append(run_num) + summary['both_criteria_runs'].append(run_num) + elif (val_info and 'error' not in val_info and + val_info.get('status') != 'no_spec_file'): + summary['not_data_compatible_runs'].append(run_num) + else: + # Not steady state - needs restart + summary['not_steady_state_runs'].append(run_num) + summary['restart_needed_runs'].append(run_num) + + # Print summary + self._print_summary(summary, logger) + + # Save summary to work directory + summary_file = os.path.join(self.work_dir, + 'analysis_summary.json') + with open(summary_file, 'w') as f: + json.dump(summary, f, indent=2) + + logger.info(f"Summary saved to {summary_file}") + + def _get_all_runs(self): + """Get sorted list of run directories.""" + run_dirs = sorted(glob.glob( + os.path.join(self.ensemble_dir, 'run*'))) + return [d for d in run_dirs if os.path.isdir(d)] + + def _is_run_complete(self, run_dir): + """Check if a run has completed successfully.""" + output_file = os.path.join(run_dir, 'output', + 'globalStats.nc') + + return os.path.exists(output_file) + + def _run_analysis_on_run(self, run_dir, run_name, + analysis_config): + """Run analysis on a completed run.""" + self.logger.info(f" Analyzing {run_name}...") + + output_file = os.path.join(run_dir, 'output', + 'globalStats.nc') + results = { + 'run_name': run_name, + 'output_exists': os.path.exists(output_file), + 'analysis_timestamp': datetime.now().isoformat(), + 'steady_state': None, + 'validation': None, + 'analysis_errors': [] + } + + # Steady state analysis + try: + ss_config = analysis_config.get('steady_state', {}) + window_years = ss_config.get('window_years', 10.0) + imbalance_threshold = ss_config.get( + 'imbalance_threshold', 0.05) + plot_results = ss_config.get('plot_results', False) + + ss_results = self._run_steadystate_analysis( + output_file, window_years, imbalance_threshold, + plot_results) + results['steady_state'] = ss_results + + except Exception as e: + results['analysis_errors'].append( + f"Steady-state analysis failed: {e}") + self.logger.warning(f" {e}") + + # Validation analysis + try: + val_config = analysis_config.get('validation', {}) + spec_tiff = val_config.get('spec_tiff_file', None) + ba_threshold = val_config.get( + 'balanced_accuracy_threshold', 0.65) + plot_validation = val_config.get( + 'plot_validation', False) + + if spec_tiff and os.path.exists(spec_tiff): + output_hist = os.path.join(run_dir, 'output', + 'history.nc') + if os.path.exists(output_hist): + val_results = self._run_validation_analysis( + output_hist, spec_tiff, ba_threshold, + plot_validation) + results['validation'] = val_results + else: + results['validation'] = { + 'status': 'no_history_file'} + else: + results['validation'] = {'status': 'no_spec_file', + 'spec_tiff': spec_tiff} + + except Exception as e: + results['analysis_errors'].append( + f"Validation analysis failed: {e}") + self.logger.warning(f" {e}") + + return results + + def _run_steadystate_analysis(self, output_file, window_years, + imbalance_threshold, plot=False): + """Run steady-state analysis via subprocess.""" + script = os.path.join( + self.script_dir, + 'analyze_subglacial_water_mass_balance.py') + + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', + delete=False) as f: + temp_json = f.name + + try: + cmd = [ + 'python', script, + '-f', output_file, + '--window_years', str(window_years), + '--imbalance_threshold', str(imbalance_threshold), + '--output_json', temp_json, + ] + + if plot: + cmd.append('--plot') + + result = subprocess.run(cmd, capture_output=True, + text=True, timeout=300) + + if result.returncode == 0 and os.path.exists(temp_json): + with open(temp_json, 'r') as f: + return json.load(f) + else: + raise RuntimeError( + f"Subprocess analysis failed: " + f"{result.stderr}") + + finally: + if os.path.exists(temp_json): + os.unlink(temp_json) + + def _run_validation_analysis(self, output_file, spec_tiff, + ba_threshold, plot=False): + """Run validation analysis via subprocess.""" + script = os.path.join( + self.script_dir, 'validate_mali_with_spec.py') + + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', + delete=False) as f: + temp_json = f.name + + try: + cmd = [ + 'python', script, + '--maliFile', output_file, + '--specTiff', spec_tiff, + '--ba_threshold', str(ba_threshold), + '--output_json', temp_json + ] + + if plot: + cmd.append('--plot') + + result = subprocess.run(cmd, capture_output=True, + text=True, timeout=300) + + if result.returncode == 0 and os.path.exists(temp_json): + with open(temp_json, 'r') as f: + return json.load(f) + else: + return {'status': 'failed', 'error': result.stderr} + + finally: + if os.path.exists(temp_json): + os.unlink(temp_json) + + @staticmethod + def _load_config(config_file): + """Load configuration file.""" + config = configparser.ConfigParser() + config.read(config_file) + + config_dict = {} + for section in config.sections(): + config_dict[section] = {} + for key, value in config.items(section): + try: + config_dict[section][key] = float(value) + except ValueError: + try: + config_dict[section][key] = ( + config.getboolean(section, key)) + except ValueError: + if value.lower() == 'none': + config_dict[section][key] = None + else: + config_dict[section][key] = value + + return config_dict + + @staticmethod + def _merge_config(user_config, defaults): + """ + Merge user config with defaults. + User config values take precedence. + + Parameters + ---------- + user_config : dict + User-provided configuration + defaults : dict + Default configuration values + + Returns + ------- + dict + Merged configuration + """ + merged = defaults.copy() + merged.update(user_config) + return merged + + @staticmethod + def _get_default_steady_state_config(): + """Get default steady-state configuration.""" + return { + 'window_years': 10.0, + 'imbalance_threshold': 0.05, + 'plot_results': False, + } + + @staticmethod + def _get_default_validation_config(): + """Get default validation configuration.""" + return { + 'balanced_accuracy_threshold': 0.65, + 'spec_tiff_file': None, + 'plot_validation': False, + } + + @staticmethod + def _print_summary(summary, logger): + """Print analysis summary.""" + logger.info("") + logger.info("=" * 70) + logger.info("ENSEMBLE ANALYSIS SUMMARY") + logger.info("=" * 70) + logger.info(f"Total runs: {summary['total_runs']}") + logger.info(f" Completed: {summary['completed_runs']}") + logger.info(f" Incomplete: {summary['incomplete_runs']}") + logger.info("") + + if summary['completed_runs'] > 0: + tot_runs = summary['completed_runs'] + ss_runs = len(summary['steady_state_runs']) + dc_runs = len(summary['data_compatible_runs']) + both_runs = len(summary['both_criteria_runs']) + + pct_ss = 100.0 * ss_runs / tot_runs + pct_dc = 100.0 * dc_runs / tot_runs + pct_both = 100.0 * both_runs / tot_runs + + logger.info( + f"Steady-state runs: {ss_runs}/{tot_runs} " + f"({pct_ss:.1f}%)") + if summary['steady_state_runs']: + logger.info(f" {summary['steady_state_runs']}") + logger.info("") + logger.info( + f"Data-compatible runs: {dc_runs}/{tot_runs} " + f"({pct_dc:.1f}%)") + if summary['data_compatible_runs']: + logger.info(f" {summary['data_compatible_runs']}") + logger.info("") + logger.info( + f"Both criteria met: {both_runs}/{tot_runs} " + f"({pct_both:.1f}%)") + if summary['both_criteria_runs']: + logger.info(f" {summary['both_criteria_runs']}") + logger.info("") + logger.info( + f"Runs needing restart: " + f"{len(summary['restart_needed_runs'])}") + if summary['restart_needed_runs']: + logger.info(f" {summary['restart_needed_runs']}") + + logger.info("=" * 70) + logger.info("") diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/analyze_subglacial_water_mass_balance.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analyze_subglacial_water_mass_balance.py similarity index 94% rename from compass/landice/tests/ensemble_generator/sgh_analysis/analyze_subglacial_water_mass_balance.py rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analyze_subglacial_water_mass_balance.py index 6b1402e8f9..89ff0324a1 100644 --- a/compass/landice/tests/ensemble_generator/sgh_analysis/analyze_subglacial_water_mass_balance.py +++ b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/analyze_subglacial_water_mass_balance.py @@ -217,7 +217,11 @@ def check_steady_state( # Determine steady state: when relative imbalance is below threshold # for the final portion of the simulation - if np.sum(np.isfinite(relative_imbalance)) > 0: + # No steady-state if run doesn't last 1.5x window length + if yr[-1] < 1.5 * window_years: + is_steady = False + final_imbalance = np.nan + elif np.sum(np.isfinite(relative_imbalance)) > 0: final_portion = \ relative_imbalance[-max(10, len(relative_imbalance) // 10):] is_steady = np.nanmean(final_portion) < imbalance_threshold @@ -483,6 +487,25 @@ def check_steady_state( 'file': args.filename, } + +def convert_to_serializable(obj): + """Convert numpy/non-serializable types to JSON-serializable types.""" + if isinstance(obj, dict): + return {k: convert_to_serializable(v) for k, v in obj.items()} + elif isinstance(obj, (list, tuple)): + return [convert_to_serializable(item) for item in obj] + elif isinstance(obj, (np.bool_, bool)): + return bool(obj) + elif isinstance(obj, (np.integer, int)): + return int(obj) + elif isinstance(obj, (np.floating, float)): + return float(obj) + else: + return obj + + +results = convert_to_serializable(results) + with open(args.output_json, 'w') as f: json.dump(results, f, indent=2) diff --git a/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/provenance b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/provenance new file mode 100644 index 0000000000..b9eacac48a --- /dev/null +++ b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/provenance @@ -0,0 +1,1011 @@ +*********************************************************************** +MPAS git version: archive/MALI-Dev/tc_2025_mali_ismip6_ais_2300-4410-g41b26f6179 + +command: /global/cfs/cdirs/fanssie/users/ahager/mambaforge/envs/dev_compass_1.9.0-alpha.2/bin/compass setup -t landice/ensemble_generator/sgh_ensemble_analysis -f analysis_ensemble.cfg + +test cases: + path: landice/ensemble_generator/sgh_ensemble_analysis + name: sgh_ensemble_analysis + MPAS core: landice + test group: ensemble_generator + subdir: sgh_ensemble_analysis + steps: + +conda list: +# packages in environment at /global/cfs/cdirs/fanssie/users/ahager/mambaforge/envs/dev_compass_1.9.0-alpha.2: +# +# Name Version Build Channel +_libgcc_mutex 0.1 conda_forge conda-forge +_openmp_mutex 4.5 2_gnu conda-forge +_python_abi3_support 1.0 hd8ed1ab_2 conda-forge +alabaster 1.0.0 pyhd8ed1ab_1 conda-forge +alsa-lib 1.2.14 hb9d3cd8_0 conda-forge +anyio 4.11.0 pyhcf101f3_0 conda-forge +aom 3.9.1 hac33072_0 conda-forge +argon2-cffi 25.1.0 pyhd8ed1ab_0 conda-forge +argon2-cffi-bindings 25.1.0 py313h07c4f96_2 conda-forge +arpack 3.9.1 nompi_hf03ea27_102 conda-forge +arrow 1.4.0 pyhcf101f3_0 conda-forge +asttokens 3.0.0 pyhd8ed1ab_1 conda-forge +async-lru 2.0.5 pyh29332c3_0 conda-forge +attr 2.5.2 h39aace5_0 conda-forge +attrs 25.4.0 pyh71513ae_0 conda-forge +autopep8 2.3.2 pypi_0 pypi +aws-c-auth 0.9.1 h194c533_5 conda-forge +aws-c-cal 0.9.8 h346e085_0 conda-forge +aws-c-common 0.12.5 hb03c661_1 conda-forge +aws-c-compression 0.3.1 h7e655bb_8 conda-forge +aws-c-event-stream 0.5.6 h1deb5b9_4 conda-forge +aws-c-http 0.10.7 had4b759_1 conda-forge +aws-c-io 0.23.2 hbff472d_2 conda-forge +aws-c-mqtt 0.13.3 h8ba2272_8 conda-forge +aws-c-s3 0.8.6 h493c25d_7 conda-forge +aws-c-sdkutils 0.2.4 h7e655bb_3 conda-forge +aws-checksums 0.2.7 h7e655bb_4 conda-forge +aws-crt-cpp 0.35.0 h719b17a_2 conda-forge +aws-sdk-cpp 1.11.606 h522d481_6 conda-forge +azure-core-cpp 1.16.1 h3a458e0_0 conda-forge +azure-identity-cpp 1.13.2 h3a5f585_1 conda-forge +azure-storage-blobs-cpp 12.15.0 h2a74896_1 conda-forge +azure-storage-common-cpp 12.11.0 h3d7a050_1 conda-forge +azure-storage-files-datalake-cpp 12.13.0 hf38f1be_1 conda-forge +babel 2.17.0 pyhd8ed1ab_0 conda-forge +beautifulsoup4 4.14.2 pyha770c72_0 conda-forge +bleach 6.2.0 pyh29332c3_4 conda-forge +blosc 1.21.6 he440d0b_1 conda-forge +bokeh 3.8.1 pyhd8ed1ab_0 conda-forge +brotli 1.2.0 h41a2e66_0 conda-forge +brotli-bin 1.2.0 hf2c8021_0 conda-forge +brotli-python 1.2.0 py313h09d1b84_0 conda-forge +brunsli 0.1 hd1e3526_2 conda-forge +bzip2 1.0.8 hda65f42_8 conda-forge +c-ares 1.34.5 hb9d3cd8_0 conda-forge +c-blosc2 2.22.0 h4cfbee9_0 conda-forge +ca-certificates 2026.1.4 hbd8a1cb_0 conda-forge +cached-property 1.5.2 hd8ed1ab_1 conda-forge +cached_property 1.5.2 pyha770c72_1 conda-forge +cairo 1.18.4 h3394656_0 conda-forge +cartopy 0.25.0 py313h08cd8bf_1 conda-forge +cartopy_offlinedata 0.25.0 pyhd8ed1ab_0 conda-forge +certifi 2026.1.4 pyhd8ed1ab_0 conda-forge +cfchecker 4.1.0 pyhd8ed1ab_1 conda-forge +cffi 2.0.0 py313hf46b229_1 conda-forge +cfgv 3.3.1 pyhd8ed1ab_1 conda-forge +cftime 1.6.4 py313h29aa505_2 conda-forge +cfunits 3.3.7 pyhd8ed1ab_1 conda-forge +charls 2.4.2 h59595ed_0 conda-forge +charset-normalizer 3.4.4 pyhd8ed1ab_0 conda-forge +click 8.3.0 pyh707e725_0 conda-forge +cloudpickle 3.1.2 pyhd8ed1ab_0 conda-forge +cmocean 4.0.3 pyhd8ed1ab_1 conda-forge +colorama 0.4.6 pyhd8ed1ab_1 conda-forge +colorspacious 1.1.2 pyhecae5ae_1 conda-forge +comm 0.2.3 pyhe01879c_0 conda-forge +compass 1.9.0a2 pypi_0 pypi +contourpy 1.3.3 py313h7037e92_3 conda-forge +cpython 3.13.9 py313hd8ed1ab_101 conda-forge +cycler 0.12.1 pyhd8ed1ab_1 conda-forge +cytoolz 1.1.0 py313h07c4f96_1 conda-forge +dask 2025.11.0 pyhcf101f3_0 conda-forge +dask-core 2025.11.0 pyhcf101f3_0 conda-forge +dav1d 1.2.1 hd590300_0 conda-forge +dbus 1.16.2 h3c4dab8_0 conda-forge +debugpy 1.8.17 py313h5d5ffb9_0 conda-forge +decorator 5.2.1 pyhd8ed1ab_0 conda-forge +defusedxml 0.7.1 pyhd8ed1ab_0 conda-forge +distlib 0.4.0 pyhd8ed1ab_0 conda-forge +distributed 2025.11.0 pyhcf101f3_0 conda-forge +docutils 0.21.2 pyhd8ed1ab_1 conda-forge +entrypoints 0.4 pyhd8ed1ab_1 conda-forge +esmf 8.9.0 nompi_h8d4c64c_3 conda-forge +exceptiongroup 1.3.0 pyhd8ed1ab_0 conda-forge +executing 2.2.1 pyhd8ed1ab_0 conda-forge +ffmpeg 8.0.0 gpl_h5c0ada0_706 conda-forge +filelock 3.20.0 pyhd8ed1ab_0 conda-forge +flake8 7.3.0 pyhd8ed1ab_0 conda-forge +font-ttf-dejavu-sans-mono 2.37 hab24e00_0 conda-forge +font-ttf-inconsolata 3.000 h77eed37_0 conda-forge +font-ttf-source-code-pro 2.038 h77eed37_0 conda-forge +font-ttf-ubuntu 0.83 h77eed37_3 conda-forge +fontconfig 2.15.0 h7e30c49_1 conda-forge +fonts-conda-ecosystem 1 0 conda-forge +fonts-conda-forge 1 hc364b38_1 conda-forge +fonttools 4.60.1 py313h3dea7bd_0 conda-forge +fqdn 1.5.1 pyhd8ed1ab_1 conda-forge +freetype 2.14.1 ha770c72_0 conda-forge +fribidi 1.0.16 hb03c661_0 conda-forge +fsspec 2025.10.0 pyhd8ed1ab_0 conda-forge +future 1.0.0 pyhd8ed1ab_2 conda-forge +gdk-pixbuf 2.44.4 h2b0a6b4_0 conda-forge +geometric_features 1.6.1 pyhd8ed1ab_0 conda-forge +geos 3.14.1 h480dda7_0 conda-forge +gettext 0.25.1 h3f43e3d_1 conda-forge +gettext-tools 0.25.1 h3f43e3d_1 conda-forge +gflags 2.2.2 h5888daf_1005 conda-forge +giflib 5.2.2 hd590300_0 conda-forge +git 2.52.0 pl5321h28be001_0 conda-forge +glog 0.7.1 hbabe93e_0 conda-forge +glpk 5.0 h445213a_0 conda-forge +glslang 16.0.0 hfd11570_0 conda-forge +gmp 6.3.0 hac33072_2 conda-forge +graphite2 1.3.14 hecca717_2 conda-forge +gsl 2.7 he838d99_0 conda-forge +gsw 3.6.20 py313h29aa505_1 conda-forge +h11 0.16.0 pyhd8ed1ab_0 conda-forge +h2 4.3.0 pyhcf101f3_0 conda-forge +h5py 3.15.1 nompi_py313h253c126_101 conda-forge +harfbuzz 12.2.0 h15599e2_0 conda-forge +hdf4 4.2.15 h2a13503_7 conda-forge +hdf5 1.14.6 nompi_h6e4c0c1_103 conda-forge +hpack 4.1.0 pyhd8ed1ab_0 conda-forge +httpcore 1.0.9 pyh29332c3_0 conda-forge +httpx 0.28.1 pyhd8ed1ab_0 conda-forge +hyperframe 6.1.0 pyhd8ed1ab_0 conda-forge +icu 75.1 he02047a_0 conda-forge +identify 2.6.15 pyhd8ed1ab_0 conda-forge +idna 3.11 pyhd8ed1ab_0 conda-forge +igraph 1.0.0 hfe3e89f_0 conda-forge +imagecodecs 2025.11.11 py313h2d3cd63_0 conda-forge +imageio 2.37.0 pyhfb79c49_0 conda-forge +imagesize 1.4.1 pyhd8ed1ab_0 conda-forge +importlib-metadata 8.7.0 pyhe01879c_1 conda-forge +importlib_resources 6.5.2 pyhd8ed1ab_0 conda-forge +iniconfig 2.3.0 pyhd8ed1ab_0 conda-forge +inpoly 0.2.0 py313h29aa505_9 conda-forge +intel-gmmlib 22.8.2 hb700be7_0 conda-forge +intel-media-driver 25.3.4 hecca717_0 conda-forge +ipykernel 7.1.0 pyha191276_0 conda-forge +ipython 9.9.0 pyh53cf698_0 conda-forge +ipython_pygments_lexers 1.1.1 pyhd8ed1ab_0 conda-forge +ipywidgets 8.1.8 pyhd8ed1ab_0 conda-forge +isoduration 20.11.0 pyhd8ed1ab_1 conda-forge +isort 7.0.0 pyhd8ed1ab_0 conda-forge +jedi 0.19.2 pyhd8ed1ab_1 conda-forge +jigsawpy 1.1.0 pypi_0 pypi +jinja2 3.1.6 pyhd8ed1ab_0 conda-forge +json5 0.12.1 pyhd8ed1ab_0 conda-forge +jsonpointer 3.0.0 py313h78bf25f_2 conda-forge +jsonschema 4.25.1 pyhe01879c_0 conda-forge +jsonschema-specifications 2025.9.1 pyhcf101f3_0 conda-forge +jsonschema-with-format-nongpl 4.25.1 he01879c_0 conda-forge +jupyter 1.1.1 pyhd8ed1ab_1 conda-forge +jupyter-lsp 2.3.0 pyhcf101f3_0 conda-forge +jupyter_client 8.6.3 pyhd8ed1ab_1 conda-forge +jupyter_console 6.6.3 pyhd8ed1ab_1 conda-forge +jupyter_core 5.9.1 pyhc90fa1f_0 conda-forge +jupyter_events 0.12.0 pyh29332c3_0 conda-forge +jupyter_server 2.17.0 pyhcf101f3_0 conda-forge +jupyter_server_terminals 0.5.3 pyhd8ed1ab_1 conda-forge +jupyterlab 4.4.10 pyhd8ed1ab_0 conda-forge +jupyterlab_pygments 0.3.0 pyhd8ed1ab_2 conda-forge +jupyterlab_server 2.28.0 pyhcf101f3_0 conda-forge +jupyterlab_widgets 3.0.16 pyhcf101f3_1 conda-forge +jxrlib 1.1 hd590300_3 conda-forge +keyutils 1.6.3 hb9d3cd8_0 conda-forge +kiwisolver 1.4.9 py313hc8edb43_2 conda-forge +krb5 1.21.3 h659f571_0 conda-forge +lame 3.100 h166bdaf_1003 conda-forge +lark 1.3.1 pyhd8ed1ab_0 conda-forge +lazy-loader 0.4 pyhd8ed1ab_2 conda-forge +lcms2 2.17 h717163a_0 conda-forge +ld_impl_linux-64 2.44 h1aa0949_5 conda-forge +lerc 4.0.0 h0aef613_1 conda-forge +level-zero 1.26.0 hb700be7_0 conda-forge +libabseil 20250512.1 cxx17_hba17884_0 conda-forge +libaec 1.1.4 h3f801dc_0 conda-forge +libarrow 22.0.0 h99e40f8_3_cpu conda-forge +libarrow-acero 22.0.0 h635bf11_3_cpu conda-forge +libarrow-compute 22.0.0 h8c2c5c3_3_cpu conda-forge +libarrow-dataset 22.0.0 h635bf11_3_cpu conda-forge +libarrow-substrait 22.0.0 h3f74fd7_3_cpu conda-forge +libasprintf 0.25.1 h3f43e3d_1 conda-forge +libasprintf-devel 0.25.1 h3f43e3d_1 conda-forge +libass 0.17.4 h96ad9f0_0 conda-forge +libavif16 1.3.0 h6395336_2 conda-forge +libblas 3.9.0 38_h4a7cf45_openblas conda-forge +libbrotlicommon 1.2.0 h09219d5_0 conda-forge +libbrotlidec 1.2.0 hd53d788_0 conda-forge +libbrotlienc 1.2.0 h02bd7ab_0 conda-forge +libcap 2.77 h3ff7636_0 conda-forge +libcblas 3.9.0 38_h0358290_openblas conda-forge +libcrc32c 1.1.2 h9c3ff4c_0 conda-forge +libcurl 8.17.0 h4e3cde8_0 conda-forge +libdeflate 1.25 h17f619e_0 conda-forge +libdrm 2.4.125 hb03c661_1 conda-forge +libedit 3.1.20250104 pl5321h7949ede_0 conda-forge +libegl 1.7.0 ha4b6fd6_2 conda-forge +libev 4.33 hd590300_2 conda-forge +libevent 2.1.12 hf998b51_1 conda-forge +libexpat 2.7.1 hecca717_0 conda-forge +libffi 3.5.2 h9ec8514_0 conda-forge +libflac 1.4.3 h59595ed_0 conda-forge +libfreetype 2.14.1 ha770c72_0 conda-forge +libfreetype6 2.14.1 h73754d4_0 conda-forge +libgcc 15.2.0 h767d61c_7 conda-forge +libgcc-ng 15.2.0 h69a702a_7 conda-forge +libgettextpo 0.25.1 h3f43e3d_1 conda-forge +libgettextpo-devel 0.25.1 h3f43e3d_1 conda-forge +libgfortran 15.2.0 h69a702a_7 conda-forge +libgfortran-ng 15.2.0 h69a702a_7 conda-forge +libgfortran5 15.2.0 hcd61629_7 conda-forge +libgl 1.7.0 ha4b6fd6_2 conda-forge +libglib 2.86.1 h32235b2_2 conda-forge +libglvnd 1.7.0 ha4b6fd6_2 conda-forge +libglx 1.7.0 ha4b6fd6_2 conda-forge +libgomp 15.2.0 h767d61c_7 conda-forge +libgoogle-cloud 2.39.0 hdb79228_0 conda-forge +libgoogle-cloud-storage 2.39.0 hdbdcf42_0 conda-forge +libgrpc 1.73.1 h3288cfb_1 conda-forge +libhwloc 2.12.1 default_h7f8ec31_1002 conda-forge +libhwy 1.3.0 h4c17acf_1 conda-forge +libiconv 1.18 h3b78370_2 conda-forge +libjpeg-turbo 3.1.2 hb03c661_0 conda-forge +libjxl 0.11.1 hf08fa70_5 conda-forge +liblapack 3.9.0 38_h47877c9_openblas conda-forge +liblzma 5.8.1 hb9d3cd8_2 conda-forge +libmpdec 4.0.0 hb9d3cd8_0 conda-forge +libnetcdf 4.9.3 nompi_h11f7409_103 conda-forge +libnghttp2 1.67.0 had1ee68_0 conda-forge +libogg 1.3.5 hd0c01bc_1 conda-forge +libopenblas 0.3.30 pthreads_h94d23a6_3 conda-forge +libopentelemetry-cpp 1.21.0 hb9b0907_1 conda-forge +libopentelemetry-cpp-headers 1.21.0 ha770c72_1 conda-forge +libopenvino 2025.2.0 hb617929_1 conda-forge +libopenvino-auto-batch-plugin 2025.2.0 hed573e4_1 conda-forge +libopenvino-auto-plugin 2025.2.0 hed573e4_1 conda-forge +libopenvino-hetero-plugin 2025.2.0 hd41364c_1 conda-forge +libopenvino-intel-cpu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-intel-gpu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-intel-npu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-ir-frontend 2025.2.0 hd41364c_1 conda-forge +libopenvino-onnx-frontend 2025.2.0 h1862bb8_1 conda-forge +libopenvino-paddle-frontend 2025.2.0 h1862bb8_1 conda-forge +libopenvino-pytorch-frontend 2025.2.0 hecca717_1 conda-forge +libopenvino-tensorflow-frontend 2025.2.0 h0767aad_1 conda-forge +libopenvino-tensorflow-lite-frontend 2025.2.0 hecca717_1 conda-forge +libopus 1.5.2 hd0c01bc_0 conda-forge +libparquet 22.0.0 h7376487_3_cpu conda-forge +libpciaccess 0.18 hb9d3cd8_0 conda-forge +libpng 1.6.50 h421ea60_1 conda-forge +libprotobuf 6.31.1 h49aed37_2 conda-forge +libre2-11 2025.11.05 h7b12aa8_0 conda-forge +librsvg 2.60.0 h61e6d4b_0 conda-forge +libsndfile 1.2.2 hc60ed4a_1 conda-forge +libsodium 1.0.20 h4ab18f5_0 conda-forge +libsqlite 3.51.0 hee844dc_0 conda-forge +libssh2 1.11.1 hcf80075_0 conda-forge +libstdcxx 15.2.0 h8f9b012_7 conda-forge +libstdcxx-ng 15.2.0 h4852527_7 conda-forge +libsystemd0 257.10 hd0affe5_2 conda-forge +libthrift 0.22.0 h454ac66_1 conda-forge +libtiff 4.7.1 h9d88235_1 conda-forge +libudev1 257.10 hd0affe5_2 conda-forge +libudunits2 2.2.28 h40f5838_3 conda-forge +libunwind 1.8.3 h65a8314_0 conda-forge +liburing 2.12 hb700be7_0 conda-forge +libusb 1.0.29 h73b1eb8_0 conda-forge +libutf8proc 2.11.0 hb04c3b8_0 conda-forge +libuuid 2.41.2 he9a06e4_0 conda-forge +libva 2.22.0 h4f16b4b_2 conda-forge +libvorbis 1.3.7 h54a6638_2 conda-forge +libvpl 2.15.0 h54a6638_1 conda-forge +libvpx 1.14.1 hac33072_0 conda-forge +libvulkan-loader 1.4.328.1 h5279c79_0 conda-forge +libwebp-base 1.6.0 hd42ef1d_0 conda-forge +libxcb 1.17.0 h8a09558_0 conda-forge +libxcrypt 4.4.36 hd590300_1 conda-forge +libxkbcommon 1.13.0 hca5e8e5_0 conda-forge +libxml2 2.15.1 h26afc86_0 conda-forge +libxml2-16 2.15.1 ha9997c6_0 conda-forge +libxml2-devel 2.15.1 h26afc86_0 conda-forge +libxslt 1.1.43 h711ed8c_1 conda-forge +libzip 1.11.2 h6991a6a_0 conda-forge +libzlib 1.3.1 hb9d3cd8_2 conda-forge +libzopfli 1.0.3 h9c3ff4c_0 conda-forge +locket 1.0.0 pyhd8ed1ab_0 conda-forge +lxml 6.0.2 py313h4a16004_2 conda-forge +lz4 4.4.5 py313h28739b2_0 conda-forge +lz4-c 1.10.0 h5888daf_1 conda-forge +m2r 0.3.1 pyhd8ed1ab_1 conda-forge +mache 1.32.0 pyhd8ed1ab_0 conda-forge +markupsafe 3.0.3 py313h3dea7bd_0 conda-forge +matplotlib-base 3.10.8 py313h683a580_0 conda-forge +matplotlib-inline 0.2.1 pyhd8ed1ab_0 conda-forge +mccabe 0.7.0 pyhd8ed1ab_1 conda-forge +metis 5.1.0 hd0bcaf9_1007 conda-forge +mistune 0.8.4 pyh1a96a4e_1006 conda-forge +moab 5.6.0 nompi_tempest_h3811aa1_0 conda-forge +mock 5.2.0 pyhd8ed1ab_0 conda-forge +mpas_tools 1.3.2 py313h76c60f6_1 conda-forge +mpg123 1.32.9 hc50e24c_0 conda-forge +msgpack-python 1.1.2 py313h7037e92_1 conda-forge +munkres 1.1.4 pyhd8ed1ab_1 conda-forge +narwhals 2.11.0 pyhcf101f3_0 conda-forge +nbclient 0.10.2 pyhd8ed1ab_0 conda-forge +nbconvert-core 6.5.3 pyhd8ed1ab_0 conda-forge +nbformat 5.10.4 pyhd8ed1ab_1 conda-forge +nco 5.3.6 hfb7d223_0 conda-forge +ncurses 6.5 h2d0b736_3 conda-forge +ncview 2.1.8 h4debd67_12 conda-forge +nest-asyncio 1.6.0 pyhd8ed1ab_1 conda-forge +netcdf-fortran 4.6.2 nompi_h90de81b_102 conda-forge +netcdf4 1.7.4 nompi_py313h16051e2_102 conda-forge +networkx 3.5 pyhe01879c_0 conda-forge +nlohmann_json 3.12.0 h54a6638_1 conda-forge +nodeenv 1.9.1 pyhd8ed1ab_1 conda-forge +notebook 7.4.7 pyhd8ed1ab_0 conda-forge +notebook-shim 0.2.4 pyhd8ed1ab_1 conda-forge +numpy 2.4.1 py313hf6604e3_0 conda-forge +ocl-icd 2.3.3 hb9d3cd8_0 conda-forge +opencl-headers 2025.06.13 h5888daf_0 conda-forge +openh264 2.6.0 hc22cd8d_0 conda-forge +openjpeg 2.5.4 h55fea9a_0 conda-forge +openssl 3.6.0 h26f9b46_0 conda-forge +orc 2.2.1 hd747db4_0 conda-forge +otps 2021.10 h4839124_0 e3sm/label/compass +overrides 7.7.0 pyhd8ed1ab_1 conda-forge +packaging 25.0 pyh29332c3_1 conda-forge +pandas 2.3.3 py313h08cd8bf_1 conda-forge +pandocfilters 1.5.0 pyhd8ed1ab_0 conda-forge +pango 1.56.4 hadf4263_0 conda-forge +parso 0.8.5 pyhcf101f3_0 conda-forge +partd 1.4.2 pyhd8ed1ab_0 conda-forge +pcre2 10.46 h1321c63_0 conda-forge +perl 5.32.1 7_hd590300_perl5 conda-forge +pexpect 4.9.0 pyhd8ed1ab_1 conda-forge +pillow 12.0.0 py313h50355cd_0 conda-forge +pip 25.3 pyh145f28c_0 conda-forge +pixman 0.46.4 h54a6638_1 conda-forge +platformdirs 4.5.0 pyhcf101f3_0 conda-forge +pluggy 1.6.0 pyhd8ed1ab_0 conda-forge +popt 1.16 h0b475e3_2002 conda-forge +pre-commit 4.5.1 pyha770c72_0 conda-forge +progressbar2 4.5.0 pyhd8ed1ab_1 conda-forge +proj 9.7.0 hb72c0af_0 conda-forge +prometheus-cpp 1.3.0 ha5d0236_0 conda-forge +prometheus_client 0.23.1 pyhd8ed1ab_0 conda-forge +prompt-toolkit 3.0.52 pyha770c72_0 conda-forge +prompt_toolkit 3.0.52 hd8ed1ab_0 conda-forge +psutil 7.1.3 py313h54dd161_0 conda-forge +pthread-stubs 0.4 hb9d3cd8_1002 conda-forge +ptyprocess 0.7.0 pyhd8ed1ab_1 conda-forge +pugixml 1.15 h3f63f65_0 conda-forge +pulseaudio-client 17.0 h9a8bead_2 conda-forge +pure_eval 0.2.3 pyhd8ed1ab_1 conda-forge +pyamg 5.3.0 py313hfaae9d9_1 conda-forge +pyarrow 22.0.0 py313h78bf25f_0 conda-forge +pyarrow-core 22.0.0 py313he109ebe_0_cpu conda-forge +pycodestyle 2.14.0 pyhd8ed1ab_0 conda-forge +pycparser 2.22 pyh29332c3_1 conda-forge +pyevtk 1.6.0 pyhb0bfe47_1 conda-forge +pyflakes 3.4.0 pyhd8ed1ab_0 conda-forge +pygments 2.19.2 pyhd8ed1ab_0 conda-forge +pyparsing 3.2.5 pyhcf101f3_0 conda-forge +pyproj 3.7.2 py313h77f6078_2 conda-forge +pyremap 2.1.0 pyhd8ed1ab_0 conda-forge +pyshp 3.0.2 pyhd8ed1ab_0 conda-forge +pysocks 1.7.1 pyha55dd90_7 conda-forge +pytest 9.0.2 pyhcf101f3_0 conda-forge +python 3.13.9 hc97d973_101_cp313 conda-forge +python-dateutil 2.9.0.post0 pyhe01879c_2 conda-forge +python-fastjsonschema 2.21.2 pyhe01879c_0 conda-forge +python-gil 3.13.9 h4df99d1_101 conda-forge +python-igraph 1.0.0 py313h7033f15_0 conda-forge +python-json-logger 2.0.7 pyhd8ed1ab_0 conda-forge +python-tzdata 2025.2 pyhd8ed1ab_0 conda-forge +python-utils 3.9.1 pyhff2d567_1 conda-forge +python_abi 3.13 8_cp313 conda-forge +pytz 2025.2 pyhd8ed1ab_0 conda-forge +pywavelets 1.9.0 py313h29aa505_2 conda-forge +pyyaml 6.0.3 py313h3dea7bd_0 conda-forge +pyzmq 27.1.0 py312hfb55c3c_0 conda-forge +qhull 2020.2 h434a139_5 conda-forge +rav1e 0.7.1 h8fae777_3 conda-forge +re2 2025.11.05 h5301d42_0 conda-forge +readline 8.2 h8c095d6_2 conda-forge +referencing 0.37.0 pyhcf101f3_0 conda-forge +requests 2.32.5 pyhcf101f3_1 conda-forge +rfc3339-validator 0.1.4 pyhd8ed1ab_1 conda-forge +rfc3986-validator 0.1.1 pyh9f0ad1d_0 conda-forge +rfc3987-syntax 1.1.0 pyhe01879c_1 conda-forge +roman-numerals-py 3.1.0 pyhd8ed1ab_0 conda-forge +rpds-py 0.28.0 py313h843e2db_2 conda-forge +rsync 3.4.1 h81c0278_2 conda-forge +ruamel.yaml 0.18.17 py313h07c4f96_0 conda-forge +ruamel.yaml.clib 0.2.14 py313h07c4f96_0 conda-forge +s2n 1.6.0 h8399546_1 conda-forge +scikit-image 0.26.0 np2py313hb172dc5_0 conda-forge +scipy 1.17.0 py313h4b8bb8b_1 conda-forge +sdl2 2.32.56 h54a6638_0 conda-forge +sdl3 3.2.26 h68140b3_0 conda-forge +send2trash 1.8.3 pyh0d859eb_1 conda-forge +setuptools 80.9.0 pyhff2d567_0 conda-forge +shaderc 2025.4 h3e344bc_0 conda-forge +shapely 2.1.2 py313had47c43_2 conda-forge +six 1.17.0 pyhe01879c_1 conda-forge +snappy 1.2.2 h03e3b7b_1 conda-forge +sniffio 1.3.1 pyhd8ed1ab_2 conda-forge +snowballstemmer 3.0.1 pyhd8ed1ab_0 conda-forge +sortedcontainers 2.4.0 pyhd8ed1ab_1 conda-forge +soupsieve 2.8 pyhd8ed1ab_0 conda-forge +sphinx 8.2.3 pyhd8ed1ab_0 conda-forge +sphinx_rtd_theme 3.0.2 pyha770c72_0 conda-forge +sphinxcontrib-applehelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-devhelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-htmlhelp 2.1.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-jquery 4.1 pyhd8ed1ab_1 conda-forge +sphinxcontrib-jsmath 1.0.1 pyhd8ed1ab_1 conda-forge +sphinxcontrib-qthelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-serializinghtml 1.1.10 pyhd8ed1ab_1 conda-forge +spirv-tools 2025.4 hb700be7_0 conda-forge +sqlite 3.51.0 heff268d_0 conda-forge +stack_data 0.6.3 pyhd8ed1ab_1 conda-forge +svt-av1 3.1.2 hecca717_0 conda-forge +tbb 2022.3.0 h8d10470_1 conda-forge +tblib 3.2.2 pyhcf101f3_0 conda-forge +tempest-remap 2.2.0 h397758c_8 conda-forge +termcolor 3.2.0 pyhd8ed1ab_0 conda-forge +terminado 0.18.1 pyh0d859eb_0 conda-forge +texttable 1.7.0 pyhd8ed1ab_1 conda-forge +tifffile 2025.10.16 pyhd8ed1ab_0 conda-forge +tinycss2 1.4.0 pyhd8ed1ab_0 conda-forge +tk 8.6.13 noxft_ha0e22de_103 conda-forge +tomli 2.3.0 pyhcf101f3_0 conda-forge +toolz 1.1.0 pyhd8ed1ab_1 conda-forge +tornado 6.5.2 py313h07c4f96_2 conda-forge +tqdm 4.67.1 pyhd8ed1ab_1 conda-forge +traitlets 5.14.3 pyhd8ed1ab_1 conda-forge +typing-extensions 4.15.0 h396c80c_0 conda-forge +typing_extensions 4.15.0 pyhcf101f3_0 conda-forge +typing_utils 0.1.0 pyhd8ed1ab_1 conda-forge +tzdata 2025b h78e105d_0 conda-forge +udunits2 2.2.28 h40f5838_3 conda-forge +ukkonen 1.0.1 py313h7037e92_6 conda-forge +uri-template 1.3.0 pyhd8ed1ab_1 conda-forge +urllib3 2.5.0 pyhd8ed1ab_0 conda-forge +virtualenv 20.35.4 pyhd8ed1ab_0 conda-forge +wayland 1.24.0 hd6090a7_1 conda-forge +wayland-protocols 1.45 hd8ed1ab_0 conda-forge +wcwidth 0.2.14 pyhd8ed1ab_0 conda-forge +webcolors 25.10.0 pyhd8ed1ab_0 conda-forge +webencodings 0.5.1 pyhd8ed1ab_3 conda-forge +websocket-client 1.9.0 pyhd8ed1ab_0 conda-forge +widgetsnbextension 4.0.15 pyhd8ed1ab_0 conda-forge +x264 1!164.3095 h166bdaf_2 conda-forge +x265 3.5 h924138e_3 conda-forge +xarray 2025.12.0 pyhcf101f3_0 conda-forge +xkeyboard-config 2.46 hb03c661_0 conda-forge +xorg-libice 1.1.2 hb9d3cd8_0 conda-forge +xorg-libsm 1.2.6 he73a12e_0 conda-forge +xorg-libx11 1.8.12 h4f16b4b_0 conda-forge +xorg-libxau 1.0.12 hb03c661_1 conda-forge +xorg-libxaw 1.0.16 hb9d3cd8_0 conda-forge +xorg-libxcursor 1.2.3 hb9d3cd8_0 conda-forge +xorg-libxdmcp 1.1.5 hb03c661_1 conda-forge +xorg-libxext 1.3.6 hb9d3cd8_0 conda-forge +xorg-libxfixes 6.0.2 hb03c661_0 conda-forge +xorg-libxmu 1.2.1 hb9d3cd8_1 conda-forge +xorg-libxpm 3.5.17 hb9d3cd8_1 conda-forge +xorg-libxrandr 1.5.4 hb9d3cd8_0 conda-forge +xorg-libxrender 0.9.12 hb9d3cd8_0 conda-forge +xorg-libxscrnsaver 1.2.4 hb9d3cd8_0 conda-forge +xorg-libxt 1.3.1 hb9d3cd8_0 conda-forge +xxhash 0.8.3 hb47aa4a_0 conda-forge +xyzservices 2025.10.0 pyhd8ed1ab_0 conda-forge +yaml 0.2.5 h280c20c_3 conda-forge +zeromq 4.3.5 h387f397_9 conda-forge +zfp 1.0.1 h909a3a2_3 conda-forge +zict 3.0.0 pyhd8ed1ab_1 conda-forge +zipp 3.23.0 pyhd8ed1ab_0 conda-forge +zlib 1.3.1 hb9d3cd8_2 conda-forge +zlib-ng 2.2.5 hde8ca8f_0 conda-forge +zstandard 0.25.0 py313h54dd161_1 conda-forge +zstd 1.5.7 hb8e6e7a_2 conda-forge + +*********************************************************************** + +*********************************************************************** +MPAS git version: archive/MALI-Dev/tc_2025_mali_ismip6_ais_2300-4410-g41b26f6179 + +command: /global/cfs/cdirs/fanssie/users/ahager/mambaforge/envs/dev_compass_1.9.0-alpha.2/bin/compass setup -t landice/ensemble_generator/sgh_ensemble_analysis -f analysis_ensemble.cfg + +test cases: + path: landice/ensemble_generator/sgh_ensemble_analysis + name: sgh_ensemble_analysis + MPAS core: landice + test group: ensemble_generator + subdir: sgh_ensemble_analysis + steps: + +conda list: +# packages in environment at /global/cfs/cdirs/fanssie/users/ahager/mambaforge/envs/dev_compass_1.9.0-alpha.2: +# +# Name Version Build Channel +_libgcc_mutex 0.1 conda_forge conda-forge +_openmp_mutex 4.5 2_gnu conda-forge +_python_abi3_support 1.0 hd8ed1ab_2 conda-forge +alabaster 1.0.0 pyhd8ed1ab_1 conda-forge +alsa-lib 1.2.14 hb9d3cd8_0 conda-forge +anyio 4.11.0 pyhcf101f3_0 conda-forge +aom 3.9.1 hac33072_0 conda-forge +argon2-cffi 25.1.0 pyhd8ed1ab_0 conda-forge +argon2-cffi-bindings 25.1.0 py313h07c4f96_2 conda-forge +arpack 3.9.1 nompi_hf03ea27_102 conda-forge +arrow 1.4.0 pyhcf101f3_0 conda-forge +asttokens 3.0.0 pyhd8ed1ab_1 conda-forge +async-lru 2.0.5 pyh29332c3_0 conda-forge +attr 2.5.2 h39aace5_0 conda-forge +attrs 25.4.0 pyh71513ae_0 conda-forge +autopep8 2.3.2 pypi_0 pypi +aws-c-auth 0.9.1 h194c533_5 conda-forge +aws-c-cal 0.9.8 h346e085_0 conda-forge +aws-c-common 0.12.5 hb03c661_1 conda-forge +aws-c-compression 0.3.1 h7e655bb_8 conda-forge +aws-c-event-stream 0.5.6 h1deb5b9_4 conda-forge +aws-c-http 0.10.7 had4b759_1 conda-forge +aws-c-io 0.23.2 hbff472d_2 conda-forge +aws-c-mqtt 0.13.3 h8ba2272_8 conda-forge +aws-c-s3 0.8.6 h493c25d_7 conda-forge +aws-c-sdkutils 0.2.4 h7e655bb_3 conda-forge +aws-checksums 0.2.7 h7e655bb_4 conda-forge +aws-crt-cpp 0.35.0 h719b17a_2 conda-forge +aws-sdk-cpp 1.11.606 h522d481_6 conda-forge +azure-core-cpp 1.16.1 h3a458e0_0 conda-forge +azure-identity-cpp 1.13.2 h3a5f585_1 conda-forge +azure-storage-blobs-cpp 12.15.0 h2a74896_1 conda-forge +azure-storage-common-cpp 12.11.0 h3d7a050_1 conda-forge +azure-storage-files-datalake-cpp 12.13.0 hf38f1be_1 conda-forge +babel 2.17.0 pyhd8ed1ab_0 conda-forge +beautifulsoup4 4.14.2 pyha770c72_0 conda-forge +bleach 6.2.0 pyh29332c3_4 conda-forge +blosc 1.21.6 he440d0b_1 conda-forge +bokeh 3.8.1 pyhd8ed1ab_0 conda-forge +brotli 1.2.0 h41a2e66_0 conda-forge +brotli-bin 1.2.0 hf2c8021_0 conda-forge +brotli-python 1.2.0 py313h09d1b84_0 conda-forge +brunsli 0.1 hd1e3526_2 conda-forge +bzip2 1.0.8 hda65f42_8 conda-forge +c-ares 1.34.5 hb9d3cd8_0 conda-forge +c-blosc2 2.22.0 h4cfbee9_0 conda-forge +ca-certificates 2026.1.4 hbd8a1cb_0 conda-forge +cached-property 1.5.2 hd8ed1ab_1 conda-forge +cached_property 1.5.2 pyha770c72_1 conda-forge +cairo 1.18.4 h3394656_0 conda-forge +cartopy 0.25.0 py313h08cd8bf_1 conda-forge +cartopy_offlinedata 0.25.0 pyhd8ed1ab_0 conda-forge +certifi 2026.1.4 pyhd8ed1ab_0 conda-forge +cfchecker 4.1.0 pyhd8ed1ab_1 conda-forge +cffi 2.0.0 py313hf46b229_1 conda-forge +cfgv 3.3.1 pyhd8ed1ab_1 conda-forge +cftime 1.6.4 py313h29aa505_2 conda-forge +cfunits 3.3.7 pyhd8ed1ab_1 conda-forge +charls 2.4.2 h59595ed_0 conda-forge +charset-normalizer 3.4.4 pyhd8ed1ab_0 conda-forge +click 8.3.0 pyh707e725_0 conda-forge +cloudpickle 3.1.2 pyhd8ed1ab_0 conda-forge +cmocean 4.0.3 pyhd8ed1ab_1 conda-forge +colorama 0.4.6 pyhd8ed1ab_1 conda-forge +colorspacious 1.1.2 pyhecae5ae_1 conda-forge +comm 0.2.3 pyhe01879c_0 conda-forge +compass 1.9.0a2 pypi_0 pypi +contourpy 1.3.3 py313h7037e92_3 conda-forge +cpython 3.13.9 py313hd8ed1ab_101 conda-forge +cycler 0.12.1 pyhd8ed1ab_1 conda-forge +cytoolz 1.1.0 py313h07c4f96_1 conda-forge +dask 2025.11.0 pyhcf101f3_0 conda-forge +dask-core 2025.11.0 pyhcf101f3_0 conda-forge +dav1d 1.2.1 hd590300_0 conda-forge +dbus 1.16.2 h3c4dab8_0 conda-forge +debugpy 1.8.17 py313h5d5ffb9_0 conda-forge +decorator 5.2.1 pyhd8ed1ab_0 conda-forge +defusedxml 0.7.1 pyhd8ed1ab_0 conda-forge +distlib 0.4.0 pyhd8ed1ab_0 conda-forge +distributed 2025.11.0 pyhcf101f3_0 conda-forge +docutils 0.21.2 pyhd8ed1ab_1 conda-forge +entrypoints 0.4 pyhd8ed1ab_1 conda-forge +esmf 8.9.0 nompi_h8d4c64c_3 conda-forge +exceptiongroup 1.3.0 pyhd8ed1ab_0 conda-forge +executing 2.2.1 pyhd8ed1ab_0 conda-forge +ffmpeg 8.0.0 gpl_h5c0ada0_706 conda-forge +filelock 3.20.0 pyhd8ed1ab_0 conda-forge +flake8 7.3.0 pyhd8ed1ab_0 conda-forge +font-ttf-dejavu-sans-mono 2.37 hab24e00_0 conda-forge +font-ttf-inconsolata 3.000 h77eed37_0 conda-forge +font-ttf-source-code-pro 2.038 h77eed37_0 conda-forge +font-ttf-ubuntu 0.83 h77eed37_3 conda-forge +fontconfig 2.15.0 h7e30c49_1 conda-forge +fonts-conda-ecosystem 1 0 conda-forge +fonts-conda-forge 1 hc364b38_1 conda-forge +fonttools 4.60.1 py313h3dea7bd_0 conda-forge +fqdn 1.5.1 pyhd8ed1ab_1 conda-forge +freetype 2.14.1 ha770c72_0 conda-forge +fribidi 1.0.16 hb03c661_0 conda-forge +fsspec 2025.10.0 pyhd8ed1ab_0 conda-forge +future 1.0.0 pyhd8ed1ab_2 conda-forge +gdk-pixbuf 2.44.4 h2b0a6b4_0 conda-forge +geometric_features 1.6.1 pyhd8ed1ab_0 conda-forge +geos 3.14.1 h480dda7_0 conda-forge +gettext 0.25.1 h3f43e3d_1 conda-forge +gettext-tools 0.25.1 h3f43e3d_1 conda-forge +gflags 2.2.2 h5888daf_1005 conda-forge +giflib 5.2.2 hd590300_0 conda-forge +git 2.52.0 pl5321h28be001_0 conda-forge +glog 0.7.1 hbabe93e_0 conda-forge +glpk 5.0 h445213a_0 conda-forge +glslang 16.0.0 hfd11570_0 conda-forge +gmp 6.3.0 hac33072_2 conda-forge +graphite2 1.3.14 hecca717_2 conda-forge +gsl 2.7 he838d99_0 conda-forge +gsw 3.6.20 py313h29aa505_1 conda-forge +h11 0.16.0 pyhd8ed1ab_0 conda-forge +h2 4.3.0 pyhcf101f3_0 conda-forge +h5py 3.15.1 nompi_py313h253c126_101 conda-forge +harfbuzz 12.2.0 h15599e2_0 conda-forge +hdf4 4.2.15 h2a13503_7 conda-forge +hdf5 1.14.6 nompi_h6e4c0c1_103 conda-forge +hpack 4.1.0 pyhd8ed1ab_0 conda-forge +httpcore 1.0.9 pyh29332c3_0 conda-forge +httpx 0.28.1 pyhd8ed1ab_0 conda-forge +hyperframe 6.1.0 pyhd8ed1ab_0 conda-forge +icu 75.1 he02047a_0 conda-forge +identify 2.6.15 pyhd8ed1ab_0 conda-forge +idna 3.11 pyhd8ed1ab_0 conda-forge +igraph 1.0.0 hfe3e89f_0 conda-forge +imagecodecs 2025.11.11 py313h2d3cd63_0 conda-forge +imageio 2.37.0 pyhfb79c49_0 conda-forge +imagesize 1.4.1 pyhd8ed1ab_0 conda-forge +importlib-metadata 8.7.0 pyhe01879c_1 conda-forge +importlib_resources 6.5.2 pyhd8ed1ab_0 conda-forge +iniconfig 2.3.0 pyhd8ed1ab_0 conda-forge +inpoly 0.2.0 py313h29aa505_9 conda-forge +intel-gmmlib 22.8.2 hb700be7_0 conda-forge +intel-media-driver 25.3.4 hecca717_0 conda-forge +ipykernel 7.1.0 pyha191276_0 conda-forge +ipython 9.9.0 pyh53cf698_0 conda-forge +ipython_pygments_lexers 1.1.1 pyhd8ed1ab_0 conda-forge +ipywidgets 8.1.8 pyhd8ed1ab_0 conda-forge +isoduration 20.11.0 pyhd8ed1ab_1 conda-forge +isort 7.0.0 pyhd8ed1ab_0 conda-forge +jedi 0.19.2 pyhd8ed1ab_1 conda-forge +jigsawpy 1.1.0 pypi_0 pypi +jinja2 3.1.6 pyhd8ed1ab_0 conda-forge +json5 0.12.1 pyhd8ed1ab_0 conda-forge +jsonpointer 3.0.0 py313h78bf25f_2 conda-forge +jsonschema 4.25.1 pyhe01879c_0 conda-forge +jsonschema-specifications 2025.9.1 pyhcf101f3_0 conda-forge +jsonschema-with-format-nongpl 4.25.1 he01879c_0 conda-forge +jupyter 1.1.1 pyhd8ed1ab_1 conda-forge +jupyter-lsp 2.3.0 pyhcf101f3_0 conda-forge +jupyter_client 8.6.3 pyhd8ed1ab_1 conda-forge +jupyter_console 6.6.3 pyhd8ed1ab_1 conda-forge +jupyter_core 5.9.1 pyhc90fa1f_0 conda-forge +jupyter_events 0.12.0 pyh29332c3_0 conda-forge +jupyter_server 2.17.0 pyhcf101f3_0 conda-forge +jupyter_server_terminals 0.5.3 pyhd8ed1ab_1 conda-forge +jupyterlab 4.4.10 pyhd8ed1ab_0 conda-forge +jupyterlab_pygments 0.3.0 pyhd8ed1ab_2 conda-forge +jupyterlab_server 2.28.0 pyhcf101f3_0 conda-forge +jupyterlab_widgets 3.0.16 pyhcf101f3_1 conda-forge +jxrlib 1.1 hd590300_3 conda-forge +keyutils 1.6.3 hb9d3cd8_0 conda-forge +kiwisolver 1.4.9 py313hc8edb43_2 conda-forge +krb5 1.21.3 h659f571_0 conda-forge +lame 3.100 h166bdaf_1003 conda-forge +lark 1.3.1 pyhd8ed1ab_0 conda-forge +lazy-loader 0.4 pyhd8ed1ab_2 conda-forge +lcms2 2.17 h717163a_0 conda-forge +ld_impl_linux-64 2.44 h1aa0949_5 conda-forge +lerc 4.0.0 h0aef613_1 conda-forge +level-zero 1.26.0 hb700be7_0 conda-forge +libabseil 20250512.1 cxx17_hba17884_0 conda-forge +libaec 1.1.4 h3f801dc_0 conda-forge +libarrow 22.0.0 h99e40f8_3_cpu conda-forge +libarrow-acero 22.0.0 h635bf11_3_cpu conda-forge +libarrow-compute 22.0.0 h8c2c5c3_3_cpu conda-forge +libarrow-dataset 22.0.0 h635bf11_3_cpu conda-forge +libarrow-substrait 22.0.0 h3f74fd7_3_cpu conda-forge +libasprintf 0.25.1 h3f43e3d_1 conda-forge +libasprintf-devel 0.25.1 h3f43e3d_1 conda-forge +libass 0.17.4 h96ad9f0_0 conda-forge +libavif16 1.3.0 h6395336_2 conda-forge +libblas 3.9.0 38_h4a7cf45_openblas conda-forge +libbrotlicommon 1.2.0 h09219d5_0 conda-forge +libbrotlidec 1.2.0 hd53d788_0 conda-forge +libbrotlienc 1.2.0 h02bd7ab_0 conda-forge +libcap 2.77 h3ff7636_0 conda-forge +libcblas 3.9.0 38_h0358290_openblas conda-forge +libcrc32c 1.1.2 h9c3ff4c_0 conda-forge +libcurl 8.17.0 h4e3cde8_0 conda-forge +libdeflate 1.25 h17f619e_0 conda-forge +libdrm 2.4.125 hb03c661_1 conda-forge +libedit 3.1.20250104 pl5321h7949ede_0 conda-forge +libegl 1.7.0 ha4b6fd6_2 conda-forge +libev 4.33 hd590300_2 conda-forge +libevent 2.1.12 hf998b51_1 conda-forge +libexpat 2.7.1 hecca717_0 conda-forge +libffi 3.5.2 h9ec8514_0 conda-forge +libflac 1.4.3 h59595ed_0 conda-forge +libfreetype 2.14.1 ha770c72_0 conda-forge +libfreetype6 2.14.1 h73754d4_0 conda-forge +libgcc 15.2.0 h767d61c_7 conda-forge +libgcc-ng 15.2.0 h69a702a_7 conda-forge +libgettextpo 0.25.1 h3f43e3d_1 conda-forge +libgettextpo-devel 0.25.1 h3f43e3d_1 conda-forge +libgfortran 15.2.0 h69a702a_7 conda-forge +libgfortran-ng 15.2.0 h69a702a_7 conda-forge +libgfortran5 15.2.0 hcd61629_7 conda-forge +libgl 1.7.0 ha4b6fd6_2 conda-forge +libglib 2.86.1 h32235b2_2 conda-forge +libglvnd 1.7.0 ha4b6fd6_2 conda-forge +libglx 1.7.0 ha4b6fd6_2 conda-forge +libgomp 15.2.0 h767d61c_7 conda-forge +libgoogle-cloud 2.39.0 hdb79228_0 conda-forge +libgoogle-cloud-storage 2.39.0 hdbdcf42_0 conda-forge +libgrpc 1.73.1 h3288cfb_1 conda-forge +libhwloc 2.12.1 default_h7f8ec31_1002 conda-forge +libhwy 1.3.0 h4c17acf_1 conda-forge +libiconv 1.18 h3b78370_2 conda-forge +libjpeg-turbo 3.1.2 hb03c661_0 conda-forge +libjxl 0.11.1 hf08fa70_5 conda-forge +liblapack 3.9.0 38_h47877c9_openblas conda-forge +liblzma 5.8.1 hb9d3cd8_2 conda-forge +libmpdec 4.0.0 hb9d3cd8_0 conda-forge +libnetcdf 4.9.3 nompi_h11f7409_103 conda-forge +libnghttp2 1.67.0 had1ee68_0 conda-forge +libogg 1.3.5 hd0c01bc_1 conda-forge +libopenblas 0.3.30 pthreads_h94d23a6_3 conda-forge +libopentelemetry-cpp 1.21.0 hb9b0907_1 conda-forge +libopentelemetry-cpp-headers 1.21.0 ha770c72_1 conda-forge +libopenvino 2025.2.0 hb617929_1 conda-forge +libopenvino-auto-batch-plugin 2025.2.0 hed573e4_1 conda-forge +libopenvino-auto-plugin 2025.2.0 hed573e4_1 conda-forge +libopenvino-hetero-plugin 2025.2.0 hd41364c_1 conda-forge +libopenvino-intel-cpu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-intel-gpu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-intel-npu-plugin 2025.2.0 hb617929_1 conda-forge +libopenvino-ir-frontend 2025.2.0 hd41364c_1 conda-forge +libopenvino-onnx-frontend 2025.2.0 h1862bb8_1 conda-forge +libopenvino-paddle-frontend 2025.2.0 h1862bb8_1 conda-forge +libopenvino-pytorch-frontend 2025.2.0 hecca717_1 conda-forge +libopenvino-tensorflow-frontend 2025.2.0 h0767aad_1 conda-forge +libopenvino-tensorflow-lite-frontend 2025.2.0 hecca717_1 conda-forge +libopus 1.5.2 hd0c01bc_0 conda-forge +libparquet 22.0.0 h7376487_3_cpu conda-forge +libpciaccess 0.18 hb9d3cd8_0 conda-forge +libpng 1.6.50 h421ea60_1 conda-forge +libprotobuf 6.31.1 h49aed37_2 conda-forge +libre2-11 2025.11.05 h7b12aa8_0 conda-forge +librsvg 2.60.0 h61e6d4b_0 conda-forge +libsndfile 1.2.2 hc60ed4a_1 conda-forge +libsodium 1.0.20 h4ab18f5_0 conda-forge +libsqlite 3.51.0 hee844dc_0 conda-forge +libssh2 1.11.1 hcf80075_0 conda-forge +libstdcxx 15.2.0 h8f9b012_7 conda-forge +libstdcxx-ng 15.2.0 h4852527_7 conda-forge +libsystemd0 257.10 hd0affe5_2 conda-forge +libthrift 0.22.0 h454ac66_1 conda-forge +libtiff 4.7.1 h9d88235_1 conda-forge +libudev1 257.10 hd0affe5_2 conda-forge +libudunits2 2.2.28 h40f5838_3 conda-forge +libunwind 1.8.3 h65a8314_0 conda-forge +liburing 2.12 hb700be7_0 conda-forge +libusb 1.0.29 h73b1eb8_0 conda-forge +libutf8proc 2.11.0 hb04c3b8_0 conda-forge +libuuid 2.41.2 he9a06e4_0 conda-forge +libva 2.22.0 h4f16b4b_2 conda-forge +libvorbis 1.3.7 h54a6638_2 conda-forge +libvpl 2.15.0 h54a6638_1 conda-forge +libvpx 1.14.1 hac33072_0 conda-forge +libvulkan-loader 1.4.328.1 h5279c79_0 conda-forge +libwebp-base 1.6.0 hd42ef1d_0 conda-forge +libxcb 1.17.0 h8a09558_0 conda-forge +libxcrypt 4.4.36 hd590300_1 conda-forge +libxkbcommon 1.13.0 hca5e8e5_0 conda-forge +libxml2 2.15.1 h26afc86_0 conda-forge +libxml2-16 2.15.1 ha9997c6_0 conda-forge +libxml2-devel 2.15.1 h26afc86_0 conda-forge +libxslt 1.1.43 h711ed8c_1 conda-forge +libzip 1.11.2 h6991a6a_0 conda-forge +libzlib 1.3.1 hb9d3cd8_2 conda-forge +libzopfli 1.0.3 h9c3ff4c_0 conda-forge +locket 1.0.0 pyhd8ed1ab_0 conda-forge +lxml 6.0.2 py313h4a16004_2 conda-forge +lz4 4.4.5 py313h28739b2_0 conda-forge +lz4-c 1.10.0 h5888daf_1 conda-forge +m2r 0.3.1 pyhd8ed1ab_1 conda-forge +mache 1.32.0 pyhd8ed1ab_0 conda-forge +markupsafe 3.0.3 py313h3dea7bd_0 conda-forge +matplotlib-base 3.10.8 py313h683a580_0 conda-forge +matplotlib-inline 0.2.1 pyhd8ed1ab_0 conda-forge +mccabe 0.7.0 pyhd8ed1ab_1 conda-forge +metis 5.1.0 hd0bcaf9_1007 conda-forge +mistune 0.8.4 pyh1a96a4e_1006 conda-forge +moab 5.6.0 nompi_tempest_h3811aa1_0 conda-forge +mock 5.2.0 pyhd8ed1ab_0 conda-forge +mpas_tools 1.3.2 py313h76c60f6_1 conda-forge +mpg123 1.32.9 hc50e24c_0 conda-forge +msgpack-python 1.1.2 py313h7037e92_1 conda-forge +munkres 1.1.4 pyhd8ed1ab_1 conda-forge +narwhals 2.11.0 pyhcf101f3_0 conda-forge +nbclient 0.10.2 pyhd8ed1ab_0 conda-forge +nbconvert-core 6.5.3 pyhd8ed1ab_0 conda-forge +nbformat 5.10.4 pyhd8ed1ab_1 conda-forge +nco 5.3.6 hfb7d223_0 conda-forge +ncurses 6.5 h2d0b736_3 conda-forge +ncview 2.1.8 h4debd67_12 conda-forge +nest-asyncio 1.6.0 pyhd8ed1ab_1 conda-forge +netcdf-fortran 4.6.2 nompi_h90de81b_102 conda-forge +netcdf4 1.7.4 nompi_py313h16051e2_102 conda-forge +networkx 3.5 pyhe01879c_0 conda-forge +nlohmann_json 3.12.0 h54a6638_1 conda-forge +nodeenv 1.9.1 pyhd8ed1ab_1 conda-forge +notebook 7.4.7 pyhd8ed1ab_0 conda-forge +notebook-shim 0.2.4 pyhd8ed1ab_1 conda-forge +numpy 2.4.1 py313hf6604e3_0 conda-forge +ocl-icd 2.3.3 hb9d3cd8_0 conda-forge +opencl-headers 2025.06.13 h5888daf_0 conda-forge +openh264 2.6.0 hc22cd8d_0 conda-forge +openjpeg 2.5.4 h55fea9a_0 conda-forge +openssl 3.6.0 h26f9b46_0 conda-forge +orc 2.2.1 hd747db4_0 conda-forge +otps 2021.10 h4839124_0 e3sm/label/compass +overrides 7.7.0 pyhd8ed1ab_1 conda-forge +packaging 25.0 pyh29332c3_1 conda-forge +pandas 2.3.3 py313h08cd8bf_1 conda-forge +pandocfilters 1.5.0 pyhd8ed1ab_0 conda-forge +pango 1.56.4 hadf4263_0 conda-forge +parso 0.8.5 pyhcf101f3_0 conda-forge +partd 1.4.2 pyhd8ed1ab_0 conda-forge +pcre2 10.46 h1321c63_0 conda-forge +perl 5.32.1 7_hd590300_perl5 conda-forge +pexpect 4.9.0 pyhd8ed1ab_1 conda-forge +pillow 12.0.0 py313h50355cd_0 conda-forge +pip 25.3 pyh145f28c_0 conda-forge +pixman 0.46.4 h54a6638_1 conda-forge +platformdirs 4.5.0 pyhcf101f3_0 conda-forge +pluggy 1.6.0 pyhd8ed1ab_0 conda-forge +popt 1.16 h0b475e3_2002 conda-forge +pre-commit 4.5.1 pyha770c72_0 conda-forge +progressbar2 4.5.0 pyhd8ed1ab_1 conda-forge +proj 9.7.0 hb72c0af_0 conda-forge +prometheus-cpp 1.3.0 ha5d0236_0 conda-forge +prometheus_client 0.23.1 pyhd8ed1ab_0 conda-forge +prompt-toolkit 3.0.52 pyha770c72_0 conda-forge +prompt_toolkit 3.0.52 hd8ed1ab_0 conda-forge +psutil 7.1.3 py313h54dd161_0 conda-forge +pthread-stubs 0.4 hb9d3cd8_1002 conda-forge +ptyprocess 0.7.0 pyhd8ed1ab_1 conda-forge +pugixml 1.15 h3f63f65_0 conda-forge +pulseaudio-client 17.0 h9a8bead_2 conda-forge +pure_eval 0.2.3 pyhd8ed1ab_1 conda-forge +pyamg 5.3.0 py313hfaae9d9_1 conda-forge +pyarrow 22.0.0 py313h78bf25f_0 conda-forge +pyarrow-core 22.0.0 py313he109ebe_0_cpu conda-forge +pycodestyle 2.14.0 pyhd8ed1ab_0 conda-forge +pycparser 2.22 pyh29332c3_1 conda-forge +pyevtk 1.6.0 pyhb0bfe47_1 conda-forge +pyflakes 3.4.0 pyhd8ed1ab_0 conda-forge +pygments 2.19.2 pyhd8ed1ab_0 conda-forge +pyparsing 3.2.5 pyhcf101f3_0 conda-forge +pyproj 3.7.2 py313h77f6078_2 conda-forge +pyremap 2.1.0 pyhd8ed1ab_0 conda-forge +pyshp 3.0.2 pyhd8ed1ab_0 conda-forge +pysocks 1.7.1 pyha55dd90_7 conda-forge +pytest 9.0.2 pyhcf101f3_0 conda-forge +python 3.13.9 hc97d973_101_cp313 conda-forge +python-dateutil 2.9.0.post0 pyhe01879c_2 conda-forge +python-fastjsonschema 2.21.2 pyhe01879c_0 conda-forge +python-gil 3.13.9 h4df99d1_101 conda-forge +python-igraph 1.0.0 py313h7033f15_0 conda-forge +python-json-logger 2.0.7 pyhd8ed1ab_0 conda-forge +python-tzdata 2025.2 pyhd8ed1ab_0 conda-forge +python-utils 3.9.1 pyhff2d567_1 conda-forge +python_abi 3.13 8_cp313 conda-forge +pytz 2025.2 pyhd8ed1ab_0 conda-forge +pywavelets 1.9.0 py313h29aa505_2 conda-forge +pyyaml 6.0.3 py313h3dea7bd_0 conda-forge +pyzmq 27.1.0 py312hfb55c3c_0 conda-forge +qhull 2020.2 h434a139_5 conda-forge +rav1e 0.7.1 h8fae777_3 conda-forge +re2 2025.11.05 h5301d42_0 conda-forge +readline 8.2 h8c095d6_2 conda-forge +referencing 0.37.0 pyhcf101f3_0 conda-forge +requests 2.32.5 pyhcf101f3_1 conda-forge +rfc3339-validator 0.1.4 pyhd8ed1ab_1 conda-forge +rfc3986-validator 0.1.1 pyh9f0ad1d_0 conda-forge +rfc3987-syntax 1.1.0 pyhe01879c_1 conda-forge +roman-numerals-py 3.1.0 pyhd8ed1ab_0 conda-forge +rpds-py 0.28.0 py313h843e2db_2 conda-forge +rsync 3.4.1 h81c0278_2 conda-forge +ruamel.yaml 0.18.17 py313h07c4f96_0 conda-forge +ruamel.yaml.clib 0.2.14 py313h07c4f96_0 conda-forge +s2n 1.6.0 h8399546_1 conda-forge +scikit-image 0.26.0 np2py313hb172dc5_0 conda-forge +scipy 1.17.0 py313h4b8bb8b_1 conda-forge +sdl2 2.32.56 h54a6638_0 conda-forge +sdl3 3.2.26 h68140b3_0 conda-forge +send2trash 1.8.3 pyh0d859eb_1 conda-forge +setuptools 80.9.0 pyhff2d567_0 conda-forge +shaderc 2025.4 h3e344bc_0 conda-forge +shapely 2.1.2 py313had47c43_2 conda-forge +six 1.17.0 pyhe01879c_1 conda-forge +snappy 1.2.2 h03e3b7b_1 conda-forge +sniffio 1.3.1 pyhd8ed1ab_2 conda-forge +snowballstemmer 3.0.1 pyhd8ed1ab_0 conda-forge +sortedcontainers 2.4.0 pyhd8ed1ab_1 conda-forge +soupsieve 2.8 pyhd8ed1ab_0 conda-forge +sphinx 8.2.3 pyhd8ed1ab_0 conda-forge +sphinx_rtd_theme 3.0.2 pyha770c72_0 conda-forge +sphinxcontrib-applehelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-devhelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-htmlhelp 2.1.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-jquery 4.1 pyhd8ed1ab_1 conda-forge +sphinxcontrib-jsmath 1.0.1 pyhd8ed1ab_1 conda-forge +sphinxcontrib-qthelp 2.0.0 pyhd8ed1ab_1 conda-forge +sphinxcontrib-serializinghtml 1.1.10 pyhd8ed1ab_1 conda-forge +spirv-tools 2025.4 hb700be7_0 conda-forge +sqlite 3.51.0 heff268d_0 conda-forge +stack_data 0.6.3 pyhd8ed1ab_1 conda-forge +svt-av1 3.1.2 hecca717_0 conda-forge +tbb 2022.3.0 h8d10470_1 conda-forge +tblib 3.2.2 pyhcf101f3_0 conda-forge +tempest-remap 2.2.0 h397758c_8 conda-forge +termcolor 3.2.0 pyhd8ed1ab_0 conda-forge +terminado 0.18.1 pyh0d859eb_0 conda-forge +texttable 1.7.0 pyhd8ed1ab_1 conda-forge +tifffile 2025.10.16 pyhd8ed1ab_0 conda-forge +tinycss2 1.4.0 pyhd8ed1ab_0 conda-forge +tk 8.6.13 noxft_ha0e22de_103 conda-forge +tomli 2.3.0 pyhcf101f3_0 conda-forge +toolz 1.1.0 pyhd8ed1ab_1 conda-forge +tornado 6.5.2 py313h07c4f96_2 conda-forge +tqdm 4.67.1 pyhd8ed1ab_1 conda-forge +traitlets 5.14.3 pyhd8ed1ab_1 conda-forge +typing-extensions 4.15.0 h396c80c_0 conda-forge +typing_extensions 4.15.0 pyhcf101f3_0 conda-forge +typing_utils 0.1.0 pyhd8ed1ab_1 conda-forge +tzdata 2025b h78e105d_0 conda-forge +udunits2 2.2.28 h40f5838_3 conda-forge +ukkonen 1.0.1 py313h7037e92_6 conda-forge +uri-template 1.3.0 pyhd8ed1ab_1 conda-forge +urllib3 2.5.0 pyhd8ed1ab_0 conda-forge +virtualenv 20.35.4 pyhd8ed1ab_0 conda-forge +wayland 1.24.0 hd6090a7_1 conda-forge +wayland-protocols 1.45 hd8ed1ab_0 conda-forge +wcwidth 0.2.14 pyhd8ed1ab_0 conda-forge +webcolors 25.10.0 pyhd8ed1ab_0 conda-forge +webencodings 0.5.1 pyhd8ed1ab_3 conda-forge +websocket-client 1.9.0 pyhd8ed1ab_0 conda-forge +widgetsnbextension 4.0.15 pyhd8ed1ab_0 conda-forge +x264 1!164.3095 h166bdaf_2 conda-forge +x265 3.5 h924138e_3 conda-forge +xarray 2025.12.0 pyhcf101f3_0 conda-forge +xkeyboard-config 2.46 hb03c661_0 conda-forge +xorg-libice 1.1.2 hb9d3cd8_0 conda-forge +xorg-libsm 1.2.6 he73a12e_0 conda-forge +xorg-libx11 1.8.12 h4f16b4b_0 conda-forge +xorg-libxau 1.0.12 hb03c661_1 conda-forge +xorg-libxaw 1.0.16 hb9d3cd8_0 conda-forge +xorg-libxcursor 1.2.3 hb9d3cd8_0 conda-forge +xorg-libxdmcp 1.1.5 hb03c661_1 conda-forge +xorg-libxext 1.3.6 hb9d3cd8_0 conda-forge +xorg-libxfixes 6.0.2 hb03c661_0 conda-forge +xorg-libxmu 1.2.1 hb9d3cd8_1 conda-forge +xorg-libxpm 3.5.17 hb9d3cd8_1 conda-forge +xorg-libxrandr 1.5.4 hb9d3cd8_0 conda-forge +xorg-libxrender 0.9.12 hb9d3cd8_0 conda-forge +xorg-libxscrnsaver 1.2.4 hb9d3cd8_0 conda-forge +xorg-libxt 1.3.1 hb9d3cd8_0 conda-forge +xxhash 0.8.3 hb47aa4a_0 conda-forge +xyzservices 2025.10.0 pyhd8ed1ab_0 conda-forge +yaml 0.2.5 h280c20c_3 conda-forge +zeromq 4.3.5 h387f397_9 conda-forge +zfp 1.0.1 h909a3a2_3 conda-forge +zict 3.0.0 pyhd8ed1ab_1 conda-forge +zipp 3.23.0 pyhd8ed1ab_0 conda-forge +zlib 1.3.1 hb9d3cd8_2 conda-forge +zlib-ng 2.2.5 hde8ca8f_0 conda-forge +zstandard 0.25.0 py313h54dd161_1 conda-forge +zstd 1.5.7 hb8e6e7a_2 conda-forge + +*********************************************************************** diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/results_aggregator.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/results_aggregator.py similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_analysis/results_aggregator.py rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/results_aggregator.py diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/test_case.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/test_case.py similarity index 79% rename from compass/landice/tests/ensemble_generator/sgh_analysis/test_case.py rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/test_case.py index 06a0e1a4fa..9e2b9c5486 100644 --- a/compass/landice/tests/ensemble_generator/sgh_analysis/test_case.py +++ b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/test_case.py @@ -44,17 +44,18 @@ def configure(self): Configure analysis by reading ensemble directory to analyze. """ config = self.config - section = config.get('analysis_ensemble', {}) - ensemble_dir = section.get('ensemble_work_dir') - - if not ensemble_dir: + try: + ensemble_dir = config.get('analysis_ensemble', + 'ensemble_work_dir') + except Exception: raise ValueError( - "analysis_ensemble config must specify " - "ensemble_work_dir\n" + "analysis_ensemble config must specify:\n" + " ensemble_work_dir\n" "Add to config file:\n" "[analysis_ensemble]\n" - "ensemble_work_dir = /path/to/completed/ensemble" + "ensemble_work_dir = /path/to/ensemble/work/dir\n" + "ensemble_config_file = /path/to/ensemble.cfg" ) if not os.path.exists(ensemble_dir): @@ -62,7 +63,7 @@ def configure(self): f"ensemble_work_dir not found: {ensemble_dir}" ) - # Add single analysis step (config file will be auto-detected) + # Add single analysis step self.add_step(AnalysisStep( test_case=self, ensemble_dir=ensemble_dir diff --git a/compass/landice/tests/ensemble_generator/sgh_analysis/validate_mali_with_spec.py b/compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/validate_mali_with_spec.py similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_analysis/validate_mali_with_spec.py rename to compass/landice/tests/ensemble_generator/sgh_ensemble_analysis/validate_mali_with_spec.py diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/restart_member.py b/compass/landice/tests/ensemble_generator/sgh_restart/restart_member.py deleted file mode 100644 index d80f062360..0000000000 --- a/compass/landice/tests/ensemble_generator/sgh_restart/restart_member.py +++ /dev/null @@ -1,241 +0,0 @@ -""" -Step for restarting a single incomplete ensemble member. -""" - -import os -import shutil - -from compass.io import symlink -from compass.job import write_job_script -from compass.model import run_model -from compass.step import Step - - -class RestartMember(Step): - """ - A step for restarting an incomplete ensemble member from checkpoint. - - This step: - 1. Links to the original run's restart files - 2. Updates configuration for restart (timestamps, namelist) - 3. Sets up job script - 4. Runs the restart - - Attributes - ---------- - run_num : int - The run number for this ensemble member - - spinup_work_dir : str - Path to the original spinup ensemble work directory - - restart_attempt : int - Which restart attempt this is (1 = first, 2 = second, etc.) - """ - - def __init__(self, test_case, run_num, spinup_work_dir): - """ - Create a restart step for an ensemble member - - Parameters - ---------- - test_case : compass.TestCase - The test case this step belongs to - - run_num : int - The run number for this ensemble member - - spinup_work_dir : str - Path to the directory containing the original spinup runs - """ - self.run_num = run_num - self.spinup_work_dir = spinup_work_dir - self.name = f'run{run_num:03}_restart' - - super().__init__(test_case=test_case, name=self.name) - - def setup(self): - """ - Set up this restart by: - 1. Identifying the restart attempt number - 2. Copying necessary files from original run - 3. Updating restart configuration - 4. Setting up job script - """ - - print(f'Setting up restart for run number {self.run_num}') - - config = self.config - run_name = f'run{self.run_num:03}' - original_run_dir = os.path.join(self.spinup_work_dir, run_name) - - if not os.path.exists(original_run_dir): - raise RuntimeError( - f"Original run directory not found: {original_run_dir}") - - # Determine restart attempt number - self.restart_attempt = self._get_restart_attempt_number( - original_run_dir) - restart_subdir = os.path.join( - self.work_dir, f'restart_attempt_{ - self.restart_attempt}') - os.makedirs(restart_subdir, exist_ok=True) - - # Read restart timestamp to determine simulation state - restart_timestamp_file = os.path.join( - original_run_dir, 'restart_timestamp') - if not os.path.exists(restart_timestamp_file): - raise RuntimeError(f"No restart_timestamp in {original_run_dir}") - - with open(restart_timestamp_file, 'r') as f: - restart_time = f.read().strip() - - print(f" {run_name}: Restarting from timestamp {restart_time}") - print(f" {run_name}: Restart attempt {self.restart_attempt}") - - # Copy essential configuration files - files_to_copy = [ - 'namelist.landice', - 'streams.landice', - 'albany_input.yaml', - 'run_info.cfg' - ] - - for fname in files_to_copy: - src = os.path.join(original_run_dir, fname) - dst = os.path.join(restart_subdir, fname) - if os.path.exists(src): - shutil.copy(src, dst) - - # Add model as input - self.add_model_as_input() - - # Copy or symlink restart file from original run - self._copy_restart_file(original_run_dir, restart_subdir, restart_time) - - # Copy graph file if it exists - graph_file = os.path.join(original_run_dir, 'graph.info') - if os.path.exists(graph_file): - shutil.copy(graph_file, restart_subdir) - - # Set up job script - self.ntasks = config.getint('ensemble', 'ntasks', fallback=128) - self.min_tasks = self.ntasks - - config.set('job', 'job_name', f'uq_{run_name}_r{self.restart_attempt}') - machine = config.get('deploy', 'machine') - - # Create pre/post run commands - pre_run_cmd = ( - 'LOGDIR=restart_logs_`date +"%Y-%m-%d_%H-%M-%S"`;' - 'mkdir -p $LOGDIR; cp log* $LOGDIR 2>/dev/null || true; ' - 'date' - ) - post_run_cmd = "date" - - write_job_script(config, machine, - target_cores=self.ntasks, min_cores=self.min_tasks, - work_dir=restart_subdir, - pre_run_commands=pre_run_cmd, - post_run_commands=post_run_cmd) - - # Create symlink to load script if available - if 'LOAD_COMPASS_ENV' in os.environ: - script_filename = os.environ['LOAD_COMPASS_ENV'] - symlink(script_filename, os.path.join(restart_subdir, - 'load_compass_env.sh')) - - # Store for run method - self.restart_work_dir = restart_subdir - self.original_run_dir = original_run_dir - - def run(self): - """ - Run this restart of the ensemble member. - """ - print( - f"Running restart for run{ - self.run_num:03} (attempt { - self.restart_attempt})") - run_model(self) - - def _get_restart_attempt_number(self, original_run_dir): - """ - Determine which restart attempt this is. - - Parameters - ---------- - original_run_dir : str - Directory of the original run - - Returns - ------- - int - Restart attempt number (1 for first restart, 2 for second, etc.) - """ - # Count existing restart_attempt_* subdirectories - restart_dirs = [] - if os.path.exists(original_run_dir): - restart_dirs = [d for d in os.listdir(original_run_dir) - if d.startswith('restart_attempt_')] - - return len(restart_dirs) + 1 - - def _copy_restart_file( - self, - original_run_dir, - restart_subdir, - restart_time): - """ - Copy the appropriate restart file to the restart directory. - - Parameters - ---------- - original_run_dir : str - Directory of the original run - - restart_subdir : str - Directory for this restart attempt - - restart_time : str - Time string from restart_timestamp (format: YYYY-MM-DD_HH:MM:SS) - """ - import glob - - # MALI restart files typically named as: rst.YYYY-MM-DD.nc - # Extract just the date part from restart_time - date_part = restart_time.split('_')[0] # YYYY-MM-DD - - # Look for restart file with this date in output directory - output_dir = os.path.join(original_run_dir, 'output') - if os.path.exists(output_dir): - pattern = os.path.join(output_dir, f'rst.{date_part}*.nc') - restart_files = glob.glob(pattern) - - if restart_files: - # Use the most recent (last) restart file - src_file = sorted(restart_files)[-1] - dst_file = os.path.join(restart_subdir, 'rst.restart.nc') - shutil.copy(src_file, dst_file) - print(f" Copied restart file: {os.path.basename(src_file)}") - return - - # Look in run directory directly (older style) - pattern = os.path.join(original_run_dir, f'rst.{date_part}*.nc') - restart_files = glob.glob(pattern) - - if restart_files: - src_file = sorted(restart_files)[-1] - dst_file = os.path.join(restart_subdir, 'rst.restart.nc') - shutil.copy(src_file, dst_file) - print(f" Copied restart file: {os.path.basename(src_file)}") - else: - print( - f" WARNING: No restart file found matching date {date_part}") - print(" Searched patterns:") - print(f" {os.path.join(output_dir, f'rst.{date_part}*.nc')}") - print( - f" { - os.path.join( - original_run_dir, - f'rst.{date_part}*.nc')}") diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/README.md b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/README.md similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_restart/README.md rename to compass/landice/tests/ensemble_generator/sgh_restart_ensemble/README.md diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/__init__.py b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/__init__.py similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_restart/__init__.py rename to compass/landice/tests/ensemble_generator/sgh_restart_ensemble/__init__.py diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/ensemble_generator.cfg b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/ensemble_generator.cfg similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_restart/ensemble_generator.cfg rename to compass/landice/tests/ensemble_generator/sgh_restart_ensemble/ensemble_generator.cfg diff --git a/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/restart_member.py b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/restart_member.py new file mode 100644 index 0000000000..e459d20691 --- /dev/null +++ b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/restart_member.py @@ -0,0 +1,100 @@ +""" +Step for restarting a single incomplete ensemble member in-place. +""" + +import os + +from compass.step import Step + + +def _set_restart_in_namelist(namelist_path): + """Set config_do_restart = .true. in-place in namelist.landice.""" + with open(namelist_path, 'r') as f: + lines = f.readlines() + + updated = False + new_lines = [] + for line in lines: + stripped = line.strip() + if stripped.startswith('config_do_restart'): + # Replace whatever value it has with .true. + new_lines.append( + line[:line.index('config_do_restart')] + + 'config_do_restart = .true.\n' + ) + updated = True + else: + new_lines.append(line) + + if not updated: + # Append if not present + new_lines.append('\nconfig_do_restart = .true.\n') + + with open(namelist_path, 'w') as f: + f.writelines(new_lines) + + +class InPlaceRestartMember(Step): + """ + A step for restarting an incomplete ensemble member in-place. + + Rather than copying files to a new directory, this step operates directly + in the original run directory. It sets ``config_do_restart = .true.`` in + ``namelist.landice`` so that the run continues from its last checkpoint + when ``job_script.sh`` is resubmitted by ``EnsembleManager``. + + Attributes + ---------- + run_num : int + The run number for this ensemble member + + spinup_work_dir : str + Path to the original spinup ensemble work directory + """ + + def __init__(self, test_case, run_num, spinup_work_dir): + """ + Create an in-place restart step for an ensemble member + + Parameters + ---------- + test_case : compass.TestCase + The test case this step belongs to + + run_num : int + The run number for this ensemble member + + spinup_work_dir : str + Path to the directory containing the original spinup runs + """ + self.run_num = run_num + self.spinup_work_dir = spinup_work_dir + + name = f'run{run_num:03}_restart' + super().__init__(test_case=test_case, name=name) + + # Override work_dir to point to the original run directory so that + # EnsembleManager submits job_script.sh from the correct location. + self.work_dir = os.path.join(spinup_work_dir, f'run{run_num:03}') + + def setup(self): + """ + Edit ``namelist.landice`` in-place in the original run directory, + setting ``config_do_restart = .true.``. + + No files are copied and no new subdirectories are created. + """ + run_dir = os.path.join(self.spinup_work_dir, f'run{self.run_num:03}') + + if not os.path.exists(run_dir): + raise RuntimeError( + f"Original run directory not found: {run_dir}") + + namelist_path = os.path.join(run_dir, 'namelist.landice') + + if not os.path.exists(namelist_path): + raise RuntimeError( + f"namelist.landice not found in {run_dir}") + + print(f'Setting config_do_restart = .true. in {namelist_path}') + _set_restart_in_namelist(namelist_path) diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/ensemble_scheduler.py b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/restart_scheduler.py similarity index 100% rename from compass/landice/tests/ensemble_generator/sgh_restart/ensemble_scheduler.py rename to compass/landice/tests/ensemble_generator/sgh_restart_ensemble/restart_scheduler.py diff --git a/compass/landice/tests/ensemble_generator/sgh_restart/test_case.py b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/test_case.py similarity index 98% rename from compass/landice/tests/ensemble_generator/sgh_restart/test_case.py rename to compass/landice/tests/ensemble_generator/sgh_restart_ensemble/test_case.py index 1bfad8e665..16388c1dd0 100644 --- a/compass/landice/tests/ensemble_generator/sgh_restart/test_case.py +++ b/compass/landice/tests/ensemble_generator/sgh_restart_ensemble/test_case.py @@ -20,7 +20,7 @@ ) from compass.testcase import TestCase -from .restart_member import RestartMember +from .restart_member import InPlaceRestartMember class RestartEnsemble(TestCase): @@ -106,7 +106,7 @@ def configure(self): print(f"Scheduling restart for {run_name}") # Add restart member step - self.add_step(RestartMember( + self.add_step(InPlaceRestartMember( test_case=self, run_num=run_num, spinup_work_dir=spinup_work_dir