Merge pull request 'July2025updates' (!17) from July2025updates into master

Reviewed-on: #17
Reviewed-by: asia <asia@noreply.example.org>
This commit is contained in:
asia 2025-07-08 14:49:31 +02:00
commit fc2e8b53c7

View File

@ -17,7 +17,7 @@ def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max
then the program looks for a label of 'Mw' for magnitude in the catalog then the program looks for a label of 'Mw' for magnitude in the catalog
mc: The magnitude of completeness (Mc) of the catalog mc: The magnitude of completeness (Mc) of the catalog
m_max:M_max. The magnitude distribution is estimated for the range from Mc to M_max. If no value is provided, m_max:M_max. The magnitude distribution is estimated for the range from Mc to M_max. If no value is provided,
then the program sets M_max to be 3 magnitude units above the maximum magnitude value in the catalog. then the program sets M_max to be 1 magnitude units above the maximum magnitude value in the catalog.
m_kde_method: The kernel density estimator to use. m_kde_method: The kernel density estimator to use.
xy_select: If True, perform an estimation of the magnitude distribution using KDE with the chosen KDE method xy_select: If True, perform an estimation of the magnitude distribution using KDE with the chosen KDE method
grid_dim: The grid cell size (in metres) of the final ground motion product map. A smaller cell size will grid_dim: The grid cell size (in metres) of the final ground motion product map. A smaller cell size will
@ -45,20 +45,17 @@ def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max
""" """
import sys import sys
from importlib.metadata import version
import logging import logging
from base_logger import getDefaultLogger from base_logger import getDefaultLogger
from timeit import default_timer as timer from timeit import default_timer as timer
from math import ceil, floor, isnan from math import ceil, floor, isnan
import numpy as np import numpy as np
import scipy
import obspy
import dask import dask
from dask.diagnostics import ProgressBar # use Dask progress bar from dask.diagnostics import ProgressBar # use Dask progress bar
import kalepy as kale import kalepy as kale
import utm import utm
from skimage.transform import resize from skimage.transform import resize
import psutil
import openquake.engine
import igfash import igfash
from igfash.io import read_mat_cat, read_mat_m, read_mat_pdf, read_csv from igfash.io import read_mat_cat, read_mat_m, read_mat_pdf, read_csv
from igfash.window import win_CTL, win_CNE from igfash.window import win_CTL, win_CNE
@ -105,25 +102,13 @@ verbose: {verbose}")
# print key package version numbers # print key package version numbers
logger.debug(f"Python version {sys.version}") logger.debug(f"Python version {sys.version}")
logger.debug(f"Numpy version {np.__version__}") logger.debug(f"Numpy version {version('numpy')}")
logger.debug(f"Scipy version {scipy.__version__}") logger.debug(f"Scipy version {version('scipy')}")
logger.debug(f"Obspy version {obspy.__version__}") logger.debug(f"Obspy version {version('obspy')}")
logger.debug(f"Openquake version {openquake.engine.__version__}") logger.debug(f"Openquake version {version('openquake.engine')}")
logger.debug(f"Igfash version {igfash.__version__}") logger.debug(f"Igfash version {version('igfash')}")
logger.debug(f"Rbeast version {version('rbeast')}")
# print number of cpu cores available logger.debug(f"Dask version {version('dask')}")
ncpu = psutil.cpu_count(logical=False)
logger.debug(f"Number of cpu cores available: {ncpu}")
for process in psutil.process_iter():
with process.oneshot():
# cpu = process.cpu_percent()
cpu = process.cpu_percent() / ncpu
if cpu > 1:
logger.debug(f"{process.name()}, {cpu}")
logger.debug(f"BASELINE CPU LOAD% {psutil.cpu_percent(interval=None, percpu=True)}")
dask.config.set(scheduler='processes') dask.config.set(scheduler='processes')
@ -145,6 +130,12 @@ verbose: {verbose}")
time, mag, lat, lon, depth = read_mat_cat(catalog_file, mag_label=mag_label, catalog_label='Catalog') time, mag, lat, lon, depth = read_mat_cat(catalog_file, mag_label=mag_label, catalog_label='Catalog')
# check for null magnitude values
m_null_idx = np.where(np.isnan(mag))[0]
if len(m_null_idx) > 0:
msg = f"There are null values in the magnitude column of the catalog at indices {m_null_idx}"
logger.error(msg)
raise Exception(msg)
if mc != None: if mc != None:
logger.info("Mc value provided by user") logger.info("Mc value provided by user")
trim_to_mc = True trim_to_mc = True
@ -166,9 +157,10 @@ verbose: {verbose}")
lat = np.delete(lat, indices) lat = np.delete(lat, indices)
lon = np.delete(lon, indices) lon = np.delete(lon, indices)
# if user does not provide a m_max, set m_max to 3 magnitude units above max magnitude in catalog # if user does not provide a m_max, set m_max to 1 magnitude unit above max magnitude in catalog
if m_max == None: if m_max == None:
m_max = mag.max() + 3.0 m_max = mag.max() + 1.0
logger.info(f"No m_max was given. Therefore m_max is automatically set to: {m_max}")
start = timer() start = timer()
@ -233,8 +225,6 @@ verbose: {verbose}")
y_min = y.min() y_min = y.min()
x_max = x.max() x_max = x.max()
y_max = y.max() y_max = y.max()
z_min = depth.min()
z_max = depth.max()
grid_x_max = int(ceil(x_max / grid_dim) * grid_dim) grid_x_max = int(ceil(x_max / grid_dim) * grid_dim)
grid_x_min = int(floor(x_min / grid_dim) * grid_dim) grid_x_min = int(floor(x_min / grid_dim) * grid_dim)
@ -349,6 +339,12 @@ verbose: {verbose}")
elif time_unit == 'years': elif time_unit == 'years':
multiplicator = 1 / 365 multiplicator = 1 / 365
# Raise an exception when time_win_duration from the user is too large relative to the catalog
if time_win_duration/multiplicator > 0.5*(time[-1] - time[0]):
msg = "Activity rate estimation time window must be less than half the catalog length. Use a shorter time window."
logger.error(msg)
raise Exception(msg)
# Selects dates in datenum format and procceeds to forecast value # Selects dates in datenum format and procceeds to forecast value
start_date = datenum_data[-1] - (2 * time_win_duration / multiplicator) start_date = datenum_data[-1] - (2 * time_win_duration / multiplicator)
dates_calc = [date for date in datenum_data if start_date <= date <= datenum_data[-1]] dates_calc = [date for date in datenum_data if start_date <= date <= datenum_data[-1]]
@ -365,7 +361,7 @@ verbose: {verbose}")
act_rate, bin_counts, bin_edges, out, pprs, rt, idx, u_e = calc_bins(np.array(datenum_data), time_unit, act_rate, bin_counts, bin_edges, out, pprs, rt, idx, u_e = calc_bins(np.array(datenum_data), time_unit,
time_win_duration, dates_calc, time_win_duration, dates_calc,
rate_forecast, rate_unc_high, rate_unc_low, rate_forecast, rate_unc_high, rate_unc_low,
multiplicator, quiet=True) multiplicator, quiet=True, figsize=(14,9))
# Assign probabilities # Assign probabilities
lambdas, lambdas_perc = lambda_probs(act_rate, dates_calc, bin_edges) lambdas, lambdas_perc = lambda_probs(act_rate, dates_calc, bin_edges)
@ -377,18 +373,26 @@ verbose: {verbose}")
if forecast_select: if forecast_select:
products = products_string.split() products = products_string.split()
logger.info( logger.info(f"Ground motion forecasting selected with ground motion model {model} and IMT products {products_string}")
f"Ground motion forecasting selected with ground motion model {model} and IMT products {products_string}")
# validate m_max against the grond motion model
models_anthro_limited = ['Lasocki2013', 'Atkinson2015', 'ConvertitoEtAl2012Geysers'] # these models require that m_max<=4.5
if m_max > 4.5 and model in models_anthro_limited:
msg = f"Selected ground motion model {model} is only valid up to a maximum magnitude of 4.5. Please try again with a lower maximum magnitude."
logger.error(msg)
raise Exception(msg)
if not xy_select: if not xy_select:
msg = "Event location distribution modeling was not selected; cannot continue..." msg = "Event location distribution modeling was not selected; cannot continue..."
logger.error(msg) logger.error(msg)
raise Exception(msg) raise Exception(msg)
elif m_pdf[0] == None:
if m_pdf[0] == None:
msg = "Magnitude distribution modeling was not selected and magnitude PDF file was not provided; cannot continue..." msg = "Magnitude distribution modeling was not selected and magnitude PDF file was not provided; cannot continue..."
logger.error(msg) logger.error(msg)
raise Exception(msg) raise Exception(msg)
elif lambdas[0] == None:
if lambdas[0] == None:
msg = "Activity rate modeling was not selected and custom activity rate was not provided; cannot continue..." msg = "Activity rate modeling was not selected and custom activity rate was not provided; cannot continue..."
logger.error(msg) logger.error(msg)
raise Exception(msg) raise Exception(msg)
@ -426,7 +430,10 @@ verbose: {verbose}")
rx_lat[i], rx_lon[i] = utm.to_latlon(x_rx[i], y_rx[i], utm_zone_number, rx_lat[i], rx_lon[i] = utm.to_latlon(x_rx[i], y_rx[i], utm_zone_number,
utm_zone_letter) # get receiver location as lat,lon utm_zone_letter) # get receiver location as lat,lon
# experimental - compute ground motion only at grid points that have minimum probability density of thresh_fxy # convert distances from m to km because openquake ground motion models take input distances in kilometres
distances = distances/1000.0
# compute ground motion only at grid points that have minimum probability density of thresh_fxy
if exclude_low_fxy: if exclude_low_fxy:
indices = list(np.where(fxy.flatten() > thresh_fxy)[0]) indices = list(np.where(fxy.flatten() > thresh_fxy)[0])
else: else:
@ -475,6 +482,11 @@ verbose: {verbose}")
end = timer() end = timer()
logger.info(f"Ground motion exceedance computation time: {round(end - start, 1)} seconds") logger.info(f"Ground motion exceedance computation time: {round(end - start, 1)} seconds")
if np.isnan(iml_grid_raw).all():
msg = "No valid ground motion intensity measures were forecasted. Try a different ground motion model."
logger.error(msg)
raise Exception(msg)
# create list of one empty list for each imt # create list of one empty list for each imt
iml_grid = [[] for _ in range(len(products))] # final ground motion grids iml_grid = [[] for _ in range(len(products))] # final ground motion grids
iml_grid_prep = iml_grid.copy() # temp ground motion grids iml_grid_prep = iml_grid.copy() # temp ground motion grids