Compare commits

...

31 Commits

Author SHA1 Message Date
817dcf6df6 Update src/seismic_hazard_forecasting.py 2025-07-01 16:42:07 +02:00
97a44db302 Update src/seismic_hazard_forecasting.py 2025-07-01 16:39:10 +02:00
09e21014d1 Update src/seismic_hazard_forecasting.py 2025-07-01 11:55:34 +02:00
52d2c5b0f7 Update src/seismic_hazard_forecasting.py 2025-07-01 11:53:20 +02:00
85ece9b87a Update src/seismic_hazard_forecasting.py 2025-07-01 11:36:11 +02:00
7f97ec7151 Update src/seismic_hazard_forecasting.py 2025-07-01 10:06:50 +02:00
d5ed8b8aa6 Update src/seismic_hazard_forecasting.py 2025-07-01 10:04:26 +02:00
adb9fcbea6 Update src/seismic_hazard_forecasting.py 2025-07-01 10:00:47 +02:00
5661cb364d Update src/seismic_hazard_forecasting.py 2025-07-01 09:57:17 +02:00
dade43d84d Update src/seismic_hazard_forecasting.py 2025-07-01 09:55:31 +02:00
b1efc7a748 Update src/seismic_hazard_forecasting.py 2025-07-01 09:52:47 +02:00
634175a6f2 Update src/seismic_hazard_forecasting.py 2025-06-30 18:46:46 +02:00
d06e24f826 Update src/seismic_hazard_forecasting.py 2025-06-30 18:44:39 +02:00
4f6aef4359 Update src/seismic_hazard_forecasting.py 2025-06-30 18:40:09 +02:00
152f1cf9c3 Update src/seismic_hazard_forecasting.py 2025-06-30 18:30:15 +02:00
8bf954f135 Update src/seismic_hazard_forecasting.py 2025-06-30 18:29:01 +02:00
a0dcc808bc Update src/seismic_hazard_forecasting.py 2025-06-30 18:27:44 +02:00
d904101ba1 Update src/seismic_hazard_forecasting.py 2025-06-30 18:21:22 +02:00
4aef02a31e Update src/seismic_hazard_forecasting.py 2025-06-30 18:18:36 +02:00
b822019942 Update src/seismic_hazard_forecasting.py 2025-06-30 18:15:24 +02:00
6085979207 Update src/seismic_hazard_forecasting.py 2025-06-30 18:14:30 +02:00
3d12388da9 Update src/seismic_hazard_forecasting.py 2025-06-30 18:13:04 +02:00
1d6002e818 Update src/seismic_hazard_forecasting.py 2025-06-30 18:01:06 +02:00
1d787f8b81 Update src/seismic_hazard_forecasting.py 2025-06-30 17:58:14 +02:00
3ba57fc7c8 Update src/seismic_hazard_forecasting.py 2025-06-30 17:56:58 +02:00
bf0f876984 Update src/seismic_hazard_forecasting.py 2025-06-30 17:44:02 +02:00
dff7be35a4 Update src/seismic_hazard_forecasting.py 2025-06-30 17:41:17 +02:00
2df8086117 Update src/seismic_hazard_forecasting.py 2025-06-30 17:31:09 +02:00
ee0ffd90c4 Update src/seismic_hazard_forecasting.py 2025-06-30 17:28:31 +02:00
8d65c65d5b Update src/seismic_hazard_forecasting.py 2025-06-30 16:55:48 +02:00
e9fd82b040 Update src/seismic_hazard_forecasting.py
log more package version numbers
2025-06-30 16:24:35 +02:00

View File

