Skip to content

Commit c3f0846

Browse files
authored
Merge pull request #1022 from PCMDI/new_monsoon_lee1043_clean_up
New monsoon sperber code in xCDAT -- PR cleaned up
2 parents b354fbe + adeceb0 commit c3f0846

File tree

10 files changed

+859
-392
lines changed

10 files changed

+859
-392
lines changed

doc/jupyter/Demo/Demo_2b_monsoon_sperber.ipynb

Lines changed: 358 additions & 95 deletions
Large diffs are not rendered by default.

pcmdi_metrics/monsoon_sperber/driver_monsoon_sperber.py

Lines changed: 333 additions & 233 deletions
Large diffs are not rendered by default.

pcmdi_metrics/monsoon_sperber/lib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .calc_metrics import sperber_metrics # noqa
33
from .divide_chunks import divide_chunks, divide_chunks_advanced, interp1d # noqa
44
from .model_land_only import model_land_only # noqa
5+
from .lib_monsoon_sperber import pick_year_last_day, tree

pcmdi_metrics/monsoon_sperber/lib/argparse_functions.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ def AddParserArgument(P):
5353
P.add_argument(
5454
"--meyear", dest="meyear", type=int, help="End year for model data set"
5555
)
56-
P.add_argument("--modnames", type=list, default=None, help="List of models")
56+
P.add_argument("--modnames", type=str, default=None, help="List of models")
57+
P.add_argument(
58+
"--list_monsoon_regions", type=str, default=None, help="List of regions"
59+
)
5760
P.add_argument(
5861
"-r",
5962
"--realization",
@@ -95,6 +98,23 @@ def AddParserArgument(P):
9598
default=True,
9699
help="Option for update existing JSON file: True (i.e., update) (default) / False (i.e., overwrite)",
97100
)
101+
# CMEC
102+
P.add_argument(
103+
"--cmec",
104+
dest="cmec",
105+
default=False,
106+
action="store_true",
107+
help="Use to save CMEC format metrics JSON",
108+
)
109+
P.add_argument(
110+
"--no_cmec",
111+
dest="cmec",
112+
default=False,
113+
action="store_false",
114+
help="Do not save CMEC format metrics JSON",
115+
)
116+
P.set_defaults(cmec=False)
117+
98118
return P
99119

100120

