"""One chunk x one batch of ensemble members of a round 4 ensemble run. Starts from the L3 gdirs of one v5 experiment, optionally with the melt_f of a dynamic calibration (round4 DYN_CALIB run) put back into each gdir, then for each member: writes that member's climate into the gdir, runs from year 1 and compiles the chunk output. The calibration (temp_bias, prcp_fac, melt_f) stays the one made with ens183 HIST: only the forcing changes, as in round3. Do not run this directly - see run_slurm.sh and submit_ens.sh. R4_GDIR_URL base url of the v5 experiment (.../exps//) R4_PARAMS_FILE the experiment's params.cfg R4_CHUNK_IDX, R4_CHUNK_SIZE R4_YS start year of the runs R4_MELT_F_CSV dyn_calib_reg11.csv of a DYN_CALIB run, or empty R4_ENS_MEMBERS file with one member per line: "hist 053" or "nat 001" R4_ENS_BATCH, R4_ENS_BATCH_SIZE which members of the file this task runs R4_LMR_URL base url of the unstacked round (lmr_seas_fullsplit_oldBE) R4_HIST_DIR the HIST temperature folder in it (e.g. jjas_hists_v20261005) R4_NAT_URL base url of the round with the NAT temperatures (default R4_LMR_URL). Precip and elevation always come from R4_LMR_URL. OGGM_WORKDIR, OGGM_OUTDIR Output: $OGGM_OUTDIR//_reg11_chunk###.nc, with run_id lmr_mira_hist_ens### or lmr_mira_nat_ens### (round3's names). """ import os import logging import geopandas as gpd import numpy as np import pandas as pd import oggm.cfg as cfg from oggm import utils, workflow, tasks import lmr_forcing GDIR_URL = os.environ['R4_GDIR_URL'] PARAMS_FILE = os.environ['R4_PARAMS_FILE'] CHUNK_IDX = int(os.environ['R4_CHUNK_IDX']) CHUNK_SIZE = int(os.environ['R4_CHUNK_SIZE']) YS = int(os.environ['R4_YS']) MELT_F_CSV = os.environ.get('R4_MELT_F_CSV', '') MEMBERS_FILE = os.environ['R4_ENS_MEMBERS'] BATCH = int(os.environ['R4_ENS_BATCH']) BATCH_SIZE = int(os.environ['R4_ENS_BATCH_SIZE']) LMR_URL = os.environ['R4_LMR_URL'] HIST_DIR = os.environ['R4_HIST_DIR'] NAT_URL = os.environ.get('R4_NAT_URL', '') or LMR_URL RGI_REG = '11' cfg.initialize(file=PARAMS_FILE, logging_level='WORKFLOW') cfg.PARAMS['border'] = 160 WORKING_DIR = os.environ.get('OGGM_WORKDIR', '') if not WORKING_DIR: raise RuntimeError('Need a working dir') utils.mkdir(WORKING_DIR) cfg.PATHS['working_dir'] = WORKING_DIR OUTPUT_DIR = os.environ.get('OGGM_OUTDIR', '') if not OUTPUT_DIR: raise RuntimeError('Need an output dir') utils.mkdir(OUTPUT_DIR) cfg.PARAMS['use_multiprocessing'] = True cfg.PARAMS['continue_on_error'] = True # round3's variables (no mass: there is no calibration here) cfg.PARAMS['store_diagnostic_variables'] = ['volume', 'volume_bsl', 'area', 'area_min_h', 'length'] cfg.PARAMS['min_ice_thick_for_area'] = 2. cfg.PARAMS['min_ice_thick_for_length'] = 2. workflow.init_mp_pool(True) log = logging.getLogger(__name__) # Our members with open(MEMBERS_FILE) as f: members = [line.split() for line in f if line.strip()] members = members[BATCH * BATCH_SIZE:(BATCH + 1) * BATCH_SIZE] if not members: log.workflow(f'Batch {BATCH} is empty, nothing to do.') raise SystemExit(0) # Our chunk of glaciers, Alps only (see run_hist.py) rgidf = gpd.read_file(utils.get_rgi_region_file(RGI_REG, version='62')) rgidf = workflow.get_rgi_chunk(rgidf, CHUNK_IDX, chunk_size=CHUNK_SIZE) rgidf = rgidf.loc[rgidf.O2Region == '1'] if len(rgidf) == 0: log.workflow(f'Chunk {CHUNK_IDX} is empty, nothing to do.') raise SystemExit(0) log.workflow(f'Chunk {CHUNK_IDX}: {len(rgidf)} glaciers, batch {BATCH}: ' f'{len(members)} members ({members[0]} ... {members[-1]})') gdirs = workflow.init_glacier_directories(rgidf, from_prepro_level=3, prepro_base_url=GDIR_URL, prepro_rgi_version='62', prepro_border=160) # The dynamically calibrated melt_f, where there is one. Glaciers without a # value (calibration fallback or failed) keep their static melt_f, which is # also what the DYN_CALIB run used for them. Written from this process: no # worker has touched the settings yet, so they are not stale here. if MELT_F_CSV: mf = pd.read_csv(MELT_F_CSV, index_col=0, low_memory=False) mf = mf['melt_f_dynamic_calibration'] n = 0 for gd in gdirs: v = mf.get(gd.rgi_id, np.nan) if np.isfinite(v): gd.settings['melt_f'] = float(v) n += 1 log.workflow(f'Set the dynamically calibrated melt_f in {n} of ' f'{len(gdirs)} gdirs') for kind, num in members: num = int(num) run_id = f'lmr_mira_{kind}_ens{num:03d}' if kind == 'hist': kw = dict(temp_var='tp_monthly', temp_server=LMR_URL + '/' + HIST_DIR) elif kind == 'nat': kw = dict(temp_var='tp_nat_monthly', temp_server=NAT_URL) else: raise ValueError(f'Unknown member kind: {kind}') log.workflow(f'Running {run_id}, chunk {CHUNK_IDX}') suffix = f'_{kind}_ens{num:03d}' workflow.execute_entity_task(lmr_forcing.process_mira_data, gdirs, ens_member=num, server=LMR_URL, output_filesuffix=suffix, **kw) workflow.execute_entity_task(tasks.run_from_climate_data, gdirs, climate_filename='climate_historical', climate_input_filesuffix=suffix, output_filesuffix=run_id, return_value=False, ys=YS) out_dir = os.path.join(OUTPUT_DIR, run_id) utils.mkdir(out_dir) utils.compile_run_output(gdirs, input_filesuffix=run_id, path=os.path.join(out_dir, f'{run_id}_reg{RGI_REG}' f'_chunk{CHUNK_IDX:03d}.nc')) # Free the local disk: this member's climate and model output files for gd in gdirs: for fn, fs in [('climate_historical', suffix), ('model_diagnostics', run_id), ('model_geometry', run_id)]: fp = gd.get_filepath(fn, filesuffix=fs) if os.path.exists(fp): os.remove(fp)