Skip to content

Commit 4dbee77

Browse files
authored
Create pre-industrial and historical ozone forcing ancillaries from UK ozone CMIP7 NetCDF files (#111)
* Code to obtain ESM1.6 ozone from UKESM NetCDF files * Update ozone code after first tests * Do not regrid * Do not use the Gregorian calendar * Fix a typo in a comment * Add the PI_mean ozone script to the collection of ozone scripts (#122) * Add a PI_mean script for 1850-1870 mean as PI control * Remove the added 'month' coordinate before saving * Guess time bounds to ensure that time is contiguous * Set the calendar to Gregorian but replace the bounds (#125) * Save using the Gregorian calendar * Try replacing time bounds * Indent correctly
1 parent 5118935 commit 4dbee77

File tree

5 files changed

+243
-9
lines changed

5 files changed

+243
-9
lines changed

CMIP7/esm1p6/atmosphere/cmip7_ancil_common.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def esm_grid_mask_cube(args):
4545
return cube
4646

4747

48-
def set_gregorian(var):
48+
def set_gregorian(var, replace_bounds=False):
4949
# Change the calendar to Gregorian for the model
5050
time = var.coord("time")
5151
origin = time.units.origin
@@ -59,17 +59,43 @@ def set_gregorian(var):
5959
date.year, date.month, date.day, date.hour, date.minute, date.second
6060
)
6161
tvals[i] = newunits.date2num(newdate)
62-
for j in range(2):
63-
date = time.units.num2date(tbnds[i][j])
64-
newdate = cftime.DatetimeProlepticGregorian(
62+
if replace_bounds:
63+
beg_date = cftime.DatetimeProlepticGregorian(
6564
date.year,
6665
date.month,
67-
date.day,
66+
1,
6867
date.hour,
6968
date.minute,
7069
date.second,
7170
)
72-
tbnds[i][j] = newunits.date2num(newdate)
71+
tbnds[i][0] = newunits.date2num(beg_date)
72+
if date.month == 12:
73+
end_year = date.year + 1
74+
end_month = 1
75+
else:
76+
end_year = date.year
77+
end_month = date.month + 1
78+
end_date = cftime.DatetimeProlepticGregorian(
79+
end_year,
80+
end_month,
81+
1,
82+
date.hour,
83+
date.minute,
84+
date.second,
85+
)
86+
tbnds[i][1] = newunits.date2num(end_date)
87+
else:
88+
for j in range(2):
89+
date = time.units.num2date(tbnds[i][j])
90+
newdate = cftime.DatetimeProlepticGregorian(
91+
date.year,
92+
date.month,
93+
date.day,
94+
date.hour,
95+
date.minute,
96+
date.second,
97+
)
98+
tbnds[i][j] = newunits.date2num(newdate)
7399
time.points = tvals
74100
time.bounds = tbnds
75101
time.units = newunits
@@ -134,7 +160,9 @@ def zero_poles(cube):
134160
cube.data[:, -1] = 0.0
135161

136162

137-
def save_ancil(cubes, save_dirpath, save_filename):
163+
def save_ancil(
164+
cubes, save_dirpath, save_filename, gregorian=True, replace_bounds=False
165+
):
138166
"""
139167
Handle both a list and a single cube
140168
"""
@@ -146,8 +174,9 @@ def save_ancil(cubes, save_dirpath, save_filename):
146174
"""
147175
for cube in cubes:
148176
cube.attributes["grid_staggering"] = 3 # New dynamics
149-
cube.attributes["time_type"] = 1 # Gregorian
150-
set_gregorian(cube)
177+
if gregorian:
178+
cube.attributes["time_type"] = 1 # Gregorian
179+
set_gregorian(cube, replace_bounds=replace_bounds)
151180
"""
152181
ANTS doesn't set the calendar header for monthly fields
153182
See fileformats/ancil/time_headers.py
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from argparse import ArgumentParser
2+
from pathlib import Path
3+
4+
from cmip7_ancil_argparse import (
5+
grid_parser,
6+
path_parser,
7+
)
8+
from cmip7_ancil_common import save_ancil
9+
from cmip7_ancil_constants import ANCIL_TODAY
10+
from ozone.cmip7_ozone import (
11+
fix_cmip7_ozone,
12+
load_cmip7_ozone,
13+
ozone_parser,
14+
)
15+
16+
17+
def parse_args():
18+
parser = ArgumentParser(
19+
parents=[path_parser(), grid_parser(), ozone_parser()],
20+
prog="cmip7_HI_ozone_generate",
21+
description=(
22+
"Generate input files from UK CMIP7 historical ozone forcings"
23+
),
24+
)
25+
return parser.parse_args()
26+
27+
28+
def esm_hi_ozone_save_dirpath(args):
29+
return (
30+
Path(args.ancil_target_dirname)
31+
/ "modern"
32+
/ "historical"
33+
/ "forcing"
34+
/ args.esm_grid_rel_dirname
35+
/ ANCIL_TODAY
36+
)
37+
38+
39+
def save_cmip7_hi_ozone(args, cube):
40+
# Save as an ancillary file
41+
save_dirpath = esm_hi_ozone_save_dirpath(args)
42+
save_ancil(cube, save_dirpath, args.save_filename, replace_bounds=True)
43+
44+
45+
if __name__ == "__main__":
46+
args = parse_args()
47+
48+
# Load the CMIP7 datasets
49+
ozone_cube = load_cmip7_ozone(args)
50+
# Match the ESM1.5 mask
51+
esm_cube = fix_cmip7_ozone(args, ozone_cube)
52+
# Save the ancillary
53+
save_cmip7_hi_ozone(args, esm_cube)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from argparse import ArgumentParser
2+
from pathlib import Path
3+
4+
import iris.coord_categorisation
5+
from cmip7_ancil_argparse import (
6+
grid_parser,
7+
path_parser,
8+
)
9+
from cmip7_ancil_common import save_ancil
10+
from cmip7_ancil_constants import ANCIL_TODAY
11+
from ozone.cmip7_ozone import (
12+
fix_cmip7_ozone,
13+
load_cmip7_ozone,
14+
ozone_parser,
15+
)
16+
17+
18+
def parse_args():
19+
parser = ArgumentParser(
20+
parents=[path_parser(), grid_parser(), ozone_parser()],
21+
prog="cmip7_PI_mean_ozone_generate",
22+
description=(
23+
"Generate input files from 21 year mean of UK CMIP7 historical"
24+
"ozone forcings"
25+
),
26+
)
27+
return parser.parse_args()
28+
29+
30+
def esm_pi_ozone_save_dirpath(args):
31+
return (
32+
Path(args.ancil_target_dirname)
33+
/ "modern"
34+
/ "pre-industrial-mean"
35+
/ "forcing"
36+
/ args.esm_grid_rel_dirname
37+
/ ANCIL_TODAY
38+
)
39+
40+
41+
def save_cmip7_pi_mean_ozone(args, cube):
42+
# Add a "month_number" variable.
43+
iris.coord_categorisation.add_month_number(
44+
cube, "time", name="month_number"
45+
)
46+
# Aggregate by "month_number" to obtain a mean for each month.
47+
cube = cube.aggregated_by("month_number", iris.analysis.MEAN)
48+
# Re-create the time bounds to ensure that time is contiguous.
49+
time = cube.coord("time")
50+
time.bounds = None
51+
time.guess_bounds(bound_position=0.5)
52+
# Remove the added "month_number" coordinate before saving.
53+
cube.remove_coord("month_number")
54+
# Save as an ancillary file
55+
save_dirpath = esm_pi_ozone_save_dirpath(args)
56+
save_ancil(cube, save_dirpath, args.save_filename, replace_bounds=True)
57+
58+
59+
if __name__ == "__main__":
60+
args = parse_args()
61+
62+
# Load the CMIP7 datasets
63+
ozone_cube = load_cmip7_ozone(args)
64+
# Match the ESM1.5 mask
65+
esm_cube = fix_cmip7_ozone(args, ozone_cube)
66+
# Save the ancillary
67+
save_cmip7_pi_mean_ozone(args, esm_cube)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from argparse import ArgumentParser
2+
from pathlib import Path
3+
4+
from cmip7_ancil_argparse import (
5+
grid_parser,
6+
path_parser,
7+
)
8+
from cmip7_ancil_common import save_ancil
9+
from cmip7_ancil_constants import ANCIL_TODAY
10+
from ozone.cmip7_ozone import (
11+
fix_cmip7_ozone,
12+
load_cmip7_ozone,
13+
ozone_parser,
14+
)
15+
16+
17+
def parse_args():
18+
parser = ArgumentParser(
19+
parents=[path_parser(), grid_parser(), ozone_parser()],
20+
prog="cmip7_PI_ozone_generate",
21+
description=(
22+
"Generate input files from UK CMIP7 pre-industrial ozone forcings"
23+
),
24+
)
25+
return parser.parse_args()
26+
27+
28+
def esm_pi_ozone_save_dirpath(args):
29+
return (
30+
Path(args.ancil_target_dirname)
31+
/ "modern"
32+
/ "pre-industrial"
33+
/ "forcing"
34+
/ args.esm_grid_rel_dirname
35+
/ ANCIL_TODAY
36+
)
37+
38+
39+
def save_cmip7_pi_ozone(args, cube):
40+
# Save as an ancillary file
41+
save_dirpath = esm_pi_ozone_save_dirpath(args)
42+
save_ancil(cube, save_dirpath, args.save_filename, replace_bounds=True)
43+
44+
45+
if __name__ == "__main__":
46+
args = parse_args()
47+
48+
# Load the CMIP7 datasets
49+
ozone_cube = load_cmip7_ozone(args)
50+
# Match the ESM1.5 mask
51+
esm_cube = fix_cmip7_ozone(args, ozone_cube)
52+
# Save the ancillary
53+
save_cmip7_pi_ozone(args, esm_cube)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from argparse import ArgumentParser
2+
from pathlib import Path
3+
4+
import iris
5+
from cmip7_ancil_common import fix_coords
6+
7+
8+
def ozone_parser():
9+
parser = ArgumentParser(add_help=False)
10+
parser.add_argument("--ukesm-ancil-dirpath")
11+
parser.add_argument("--ukesm-netcdf-filename")
12+
parser.add_argument("--save-filename")
13+
return parser
14+
15+
16+
def cmip7_ozone_filepath(args):
17+
dirpath = Path(args.ukesm_ancil_dirpath)
18+
filename = args.ukesm_netcdf_filename
19+
return dirpath / filename
20+
21+
22+
def load_cmip7_ozone(args):
23+
filepath = cmip7_ozone_filepath(args)
24+
cube = iris.load_cube(filepath)
25+
return cube
26+
27+
28+
def fix_cmip7_ozone(args, cube):
29+
# Make the coordinates compatible with the ESM1.5 grid mask
30+
fix_coords(args, cube)
31+
cube.data = cube.data.filled(0.0)
32+
return cube

0 commit comments

Comments
 (0)