pcmdi_metrics/monsoon_sperber/lib/calc_metrics.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,45 @@
88
99
Drafted: Jiwoo Lee, 2018-07
1010
Revised: Jiwoo Lee, 2019-05
11+
Revised: Bo Dong, 2023-12
1112
1213
Note: Code for picking onset/decay index inspired by
1314
https://stackoverflow.com/questions/2236906/first-python-list-index-greater-than-x
1415
"""
1516

16-
import MV2
17-
1817

1918
def sperber_metrics(d, region, debug=False):
2019
"""d: input, 1d array of cumulative pentad time series"""
2120
# Convert accumulation to fractional accumulation; normalize by sum
2221
d_sum = d[-1]
22+
2323
# Normalize
24-
frac_accum = MV2.divide(d, d_sum)
24+
frac_accum = d / d_sum
25+
2526
# Stat 1: Onset
2627
onset_index = next(i for i, v in enumerate(frac_accum) if v >= 0.2)
28+
i = onset_index
29+
v = frac_accum[i]
30+
31+
if debug:
32+
print("i = , ", i, " v = ", v)
33+
2734
# Stat 2: Decay
2835
if region == "GoG":
2936
decay_threshold = 0.6
3037
else:
3138
decay_threshold = 0.8
39+
3240
decay_index = next(i for i, v in enumerate(frac_accum) if v >= decay_threshold)
41+
3342
# Stat 3: Slope
3443
slope = (frac_accum[decay_index] - frac_accum[onset_index]) / float(
3544
decay_index - onset_index
3645
)
46+
3747
# Stat 4: Duration
3848
duration = decay_index - onset_index + 1
49+
3950
# Calc done, return result as dic
4051
return {
4152
"frac_accum": frac_accum,

pcmdi_metrics/monsoon_sperber/lib/divide_chunks.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import print_function
2-
31
import sys
42

53
import numpy as np
@@ -14,7 +12,7 @@
1412

1513
def divide_chunks(data, n):
1614
# looping till length data
17-
for i in range(0, len(data), n):
15+
for i in range(0, data.time.shape[0], n):
1816
yield data[i : i + n]
1917

2018

@@ -24,43 +22,51 @@ def divide_chunks(data, n):
2422

2523
def divide_chunks_advanced(data, n, debug=False):
2624
# Double check first date should be Jan 1 (except for SH monsoon)
27-
tim = data.getTime()
28-
calendar = tim.calendar
29-
month = tim.asComponentTime()[0].month
30-
day = tim.asComponentTime()[0].day
25+
26+
tim = data.time.dt
27+
month = tim.month[0]
28+
day = tim.day[0]
29+
month = month.values
30+
day = day.values
31+
calendar = "gregorian"
32+
3133
if debug:
3234
print("debug: first day of year is " + str(month) + "/" + str(day))
35+
3336
if month not in [1, 7] or day != 1:
3437
sys.exit(
3538
"error: first day of year time series is " + str(month) + "/" + str(day)
3639
)
3740

3841
# Check number of days in given year
39-
nday = len(data)
42+
nday = data.time.shape[0]
4043

4144
if nday in [365, 360]:
4245
# looping till length data
4346
for i in range(0, nday, n):
4447
yield data[i : i + n]
48+
4549
elif nday == 366:
4650
# until leap year day detected
4751
for i in range(0, nday, n):
4852
# Check if leap year date included
4953
leap_detect = False
5054
for ii in range(i, i + n):
51-
date = data.getTime().asComponentTime()[ii]
52-
month = date.month
53-
day = date.day
55+
date = data.time.dt
56+
month = date.month[ii]
57+
day = date.day[ii]
5458
if month == 2 and day > 28:
5559
if debug:
5660
print("debug: leap year detected:", month, "/", day)
5761
leap_detect = True
62+
5863
if leap_detect:
5964
yield data[i : i + n + 1]
6065
tmp = i + n + 1
6166
break
6267
else:
6368
yield data[i : i + n]
69+
6470
# after leap year day passed
6571
if leap_detect:
6672
for i in range(tmp, nday, n):
@@ -76,6 +82,7 @@ def divide_chunks_advanced(data, n, debug=False):
7682
# looping till length data
7783
for i in range(0, nday, n):
7884
yield data[i : i + n]
85+
7986
else:
8087
sys.exit("error: number of days in year is " + str(nday))
8188

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from collections import defaultdict
2+
3+
import xcdat as xc
4+
5+
6+
def tree():
7+
return defaultdict(tree)
8+
9+
10+
def pick_year_last_day(ds):
11+
eday = 31
12+
try:
13+
time_key = xc.axis.get_dim_keys(ds, axis="T")
14+
if "calendar" in ds[time_key].attrs.keys():
15+
if "360" in ds[time_key]["calendar"]:
16+
eday = 30
17+
else:
18+
if "360" in ds[time_key][0].values.item().calendar:
19+
eday = 30
20+
except Exception:
21+
pass
22+
return eday

pcmdi_metrics/monsoon_sperber/lib/model_land_only.py

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,47 @@
11
import cartopy.crs as ccrs
2-
import genutil
32
import matplotlib.pyplot as plt
4-
import MV2
3+
import numpy as np
54

65

76
def model_land_only(model, model_timeseries, lf, debug=False):
87
# -------------------------------------------------
98
# Mask out over ocean grid
109
# - - - - - - - - - - - - - - - - - - - - - - - - -
10+
1111
if debug:
12-
plot_map(model_timeseries[0], "_".join(["test", model, "beforeMask.png"]))
12+
# plot_map(model_timeseries[0], "_".join(["test", model, "beforeMask.png"]))
1313
print("debug: plot for beforeMask done")
1414

1515
# Check land fraction variable to see if it meet criteria
1616
# (0 for ocean, 100 for land, no missing value)
17-
lat_c = lf.getAxis(0)
18-
lon_c = lf.getAxis(1)
19-
lf_id = lf.id
20-
21-
lf = MV2.array(lf.filled(0.0))
22-
23-
lf.setAxis(0, lat_c)
24-
lf.setAxis(1, lon_c)
25-
lf.id = lf_id
2617

27-
if float(MV2.max(lf)) == 1.0:
28-
lf = MV2.multiply(lf, 100.0)
29-
30-
# Matching dimension
31-
if debug:
32-
print("debug: match dimension in model_land_only")
33-
model_timeseries, lf_timeConst = genutil.grower(model_timeseries, lf)
34-
35-
# Conserve axes
36-
time_c = model_timeseries.getAxis(0)
37-
lat_c2 = model_timeseries.getAxis(1)
38-
lon_c2 = model_timeseries.getAxis(2)
18+
if np.max(lf) == 1.0:
19+
lf = lf * 100.0
3920

4021
opt1 = False
4122

4223
if opt1: # Masking out partial ocean grids as well
4324
# Mask out ocean even fractional (leave only pure ocean grid)
44-
model_timeseries_masked = MV2.masked_where(lf_timeConst < 100, model_timeseries)
25+
model_timeseries_masked = model_timeseries.where(lf > 0 & lf < 100)
26+
4527
else: # Mask out only full ocean grid & use weighting for partial ocean grid
46-
model_timeseries_masked = MV2.masked_where(
47-
lf_timeConst == 0, model_timeseries
48-
) # mask out pure ocean grids
28+
model_timeseries_masked = model_timeseries.where(lf > 0)
29+
4930
if model == "EC-EARTH":
5031
# Mask out over 90% land grids for models those consider river as
5132
# part of land-sea fraction. So far only 'EC-EARTH' does..
52-
model_timeseries_masked = MV2.masked_where(
53-
lf_timeConst < 90, model_timeseries
54-
)
55-
lf2 = MV2.divide(lf, 100.0)
56-
model_timeseries, lf2_timeConst = genutil.grower(
57-
model_timeseries, lf2
58-
) # Matching dimension
59-
model_timeseries_masked = MV2.multiply(
60-
model_timeseries_masked, lf2_timeConst
61-
) # consider land fraction like as weighting
62-
63-
# Make sure to have consistent axes
64-
model_timeseries_masked.setAxis(0, time_c)
65-
model_timeseries_masked.setAxis(1, lat_c2)
66-
model_timeseries_masked.setAxis(2, lon_c2)
33+
model_timeseries_masked = model_timeseries.where(lf > 90)
6734

6835
if debug:
69-
plot_map(model_timeseries_masked[0], "_".join(["test", model, "afterMask.png"]))
36+
# plot_map(model_timeseries_masked[0], "_".join(["test", model, "afterMask.png"]))
7037
print("debug: plot for afterMask done")
7138

7239
return model_timeseries_masked
7340

7441

7542
def plot_map(data, filename):
76-
lons = data.getLongitude()
77-
lats = data.getLatitude()
43+
lons = data["lon"]
44+
lats = data["lat"]
7845
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180))
7946
ax.contourf(lons, lats, data, transform=ccrs.PlateCarree(), cmap="viridis")
8047
ax.coastlines()
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import datetime
2+
import os
3+
4+
# =================================================
5+
# Background Information
6+
# -------------------------------------------------
7+
mip = "cmip5"
8+
exp = "historical"
9+
frequency = "da"
10+
realm = "atm"
11+
12+
# =================================================
13+
# Miscellaneous
14+
# -------------------------------------------------
15+
update_json = False
16+
debug = False
17+
# debug = True
18+
19+
# list_monsoon_regions = ["AIR", "AUS", "Sahel", "GoG", "NAmo", "SAmo"]
20+
list_monsoon_regions = ["AUS"]
21+
# =================================================
22+
# Observation
23+
# -------------------------------------------------
24+
reference_data_name = "GPCP-1-3"
25+
reference_data_path = "/p/user_pub/PCMDIobs/obs4MIPs/NASA-GSFC/GPCP-1DD-CDR-v1-3/day/pr/1x1/latest/pr_day_GPCP-1DD-CDR-v1-3_PCMDIFROGS_1x1_19961001-20201231.nc"
26+
reference_data_lf_path = (
27+
"/work/lee1043/DATA/LandSeaMask_1x1_NCL/NCL_LandSeaMask_rewritten.nc" # noqa
28+
)
29+
30+
varOBS = "pr"
31+
ObsUnitsAdjust = (True, "multiply", 86400.0) # kg m-2 s-1 to mm day-1
32+
33+
osyear = 1998
34+
oeyear = 1999
35+
36+
includeOBS = True
37+
38+
# =================================================
39+
# Models
40+
# -------------------------------------------------
41+
modpath = "/work/lee1043/ESGF/xmls/cmip5/historical/day/pr/cmip5.%(model).%(exp).%(realization).day.pr.xml"
42+
modpath_lf = "/work/lee1043/ESGF/xmls/cmip5/historical/fx/sftlf/cmip5.%(model).historical.r0i0p0.fx.sftlf.xml"
43+
44+
# /p/css03/scratch/published-older/cmip5/output1/CSIRO-BOM/ACCESS1-0/historical/day/atmos/day/r1i1p1/v4/pr/pr_day_ACCESS1-0_historical_r1i1p1_19750101-19991231.nc
45+
46+
# modnames = ['ACCESS1-0', 'ACCESS1-3', 'BCC-CSM1-1', 'BCC-CSM1-1-M', 'BNU-ESM', 'CanCM4', 'CanESM2', 'CCSM4', 'CESM1-BGC', 'CESM1-CAM5', 'CESM1-FASTCHEM', 'CMCC-CESM', 'CMCC-CM', 'CMCC-CMS', 'CNRM-CM5', 'CSIRO-Mk3-6-0', 'EC-EARTH', 'FGOALS-g2', 'GFDL-CM3', 'GFDL-ESM2G', 'GFDL-ESM2M', 'GISS-E2-H', 'GISS-E2-R', 'HadGEM2-AO', 'HadGEM2-CC', 'HadGEM2-ES', 'INMCM4', 'IPSL-CM5A-LR', 'IPSL-CM5A-MR', 'IPSL-CM5B-LR', 'MIROC-ESM', 'MIROC-ESM-CHEM', 'MIROC4h', 'MIROC5', 'MPI-ESM-MR', 'MPI-ESM-P', 'MRI-CGCM3', 'MRI-ESM1', 'NorESM1-M'] # noqa
47+
48+
modnames = ["ACCESS1-0"]
49+
50+
realization = "r1i1p1"
51+
# realization = '*'
52+
53+
varModel = "pr"
54+
ModUnitsAdjust = (True, "multiply", 86400.0) # kg m-2 s-1 to mm day-1
55+
units = "mm/d"
56+
57+
msyear = 1998
58+
meyear = 1999
59+
60+
# =================================================
61+
# Output
62+
# -------------------------------------------------
63+
# pmprdir = "/p/user_pub/pmp/pmp_results/pmp_v1.1.2"
64+
pmprdir = "/p/user_pub/climate_work/dong12/PMP_result/"
65+
case_id = "{:v%Y%m%d}".format(datetime.datetime.now())
66+
67+
if debug:
68+
pmprdir = "/p/user_pub/climate_work/dong12/PMP_result/"
69+
case_id = "{:v%Y%m%d-%H%M}".format(datetime.datetime.now())
70+
71+
results_dir = os.path.join(
72+
pmprdir, "%(output_type)", "monsoon", "monsoon_sperber", mip, exp, case_id
73+
)
74+
75+
nc_out = True # Write output in NetCDF
76+
plot = True # Create map graphics

pcmdi_metrics/monsoon_sperber/param/myParam.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
varOBS = "pr"
2828
ObsUnitsAdjust = (True, "multiply", 86400.0) # kg m-2 s-1 to mm day-1
2929

30-
osyear = 1996
31-
oeyear = 2016
30+
osyear = 1998
31+
oeyear = 1999
3232

3333
includeOBS = True
3434

@@ -49,7 +49,7 @@
4949
ModUnitsAdjust = (True, "multiply", 86400.0) # kg m-2 s-1 to mm day-1
5050
units = "mm/d"
5151

52-
msyear = 1961
52+
msyear = 1998
5353
meyear = 1999
5454

5555
# =================================================

0 commit comments

Comments
 (0)