#!/usr/bin/env python
import nes
from numpy import float32, array, ndarray, empty, int32, float64
from netCDF4 import Dataset
from mpi4py import MPI
from copy import deepcopy
from datetime import datetime
GLOBAL_ATTRIBUTES_ORDER = [
"IOAPI_VERSION", "EXEC_ID", "FTYPE", "CDATE", "CTIME", "WDATE", "WTIME", "SDATE", "STIME", "TSTEP", "NTHIK",
"NCOLS", "NROWS", "NLAYS", "NVARS", "GDTYP", "P_ALP", "P_BET", "P_GAM", "XCENT", "YCENT", "XORIG", "YORIG",
"XCELL", "YCELL", "VGTYP", "VGTOP", "VGLVLS", "GDNAM", "UPNAM", "FILEDESC", "HISTORY", "VAR-LIST"]
# noinspection DuplicatedCode
[docs]
def to_netcdf_cmaq(self, path, keep_open=False):
"""
Create the NetCDF using netcdf4-python methods.
Parameters
----------
self : nes.Nes
Source projection Nes Object.
path : str
Path to the output netCDF file.
keep_open : bool
Indicates if you want to keep open the NetCDH to fill the data by time-step.
"""
self.to_dtype(float32)
set_global_attributes(self)
change_variable_attributes(self)
# Open NetCDF
if self.info:
print("Rank {0:03d}: Creating {1}".format(self.rank, path))
if self.size > 1:
netcdf = Dataset(path, format="NETCDF4", mode="w", parallel=True, comm=self.comm, info=MPI.Info())
else:
netcdf = Dataset(path, format="NETCDF4", mode="w", parallel=False)
if self.info:
print("Rank {0:03d}: NetCDF ready to write".format(self.rank))
# Create dimensions
create_dimensions(self, netcdf)
create_dimension_variables(self, netcdf)
if self.info:
print("Rank {0:03d}: Dimensions done".format(self.rank))
# Create variables
create_variables(self, netcdf)
for att_name in GLOBAL_ATTRIBUTES_ORDER:
netcdf.setncattr(att_name, self.global_attrs[att_name])
# Close NetCDF
if keep_open:
self.dataset = netcdf
else:
netcdf.close()
return None
[docs]
def change_variable_attributes(self):
"""
Modify the emission list to be consistent to use the output as input for CMAQ model.
Parameters
----------
self : nes.Nes
A Nes Object.
"""
for var_name in self.variables.keys():
if self.variables[var_name]["units"] == "mol.s-1":
self.variables[var_name]["units"] = "{:<16}".format("mole/s")
self.variables[var_name]["var_desc"] = "{:<80}".format(self.variables[var_name]["long_name"])
self.variables[var_name]["long_name"] = "{:<16}".format(var_name)
elif self.variables[var_name]["units"] == "g.s-1":
self.variables[var_name]["units"] = "{:<16}".format("g/s")
self.variables[var_name]["var_desc"] = "{:<80}".format(self.variables[var_name]["long_name"])
self.variables[var_name]["long_name"] = "{:<16}".format(var_name)
else:
raise TypeError("The unit '{0}' of specie {1} is not defined correctly. ".format(
self.variables[var_name]["units"], var_name) + "Should be 'mol.s-1' or 'g.s-1'")
return None
[docs]
def to_cmaq_units(self):
"""
Change the data values according to the CMAQ conventions
Parameters
----------
self : nes.Nes
A Nes Object.
Returns
-------
dict
Variable in the MONARCH units.
"""
self.calculate_grid_area(overwrite=False)
for var_name in self.variables.keys():
if isinstance(self.variables[var_name]["data"], ndarray):
if self.variables[var_name]["units"] == "mol.s-1":
# Kmol.m-2.s-1 to mol.s-1
self.variables[var_name]["data"] = array(
self.variables[var_name]["data"] * 1000 * self.cell_measures["cell_area"]["data"], dtype=float32)
elif self.variables[var_name]["units"] == "g.s-1":
# Kg.m-2.s-1 to g.s-1
self.variables[var_name]["data"] = array(
self.variables[var_name]["data"] * 1000 * self.cell_measures["cell_area"]["data"], dtype=float32)
else:
raise TypeError("The unit '{0}' of specie {1} is not defined correctly. ".format(
self.variables[var_name]["units"], var_name) + "Should be 'mol.s-1' or 'g.s-1'")
self.variables[var_name]["dtype"] = float32
return self.variables
[docs]
def create_tflag(self):
"""
Create the content of the CMAQ variable TFLAG.
Parameters
----------
self : nes.Nes
A Nes Object.
Returns
-------
numpy.ndarray
Array with the content of TFLAG.
"""
t_flag = empty((len(self.time), len(self.variables), 2))
for i_d, aux_date in enumerate(self.time):
y_d = int(aux_date.strftime("%Y%j"))
hms = int(aux_date.strftime("%H%M%S"))
for i_p in range(len(self.variables)):
t_flag[i_d, i_p, 0] = y_d
t_flag[i_d, i_p, 1] = hms
return t_flag
[docs]
def str_var_list(self):
"""
Transform the list of variable names to a string with the elements with 16 white spaces.
Parameters
----------
self : nes.Nes
A Nes Object.
Returns
-------
str
List of variable names transformed on string.
"""
str_var_list_aux = ""
for var in self.variables.keys():
str_var_list_aux += "{:<16}".format(var)
return str_var_list_aux
# noinspection DuplicatedCode
[docs]
def set_global_attributes(self):
"""
Set the NetCDF global attributes.
Parameters
----------
self : nes.Nes
A Nes Object.
"""
now = datetime.now()
if len(self.time) > 1:
tstep = ((self.time[1] - self.time[0]).seconds // 3600) * 10000
else:
tstep = 1 * 10000
current_attributes = deepcopy(self.global_attrs)
del self.global_attrs
self.global_attrs = {"IOAPI_VERSION": "None: made only with NetCDF libraries",
"EXEC_ID": "{:<80}".format("0.1alpha"), # Editable
"FTYPE": int32(1), # Editable
"CDATE": int32(now.strftime("%Y%j")),
"CTIME": int32(now.strftime("%H%M%S")),
"WDATE": int32(now.strftime("%Y%j")),
"WTIME": int32(now.strftime("%H%M%S")),
"SDATE": int32(self.time[0].strftime("%Y%j")),
"STIME": int32(self.time[0].strftime("%H%M%S")),
"TSTEP": int32(tstep),
"NTHIK": int32(1), # Editable
"NCOLS": None, # Projection dependent
"NROWS": None, # Projection dependent
"NLAYS": int32(len(self.lev["data"])),
"NVARS": None, # Projection dependent
"GDTYP": None, # Projection dependent
"P_ALP": None, # Projection dependent
"P_BET": None, # Projection dependent
"P_GAM": None, # Projection dependent
"XCENT": None, # Projection dependent
"YCENT": None, # Projection dependent
"XORIG": None, # Projection dependent
"YORIG": None, # Projection dependent
"XCELL": None, # Projection dependent
"YCELL": None, # Projection dependent
"VGTYP": int32(7), # Editable
"VGTOP": float32(5000.), # Editable
"VGLVLS": array([1., 0.], dtype=float32), # Editable
"GDNAM": "{:<16}".format(""), # Editable
"UPNAM": "{:<16}".format("HERMESv3"),
"FILEDESC": "", # Editable
"HISTORY": "", # Editable
"VAR-LIST": str_var_list(self)}
# Editable attributes
for att_name, att_value in current_attributes.items():
if att_name == "EXEC_ID":
self.global_attrs[att_name] = "{:<80}".format(att_value) # Editable
elif att_name == "FTYPE":
self.global_attrs[att_name] = int32(att_value) # Editable
elif att_name == "NTHIK":
self.global_attrs[att_name] = int32(att_value) # Editable
elif att_name == "VGTYP":
self.global_attrs[att_name] = int32(att_value) # Editable
elif att_name == "VGTOP":
self.global_attrs[att_name] = float32(att_value) # Editable
elif att_name == "VGLVLS":
self.global_attrs[att_name] = array(att_value.split(), dtype=float32) # Editable
elif att_name == "GDNAM":
self.global_attrs[att_name] = "{:<16}".format(att_value) # Editable
elif att_name == "FILEDESC":
self.global_attrs[att_name] = att_value # Editable
elif att_name == "HISTORY":
self.global_attrs[att_name] = att_value # Editable
# Projection dependent attributes
if isinstance(self, nes.LCCNes):
self.global_attrs["NCOLS"] = int32(len(self._full_x["data"]))
self.global_attrs["NROWS"] = int32(len(self._full_y["data"]))
self.global_attrs["NVARS"] = int32(len(self.variables))
self.global_attrs["GDTYP"] = int32(2)
self.global_attrs["P_ALP"] = float64(self.projection_data["standard_parallel"][0])
self.global_attrs["P_BET"] = float64(self.projection_data["standard_parallel"][1])
self.global_attrs["P_GAM"] = float64(self.projection_data["longitude_of_central_meridian"])
self.global_attrs["XCENT"] = float64(self.projection_data["longitude_of_central_meridian"])
self.global_attrs["YCENT"] = float64(self.projection_data["latitude_of_projection_origin"])
self.global_attrs["XORIG"] = float64(
self._full_x["data"][0]) - (float64(self._full_x["data"][1] - self._full_x["data"][0]) / 2)
self.global_attrs["YORIG"] = float64(
self._full_y["data"][0]) - (float64(self._full_y["data"][1] - self._full_y["data"][0]) / 2)
self.global_attrs["XCELL"] = float64(self._full_x["data"][1] - self._full_x["data"][0])
self.global_attrs["YCELL"] = float64(self._full_y["data"][1] - self._full_y["data"][0])
return None
[docs]
def create_dimensions(self, netcdf):
"""
Create "time", "time_bnds", "lev", "lon" and "lat" dimensions.
Parameters
----------
self : nes.Nes
Nes Object.
netcdf : Dataset
netcdf4-python open dataset.
"""
netcdf.createDimension("TSTEP", len(self.get_full_times()))
netcdf.createDimension("DATE-TIME", 2)
netcdf.createDimension("LAY", len(self.get_full_levels()["data"]))
netcdf.createDimension("VAR", len(self.variables))
if isinstance(self, nes.LCCNes):
netcdf.createDimension("COL", len(self._full_x["data"]))
netcdf.createDimension("ROW", len(self._full_y["data"]))
return None
[docs]
def create_dimension_variables(self, netcdf):
"""
Create the "y" and "x" variables.
Parameters
----------
self : nes.Nes
A Nes Object.
netcdf : Dataset
NetCDF object.
"""
tflag = netcdf.createVariable("TFLAG", "i", ("TSTEP", "VAR", "DATE-TIME",))
tflag.setncatts({"units": "{:<16}".format("<YYYYDDD,HHMMSS>"), "long_name": "{:<16}".format("TFLAG"),
"var_desc": "{:<80}".format("Timestep-valid flags: (1) YYYYDDD or (2) HHMMSS")})
tflag[:] = create_tflag(self)
return None
# noinspection DuplicatedCode
[docs]
def create_variables(self, netcdf):
"""
Create the netCDF file variables.
Parameters
----------
self : nes.Nes
Nes Object.
netcdf : Dataset
netcdf4-python open dataset.
"""
for var_name, var_info in self.variables.items():
var = netcdf.createVariable(var_name, "f", ("TSTEP", "LAY", "ROW", "COL",),
zlib=self.zip_lvl > 0, complevel=self.zip_lvl)
var.units = var_info["units"]
var.long_name = str(var_info["long_name"])
var.var_desc = str(var_info["var_desc"])
if var_info["data"] is not None:
if self.info:
print("Rank {0:03d}: Filling {1})".format(self.rank, var_name))
if isinstance(var_info["data"], int) and var_info["data"] == 0:
var[self.write_axis_limits["t_min"]:self.write_axis_limits["t_max"],
self.write_axis_limits["z_min"]:self.write_axis_limits["z_max"],
self.write_axis_limits["y_min"]:self.write_axis_limits["y_max"],
self.write_axis_limits["x_min"]:self.write_axis_limits["x_max"]] = 0
elif len(var_info["data"].shape) == 4:
var[self.write_axis_limits["t_min"]:self.write_axis_limits["t_max"],
self.write_axis_limits["z_min"]:self.write_axis_limits["z_max"],
self.write_axis_limits["y_min"]:self.write_axis_limits["y_max"],
self.write_axis_limits["x_min"]:self.write_axis_limits["x_max"]] = var_info["data"]
return None