Source code for hydromodpy.pyhelp.processing

# -*- coding: utf-8 -*-
"""
 * Copyright (c) 2023 Alexandre Gauvain, Ronan Abhervé, Jean-Raynald de Dreuzy
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
"""


# ---- Standard Library Imports
import os
import os.path as osp
import multiprocessing as mp
import time
import csv
import calendar

# ---- Third Party imports
import numpy as np

# ---- Local Libraries Imports
from hydromodpy.pyhelp import ensure_help3o_loaded
from hydromodpy.tools import get_logger

logger = get_logger(__name__)

DEL_TEMPFILES = False


# ---- Run HELP

[docs] def run_help_singlecell(item): """Run HELP for a single cell.""" cellname, outparam = item # Check if HELP3O is available help3o = ensure_help3o_loaded() if help3o is None: raise RuntimeError( "HELP3O Fortran extension is not available. " "This usually happens when:\n" "1. The binary could not be downloaded from GitHub\n" "2. The binary is not compatible with your system\n" "3. You are using an unsupported Python version\n" "Please check your internet connection and ensure you are using " "Python 3.11, 3.12, or 3.13 on Linux, macOS, or Windows." ) help3o.run_simulation(*outparam) results = read_monthly_help_output(outparam[5]) if DEL_TEMPFILES: os.remove(outparam[5]) return (cellname, results)
[docs] def run_help_allcells(cellparams, ncore=None): """Run HELP in batch for multiple cells.""" output = {} ncore = max(mp.cpu_count() if ncore is None else ncore, 1) tstart = time.perf_counter() calcul_progress = 0 N = len(cellparams) pool = mp.get_context("spawn").Pool(ncore) for cell in pool.imap_unordered(run_help_singlecell, cellparams.items()): output[cell[0]] = cell[1] calcul_progress += 1 if calcul_progress % 100 == 0 or calcul_progress == N: progress_pct = calcul_progress/N*100 tpassed = time.perf_counter() - tstart tremain = (100-progress_pct)*tpassed/progress_pct/60 if progress_pct > 0 else 0 logger.debug("HELP simulation in progress: %3.1f%% (%0.1f min remaining)", progress_pct, tremain) calcul_time = (time.perf_counter() - tstart) logger.info("HELP simulation completed for %d cells in %0.2f sec", N, calcul_time) return output
# ---- Read HELP output
[docs] def read_monthly_help_output(filename): """ Read the monthly output from .OUT HELP file and return the data as numpy arrays stored in a dictionary. Support the output format that was modified from HELP 3.07 (see PR#2). """ with open(filename, 'r') as csvfile: csvread = list(csv.reader(csvfile)) arr_years = [] vstack_precip = [] vstack_runoff = [] vstack_evapo = [] vstack_subrun1 = [] vstack_subrun2 = [] vstack_percol = [] vstack_rechg = [] year = None i = 0 while True: if i+1 >= len(csvread): break if len(csvread[i]) == 0: i += 1 continue line = csvread[i][0] if 'MONTHLY TOTALS' in line: year = int(line.split()[-1]) arr_years.append(year) subrun1 = None subrun2 = np.zeros(12).astype('float32') percol = None while True: i += 1 if len(csvread[i]) == 0: continue line = csvread[i][0] if '**********' in line: break elif 'PRECIPITATION' in line: precip = np.array(line.split()[-12:]).astype('float32') elif 'RUNOFF' in line: runoff = np.array(line.split()[-12:]).astype('float32') elif 'EVAPOTRANSPIRATION' in line: evapo = np.array(line.split()[-12:]).astype('float32') elif 'LAT. DRAINAGE' in line: if subrun1 is None: subrun1 = np.array( line.split()[-12:]).astype('float32') else: subrun2 += np.array( line.split()[-12:]).astype('float32') elif 'PERCOLATION' in line: if percol is None: percol = np.array(line.split()[-12:]).astype('float32') rechg = np.array(line.split()[-12:]).astype('float32') vstack_precip.append(precip) vstack_runoff.append(runoff) vstack_evapo.append(np.array(evapo).astype('float32')) vstack_rechg.append(np.array(rechg).astype('float32')) vstack_percol.append(np.array(percol).astype('float32')) if subrun1 is None: vstack_subrun1.append(np.zeros(12).astype('float32')) else: vstack_subrun1.append(subrun1) vstack_subrun2.append(subrun2) elif 'FINAL WATER STORAGE' in line: break i += 1 data = {'years': np.array(arr_years).astype('uint16'), 'precip': np.vstack(vstack_precip), 'runoff': np.vstack(vstack_runoff), 'evapo': np.vstack(vstack_evapo), 'subrun1': np.vstack(vstack_subrun1), 'subrun2': np.vstack(vstack_subrun2), 'perco': np.vstack(vstack_percol), 'rechg': np.vstack(vstack_rechg)} return data
[docs] def read_daily_help_output(filename): """ Read the daily output from .OUT HELP file and return the data as numpy arrays stored in a dictionary. """ with open(filename, 'r') as csvfile: csvread = list(csv.reader(csvfile)) nlay = None arr_years = [] arr_days = [] arr_rain = [] arr_ru = [] arr_et = [] arr_ezone = [] arr_headfirst = [] arr_drainfirst = [] arr_leakfirst = [] arr_leaklast = [] year = None nlay = nsub = None for i, line in enumerate(csvread): if line: line = line[0] if 'TOTAL NUMBER OF LAYERS' in line: nlay = int(line.split()[-1]) elif 'TOTAL NUMBER OF SUBPROFILES' in line: nsub = int(line.split()[-1]) if 'DAILY OUTPUT FOR YEAR' in line: year = int(line.split()[-1]) days_in_year = 366 if calendar.isleap(year) else 365 elif year is not None: try: day = int(line[2:5]) rain = float(line[13:19]) ru = float(line[19:26]) et = float(line[26:33]) ezone = float(line[33:41]) headfirst = float(line[41:51]) drainfirst = float(line[51:61]) leakfirst = float(line[61:71]) leaklast = float(line[-10:]) except ValueError: pass else: arr_years.append(year) arr_days.append(day) arr_rain.append(rain) arr_ru.append(ru) arr_et.append(et) arr_ezone.append(ezone) arr_headfirst.append(headfirst) arr_drainfirst.append(drainfirst) arr_leakfirst.append(leakfirst) arr_leaklast.append(leaklast) if day == days_in_year: year = None dataf = {'years': np.array(arr_years).astype('uint16'), 'days': np.array(arr_days).astype('uint16'), 'rain': np.array(arr_rain).astype('float32'), 'runoff': np.array(arr_ru).astype('float32'), 'et': np.array(arr_et).astype('float32'), 'ezone': np.array(arr_ezone).astype('float32'), 'head first': np.array(arr_headfirst).astype('float32'), 'drain first': np.array(arr_drainfirst).astype('float32'), 'leak first': np.array(arr_leakfirst).astype('float32'), 'leak last': np.array(arr_leaklast).astype('float32') } return dataf