Skip to content

Commit 8c7b3f7

Browse files
committed
update cmorizer
1 parent 889cbf4 commit 8c7b3f7

6 files changed

Lines changed: 123 additions & 114 deletions

File tree

esmvaltool/cmorizers/data/cmor_config/CarboScope.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
# Filename (will be extended by variable name)
3-
filename: 'nbetEXToc_v2024E.flux.nc'
3+
filename: 'nbetEXToc_v2025.flux.nc'
44

55
attributes:
66
dataset_id: CarboScope
7-
version: 'v2024E'
7+
version: 'v2025'
88
tier: 2
9-
modeling_realm: reanaly
9+
type: reanaly
1010
project_id: OBS6
11-
source: 'https://www.bgc-jena.mpg.de/CarboScope/?file=nbetEXToc_v2024E.flux.nc'
12-
reference: 'carboscope2024'
11+
source: 'https://www.bgc-jena.mpg.de/CarboScope/?file=nbetEXToc_v2025.flux.nc'
12+
reference: 'carboscope2025'
1313
comment: ''
1414

1515
# Variables to cmorize

esmvaltool/cmorizers/data/datasets.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ datasets:
124124
CarboScope:
125125
tier: 2
126126
source: https://www.bgc-jena.mpg.de/CarboScope/?ID=s
127-
last_access: 2024-09-09
127+
last_access: 2026-07-16
128128
info: |
129129
Data is freely available.
130130
For previous versions, click on archive of outdated results.

esmvaltool/cmorizers/data/downloaders/datasets/carboscope.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@
88

99

1010
def download_dataset(
11-
config, dataset, dataset_info, start_date, end_date, overwrite
11+
original_data_dir,
12+
dataset,
13+
dataset_info,
14+
start_date,
15+
end_date,
16+
overwrite,
1217
):
1318
"""Download dataset.
1419
1520
Parameters
1621
----------
17-
config : dict
18-
ESMValTool's user configuration
22+
original_data_dir : Path
23+
Directory where original data will be stored.
1924
dataset : str
2025
Name of the dataset
2126
dataset_info : dict
@@ -28,14 +33,14 @@ def download_dataset(
2833
Overwrite already downloaded files
2934
"""
3035
downloader = WGetDownloader(
31-
config=config,
36+
original_data_dir=original_data_dir,
3237
dataset=dataset,
3338
dataset_info=dataset_info,
3439
overwrite=overwrite,
3540
)
3641

3742
downloader.download_file(
3843
"https://www.bgc-jena.mpg.de/CarboScope/s/INVERSION/OUTPUT/"
39-
"nbetEXToc_v2024E/nbetEXToc_v2024E.flux.nc",
44+
"nbetEXToc_v2025/nbetEXToc_v2025.flux.nc",
4045
[],
4146
)

esmvaltool/cmorizers/data/formatters/datasets/carboscope.py

