Skip to content

Commit b520de1

Browse files
authored
Merge pull request #81 from steinadio/feature/bmorph_calibration
Feature/bmorph calibration
2 parents 20146d0 + b66d8fc commit b520de1

File tree

2 files changed

+49
-17
lines changed

2 files changed

+49
-17
lines changed

bmorph/core/bmorph.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ def marginalize_cdf(y_raw, z_raw, vals):
5151

5252
def cqm(raw_x: pd.Series, train_x: pd.Series, ref_x: pd.Series,
5353
raw_y: pd.Series, train_y: pd.Series, ref_y: pd.Series=None,
54-
method='hist', xbins=200, ybins=10, bw=3, rtol=1e-7, atol=0, nsmooth=5) -> pd.Series:
54+
method='hist', xbins=200, ybins=10, bw=3, rtol=1e-7, atol=0,
55+
nsmooth=5, train_cdf_min=1e-4) -> pd.Series:
5556
"""Conditional Quantile Mapping
5657
5758
Multidimensional conditional equidistant CDF matching function:
@@ -89,15 +90,15 @@ def cqm(raw_x: pd.Series, train_x: pd.Series, ref_x: pd.Series,
8990
# Lookup the associated flow values in the CDFs for the reference and training periods
9091
mapped_ref = x_ref[np.argmin(np.abs(u_t - ref_cdf[nx_raw, :]), axis=1)]
9192
mapped_train = x_train[np.argmin(np.abs(u_t - train_cdf[nx_raw, :]), axis=1)]
92-
mapped_train[mapped_train < 1e-6] = 1e-6
93+
mapped_train[mapped_train < train_cdf_min] = train_cdf_min
9394
multipliers = pd.Series(mapped_ref / mapped_train, index=raw_x.index, name='multiplier')
9495
if method == 'hist':
9596
# Do some smoothing just to reduce effects of histogram bin edges
9697
multipliers = multipliers.rolling(nsmooth, win_type='triang', min_periods=1).mean()
9798
return multipliers
9899

99100

100-
def edcdfm(raw_x, raw_cdf, train_cdf, ref_cdf):
101+
def edcdfm(raw_x, raw_cdf, train_cdf, ref_cdf, train_cdf_min=1e-4):
101102
'''Calculate multipliers using an adapted version of the EDCDFm technique
102103
103104
This routine implements part of the PresRat bias correction method from
@@ -156,7 +157,7 @@ def edcdfm(raw_x, raw_cdf, train_cdf, ref_cdf):
156157

157158
# Given u_t and train_cdf determine train_x
158159
train_x = np.percentile(train_cdf, u_t)
159-
train_x[train_x < 1e-6] = 1e-6
160+
train_x[train_x < train_cdf_min] = train_cdf_min
160161

161162
# Given u_t and ref_cdf determine ref_x
162163
ref_x = np.percentile(ref_cdf, u_t)
@@ -170,7 +171,7 @@ def bmorph(raw_ts, train_ts, ref_ts,
170171
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
171172
raw_y=None, ref_y=None, train_y=None,
172173
nsmooth=12, bw=3, xbins=200, ybins=10, rtol=1e-7, atol=0, method='hist',
173-
smooth_multipliers=True):
174+
smooth_multipliers=True, train_cdf_min=1e-4):
174175
'''Morph `raw_ts` based on differences between `ref_ts` and `train_ts`
175176
176177
bmorph is an adaptation of the PresRat bias correction procedure from
@@ -221,6 +222,10 @@ def bmorph(raw_ts, train_ts, ref_ts,
221222
Bins for the flow time series
222223
ybins : int
223224
Bins for the second time series
225+
train_cdf_min : float (optional)
226+
Minimum percentile allowed for train cdf. Defaults as 1e-4 to help handle
227+
data spikes in corrections caused by multipliers being too large from
228+
the ratio between reference and training flows being large.
224229
225230
Returns
226231
-------
@@ -253,7 +258,7 @@ def bmorph(raw_ts, train_ts, ref_ts,
253258
# Calculate the bmorph multipliers based on the smoothed time series and
254259
# PDFs
255260
bmorph_multipliers = edcdfm(raw_smoothed_ts[raw_apply_window],
256-
raw_cdf, train_cdf, ref_cdf)
261+
raw_cdf, train_cdf, ref_cdf, train_cdf_min)
257262

258263
# Apply the bmorph multipliers to the raw time series
259264
bmorph_ts = bmorph_multipliers * raw_ts[raw_apply_window]
@@ -277,7 +282,8 @@ def bmorph(raw_ts, train_ts, ref_ts,
277282
raw_smoothed_y[raw_cdf_window],
278283
train_smoothed_y, ref_smoothed_y,
279284
bw=bw, xbins=xbins, ybins=ybins,
280-
rtol=rtol, atol=atol, method=method)
285+
rtol=rtol, atol=atol, method=method,
286+
train_cdf_min=train_cdf_min)
281287
if smooth_multipliers:
282288
bmorph_multipliers = bmorph_multipliers.rolling(window=nsmooth, min_periods=1, center=True).mean()
283289
bmorph_ts = bmorph_multipliers[raw_apply_window] * raw_ts[raw_apply_window]

bmorph/core/workflows.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from functools import partial
66
from tqdm.autonotebook import tqdm
77
from bmorph.util import mizuroute_utils as mizutil
8+
import os
89

910
def apply_bmorph(raw_ts, train_ts, ref_ts,
1011
apply_window, raw_train_window, ref_train_window,
@@ -13,7 +14,7 @@ def apply_bmorph(raw_ts, train_ts, ref_ts,
1314
interval=pd.DateOffset(years=1),
1415
overlap=60, n_smooth_long=None, n_smooth_short=5,
1516
bw=3, xbins=200, ybins=10,
16-
rtol=1e-6, atol=1e-8, method='hist', **kwargs):
17+
rtol=1e-6, atol=1e-8, method='hist', train_cdf_min=1e-4, **kwargs):
1718
"""Bias correction is performed by bmorph on user-defined intervals.
1819
1920
Parameters
@@ -122,7 +123,7 @@ def apply_bmorph(raw_ts, train_ts, ref_ts,
122123
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
123124
raw_y, ref_y, train_y,
124125
n_smooth_short, bw=bw, xbins=xbins, ybins=ybins, rtol=rtol, atol=atol,
125-
method=method)
126+
method=method, train_cdf_min=train_cdf_min)
126127
bmorph_ts = bmorph_ts.append(bc_total)
127128
bmorph_multipliers = bmorph_multipliers.append(bc_mult)
128129

