Source code for pycif.plugins.datastreams.fluxes.tm5.read

import datetime
import os

import numpy as np
import xarray as xr
from logging import debug
from .....utils.netcdf import readnc


# JvP 20210609: added import statements, MODULE_NAME module level logger
import logging
import sys
import netCDF4 as nc4
MODULE_NAME = __name__[__name__.index('TM5'):] if 'TM5' in __name__ else __name__
logger = logging.getLogger(MODULE_NAME)



# Original read function by A. Berchet
[docs] def read_AB( self, name, tracdir, tracfile, varnames, dates, interpol_flx=False, tracer=None, model=None, **kwargs ): """Get fluxes from pre-computed fluxes and load them into a pyCIF variables Args: self: the fluxes Plugin name: the name of the component tracdir, tracfile: flux directory and file format dates: list of dates to extract interpol_flx (bool): if True, interpolates fluxes at time t from values of surrounding available files """ # Replace tracfile by available information from model if tracfile == "": tracfile = model.flux.file # Available files in the directory list_files = os.listdir(tracdir) list_available = [] for flx_file in list_files: try: list_available.append( datetime.datetime.strptime(flx_file, tracfile) ) except BaseException: continue list_available = np.array(list_available) # Reading required fluxes files trcr_flx = [] for dd in dates: debug("Putting random values in fluxes for date:" + str(dd)) nmerid, nzonal = self.domain.zlat.shape trcr_flx.append(np.random.rand(1, nmerid, nzonal)) # Building a xarray xmod = xr.DataArray( trcr_flx, coords={"time": dates}, dims=("time", "lev", "lat", "lon") ) return xmod
# New read function by J.C.A. van Peet
[docs] def read( self, name, varnames, dates, files, interpol_flx=False, tracer=None, model=None, ddi=None, **kwargs ): """ PURPOSE Get fluxes from pre-computed fluxes and load them into a pyCIF variables ARGS self = the fluxes Plugin name = the name of the component tracdir = flux directory tracfile = flux file format varnames = ??? dates = list of dates to extract interpol_flx = (bool): if True, interpolates fluxes at time t from values of surrounding available files KWARGS **kwargs = ??? VERSION HISTORY 2.0 09-06-2021 by J.C.A. van Peet Adapted for TM5. 1.0 ??-??-???? by A. Berchet See original code above. """ # Set the name of this function PROG_NAME = MODULE_NAME+".read" # Local logger logger = logging.getLogger(PROG_NAME) logger.setLevel(logging.DEBUG) # Printing options for arrays using np.array2string # https://docs.scipy.org/doc/numpy/reference/generated/numpy.array2string.html print_ops = { 'max_line_width': 140, 'prefix': ' ' * 26 + ' = ', 'formatter': {'float': lambda x: ' %7.2f' % x, 'int': lambda x: ' %7i' % x, 'bool': lambda x: 'T' if x else 'F'}} # Some debug statements... logger.debug("") logger.debug("*"*30) logger.debug(PROG_NAME+" => DEBUG:" ) logger.debug(" name = %s", name ) logger.debug(" files = %s", files ) logger.debug(" dates = %s", dates ) logger.debug(" varnames = %s", varnames ) logger.debug(" dates = %s", dates ) logger.debug(" interpol_flx = %s", interpol_flx) logger.debug(" tracer = %s", tracer ) logger.debug(" model = %s", model ) #import traceback #logger.debug("") #logger.debug(PROG_NAME+" => called from:" ) #logger.debug(" traceback.filename = %s" % traceback.extract_stack(limit=2)[-2].filename) #logger.debug(" traceback.lineno = %s" % traceback.extract_stack(limit=2)[-2].lineno ) #logger.debug(" traceback.name = %s" % traceback.extract_stack(limit=2)[-2].name ) #logger.debug(" traceback.line = %s" % traceback.extract_stack(limit=2)[-2].line ) #if( 'native2inputs_adj' in traceback.extract_stack(limit=2)[-2].name ): # raise RuntimeError("JUST DEBUGGING TO SEE FROM WHICH FUNCTION THIS FUNCTION IS CALLED...") ## end if # # Replace tracfile by available information from model. # # Part from original code, extended with try-except. # if tracfile == "": # try: # tracfile = model.flux.file # except AttributeError: # logger.critical("") # logger.critical("*"*30) # logger.critical(PROG_NAME+" => ERROR: no model.flux.file attribute!") # logger.critical("*"*30) # logger.critical("") # raise # # end try # # end if( tracfile ) # >>> Read netCDF file with TM5 emissions # List the sources present in the file sources = ['biomass-burning', 'rice', 'wetlands', 'other'] # The start times in the file has not yet been read time_start = None # Loop over dates and files total_emis_sel = [] emis_dates = [] for date, tracfile in zip(dates, files): emis_dates.append(date[0]) # Open file # Starting with version > 1.4, netcdf4-python returns masked arrays by # default. Previous versions only returned masked arrays if there were # actually missing of fill values in the data. Not all matplotlib/cartopy # plot functions can handle masked arrays very well, so I added # nc_id.set_auto_maskandscale(False). Note that you could also use # set_auto_mask() only nc_id = nc4.Dataset( tracfile, 'r' ) nc_id.set_auto_maskandscale(False) # Check if there's only one group in the netcdf-4 file. if( len(nc_id.groups) != 1 ): logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: unknown number of groups in netcdf-4 file!") # logger.critical(" tracdir = %s", tracdir ) logger.critical(" tracfile = %s", tracfile ) logger.critical(" nc_id.groups = %s", nc_id.groups) logger.critical("*"*30) logger.critical("") raise RuntimeError # end if( len(nc_id.groups) ) # Check the group #for region in nc_id.groups.keys(): for region in ['glb600x400']: logger.debug(" region = %s", region) # The region name should be something like 'glb600x400', which means # 'global grid, cell size is 6 degrees in longitude by 4 degrees in # latitude'. So now you "just" have to split the group name into its # components. if( region == 'glb600x400' ): d_lon = 6.0 d_lat = 4.0 n_lon = 60 n_lat = 45 elif( region == 'glb300x200' ): d_lon = 3.0 d_lat = 2.0 n_lon = 120 n_lat = 90 else: logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: Unknown group!") logger.critical(" region = %s", region) logger.critical("*"*30) logger.critical("") raise RuntimeError # end if(group) # Since the longitude and latitude dimensions are present in the # emission files, but do not contain the actual value, you have to # generate them yourself. The big assumption here is that the TM5 # grid runs from -180 <= lon <= 180 and -90 <= lat <= 90. lons = np.linspace( -180.0, 180.0, num=n_lon+1 ) lats = np.linspace( -90.0, 90.0, num=n_lat+1 ) lon = 0.5*(lons[0:-1]+lons[1:]) lat = 0.5*(lats[0:-1]+lats[1:]) logger.debug(" lon = %s", np.array2string(lon, **print_ops) ) logger.debug(" lat = %s", np.array2string(lat, **print_ops) ) # Loop over the emission categories for i_source, source in enumerate(sources): # Get the path to the current source and print it path = region+'/'+name+'/'+source+'/' logger.debug(" source = %s", source) logger.debug(" path = %s", path ) # Check the array sizes if( (nc_id[path+'emission'].shape[1] != n_lat) or (nc_id[path+'emission'].shape[2] != n_lon) ): logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: shape of emission category does not comply with TM5 grid!") logger.critical(" n_lon = %s", n_lon ) logger.critical(" n_lat = %s", n_lat ) logger.critical(" source = %s", source ) logger.critical(" path = %s", path ) logger.critical(" emission.shape = %s", nc_id[path+'emission'].shape ) logger.critical("*"*30) logger.critical("") raise RuntimeError # end if # Save time variables # Note the use of argument unpacking (?) with *bagger. nc_time_start = np.array( [datetime.datetime( *bagger ) for bagger in nc_id[path+'time_start'] ] ) nc_time_mid = np.array( [datetime.datetime( *bagger ) for bagger in nc_id[path+'time_mid' ] ] ) nc_time_end = np.array( [datetime.datetime( *bagger ) for bagger in nc_id[path+'time_end' ] ] ) # Save time ranges if( time_start is None ): time_start = nc_time_start time_mid = nc_time_mid time_end = nc_time_end # Set a numpy array which will contain the source emission data sources_emis = np.full( (len(sources), time_start.size, n_lat, n_lon), np.nan ) else: if( not( np.array_equal( time_start, nc_time_start ) and \ np.array_equal( time_mid, nc_time_mid ) and np.array_equal( time_end, nc_time_end ) ) ): logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: time arrays not equal!") logger.critical(" time_start = %s", time_start ) logger.critical(" time_mid = %s", time_mid ) logger.critical(" time_end = %s", time_end ) logger.critical(" nc_time_start = %s", nc_time_start) logger.critical(" nc_time_mid = %s", nc_time_mid ) logger.critical(" nc_time_end = %s", nc_time_end ) logger.critical("*"*30) logger.critical("") raise RuntimeError # end if check time arrays # end if save time variables # Read the source data. Unit = kg CH4 s^-1 sources_emis[i_source, :, :, :] = nc_id[path+'emission'][()] # end for source # end for region # Close file nc_id.close() # Calculate total emissions total_emis = np.sum(sources_emis, axis=0) logger.debug(" total_emis.shape = %s", total_emis.shape) # <<< Read netCDF file with TM5 emissions # Get a selection of emissions corresponding to "dates". date_idx = np.nonzero(date[0] == nc_time_start)[0] if date_idx.size != 1: logger.critical("") logger.critical("*"*30) logger.critical(PROG_NAME+" => ERROR: date not in emission file!") logger.critical(" time_start = %s", time_start) logger.critical(" time_mid = %s", time_mid ) logger.critical(" time_end = %s", time_end ) logger.critical(" dates = %s", dates ) logger.critical(" date = %s", date ) logger.critical("*"*30) logger.critical("") raise RuntimeError # end if # Append the found emission field to the list. Note that using the # np.newaxis will result in a [1, n_lat, n_lon] array, where the # first dimension is the level, even though the emissions are always # at the surface. total_emis_sel.append(total_emis[date_idx[0], np.newaxis, :, :]) # end for logger.debug(" total_emis_sel = %s", np.array2string(np.asarray(total_emis_sel), **print_ops)) logger.debug(" len(total_emis_sel) = %s", len(total_emis_sel) ) logger.debug(" total_emis_sel[0].shape = %s", total_emis_sel[0].shape ) # ... final debug statements ... logger.debug("*"*30) logger.debug("") #logger.debug("") #logger.debug("*"*30) #logger.debug(PROG_NAME+" => Computer says no!") #logger.debug("*"*30) #logger.debug("") #try: # raise RuntimeError #except RuntimeError as e: # #logger.exception("OOPS!") # logger.critical(e, exc_info=True) # #raise # => Will display the traceback on screen a second time # sys.exit() # => Just exit. ## end try # Exponential conversion total_emis_sel = np.array(total_emis_sel) if tracer is not None: if getattr(tracer, "exp_conversion"): mask = total_emis_sel < 0 np.exp(total_emis_sel, out=total_emis_sel, where=mask) np.add(total_emis_sel, 1.0, out=total_emis_sel, where=~mask) np.multiply(total_emis_sel, total_emis_sel, out=total_emis_sel) # Transfrom total_emis_sel into an xarray and return it to the calling # function. xmod = xr.DataArray( total_emis_sel, coords={"time": emis_dates}, dims=("time", "lev", "lat", "lon") ) logger.debug("") logger.debug(" xmod = %s", xmod) return xmod
# end function read