Skip to content

Commit c72f1e4

Browse files
authored
Merge pull request #13 from ClimateImpactLab/bugfix/more-dep-fixes
Draft: fix surge lookup creation for new Xarray
2 parents f5ec63a + 07a2d27 commit c72f1e4

File tree

4 files changed

+62
-52
lines changed

4 files changed

+62
-52
lines changed

pyCIAM/io.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def prep_sliiders(
3636
seg_var="seg_adm",
3737
selectors={},
3838
calc_popdens_with_wetland_area=True,
39+
expand_exposure=True,
3940
storage_options={},
4041
):
4142
"""Import the SLIIDERS dataset (or a different dataset formatted analogously),
@@ -66,6 +67,11 @@ def prep_sliiders(
6667
If True, assume that population can also exist in Wetland area. This is
6768
observed empirically, but presumably at a lower density. Diaz 2016 assumes False
6869
but Depsky 2023 assumes True.
70+
expand_exposure : bool, default True
71+
If the input contains population ("pop") and capital ("K") for a fixed year,
72+
plus a country-level scaling factor for each year, setting this to True
73+
(default) expands this to a panel dataset of each variable. This substantially
74+
increases size of the dataset, so can be set to False if not Needed
6975
storage_options : dict, optional
7076
Passed to :py:function:`xarray.open_zarr`
7177
@@ -99,14 +105,14 @@ def prep_sliiders(
99105
).sel(selectors, drop=True)
100106

101107
inputs = inputs_all.sel({seg_var: seg_vals})
102-
inputs = _s2d(inputs).assign(constants.to_dict())
108+
inputs = _s2d(inputs).assign(constants)
103109

104110
# assign country level vars to each segment
105111
for v in inputs.data_vars:
106112
if "country" in inputs[v].dims:
107113
inputs[v] = inputs[v].sel(country=inputs.seg_country).drop("country")
108114

109-
if "vsl" not in inputs.data_vars:
115+
if "vsl" not in inputs.data_vars and "vsl_ypc_mult" in inputs.data_vars:
110116
if "ref_income" in inputs:
111117
ref_income = inputs.ref_income
112118
else:
@@ -118,7 +124,7 @@ def prep_sliiders(
118124
* (inputs.ypcc / ref_income) ** inputs.vsl_inc_elast
119125
)
120126

121-
if "pop" not in inputs.data_vars:
127+
if expand_exposure and "pop" not in inputs.data_vars:
122128
exp_year = [
123129
v for v in inputs.data_vars if v.startswith("pop_") and "scale" not in v
124130
]
@@ -127,19 +133,23 @@ def prep_sliiders(
127133
pop_var = "pop_" + exp_year
128134
inputs["pop"] = inputs[pop_var] * inputs.pop_scale
129135
inputs = inputs.drop(pop_var)
130-
if "K" not in inputs.data_vars:
136+
if expand_exposure and "K" not in inputs.data_vars:
131137
K_var = "K_" + exp_year
132138
inputs["K"] = inputs[K_var] * inputs.K_scale
133139
inputs = inputs.drop(K_var)
134-
if "dfact" not in inputs.data_vars:
140+
if "dfact" not in inputs.data_vars and "npv_start" in inputs.data_vars:
135141
inputs["dfact"] = (1 / (1 + inputs.dr)) ** (inputs.year - inputs.npv_start)
136142

137143
if "landrent" or "ypc" not in inputs.data_vars:
138144
area = inputs.landarea
139145
if calc_popdens_with_wetland_area:
140146
area = area + inputs.wetland
141147
popdens = (inputs.pop / area).fillna(0)
142-
if "landrent" not in inputs.data_vars:
148+
if (
149+
"landrent" not in inputs.data_vars
150+
and "min_coastland_scale" in inputs.data_vars
151+
and "dr" in inputs.data_vars
152+
):
143153
coastland_scale = np.minimum(
144154
1,
145155
np.maximum(
@@ -149,26 +159,32 @@ def prep_sliiders(
149159
)
150160
inputs["landrent"] = inputs.interior * coastland_scale * inputs.dr
151161

152-
if "ypc" not in inputs.data_vars:
162+
if (
163+
"ypc" not in inputs.data_vars
164+
and "min_pyc_scale" in inputs.data_vars
165+
and "ypc_scale_denom" in inputs.data_vars
166+
and "ypc_scale_elast" in inputs.data_vars
167+
):
153168
ypc_scale = np.maximum(
154169
inputs.min_ypc_scale,
155170
(popdens / inputs.ypc_scale_denom) ** inputs.ypc_scale_elast,
156171
)
157172
inputs["ypc"] = ypc_scale * inputs.ypcc
158173

174+
to_drop = [
175+
"interior",
176+
"dr",
177+
"min_coastland_scale",
178+
"min_ypc_scale",
179+
"ypc_scale_denom",
180+
"ypc_scale_elast",
181+
"vsl_ypc_mult",
182+
"vsl_inc_elast",
183+
]
184+
if expand_exposure:
185+
to_drop += ["pop_scale", "K_scale"]
159186
return inputs.drop(
160-
[
161-
"pop_scale",
162-
"K_scale",
163-
"interior",
164-
"dr",
165-
"min_coastland_scale",
166-
"min_ypc_scale",
167-
"ypc_scale_denom",
168-
"ypc_scale_elast",
169-
"vsl_ypc_mult",
170-
"vsl_inc_elast",
171-
],
187+
to_drop,
172188
errors="ignore",
173189
)
174190

@@ -555,7 +571,8 @@ def get_nearest_slrs(slr_ds, lonlats, x1="seg_lon", y1="seg_lat"):
555571

556572
def add_nearest_slrs(sliiders_ds, slr_ds):
557573
"""Add a variable to ``sliiders_ds`` called `SLR_site_id` that contains the nearest
558-
SLR site to each segment."""
574+
SLR site to each segment.
575+
"""
559576
sliiders_lonlat = sliiders_ds[["seg_lon", "seg_lat"]].to_dataframe()
560577
return sliiders_ds.assign(
561578
SLR_site_id=get_nearest_slrs(slr_ds, sliiders_lonlat).to_xarray()
@@ -672,7 +689,7 @@ def load_ciam_inputs(
672689
seg_vals,
673690
# dropping the "refA_scenario_selectors" b/c this doesn't need to be added to
674691
# the input dataset object
675-
constants=params[params.map(type) != dict],
692+
constants=params[params.map(type) != dict].to_dict(),
676693
seg_var=seg_var,
677694
selectors=selectors,
678695
storage_options=storage_options,
@@ -771,7 +788,7 @@ def load_diaz_inputs(
771788
inputs = prep_sliiders(
772789
input_store,
773790
seg_vals,
774-
constants=params[params.map(type) != dict],
791+
constants=params[params.map(type) != dict].to_dict(),
775792
seg_var="seg",
776793
calc_popdens_with_wetland_area=False,
777794
storage_options=storage_options,

pyCIAM/run.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@ def calc_costs(
504504
# --------- ELEVATION DISTRIBUTION-DEPENENT COSTS ----------
505505
def calc_elev_bin_weights(slr, lb_elevs, bin_width):
506506
"""Calculates the fraction of a cell inundated/abandoned given a defined
507-
slr/retreat height."""
507+
slr/retreat height.
508+
"""
508509
return _pos(np.minimum(slr - lb_elevs, bin_width)) / bin_width
509510

510511
# loop over each elevation band to sum up elevation-distribution-dependent costs
@@ -832,7 +833,6 @@ def select_optimal_case(
832833
``optimalfixed``, which represents the optimal adaptation choice for this region
833834
for each socioeconomic and SLR trajectory.
834835
"""
835-
836836
opt_case = (
837837
xr.open_zarr(
838838
str(all_case_cost_path), chunks=None, storage_options=storage_options
@@ -898,7 +898,7 @@ def execute_pyciam(
898898
diaz_config=False,
899899
dask_client_func=Client,
900900
storage_options=None,
901-
**model_kwargs
901+
**model_kwargs,
902902
):
903903
"""Execute the full pyCIAM model. The following inputs are assumed:
904904
@@ -1032,7 +1032,6 @@ def execute_pyciam(
10321032
**model_kwargs
10331033
Passed directly to :py:func:`pyCIAM.calc_costs`
10341034
"""
1035-
10361035
# convert filepaths to appropriate path representation
10371036
(
10381037
params_path,
@@ -1190,7 +1189,7 @@ def execute_pyciam(
11901189
"case": CASES,
11911190
"costtype": COSTTYPES,
11921191
seg_var: ciam_in[seg_var].values,
1193-
"scenario": slr.scenario,
1192+
"scenario": slr.scenario.astype("unicode"),
11941193
"quantile": quantiles,
11951194
"year": np.arange(params.model_start, ciam_in.year.max().item() + 1),
11961195
**{
@@ -1251,7 +1250,7 @@ def execute_pyciam(
12511250
quantiles=quantiles,
12521251
diaz_inputs=diaz_inputs,
12531252
eps=eps,
1254-
**model_kwargs
1253+
**model_kwargs,
12551254
),
12561255
dim="seg",
12571256
)
@@ -1316,7 +1315,7 @@ def execute_pyciam(
13161315
storage_options=storage_options,
13171316
diaz_inputs=diaz_inputs,
13181317
check=check,
1319-
**model_kwargs
1318+
**model_kwargs,
13201319
)
13211320
)
13221321

@@ -1365,7 +1364,7 @@ def execute_pyciam(
13651364
seg_var=seg_var,
13661365
eps=eps,
13671366
check=check,
1368-
storage_options=storage_options
1367+
storage_options=storage_options,
13691368
),
13701369
axis=1,
13711370
)
@@ -1439,7 +1438,7 @@ def get_refA(
14391438
quantiles=[0.5],
14401439
eps=1,
14411440
diaz_inputs=False,
1442-
**model_kwargs
1441+
**model_kwargs,
14431442
):
14441443
if diaz_inputs:
14451444
inputs, slr = load_diaz_inputs(
@@ -1463,7 +1462,7 @@ def get_refA(
14631462
include_ncc=True,
14641463
storage_options=storage_options,
14651464
quantiles=quantiles,
1466-
**params.refA_scenario_selectors
1465+
**params.refA_scenario_selectors,
14671466
)
14681467
slr = slr.unstack("scen_mc")
14691468
slr = slr.squeeze(drop=True)
@@ -1514,7 +1513,7 @@ def calc_all_cases(
15141513
storage_options={},
15151514
check=True,
15161515
diaz_inputs=False,
1517-
**model_kwargs
1516+
**model_kwargs,
15181517
):
15191518
if check_finished_zarr_workflow(
15201519
finalstore=output_path if check else None,
@@ -1565,7 +1564,7 @@ def calc_all_cases(
15651564
surge_lookup=surge,
15661565
elev_chunksize=None,
15671566
min_R_noadapt=refA,
1568-
**model_kwargs
1567+
**model_kwargs,
15691568
).to_dataset(name="costs")
15701569
if seg_var != "seg":
15711570
out = out.rename(seg=seg_var)
@@ -1589,7 +1588,7 @@ def optimize_case(
15891588
seg_var="seg_adm",
15901589
check=True,
15911590
eps=1,
1592-
storage_options={}
1591+
storage_options={},
15931592
):
15941593
# use last fpath to check if this task has already been run
15951594
if check and check_finished_zarr_workflow(

pyCIAM/surge/lookup.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
"""This module contains functions related to creating a storm surge lookup table used
1+
"""Functions to create a storm surge lookup table.
2+
3+
This module contains functions related to creating a storm surge lookup table used
24
when running pyCIAM in "probabilistic" mode (i.e. running on many thousands of Monte
35
Carlo samples of sea level rise trajectories). In this mode, calculating storm surge
46
damages for each elevation slice, each year, each segment, each socioeconomic
@@ -61,8 +63,7 @@ def _get_lslr_rhdiff_range(
6163
mc_dim="mc_sample_id",
6264
storage_options={},
6365
):
64-
"""Get the range of lslr and rhdiff that we need to model to cover the full range
65-
across scenario/mcs.
66+
"""Get range of lslr and rhdiff that we need to model to cover the full range.
6667
6768
The minimum LSLR value we'll need to model for the purposes of
6869
assessing storm damage is the minimum across sites of: the site-level maximum of "0
@@ -71,7 +72,6 @@ def _get_lslr_rhdiff_range(
7172
maximum experienced at any site in any year for all of the sceanrio/mcs we use in
7273
the binned LSL dataset.
7374
"""
74-
7575
if isinstance(slr_0_years, int):
7676
slr_0_years = [slr_0_years] * len(slr_stores)
7777
assert len(slr_0_years) == len(slr_stores)
@@ -153,14 +153,12 @@ def _get_lslr_rhdiff_range(
153153
{
154154
"lslr_by_seg": (
155155
("lslr", seg_var),
156-
np.linspace(min_lslr, max_lslr, n_interp_pts_lslr),
156+
np.linspace(min_lslr.values, max_lslr.values, n_interp_pts_lslr),
157157
),
158158
"rh_diff_by_seg": (
159159
("rh_diff", seg_var),
160-
np.linspace(0, rh_diff_max, n_interp_pts_rhdiff),
160+
np.linspace(0, rh_diff_max.values, n_interp_pts_rhdiff),
161161
),
162-
},
163-
coords={
164162
"lslr": np.arange(n_interp_pts_lslr),
165163
"rh_diff": np.arange(n_interp_pts_lslr),
166164
seg_var: pc_in[seg_var].values,
@@ -245,7 +243,7 @@ def _save_storm_dam(
245243
slr_0_years=2005,
246244
storage_options={},
247245
):
248-
"""Function to map over each chunk to run through damage calcs."""
246+
"""Map over each chunk to run through damage calcs."""
249247
diff_ranges = _get_lslr_rhdiff_range(
250248
sliiders_store,
251249
slr_stores,
@@ -381,7 +379,9 @@ def create_surge_lookup(
381379
client_kwargs={},
382380
storage_options={},
383381
):
384-
"""Create a storm surge lookup table which is used to define a linear spline
382+
"""Create storm surge lookup table.
383+
384+
Create a storm surge lookup table which is used to define a linear spline
385385
function for each region modeled in pyCIAM. This output is not strictly necessary to
386386
run pyCIAM but substantially reduces computational expense when running pyCIAM on a
387387
large probabilistic ensemble of SLR trajectories.
@@ -460,7 +460,6 @@ def create_surge_lookup(
460460
-------
461461
Returns None, but saves storm surge lookup table to `surge_lookup_store`.
462462
"""
463-
464463
to_save = _create_surge_lookup_skeleton_store(
465464
sliiders_store,
466465
n_interp_pts_lslr,

pyproject.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,12 @@ authors = [{ name = "Ian Bolliger", email = "ian@reask.earth"}, { name = "Nichol
1919
maintainers = [{ name = "Ian Bolliger", email = "ian@reask.earth"}]
2020
dependencies = [
2121
"cloudpathlib",
22-
"dask",
23-
"distributed",
2422
"gitpython",
25-
"numpy",
2623
"rhg_compute_tools",
27-
"pandas",
2824
"parameterize_jobs",
2925
"pint-xarray",
30-
"scipy",
3126
"scikit-learn",
32-
"xarray",
27+
"xarray[accel,parallel]",
3328
"zarr"
3429
]
3530
requires-python = ">=3.6"

0 commit comments

Comments
 (0)