@@ -150,7 +151,8 @@ def apply_blendmorph(raw_upstream_ts, raw_downstream_ts,
150151
train_upstream_y=None, train_downstream_y=None,
151152
ref_upstream_y=None, ref_downstream_y=None,
152153
n_smooth_long=None, n_smooth_short=5,
153-
bw=3, xbins=200, ybins=10, rtol=1e-6, atol=1e-8, method='hist', **kwargs):
154+
bw=3, xbins=200, ybins=10, rtol=1e-6, atol=1e-8,
155+
method='hist', train_cdf_min=1e-4, **kwargs):
154156
"""Bias correction performed by blending bmorphed flows on user defined intervals.
155157
156158
Blendmorph is used to perform spatially consistent bias correction, this function
@@ -298,22 +300,22 @@ def apply_blendmorph(raw_upstream_ts, raw_downstream_ts,
298300
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
299301
raw_upstream_y, ref_upstream_y, train_upstream_y,
300302
n_smooth_short, bw=bw, xbins=xbins, ybins=ybins, rtol=rtol, atol=atol,
301-
method=method)
303+
method=method, train_cdf_min=train_cdf_min)
302304
bc_down_total, bc_down_mult = bmorph.bmorph(
303305
raw_downstream_ts, train_downstream_ts, ref_downstream_ts,
304306
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
305307
raw_downstream_y, ref_downstream_y, train_downstream_y,
306308
n_smooth_short, bw=bw, xbins=xbins, ybins=ybins, rtol=rtol, atol=atol,
307-
method=method)
309+
method=method, train_cdf_min=train_cdf_min)
308310
else:
309311
bc_up_total, bc_up_mult = bmorph.bmorph(
310312
raw_upstream_ts, train_upstream_ts, ref_upstream_ts,
311313
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
312-
n_smooth_short)
314+
n_smooth_short, train_cdf_min=train_cdf_min)
313315
bc_down_total, bc_down_mult = bmorph.bmorph(
314316
raw_downstream_ts, train_downstream_ts, ref_downstream_ts,
315317
raw_apply_window, raw_train_window, ref_train_window, raw_cdf_window,
316-
n_smooth_short)
318+
n_smooth_short, train_cdf_min=train_cdf_min)
317319

318320
bc_multiplier = (blend_factor * bc_up_mult) + ((1 - blend_factor) * bc_down_mult)
319321
bc_total = (blend_factor * bc_up_total) + ((1 - blend_factor) * bc_down_total)
@@ -392,7 +394,7 @@ def _scbc_u_seg(ds, apply_window, raw_train_window, ref_train_window,
392394
return scbc_u_flows, scbc_u_mults, scbc_u_locals
393395

394396

395-
def apply_scbc(ds, mizuroute_exe, bmorph_config, client=None):
397+
def apply_scbc(ds, mizuroute_exe, bmorph_config, client=None, save_mults=False):
396398
"""
397399
Applies Spatially Consistent Bias Correction (SCBC) by
398400
bias correcting local flows and re-routing them through
@@ -413,13 +415,17 @@ def apply_scbc(ds, mizuroute_exe, bmorph_config, client=None):
413415
for descriptions of the options and their choices.
414416
client: dask.Client (optional)
415417
A `client` object to manage parallel computation.
418+
save_mults: boolean (optional)
419+
Whether to save multipliers from bmorph for diagnosis. If True,
420+
multipliers are saved in the same directory as local flows. Defaults
421+
as False to not save multipliers.
416422
417423
Returns
418424
-------
419425
region_totals: xr.Dataset
420426
The rerouted, total, bias corrected flows for the region
421427
"""
422-
def unpack_and_write_netcdf(results, segs, file_path, out_varname='scbc_flow'):
428+
def unpack_and_write_netcdf(results, segs, file_path, out_varname='scbc_flow', mult_path=None):
423429
flows = [r[0] for r in results]
424430
mults = [r[1] for r in results]
425431
local = [r[2] for r in results]
@@ -428,8 +434,23 @@ def unpack_and_write_netcdf(results, segs, file_path, out_varname='scbc_flow'):
428434
local_ds['time'] = flows[0].index
429435
local_ds.name = out_varname
430436
local_ds = local_ds.where(local_ds >= 1e-4, other=1e-4)
437+
try:
438+
os.remove(file_path)
439+
except OSError:
440+
pass
431441
local_ds.transpose().to_netcdf(file_path)
432442

443+
if mult_path:
444+
try:
445+
os.remove(mult_path)
446+
except OSError:
447+
pass
448+
mult_ds = xr.DataArray(np.vstack(mults), dims=('seg','time'))
449+
mult_ds['seg'] = segs
450+
mult_ds['time'] = flows[0].index
451+
mult_ds.name = 'mults'
452+
mult_ds.transpose().to_netcdf(mult_path)
453+
433454
if 'condition_var' in bmorph_config.keys() and bmorph_config['condition_var']:
434455
scbc_type = 'conditional'
435456
scbc_fun = partial(_scbc_c_seg, **bmorph_config)
@@ -447,7 +468,12 @@ def unpack_and_write_netcdf(results, segs, file_path, out_varname='scbc_flow'):
447468

448469
out_file = (f'{bmorph_config["data_path"]}/input/'
449470
f'{bmorph_config["output_prefix"]}_local_{scbc_type}_scbc.nc')
450-
unpack_and_write_netcdf(results, ds['seg'], out_file)
471+
if save_mults:
472+
mult_file = (f'{bmorph_config["data_path"]}/input/'
473+
f'{bmorph_config["output_prefix"]}_local_mult_{scbc_type}_scbc.nc')
474+
else:
475+
mult_file = None
476+
unpack_and_write_netcdf(results, ds['seg'], out_file, mult_path=mult_file)
451477
config_path, mizuroute_config = mizutil.write_mizuroute_config(
452478
bmorph_config["output_prefix"],
453479
scbc_type, bmorph_config['apply_window'],

0 commit comments

Comments
 (0)