@ -1,5 +1,189 @@
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
import Rbeast as rb
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter, AutoDateLocator
import matplotlib.pyplot as plt
import pip
pip.main(['install', '--verbose', 'igfash==0.2.2'])
from igfash.rate import datenum_to_datetime, av_rates
def apply_beast(act_rate, **kwargs):
"""
Applies BEAST to the smmothed rate data using different smoothing windows.
Input
act_rate : The activity rate data array to smooth and apply BEAST.
Output
out : A list of BEAST results for each smoothed rate array.
prob : A list of probabilities and change points extracted from BEAST results.
"""
out = []; prob = []
# Define the range of smoothing windows based on 15-20% of the size of rate array
ll = int(np.ceil(0.15 * len(act_rate)))
ul = int(np.ceil(0.20 * len(act_rate)))
# Apply BEAST for each smoothed rate array
for ii in range(ll, ul + 1):
# Apply Gaussian smoothing with window size ~15-20%
bb=pd.Series(act_rate).rolling(window=ii, win_type='gaussian', center=True, min_periods=1).mean(std=ii).to_numpy()
# Apply BEAST on the smoothed array discarding periodicity
beast_result = rb.beast(bb, season='none', **kwargs)
out.append(beast_result)
# Extract probabilities and change points from BEAST results
# Gets the median number of changepoints or if median = 0 gets 90th percentile
for result in out:
if result.trend.ncp_median != 0 and not np.isnan(result.trend.ncp_median):
ncp_median = int(result.trend.ncp_median) # No need for np.max if it's scalar
cp_probs = result.trend.cpPr[:ncp_median]
cps = result.trend.cp[:ncp_median]
sorted_indices = np.argsort(cps)
sorted_cps = cps[sorted_indices]
sorted_probs = cp_probs[sorted_indices]
else:
if not np.isnan(result.trend.ncp_pct90):
ncp_pct90 = int(result.trend.ncp_pct90)
cp_probs = result.trend.cpPr[:ncp_pct90]
cps = result.trend.cp[:ncp_pct90]
sorted_indices = np.argsort(cps)
sorted_cps = cps[sorted_indices]
sorted_probs = cp_probs[sorted_indices]
else:
# Optional fallback in case both are NaN
cp_probs = []
cps = []
# for result in out:
# if result.trend.ncp_median != 0:
# ncp_median = int(np.max(result.trend.ncp_median)) #Ensure it's integer
# cp_probs = result.trend.cpPr[:ncp_median]
# cps = result.trend.cp[:ncp_median]
# else:
# ncp_pct90 = int(np.max(result.trend.ncp_pct90)) #Ensure it's integer
# cp_probs = result.trend.cpPr[:ncp_pct90]
# cps = result.trend.cp[:ncp_pct90]
# Sort the change points and corresponding probabilities
#sorted_indices = np.argsort(cps)
#sorted_cps = cps[sorted_indices]
#sorted_probs = cp_probs[sorted_indices]
# Store the sorted change points and probabilities
prob.append(np.column_stack((sorted_probs, sorted_cps)))
return out, prob
def calc_bins2(dates, unit, bin_dur, dates_calc, rate_forecast, rate_unc_high, rate_unc_low, multiplicator, filename = "activity_rate.png", **kwargs):
start_date = dates.min()
end_date = dates.max()
end_date_dt=datenum_to_datetime(end_date)
# Unit and duration check, default to 15 DAYS or 12 HOURS
# valid_units = ['hours', 'days']
# if unit not in valid_units:
# unit = 'days'
# bin_dur = 15
# Adjust to '12 hours' if data duration is less than 15 days
# if (end_date - start_date) < 15 and unit == 'days':
# unit = 'hours'
# bin_dur = 12
# -------- bin, bin edges, bin midpoint calc -------------
# Determine number of bins
bin_edges=[end_date]
while bin_edges[-1] > start_date:
bin_edges.append(bin_edges[-1] - (bin_dur/multiplicator))
bin_edges = bin_edges[::-1]
bin_edges_dt = [datenum_to_datetime(d) for d in bin_edges]
# Discretize the time data
bin_counts, _ = np.histogram(dates, bins=bin_edges)
# Calculate the rate
act_rate = [count / ((bin_edges[i + 1] - bin_edges[i]) * multiplicator) * multiplicator for i, count in enumerate(bin_counts)]
# ---------Apply BEAST (Zhao et al., 2019) 10 times--------
ppr_all = []
for _ in range(10):
out, ppr = apply_beast(act_rate, **kwargs) # Apply BEAST
ppr_iter = np.concatenate([p for p in ppr if p is not None], axis=0) # Concatenate results
ppr_all.append(ppr_iter[:, 1])
# Concatenate all probabilities from all BEAST runs
pprs = np.concatenate(ppr_all, axis=0)
pprs = pprs[~np.isnan(pprs)]
# -------Changepoint decision and rate calculations--------
rt, idx, u_e = av_rates(act_rate, pprs)
# --------------------- Line Plot -------------------------
fig, ax = plt.subplots(figsize=(14, 5))
# Plot activiry rate for all catalog
ax.plot(bin_edges_dt[1:len(bin_edges)], act_rate, '-o', linewidth=2, markersize=6)
# Plot forecasted value with uncertainties with red line connected to last value
next_date = end_date + (bin_dur/multiplicator)
ax.plot(datenum_to_datetime(next_date), rate_forecast, 'ro', label='Forecasted Rate', markersize=6)
ax.plot([bin_edges_dt[-1], datenum_to_datetime(next_date)], [act_rate[-1], rate_forecast], 'r-')
ax.vlines(datenum_to_datetime(next_date), rate_unc_low, rate_unc_high, colors='r', linewidth=2, label='Bootstrap uncertainty') # Vertical line for the uncertainty range
# Format the x-axis with dates
ax.xaxis.set_major_locator(AutoDateLocator())
ax.xaxis.set_major_formatter(DateFormatter('%d-%b-%Y'))
plt.xticks(rotation=45)
plt.title(f'Activity rate (Time Unit: {unit}, Time Window Duration: {bin_dur} {unit})')
plt.xlabel('Time (Window Center Date)')
plt.ylabel('Activity rate per selected time window')
plt.grid(True)
# Add Vertical Line and Shaded Area
sel_data = bin_edges[-3]
sel_data_dt=datenum_to_datetime(sel_data)
ax.axvline(sel_data_dt, color='r', linestyle='--', linewidth=2, label='Time window used')
def convert_dates_to_num(dates):
return [mdates.date2num(date) if isinstance(date, datetime) else date for date in dates]
# Shade the area to the right of the vertical line
ylim = ax.get_ylim()
# Add the shaded area using the Polygon function
ax.fill_between([sel_data_dt, end_date_dt], 0.0, ylim[1]+0.1, color='r', alpha=0.5)
# Add rates and changepoints from BEAST - changepoint belongs to NEXT period
if 'rt' in locals():
for i in range(len(idx)):
# Plot first period
if i == 0 and len(idx) > 1:
ax.plot(bin_edges_dt[1:len(bin_edges)][:int(u_e[idx[i]])], [rt[i]] * int(u_e[idx[i]]), linewidth=2)
# Plot Last and second to last periods when more than 1 changepoints
elif i == len(idx) - 1 and len(idx) > 1:
ax.plot(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[i - 1]]):int(u_e[idx[i]])],
[rt[i]] * (int(u_e[idx[i]]) - int(u_e[idx[i - 1]])), linewidth=2)
ax.plot(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[i]]):],
[rt[i + 1]] * (len(act_rate) - int(u_e[idx[i]])), linewidth=2)
# Plot first and last period if only one changepoint
elif i == 0 and len(idx) == 1:
ax.plot(bin_edges_dt[1:len(bin_edges)][:int(u_e[idx[i]])], [rt[i]] * int(u_e[idx[i]]), linewidth=2)
ax.plot(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[i]]):],
[rt[i + 1]] * (len(act_rate) - int(u_e[idx[i]])), linewidth=2)
# Plot intermediate value if it's only one
elif len(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[1 - 1]]):int(u_e[idx[1]])])==1:
nnn = multiplicator / bin_dur
ax.plot(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[i - 1]]):int(u_e[idx[i]]) + 1],
[rt[i]] * (int(u_e[idx[i]]) + 1 - int(u_e[idx[i - 1]])), linewidth=2)
# Plot intermediate periods
else:
ax.plot(bin_edges_dt[1:len(bin_edges)][int(u_e[idx[i - 1]]):int(u_e[idx[i]])],
[rt[i]] * (int(u_e[idx[i]]) - int(u_e[idx[i - 1]])), linewidth=2)
plt.legend(loc='best')
plt.savefig(filename)
plt.show()
return act_rate, bin_counts, bin_edges, out, pprs, rt, idx, u_e
def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max,
m_kde_method, xy_select, grid_dim, xy_win_method, rate_select, time_win_duration,
@ -45,21 +229,18 @@ def main(catalog_file, mc_file, pdf_file, m_file, m_select, mag_label, mc, m_max
"""
import sys
from importlib.metadata import version
import logging
from base_logger import getDefaultLogger
from timeit import default_timer as timer
from math import ceil, floor, isnan
import numpy as np
import scipy
import obspy
import dask
from dask.diagnostics import ProgressBar # use Dask progress bar
import kalepy as kale
import utm
from skimage.transform import resize
import psutil
import openquake.engine
import igfash
from igfash.io import read_mat_cat, read_mat_m, read_mat_pdf, read_csv
from igfash.window import win_CTL, win_CNE
import igfash.kde as kde
@ -105,11 +286,13 @@ verbose: {verbose}")
# print key package version numbers
logger.debug(f"Python version {sys.version}")
logger.debug(f"Numpy version {np.__version__}")
logger.debug(f"Scipy version {scipy.__version__}")
logger.debug(f"Obspy version {obspy.__version__}")
logger.debug(f"Openquake version {openquake.engine.__version__}")
logger.debug(f"Igfash version {igfash.__version__}")
logger.debug(f"Numpy version {version('numpy')}")
logger.debug(f"Scipy version {version('scipy')}")
logger.debug(f"Obspy version {version('obspy')}")
logger.debug(f"Openquake version {version('openquake.engine')}")
logger.debug(f"Igfash version {version('igfash')}")
logger.debug(f"Rbeast version {version('rbeast')}")
logger.debug(f"Dask version {version('dask')}")
# print number of cpu cores available
ncpu = psutil.cpu_count(logical=False)
@ -365,7 +548,7 @@ verbose: {verbose}")
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,
rate_forecast, rate_unc_high, rate_unc_low,
multiplicator, quiet=True)
multiplicator, quiet=True, figsize=(14,9))
# Assign probabilities
lambdas, lambdas_perc = lambda_probs(act_rate, dates_calc, bin_edges)