Lines changed: 97 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
Source
77
v2020:
88
https://www.bgc-jena.mpg.de/CarboScope/?ID=sEXTocNEET_v2020
9-
v2024:
10-
https://www.bgc-jena.mpg.de/CarboScope/?file=nbetEXToc_v2024E.flux.nc
9+
v2025:
10+
https://www.bgc-jena.mpg.de/CarboScope/?file=nbetEXToc_v2025.flux.nc
1111
1212
Last access
13-
20240909
13+
20260716
1414
1515
Download and processing instructions
1616
Download the file corresponding to the version you require:
1717
v2020: 'sEXTocNEET_v2020_daily.nc.gz'.
18-
v2024: nbetEXToc_v2024E.flux.nc
18+
v2025: nbetEXToc_v2025.flux.nc
1919
2020
"""
2121

@@ -28,10 +28,9 @@
2828

2929
import dask.array as da
3030
import iris
31-
from iris import NameConstraint
3231
from cf_units import Unit
33-
3432
from esmvalcore.preprocessor import monthly_statistics
33+
from iris import NameConstraint
3534

3635
from esmvaltool.cmorizers.data import utilities as utils
3736

@@ -47,29 +46,31 @@ def _clean(filepath):
4746

4847
def _calculate_flux(cube, filename, area_type):
4948
"""Calculate flux (dividing by land/sea area) and mask land/sea."""
50-
if area_type == 'land':
49+
if area_type == "land":
5150
area_idx = 0
52-
elif area_type == 'ocean':
51+
elif area_type == "ocean":
5352
area_idx = 1
5453
else:
5554
raise ValueError(
56-
f"Expected 'land' or 'ocean' for 'area_type', got '{area_type}'")
55+
f"Expected 'land' or 'ocean' for 'area_type', got '{area_type}'"
56+
)
5757

5858
# Get land/sea area fraction
5959
with warnings.catch_warnings():
6060
warnings.filterwarnings(
61-
'ignore',
61+
"ignore",
6262
message="Ignoring netCDF variable '.*?' invalid units '.*?'",
6363
category=UserWarning,
64-
module='iris',
64+
module="iris",
65+
)
66+
area_cube = iris.load_cube(
67+
str(filename), NameConstraint(var_name="area")
6568
)
66-
area_cube = iris.load_cube(str(filename),
67-
NameConstraint(var_name='area'))
6869
area = area_cube[area_idx].core_data()
6970
area = da.broadcast_to(area, cube.shape)
7071

7172
# Mask
72-
mask = (area == 0.0)
73+
mask = area == 0.0
7374
area = da.ma.masked_array(area, mask=mask)
7475
cube.data = da.ma.masked_array(cube.core_data(), mask=mask)
7576

@@ -79,7 +80,7 @@ def _calculate_flux(cube, filename, area_type):
7980

8081
# Adapt metadata
8182
cube.units /= area_cube.units
82-
cube.attributes['positive'] = 'down'
83+
cube.attributes["positive"] = "down"
8384

8485
return cube
8586

@@ -89,78 +90,85 @@ def _load_cube(filename, raw_name):
8990
logger.info("Loading '%s' from file %s", raw_name, filename)
9091
with warnings.catch_warnings():
9192
warnings.filterwarnings(
92-
'ignore',
93+
"ignore",
9394
message="Ignoring netCDF variable '.*?' invalid units '.*?'",
9495
category=UserWarning,
95-
module='iris',
96+
module="iris",
9697
)
97-
cube = iris.load_cube(str(filename),
98-
NameConstraint(var_name=raw_name))
98+
cube = iris.load_cube(str(filename), NameConstraint(var_name=raw_name))
9999
return cube
100100

101101

102102
def _time_operations(cube):
103103
"""Temporal operations on cube."""
104-
valid_start_year = cube.attributes['yrfi_valid']
105-
valid_end_year = cube.attributes['yrfe_valid']
106-
logger.info("Extracting valid years: %d-%d", valid_start_year,
107-
valid_end_year)
108-
cube = cube.extract(iris.Constraint(
109-
time=lambda cell: valid_start_year <= cell.point.year <= valid_end_year
110-
))
104+
valid_start_year = cube.attributes["yrfi_valid"]
105+
valid_end_year = cube.attributes["yrfe_valid"]
106+
logger.info(
107+
"Extracting valid years: %d-%d", valid_start_year, valid_end_year
108+
)
109+
cube = cube.extract(
110+
iris.Constraint(
111+
time=lambda cell: valid_start_year
112+
<= cell.point.year
113+
<= valid_end_year
114+
)
115+
)
111116
logger.info("Calculating monthly means")
112117
cube = monthly_statistics(cube)
113118
return cube
114119

115120

116121
def _fix_metadata(cube, short_name, var, cfg):
117122
"""Fix metadata of cube."""
118-
cmor_info = cfg['cmor_table'].get_variable(var['mip'], short_name)
123+
cmor_info = cfg["cmor_table"].get_variable(var["mip"], short_name)
119124

120125
# Fix cell measures
121-
if 'fx' not in var['mip']:
122-
cell_measure = cube.cell_measure('area per grid cell')
126+
if "fx" not in var["mip"]:
127+
cell_measure = cube.cell_measure("area per grid cell")
123128
cell_measure.attributes = {}
124-
cell_measure.standard_name = 'cell_area'
125-
if short_name == 'nbp':
126-
cell_measure.var_name = 'areacella'
127-
cell_measure.long_name = ('Grid-Cell Area for Atmospheric Grid '
128-
'Variables')
129-
elif short_name == 'fgco2':
130-
cell_measure.var_name = 'areacello'
131-
cell_measure.long_name = 'Grid-Cell Area for Ocean Variables'
129+
cell_measure.standard_name = "cell_area"
130+
if short_name == "nbp":
131+
cell_measure.var_name = "areacella"
132+
cell_measure.long_name = (
133+
"Grid-Cell Area for Atmospheric Grid Variables"
134+
)
135+
elif short_name == "fgco2":
136+
cell_measure.var_name = "areacello"
137+
cell_measure.long_name = "Grid-Cell Area for Ocean Variables"
132138

133139
# Fix cell methods
134-
if short_name == 'nbp':
135-
cube.add_cell_method(iris.coords.CellMethod('mean where land',
136-
coords='area'))
137-
elif short_name == 'fgco2':
138-
cube.add_cell_method(iris.coords.CellMethod('mean where sea',
139-
coords='area'))
140-
elif short_name in ('areacella', 'areacello'):
141-
cube.add_cell_method(iris.coords.CellMethod('sum', coords='area'))
142-
elif short_name in ('sftlf', 'sftof'):
143-
cube.add_cell_method(iris.coords.CellMethod('mean', coords='area'))
140+
if short_name == "nbp":
141+
cube.add_cell_method(
142+
iris.coords.CellMethod("mean where land", coords="area")
143+
)
144+
elif short_name == "fgco2":
145+
cube.add_cell_method(
146+
iris.coords.CellMethod("mean where sea", coords="area")
147+
)
148+
elif short_name in ("areacella", "areacello"):
149+
cube.add_cell_method(iris.coords.CellMethod("sum", coords="area"))
150+
elif short_name in ("sftlf", "sftof"):
151+
cube.add_cell_method(iris.coords.CellMethod("mean", coords="area"))
144152

145153
# Fix coordinates
146-
if 'fx' not in var['mip']:
147-
cube.remove_coord('month_number')
148-
cube.remove_coord('year')
149-
cube.coord('time').var_name = 'time'
150-
cube.coord('time').long_name = 'time'
151-
cube.coord('time').points = da.around(
152-
cube.coord('time').core_points(), 3)
153-
cube.coord('time').bounds = da.around(
154-
cube.coord('time').core_bounds(), 3)
154+
if "fx" not in var["mip"]:
155+
cube.coord("time").var_name = "time"
156+
cube.coord("time").long_name = "time"
157+
cube.coord("time").points = da.around(
158+
cube.coord("time").core_points(), 3
159+
)
160+
cube.coord("time").bounds = da.around(
161+
cube.coord("time").core_bounds(), 3
162+
)
155163
utils.fix_coords(cube)
156-
if 'depth0m' in cmor_info.dimensions:
164+
if "depth0m" in cmor_info.dimensions:
157165
depth_coord = iris.coords.AuxCoord(
158166
0.0,
159-
var_name='depth',
160-
standard_name='depth',
161-
long_name='depth',
162-
units=Unit('m'),
163-
attributes={'positive': 'down'},
167+
var_name="depth",
168+
standard_name="depth",
169+
long_name="depth",
170+
units=Unit("m"),
171+
attributes={"positive": "down"},
164172
)
165173
cube.add_aux_coord(depth_coord, ())
166174

@@ -169,82 +177,80 @@ def _fix_metadata(cube, short_name, var, cfg):
169177
utils.fix_var_metadata(cube, cmor_info)
170178

171179
# Fix global attributes
172-
attrs = cfg['attributes']
173-
attrs['mip'] = var['mip']
180+
attrs = cfg["attributes"]
181+
attrs["mip"] = var["mip"]
174182
utils.set_global_atts(cube, attrs)
175183

176184
return (cube, attrs)
177185

178186

179187
def _extract_variable(short_name, var, cfg, filename, out_dir):
180188
"""Extract variable."""
181-
raw_name = var.get('raw_name', short_name)
189+
raw_name = var.get("raw_name", short_name)
182190
cube = _load_cube(filename, raw_name)
183191

184192
# Fix metadata of raw input
185193
cube.cell_methods = ()
186-
if 'raw_units' in var:
187-
cube.units = var['raw_units']
188-
cube.attributes.pop('invalid_units', None)
194+
if "raw_units" in var:
195+
cube.units = var["raw_units"]
196+
cube.attributes.pop("invalid_units", None)
189197

190198
# Temporal operations
191-
if 'fx' not in var['mip']:
199+
if "fx" not in var["mip"]:
192200
cube = _time_operations(cube)
193201

194202
# Variable-specific calculations
195-
if short_name == 'nbp':
196-
cube = _calculate_flux(cube, filename, 'land')
197-
elif short_name == 'fgco2':
198-
cube = _calculate_flux(cube, filename, 'ocean')
199-
elif short_name in ('areacella', 'areacello'):
203+
if short_name == "nbp":
204+
cube = _calculate_flux(cube, filename, "land")
205+
elif short_name == "fgco2":
206+
cube = _calculate_flux(cube, filename, "ocean")
207+
elif short_name in ("areacella", "areacello"):
200208
cube = cube[2]
201-
cube.remove_coord('rt')
202-
elif short_name in ('sftlf', 'sftof'):
209+
cube.remove_coord("rt")
210+
elif short_name in ("sftlf", "sftof"):
203211
total_area = cube[2]
204-
area_idx = 0 if short_name == 'sftlf' else 1
212+
area_idx = 0 if short_name == "sftlf" else 1
205213
cube = cube[area_idx]
206214
cube.data = cube.core_data() / total_area.core_data()
207-
cube.units = '1'
208-
cube.remove_coord('rt')
215+
cube.units = "1"
216+
cube.remove_coord("rt")
209217

210218
# Fix metadata
211219
(cube, attrs) = _fix_metadata(cube, short_name, var, cfg)
212220

213221
# Save variable
214-
utils.save_variable(cube,
215-
short_name,
216-
out_dir,
217-
attrs,
218-
unlimited_dimensions=['time'])
222+
utils.save_variable(
223+
cube, short_name, out_dir, attrs, unlimited_dimensions=["time"]
224+
)
219225

220226

221227
def _unzip(zip_path, out_dir):
222228
"""Unzip `*.gz` file."""
223229
logger.info("Found input file '%s'", zip_path)
224-
unzip_path = Path(out_dir) / zip_path.with_suffix('').name
230+
unzip_path = Path(out_dir) / zip_path.with_suffix("").name
225231
logger.info("Unzipping file")
226-
with gzip.open(zip_path, 'rb') as zip_file:
227-
with open(unzip_path, 'wb') as unzip_file:
232+
with gzip.open(zip_path, "rb") as zip_file:
233+
with open(unzip_path, "wb") as unzip_file:
228234
shutil.copyfileobj(zip_file, unzip_file)
229235
logger.info("Succefully extracted file to %s", unzip_path)
230236
return unzip_path
231237

232238

233239
def cmorization(in_dir, out_dir, cfg, cfg_user, start_date, end_date):
234240
"""Cmorization func call."""
235-
in_path = Path(in_dir) / cfg['filename']
241+
in_path = Path(in_dir) / cfg["filename"]
236242

237243
# Unzip file if necessary
238-
if '.gz' in cfg['filename']:
244+
if ".gz" in cfg["filename"]:
239245
data_path = _unzip(in_path, out_dir)
240246
else:
241247
data_path = in_path
242248

243249
# Run the cmorization
244-
for (short_name, var) in cfg['variables'].items():
250+
for short_name, var in cfg["variables"].items():
245251
logger.info("CMORizing variable '%s'", short_name)
246252
_extract_variable(short_name, var, cfg, data_path, out_dir)
247253

248254
# Remove unzipped file if necessary
249-
if '.gz' in cfg['filename']:
255+
if ".gz" in cfg["filename"]:
250256
_clean(data_path)

0 commit comments

Comments
 (0)