#!/usr/bin/env python
import sys
import nes
from numpy import float64, float32, int32, array
from warnings import warn
from netCDF4 import Dataset
from mpi4py import MPI
from copy import copy
# noinspection DuplicatedCode
[docs]
def to_netcdf_cams_ra(self, path):
"""
Horizontal methods from one grid to another one.
Parameters
----------
self : nes.Nes
Source projection Nes Object.
path : str
Path to the output netCDF file.
"""
if not isinstance(self, nes.LatLonNes):
raise TypeError("CAMS Re-Analysis format must have Regular Lat-Lon projection")
if "<level>" not in path:
raise ValueError(f"AMS Re-Analysis path must contain '<level>' as pattern; current: '{path}'")
orig_path = copy(path)
for i_lev, level in enumerate(self.lev["data"]):
path = orig_path.replace("<level>", "l{0}".format(i_lev))
# 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))
self.to_dtype(data_type=float32)
# Create dimensions
create_dimensions(self, netcdf)
# Create variables
create_variables(self, netcdf, i_lev)
# Create dimension variables
create_dimension_variables(self, netcdf)
if self.info:
print("Rank {0:03d}: Dimensions done".format(self.rank))
# Close NetCDF
if self.global_attrs is not None:
for att_name, att_value in self.global_attrs.items():
netcdf.setncattr(att_name, att_value)
netcdf.close()
return None
[docs]
def create_dimensions(self, netcdf):
"""
Create "time", "time_bnds", "lev", "lon" and "lat" dimensions.
Parameters
----------
self : nes.Nes
Source projection Nes Object.
netcdf : Dataset
netcdf4-python open dataset.
"""
# Create time dimension
netcdf.createDimension("time", None)
# Create lev, lon and lat dimensions
netcdf.createDimension("lat", len(self.get_full_latitudes()["data"]))
netcdf.createDimension("lon", len(self.get_full_longitudes()["data"]))
return None
[docs]
def create_dimension_variables(self, netcdf):
"""
Create the "time", "time_bnds", "lev", "lat", "lat_bnds", "lon" and "lon_bnds" variables.
Parameters
----------
self : nes.Nes
Source projection Nes Object.
netcdf : Dataset
netcdf4-python open dataset.
"""
# LATITUDES
lat = netcdf.createVariable("lat", float64, ("lat",))
lat.standard_name = "latitude"
lat.long_name = "latitude"
lat.units = "degrees_north"
lat.axis = "Y"
if self.size > 1:
lat.set_collective(True)
lat[:] = self.get_full_latitudes()["data"]
# LONGITUDES
lon = netcdf.createVariable("lon", float64, ("lon",))
lon.long_name = "longitude"
lon.standard_name = "longitude"
lon.units = "degrees_east"
lon.axis = "X"
if self.size > 1:
lon.set_collective(True)
lon[:] = self.get_full_longitudes()["data"]
# TIMES
time_var = netcdf.createVariable("time", float64, ("time",))
time_var.standard_name = "time"
time_var.units = "day as %Y%m%d.%f"
time_var.calendar = "proleptic_gregorian"
time_var.axis = "T"
if self.size > 1:
time_var.set_collective(True)
time_var[:] = __date2num(self.get_full_times()[self._get_time_id(self.hours_start, first=True):
self._get_time_id(self.hours_end, first=False)])
return None
# noinspection DuplicatedCode
[docs]
def create_variables(self, netcdf, i_lev):
"""
Create and write variables to a netCDF file.
Parameters
----------
self : nes.Nes
Source projection Nes Object.
netcdf : Dataset
netcdf4-python open dataset.
i_lev : int
The specific level index to write data for.
"""
for i, (var_name, var_dict) in enumerate(self.variables.items()):
if var_dict["data"] is not None:
if self.info:
print("Rank {0:03d}: Writing {1} var ({2}/{3})".format(self.rank, var_name, i + 1, len(self.variables)))
try:
var = netcdf.createVariable(var_name, float32, ("time", "lat", "lon",),
zlib=True, complevel=7, least_significant_digit=3)
if self.info:
print("Rank {0:03d}: Var {1} created ({2}/{3})".format(
self.rank, var_name, i + 1, len(self.variables)))
if self.size > 1:
var.set_collective(True)
if self.info:
print("Rank {0:03d}: Var {1} collective ({2}/{3})".format(
self.rank, var_name, i + 1, len(self.variables)))
if self.info:
print("Rank {0:03d}: Filling {1})".format(self.rank, var_name))
var[self.write_axis_limits["t_min"]:self.write_axis_limits["t_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_dict["data"][:, i_lev, :, :]
if self.info:
print("Rank {0:03d}: Var {1} data ({2}/{3})".format(
self.rank, var_name, i + 1, len(self.variables)))
var.long_name = var_dict["long_name"]
var.units = var_dict["units"]
var.number_of_significant_digits = int32(3)
if self.info:
print("Rank {0:03d}: Var {1} completed ({2}/{3})".format(self.rank, var_name, i + 1,
len(self.variables)))
except Exception as e:
print(f"**ERROR** an error has occurred while writing the '{var_name}' variable")
raise e
else:
msg = "WARNING!!! "
msg += "Variable {0} was not loaded. It will not be written.".format(var_name)
warn(msg)
sys.stderr.flush()
return None
def __date2num(time_array):
"""
Convert an array of datetime objects to numerical values.
Parameters
----------
time_array : List[datetime.datetime]
List of datetime objects to be converted.
Returns
-------
numpy.ndarray
Array of numerical time values, with each date represented as a float.
Notes
-----
The conversion represents each datetime as a float in the format YYYYMMDD.HH/24.
"""
time_res = []
for aux_time in time_array:
time_res.append(float(aux_time.strftime("%Y%m%d")) + (float(aux_time.strftime("%H")) / 24))
time_res = array(time_res, dtype=float64)
return time_res