Source code for avni.models.model3d

#!/usr/bin/env python
"""This script/module contains routines that are used to analyze/visualize the data sets
in the standard AVNI format."""

#####################  IMPORT STANDARD MODULES   ######################################
# python 3 compatibility
from __future__ import absolute_import, division, print_function
import sys,os
if (sys.version_info[:2] < (3, 0)):
    from builtins import float,int,list,tuple

import numpy as np #for numerical analysis
import pdb    #for the debugger pdb.set_trace()
import ntpath #Using os.path.split or os.path.basename as others suggest won't work in all cases

from scipy import sparse,linalg
import re
import struct
import h5py
import traceback
import pandas as pd
import copy
import warnings
from six import string_types # to check if variable is string using isinstance
import pint
import xarray as xr

####################### IMPORT AVNI LIBRARIES  #######################################
from .. import tools
from .. import constants
from ..mapping import spher2cart,inpolygon
from .kernel_set import Kernel_set
from .realization import Realization
from .reference1d import Reference1D
from .common import getLU2symmetric,readResCov
from .profiles import Profiles
from ..f2py import drspln,drsple # cubic spline interpolation used in our codes
from ..plots import plot1section

#######################################################################################

# 3D model class
[docs]class Model3D(object): ''' A class for 3D reference Earth models used in tomography ''' ######################### magic ########################## def __init__(self,file=None,**kwargs): self.metadata ={} self.data = {} self._name = None self._type = None self._infile = None self._refmodel = None self._description = None if file is not None: self.read(file,**kwargs) def __str__(self): if self._name is not None: output = "%s is a three-dimensional model ensemble with %s resolution levels, %s realizations of %s type and %s as the reference model" % (self._name,self.num_resolutions, len(self.data['resolution_0']),self._type,self._refmodel) else: output = "No three-dimensional model has been read into this model3d instance yet" return output def __repr__(self): return '{self.__class__.__name__}({self._name})'.format(self=self) def __len__(self): return len(self.metadata) def __getitem__(self,key): """returns data and metadata from key, a tuple of (resolution, realization)""" key = tools.convert2nparray(key,int2float=False) # if only first index, return metadata for the resolution, if not len(key) in [1,2] : raise AssertionError() metadata = self.metadata['resolution_'+str(key[0])] if len(key) == 1: return metadata else: data = self.data['resolution_'+str(key[0])]['realization_'+str(key[1])] return data def __setitem__(self,key,data): """sets (data, metadata) data_meta to key, a tuple of (resolution, realization)""" if isinstance(key, (int,np.int64)): self.metadata['resolution_'+str(key)] = data elif isinstance(key, tuple): self.data['resolution_'+str(key[0])]['realization_'+str(key[1])] = data else: raise TypeError('invalid input type for model3d') def __copy__(self): cls = self.__class__ result = cls.__new__(cls) result.__dict__.update(self.__dict__) return result def __deepcopy__(self, memo): cls = self.__class__ result = cls.__new__(cls) memo[id(self)] = result for k, v in self.__dict__.items(): setattr(result, k, copy.deepcopy(v, memo)) return result ######################### decorators ########################## @property def name(self): return self._name @property def num_resolutions(self): return len(self.metadata) @property def num_realizations(self): output = [] for res in self.metadata: output.append(len(self.data[res])) return output ######################### methods #############################
[docs] def read(self,file,**kwargs): if (not os.path.isfile(file)): raise IOError("Filename ("+file+") does not exist") success = False try:# try hdf5 for the whole ensemble hf = h5py.File(file, 'r') if kwargs: self.readhdf5(hf,**kwargs) else: self.readhdf5(hf) self._description = "Read from "+file self._infile = file hf.close() success = True except: # try netcdf or ascii for a single model try: #first close the hdf5 if opened with h5py above hf.close() except NameError: hf = None var1 = traceback.format_exc() try: realization = Realization(file) # store in the last resolution self.add_resolution(metadata=realization.metadata) self.add_realization(coef=realization.data,name=realization._name) self._name = realization._name self._type = realization._type self._refmodel = realization._refmodel self._infile = file success = True except: print('############ Tried reading as hdf5 ############') print(var1) print('############ Tried reading as ascii ############') print(traceback.format_exc()) if not success: raise IOError('unable to read '+file+' as ascii, hdf5 or netcdf4')
[docs] def plot(self): from ..plots import plotmodel3d plotmodel3d(self)
[docs] def add_realization(self,coef=None,name=None,resolution=None): """ Added a set of realizations to the object. resolution is the tesselation level at which the instance is update with name and coeff (default: None, last resolution). """ if resolution==None: if self.num_resolutions == 0: raise ValueError('add_resolution first') # add a resolution if none resolution = self.num_resolutions - 1 # last available resolution realization = self.num_realizations[resolution] else: if isinstance(resolution, int) : realization = self.num_realizations['resolution_'+str(resolution)] else: raise ValueError('Invalid resolution in add_realization') #initialize self.data['resolution_'+str(resolution)]['realization_'+str(resolution)] = {} # fill it up if name == None: name = str(realization) data = {'name':name,'coef':coef} self[resolution,realization]=data return
[docs] def add_resolution(self,metadata=None): """ Added a resolution level to the object. num_realizations is the number of realizations of model coefficients within that object. """ current = self.num_resolutions if metadata == None: metadata = {'name':str(current)} self[current] = metadata self.data['resolution_'+str(current)] = {} # empty resolutions return
[docs] def readhdf5(self,hf,query=None): """ Reads a standard 3D model file from a hdf5 file Input Parameters: ---------------- query : if None, use the model available if only one is included. Choose from query hf.keys() if multiple ones are available hf: hdf5 handle from h5py """ if query is None: if len(hf.keys()) == 1: query = hf.keys()[0] else: strsum='' for str1 in hf.keys(): strsum=strsum+' , '+str1 raise ValueError("... choose one from multiple query "+strsum) # read mean model for name,value in hf[query].attrs.items(): try: setattr(self, name,value) except: setattr(self, name,None) # loop over resolution if len(self.data) < len(hf[query].keys()): # add more resolutions for _ in range(len(hf[query].keys()) - len(self.data)): self.add_resolution() for resolution in hf[query].keys(): g1 = hf[query][resolution] if not g1.attrs['type']=='resolution': raise AssertionError() for name,value in g1.attrs.items(): try: self.metadata[resolution][name] = value except: self.metadata[resolution][name] = None #now loop over every realization for case in g1.keys(): g2 = hf[query][resolution][case] if not g2.attrs['type']=='realization': raise AssertionError() kerstr = self.metadata[resolution]['kerstr'] key = self._name+'/'+kerstr+'/'+resolution+'/'+case self.data[resolution][case]['coef'] = pd.DataFrame(tools.io.load_numpy_hdf(hf,key)) self.data[resolution][case]['name'] = g2.attrs['name'] return
[docs] def writerealization(self, resolution=0,realization=0,outfile=None): realize = Realization() # transfer data and metadata realize.metadata = self[resolution] realize._name = self._name realize.data = self[resolution,realization]['coef'] realize._type = self._type realize._refmodel = self._refmodel realize._infile = self._infile #write the file realize.to_xarray(outfile=outfile,writenc4=True)
[docs] def writehdf5(self, outfile = None, overwrite = False): """ Writes the model object to hdf5 file """ if outfile == None: outfile = self._infile+'.h5' if overwrite: hf = h5py.File(outfile, 'w') else: hf = h5py.File(outfile, 'a') g1 = hf.require_group(self._name) if self._name != None: g1.attrs['name']=self._name if self._type != None: g1.attrs['type']=self._type if self._refmodel != None: g1.attrs['refmodel']=self._refmodel if self._description != None: g1.attrs['description']=self._description for ires in range(len(self.data)): g2 = g1.require_group('resolution_'+str(ires)) # write metadata for this resolution g2.attrs['type']='resolution' try: name = self.metadata['resolution_'+str(ires)]['name'] g2.attrs['name']=name except: print('Warning: No name found for resolution '+str(ires)) for key in self.metadata['resolution_'+str(ires)].keys(): keyval = self.metadata['resolution_'+str(ires)][key] try: if keyval.dtype.kind in {'U', 'S'}: keyval = [n.encode('utf8') for n in keyval] g2.attrs[key]=keyval except: if keyval != None and key != 'kernel_set': g2.attrs[key]=keyval #now loop over every realization for icase in range(len(self.data['resolution_'+str(ires)])): g3 = g2.require_group('realization_'+str(icase)) g3.attrs['type']='realization' try: name = self.data['resolution_'+str(ires)] ['realization_'+str(icase)]['name'] g3.attrs['name']=name except: print('Warning: No name found for resolution '+str(ires)+', realization '+str(icase)) # write the data array in the appropriate position #kerstr = self.metadata['resolution_'+str(ires)]['kerstr'] key = self._name+'/resolution_'+str(ires)+'/realization_'+str(icase) out = tools.df2nparray(self.data['resolution_'+str(ires)] ['realization_'+str(icase)]['coef']) tools.io.store_numpy_hdf(hf,key,out) hf.close() print('... written to '+outfile)
[docs] def buildtree3D(self,resolution=0,dbs_path=tools.get_filedir()): """ Build a KDtree interpolant based on the metadata """ typehpar = self[resolution]['typehpar'] if not len(typehpar) == 1: raise AssertionError('only one type of horizontal parameterization allowed') for types in typehpar: if not types == 'PIXELS': raise AssertionError('for interpolation with tree3D') # not make list of depths kerstr = self[resolution]['kernel_set'].name check = True; depth_shared=None for indx,variable in enumerate(self[resolution]['varstr']): depths = self.getpixeldepths(resolution,variable) if not np.any(depths == None): # ignore 2D variables in this comparisons if np.any(depth_shared == None): depth_shared = depths else: check = check and (np.all(depth_shared==depths)) if not check: raise ValueError('All 3D variables need to share the same radial parameterization for a common KDtree to work') #get full path dbs_path=tools.get_fullpath(dbs_path) treefile = dbs_path+'/'+constants.planetpreferred+'.'+kerstr+'.KDTree.3D.pkl' if os.path.isfile(treefile): tree = tools.tree3D(treefile) else: #check that the horizontal param is pixel based xlapix = self[resolution]['xlapix'][0] xlopix = self[resolution]['xlopix'][0] nlat = len(xlapix) ndep = len(depth_shared) depth_in_km = np.zeros(nlat*ndep) for indx,depth in enumerate(depth_shared): depth_in_km[indx*nlat:(indx+1)*nlat] = depth * np.ones(nlat) xlapix = np.tile(xlapix,ndep) xlopix = np.tile(xlopix,ndep) tree = tools.tree3D(treefile,xlapix,xlopix,constants.R.to('km').magnitude - depth_in_km) return tree
[docs] def ifwithinregion(self,latitude,longitude,depth_in_km=None,resolution=0): # check if the queries is within the bounds of the model lat_max = float(self[resolution]['geospatial_lat_max']) lat_min = float(self[resolution]['geospatial_lat_min']) lon_min = float(self[resolution]['geospatial_lon_min']) lon_max = float(self[resolution]['geospatial_lon_max']) lon_min = lon_min + 360. if lon_min < 0. else lon_min lon_max = lon_max + 360. if lon_max < 0. else lon_max dep_min = self[resolution]['geospatial_vertical_min'] dep_max = self[resolution]['geospatial_vertical_max'] # convert to numpy arrays latitude = tools.convert2nparray(latitude) longitude = tools.convert2nparray(longitude) if np.any(depth_in_km == None): if not(len(latitude)==len(latitude)): raise AssertionError('latitude and longitude need to be of same size') checks = (latitude <= lat_max) & (latitude >= lat_min) & \ (longitude <= lon_max) & (longitude >= lon_min) & \ (depth_in_km <= dep_max) & (depth_in_km >= dep_min) else: depth_in_km = tools.convert2nparray(depth_in_km) if not(len(latitude)==len(latitude)==len(depth_in_km)): raise AssertionError('latitude, longitude and depth need to be of same size') checks = (latitude <= lat_max) & (latitude >= lat_min) & \ (longitude <= lon_max) & (longitude >= lon_min) return checks
[docs] def average_in_polygon(self,depth_in_km,parameter,polygon_latitude, polygon_longitude,num_cores=1, orientation = 'anti-clockwise', threshold = 1E-6,grid=1.,outside=False,mask=None,**kwargs): """ Get the average values within a polygon Input Parameters: ---------------- parameter: variable whose value will be returned depth_in_km: depth where values are being queried at (km) polygon_latitude,polygon_longitude: closed points that define the polygon. First and last points need to be the same. grid: fineness of the grid to use for averaging (in degrees) num_cores: Number of cores to use for the calculations orientation: clockwise or anti-clockwise orientation of points specified above threshold: limit to which the sum of azimuth check to (-)360 degrees is permitted to be defined as within the polygon. outside: give average values outside polygon Output: ------ average: average value within the polygon of interest tree: if kwarg argument to evaluate_at_location has interpolated=True and tree==None, returns the tree data structure. """ ## convert to numpy arrays. Various checks polylat = tools.convert2nparray(polygon_latitude) polylon = tools.convert2nparray(polygon_longitude) if len(polylat) != len(polylon): raise ValueError('polygon latitude and longitude need to be of same length') if (polygon_latitude[-1] != polygon_latitude[0]) or (polygon_longitude[-1] != polygon_longitude[0]): raise ValueError('polygon should be closed and therefore have same start and end points') nvertices = len(polylon) if orientation not in ['clockwise','anti-clockwise']: raise ValueError('orientation needs to be clockwise or anti-clockwise') ## define the lat/lon options based on grid if isinstance(grid,(int,np.int64,float)): latitude = np.arange(-90+grid/2., 90,grid) longitude = np.arange(0+grid/2., 360,grid) meshgrid = True nlat = len(latitude) nlon = len(longitude) nprofiles = nlat*nlon elif isinstance(grid,string_types): meshgrid = False raise NotImplementedError('needs to be implemented soon') ## Define the output array and area data_vars={} data_vars['inside']=(('latitude', 'longitude'), np.zeros((nlat,nlon))) data_vars['mask']=(('latitude', 'longitude'), np.zeros((nlat,nlon), dtype=bool)) if outside:data_vars['outside']=(('latitude', 'longitude'), np.zeros((nlat,nlon))) outarr = xr.Dataset(data_vars = data_vars, coords = {'latitude':latitude,'longitude':longitude}) area = tools.areaxarray(outarr) ## Loop over all items, checking if it is inside the column if mask is not None: outarr['mask'] = mask else: for ii,lat in enumerate(outarr.coords['latitude'].values): for jj,lon in enumerate(outarr.coords['longitude'].values): within = inpolygon(lat,lon,polylat,polylon,num_cores,orientation,threshold) if within: outarr['mask'][ii,jj] = True ## evaluate the model rownonzero, colnonzero = np.nonzero(outarr['mask'].data) lat = outarr.coords['latitude'].values[rownonzero] lon = outarr.coords['longitude'].values[colnonzero] depths = depth_in_km*np.ones_like(lon) if kwargs: values = self.evaluate_at_location(lat,lon,depths,parameter, **kwargs) else: values = self.evaluate_at_location(lat,lon,depths,parameter) totarea=0. average=0. for ii,row in enumerate(rownonzero): col = colnonzero[ii] outarr['inside'][row,col] = values[ii] totarea = totarea + area[row,col].item() average = average + values[ii] * area[row,col].item() percentarea = totarea/np.sum(area).item()*100. average = average/totarea ## evaluate values outside polygon if outside: rowzero, colzero = np.nonzero(np.logical_not(outarr['mask']).data) lat = outarr.coords['latitude'].values[rowzero] lon = outarr.coords['longitude'].values[colzero] depths = depth_in_km*np.ones_like(lon) if kwargs: values = self.evaluate_at_location(lat,lon,depths,parameter, **kwargs) else: values = self.evaluate_at_location(lat,lon,depths,parameter) totarea_outside=0. average_outside=0. for ii,row in enumerate(rowzero): col = colzero[ii] outarr['outside'][row,col] = values[ii] totarea_outside = totarea_outside + area[row,col].item() average_outside = average_outside + values[ii] * area[row,col].item() average_outside = average_outside/totarea_outside if outside: return percentarea,outarr,average,average_outside else: return percentarea,outarr,average
[docs] def check_unit(self,units,parameter,resolution): if units == None: return resolution = tools.convert2nparray(resolution,int2float=False) checks=True for _,res in enumerate(resolution): if 'attrs' not in self[res].keys(): checks=False else: if parameter not in self[res]['attrs'].keys(): checks=False else: if 'unit' not in self[res]['attrs'][parameter].keys(): checks=False if not checks: raise ValueError('decoding units not possible without attrs unit initialized for '+parameter+' from model file and attributes.ini') if units not in ['absolute','default']: res0_unit = self[0]['attrs'][parameter]['unit'] try: junk = constants.ureg(res0_unit).to(units) except DimensionalityError: raise ValueError("Units can only be None, absolute, default or something that can be converted from default in model instance") for index,res in enumerate(resolution): if index == 0: unit = self[res]['attrs'][parameter]['unit'] else: if unit != self[res]['attrs'][parameter]['unit']: raise AssertionError('units of parameter '+parameter+' need to be same for all resolutions, not '+unit+' and '+self[res]['attrs'][parameter]['unit']) if units == 'absolute': required = ['absolute_unit','refvalue','start_depths','end_depths'] if self._type == 'netcdf4' else ['absolute_unit'] for field in required: if field not in self[res]['attrs'][parameter].keys(): raise AssertionError(field+' of parameter '+parameter+' need to be provided for resolution '+str(res)+' if units argument is '+units) # special checks for netcdf if self._type == 'netcdf4': if index == 0: refvalue = self[res]['attrs'][parameter]['refvalue'] start_depths = self[res]['attrs'][parameter]['start_depths'] end_depths = self[res]['attrs'][parameter]['end_depths'] else: if np.any(refvalue != self[res]['attrs'][parameter]['refvalue']): raise AssertionError('refvalue of parameter ',refvalue,' need to be same for all resolutions, not ',refvalue,' and ',self[res]['attrs'][parameter]['refvalue']) if self._type == 'netcdf4': if np.any(start_depths != self[res]['attrs'][parameter]['start_depths']): raise AssertionError('start_depths of parameter '+parameter+' need to be same for all resolutions, not ',start_depths,' and ',self[res]['attrs'][parameter]['start_depths']) if np.any(end_depths != self[res]['attrs'][parameter]['end_depths']): raise AssertionError('end_depths of parameter '+parameter+' need to be same for all resolutions, not ',end_depths,' and ',self[res]['attrs'][parameter]['end_depths'])
[docs] def evaluate_unit(self,parameter,values,units,depth_in_km=None,add_reference=True,resolution=0): # change units based on options if units is None: return values if depth_in_km is None and units == 'absolute': raise ValueError('absolute units do nto work withour depth_in_km') # all resolution should have same units so using 0 unit = self[resolution]['attrs'][parameter]['unit'] try: values_unit=values.to(unit) except: values_unit = values*constants.ureg(unit) warnings.warn('Assuming that the model coefficients for resolution '+str(resolution)+' are '+unit) if units == 'default': values = values_unit elif units == 'absolute': # select reference values reference = self.get_reference(parameter,depth_in_km,resolution) reference = reference.reshape(values.shape,order='C') #reference = refvalue[index].reshape((ndep,nlat),order='C') # 1 as reference value is added values_fraction = 1+values_unit.to('fraction') if add_reference else values_unit.to('fraction') values = np.multiply(values_fraction.magnitude,reference) else: values = values_unit.to(units) return values
[docs] def get_reference(self,parameter,depth_in_km,resolution=0,interpolation='linear',dbs_path=tools.get_filedir()): # convert to numpy arrays depth_in_km = tools.convert2nparray(depth_in_km) # try reading reference model first dbs_path=tools.get_fullpath(dbs_path) ref1Dfile = dbs_path+'/'+self[resolution]['refmodel'] if os.path.isfile(ref1Dfile): ref1d = Reference1D(ref1Dfile) values1D = ref1d.evaluate_at_depth(depth_in_km,parameter,interpolation=interpolation) return values1D # Try reading as netcdf if self._type == 'netcdf4': values1D = np.zeros(depth_in_km.size) refvalue = self[resolution]['attrs'][parameter]['refvalue'] start_depths = self[resolution]['attrs'][parameter]['start_depths'] end_depths = self[resolution]['attrs'][parameter]['end_depths'] absolute_unit = self[resolution]['attrs'][parameter]['absolute_unit'] interpolant = self[resolution]['interpolant'] if interpolant.lower() == 'nearest': index = tools.ifwithindepth(start_depths,end_depths,depth_in_km) nonzeros = np.where(index>=0)[0] values1D[nonzeros]=refvalue[index[nonzeros]] elif interpolant.lower() == 'smooth': mid_depths = (start_depths+end_depths)/2. # Sort depth to make drspln work x = mid_depths;xind = x.argsort();x = x[xind] x = np.array(x.tolist(), order = 'F') y = refvalue; y = y[xind] y = np.array(y.tolist(), order = 'F') (q,wk) = drspln(1,len(x),x,y) for ii,depwant in enumerate(depth_in_km): values1D[ii] = drsple(1,len(x),x,y,q,depwant) else: raise KeyError('interpolant type '+interpolant+' cannot be deciphered for get_reference query') return values1D*constants.ureg(absolute_unit) else: raise IOError('Could not fill some reference values as the 1D reference model file could not be read as Reference1D instance : '+ref1Dfile)
[docs] def evaluate_slice(self,parameter,data=None,grid=10.,depth_in_km=None,**kwargs): """ checks whether the data input is a DataArray and the coordinates are compatible Parameters ---------- parameter: 2D or 3D parameter data : xarray Dataset or a dictionary with values grid : size of pixel in degrees, ignore if data is Dataset depth_in_km: list of depths in km for a 3D parameter Returns ------- data: new or updated xarray Dataset """ # temporary variables data_vars={} if data is None else data # loop over variables for xarray Dataset if isinstance(data, xr.Dataset): latitude = data.coords['latitude'].values longitude = data.coords['longitude'].values old=True elif data is None or isinstance(data, dict): old=False # prepare grid for evaluation if isinstance(grid,(int,np.int64,float)): latitude = np.arange(-90+grid/2., 90,grid) longitude = np.arange(0+grid/2., 360,grid) meshgrid = True # check pixel width pixel_array= grid*np.ones((len(latitude),len(longitude))) if 'pix_width' in data_vars.keys(): if data_vars['pix_width'] != pixel_array: raise ValueError('incompatible data_vars') else: data_vars['pix_width']=(('latitude', 'longitude'),pixel_array) elif isinstance(grid,string_types): profiles._infile = grid meshgrid = False raise NotImplementedError('needs to be implemented soon') else: raise ValueError('input data need to be a data_vars dictionary with a grid size or an existing data array or set') # check that incompatible fields are input for key in ['grid','interpolated']: if key in kwargs.keys(): raise ValueError(key+' cannot be passed as an argument to evaluate_slice') # fill values if kwargs: value = self.evaluate_at_location(parameter,latitude,longitude,depth_in_km,grid=True,interpolated=False,**kwargs) else: value = self.evaluate_at_location(parameter,latitude,longitude,depth_in_km,grid=True,interpolated=False) # store as xarray if depth_in_km is None: fields = ('latitude', 'longitude') coords = dict(zip(fields,(latitude,longitude))) data_vars[parameter]=(fields,value[0].magnitude) else: fields = ('depth','latitude', 'longitude') coords = dict(zip(fields,(depth_in_km,latitude,longitude))) data_vars[parameter]=(fields,value.magnitude) return data_vars if old else xr.Dataset(data_vars = data_vars,coords = coords)
[docs] def evaluate_at_location(self,parameter,latitude,longitude,depth_in_km=None,resolution=0,realization=0,grid=False,interpolated=False,tree=None,nearest=None,units=None,add_reference=True,dbs_path=tools.get_filedir()): """ Evaluate the mode at a location (latitude, longitude,depth) Input Parameters: ---------------- parameter: variable whose value will be returned depth_in_km: depth where values are being queried at (km) interpolated: If True, use KDTree from a predefined grid. If False, evaluated exactly using kernel_set instance. nearest: number of points to interpolate. If None, defaults to values based on self[resolution]['interpolant'] grid: make a grid by unraveling (depth_in_km,latitude,longitude) units: if 'absolute' try converting to absolute units of the parameter. If 'default', give the default units from model instance. If None, simply return the sparse array add_reference: add the reference paramter value to perturbation, only valid if units queried is 'absolute' """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") # convert to numpy arrays latitude = tools.convert2nparray(latitude) longitude = tools.convert2nparray(longitude) resolution = tools.convert2nparray(resolution,int2float=False) tree_provided = False if tree == None else True nlat = len(latitude) nlon = len(longitude) if np.all(depth_in_km==None): ndep = 1 else: depth_in_km = tools.convert2nparray(depth_in_km) ndep = len(depth_in_km) #checks self.check_unit(units,parameter,resolution) if grid: nrows = ndep*nlat*nlon longitude,latitude = np.meshgrid(longitude,latitude) longitude = longitude.ravel() latitude = latitude.ravel() else: check1 = (len(latitude) == len(longitude) == len(depth_in_km)) if depth_in_km is not None else (len(latitude) == len(longitude)) if not check1: raise ValueError("latitude, longitude or depth_in_km should be of same length if not making grid = False") check2 = (latitude.ndim == longitude.ndim == depth_in_km.ndim == 1) if depth_in_km is not None else (latitude.ndim == longitude.ndim) if not check2: raise ValueError("latitude, longitude or depth_in_km should be one-dimensional arrays") nrows = nlat #compute values for each resolution sumvalue = np.zeros((ndep,nlat,nlon),order='C') if grid else np.zeros(nrows) for index,res in enumerate(resolution): # get the model array if parameter in self[resolution]['varstr']: mapping={'variables': [parameter], 'constants': [1.0], 'symbols': []} else: try: mapping = self[resolution]['attrs'][parameter]['mapping'] outvar=[];outsym=[];outcons=[] for ivar,variable in enumerate(mapping['variables']): findvar = tools.convert2nparray(self[res]['kernel_set'].search_radial(variable)) for itemp,var in enumerate(findvar): outvar.append(var) outcons.append(mapping['constants'][ivar]) if ivar > 0: outsym.append(mapping['symbols'][ivar-1]) else: if itemp>0: outsym.append('+') mapping['variables']=outvar;mapping['constants']=outcons mapping['symbols']=outsym except: raise KeyError('no parameter mapping or entry found for '+parameter+' in resolution '+str(res)+'. Only following queries allowed : '+str(self[resolution]['varstr'])) # No wloop over mapping and add/subtract as required for ipar,param in enumerate(mapping['variables']): modelarr = self.coeff2modelarr(resolution=res,realization=realization, parameter=param) if not interpolated: # get the projection matrix project = self.get_projection(latitude=latitude,longitude=longitude,depth_in_km=depth_in_km,parameter=param,resolution=res,grid=grid) predsparse = project['matrix']*modelarr # update locations based on grid depth_tmp= None if grid and ipar == 0: if depth_in_km is not None: depth_tmp = np.zeros(nrows) for indx,depth in enumerate(depth_in_km): depth_tmp[indx*nlat*nlon:(indx+1)*nlat*nlon] = depth * np.ones(nlat*nlon) ###### interpolant #### else: # decide on the interpolant type based on metadata if nearest == None: interpolant = self[resolution]['interpolant'] if interpolant.lower() == 'nearest': nearest=1 elif interpolant.lower() == 'smooth': nearest=6 else: raise KeyError('interpolant type '+interpolant+' cannot be deciphered for kdtree query') # update locations based on grid if grid: if depth_in_km is None: depth_tmp= None else: depth_tmp = np.zeros(nrows) for indx,depth in enumerate(depth_in_km): depth_tmp[indx*nlat*nlon:(indx+1)*nlat*nlon] = depth * np.ones(nlat*nlon) lat_tmp = np.tile(latitude,len(depth_in_km)) lon_tmp = np.tile(longitude,len(depth_in_km)) else: depth_tmp = depth_in_km lat_tmp = latitude lon_tmp = longitude # check if the queries is within the bounds of the model checks = self.ifwithinregion(lat_tmp,lon_tmp,depth_tmp,resolution) if np.count_nonzero(checks) == 0: predsparse = sparse.csr_matrix((nrows, 1)) else: if not tree_provided: tree = self.buildtree3D(resolution=resolution,dbs_path=dbs_path) self.checktree3D(tree,parameter=param,resolution=resolution) # Get modelarr modelarr = self.coeff2modelarr(resolution=resolution,realization=realization,parameter=param) xlapix = lat_tmp[checks];xlopix = lon_tmp[checks] depths = depth_tmp[checks] #KDtree evaluate print ('... evaluating KDtree ...') temp,_ = tools.querytree3D(tree=tree,latitude=xlapix,longitude=xlopix,radius_in_km= constants.R.to('km').magnitude - depths,values=modelarr,nearest=nearest) print ('... done evaluating KDtree.') if np.all(checks): # if all points within region predsparse = temp else: data = temp.toarray().ravel() row = np.where(checks)[0] col = np.zeros_like(row,dtype=int) predsparse = sparse.csr_matrix((data, (row, col)), shape=(nrows, 1)) # unravel to get it in the right order values = predsparse.toarray().reshape((ndep,nlat,nlon),order='C') if grid else predsparse.toarray().ravel() # account for units in model evaluation scale=mapping['constants'][ipar] if ipar==0: sumvalue+=scale*self.evaluate_unit(param,values,'default',depth_tmp) else: if mapping['symbols'][ipar-1]=='+': sumvalue+=scale*self.evaluate_unit(param,values,'default',depth_tmp) elif mapping['symbols'][ipar-1]=='-': sumvalue-=scale*self.evaluate_unit(param,values,'default',depth_tmp) else: raise ValueError('invalid sign '+mapping['symbols'][ipar-1]) # add backreference values if add_reference: # first convert to standard units in first resolution sumvalue = self.evaluate_unit(parameter,sumvalue,units,depth_tmp,add_reference=True,resolution=0) if interpolated and not tree_provided: return sumvalue, tree else: return sumvalue
[docs] def getpixeldepths(self,resolution,parameter): typehpar = self[resolution]['typehpar'] if not len(typehpar) == 1: raise AssertionError('only one type of horizontal parameterization allowed') for types in typehpar: if not types == 'PIXELS': raise AssertionError('for interpolation with tree3D') kernel_set = self[resolution]['kernel_set'] depths = kernel_set.pixeldepths(parameter) return depths
[docs] def coeff2modelarr(self,resolution=0,realization=0,parameter=None): """ Convert the coeffiecient matrix from the file to a sparse model array. Add up all the resolutions if a list is provided. realization : index of the set of coefficients in an ensemble. Default is 0 as there is only one set of coefficients when read from a model file. resolution : list of resolutions to include the the modelarray parameter: parameters to select. Default is to include all available. """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") # convert to numpy arrays resolution = tools.convert2nparray(resolution,int2float=False) # Loop over resolution levels, adding coefficients for ir,_ in enumerate(resolution): try: coefficients =self[resolution[ir],realization]['coef'] #if modelarr is already made, and not parameter is specified, use stored recalculate = False if parameter == None: try: modelarr = self[resolution[ir],realization]['modelarr'] except: recalculate=True Nhopar=len(self[resolution[ir]]['ihorpar']) radselect = np.arange(Nhopar) else: recalculate=True # select row indices if specific parameter is required varindex = np.where(self[resolution[ir]]['varstr']==parameter)[0]+1 if len(varindex) != 1: raise AssertionError(str(len(varindex))+' parameters found for '+parameter+'. Only one is allowed') radselect = np.where(self[resolution[ir]]['ivarkern']==varindex[0])[0] if recalculate: # if only a single parameterization, simply unravel if len(np.unique(self[resolution]['ihorpar'][radselect])) == 1: modelarr=coefficients.iloc[radselect].to_numpy().ravel() else: for irow,ihorpar in enumerate(self[resolution[ir]]['ihorpar']): # only include if selected icount=0 if irow in radselect: ncoef = self[resolution]['ncoefhor'][ihorpar-1] if icount == 1: modelarr = coefficients.iloc[irow][:ncoef].to_numpy().ravel() else: modelarr = np.concatenate((modelarr,coefficients.iloc[irow][:ncoef].to_numpy().ravel())) modelarr = sparse.csr_matrix(modelarr).transpose() # only store if all parameters are requested if parameter == None: self[resolution[ir],realization]['modelarr'] = modelarr except AttributeError: raise ValueError('resolution '+str(resolution[ir])+' and realization '+str(realization)+' not filled up yet.') return modelarr
[docs] def readprojbinary(self,lateral_basis): """ Reads Projection matrix created by plot_3dmod_pm. lateral_basis can be M362 or pixel1 """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") #read all the bytes to indata outfile = self._name+'.'+lateral_basis+'.proj.bin.h5' infile = self._name+'.'+lateral_basis+'.proj.bin' xlat=[];xlon=[];area=[];deptharr=[] cc = 0 #initialize byte counter ifswp = '' # Assuem that byte order is not be swapped unless elat is absurdly high if (not os.path.isfile(outfile)): if (not os.path.isfile(infile)): raise IOError("Filename ("+infile+") does not exist. Use shell script plot_3dmod_pm64 to create it.") nbytes = os.path.getsize(infile) print("....writing "+outfile) with open(infile, "rb") as f: # preliminary metadata indata = f.read(4); cc = cc+4 # try to read iflag iflag = struct.unpack(ifswp+'i',indata)[0] # Read flag if iflag != 1: ifswp = '!' # swap endianness from now on iflag = struct.unpack(ifswp+'i',indata)[0] if iflag != 1: sys.exit("Error: iflag != 1") ndp = struct.unpack(ifswp+'i',f.read(4))[0]; cc = cc+4 npx = struct.unpack(ifswp+'i',f.read(4))[0]; cc = cc+4 nhorcum = struct.unpack(ifswp+'i',f.read(4))[0]; cc = cc+4 for jj in np.arange(npx): xlat.append(struct.unpack(ifswp+'f',f.read(4))[0]); cc = cc+4 xlontemp = struct.unpack(ifswp+'f',f.read(4))[0]; cc = cc+4 # Change from -180 to 180 limits to be compatible with model3d format if xlontemp > 180: xlontemp = xlontemp - 360. xlon.append(xlontemp) area.append(struct.unpack(ifswp+'f',f.read(4))[0]); cc = cc+4 # loop by deptharr projarr={};refstrarr=[];refvalarr={} for ii in np.arange(ndp): deptharr.append(struct.unpack(ifswp+'f',f.read(4))[0]); cc = cc+4 for jj in np.arange(npx): if jj % (npx/5) == 0: print("deptharr # "+str(ii+1)+" out of "+str(ndp)+", pixel # "+str(jj+1)+" out of "+str(npx)) neval = struct.unpack(ifswp+'i',f.read(4))[0]; cc = cc+4 for kk in np.arange(neval): refstr = struct.unpack('80s',f.read(80))[0].strip().decode('utf-8'); cc = cc+80 refval=struct.unpack(ifswp+'f',f.read(4))[0]; cc = cc+4 if ii==0 and jj==0: refstrarr.append(refstr) if jj==0: refvalarr[ii,kk]=refval iloop=struct.unpack(ifswp+'i',f.read(4))[0]; cc = cc+4 coeirow=[];proja=[] for ll in np.arange(iloop): coeirow.append(struct.unpack(ifswp+'i',f.read(4))[0]); cc = cc+4 proja.append(struct.unpack(ifswp+'f',f.read(4))[0]); cc = cc+4 coeirow=np.array(coeirow) proja=np.array(proja) try: projarr[ii,kk]=projarr[ii,kk]+sparse.csr_matrix( (proja,(np.ones_like(coeirow)*jj,coeirow-1)), shape=(npx,nhorcum)) except KeyError: projarr[ii,kk]=sparse.csr_matrix( (proja,(np.ones_like(coeirow)*jj,coeirow-1)), shape=(npx,nhorcum)) if cc != nbytes: sys.exit("Error: number of bytes read "+str(cc)+" do not match expected ones "+str(nbytes)) deptharr=np.array(deptharr); refstrarr=np.array(refstrarr) # save grid namelist = ['xlat', 'xlon', 'area'] namelist = [str(name) for name in namelist] formatlist=['f8','f8','f8'] xlat=np.array(xlat); xlon=np.array(xlon); area=np.array(area) results = [xlat, xlon, area] results = map(list, zip(*results)) dtype = dict(names = namelist, formats=formatlist) grid = np.array([tuple(x) for x in results],dtype=dtype) model = self._name h5f = h5py.File(outfile,'w') h5f.attrs["infile"] = np.string_(ntpath.basename(infile)) h5f.attrs["ndp"] = ndp h5f.attrs["npx"] = npx h5f.attrs["nhorcum"] = nhorcum h5f.attrs["neval"] = neval h5f.attrs["deptharr"] = deptharr h5f.attrs["refstrarr"] = refstrarr tools.io.store_numpy_hdf(h5f,'grid',grid) h5f.attrs["model"] = np.string_(model) h5f.attrs["param"] = np.string_(lateral_basis) for ii in np.arange(len(deptharr)): for jj in np.arange(len(refstrarr)): proj = h5f.create_group("projection/depth_"+str(ii)+ "/refstr_"+str(jj)) proj.attrs['refvalue']=refvalarr[ii,jj] tools.io.store_sparse_hdf(h5f,"projection/depth_"+str(ii)+ "/refstr_"+str(jj),projarr[ii,jj]) h5f.close() else: print("....reading "+outfile) h5f = h5py.File(outfile,'r') ndp=h5f.attrs['ndp']; npx=h5f.attrs['npx']; nhorcum=h5f.attrs['nhorcum'] neval=h5f.attrs['neval']; deptharr=h5f.attrs['deptharr'] refstrarr=h5f.attrs['refstrarr']; grid = tools.io.load_numpy_hdf(h5f,'grid') xlat=grid['xlat']; xlon=grid['xlon']; area=grid['area'] model=h5f.attrs['model']; lateral_basis=h5f.attrs['param'] # Always read the following from hdf5 so projarr is list ofsparsehdf5-fast I/O projarr={};refvalarr={} for ii in np.arange(len(deptharr)): for jj in np.arange(len(refstrarr)): proj = h5f["projection/depth_"+str(ii)+ "/refstr_"+str(jj)] refvalarr[ii,jj] = proj.attrs['refvalue'] projarr[ii,jj] = tools.io.load_sparse_hdf(h5f,"projection/depth_"+str(ii)+ "/refstr_"+str(jj)) h5f.close() # Write to a dictionary projection = {} projection['ndp']=ndp; projection['npx']=npx; projection['nhorcum']=nhorcum; projection['neval']=neval; projection['deptharr']=deptharr; projection['refstrarr']=refstrarr projection['xlat']=xlat; projection['xlon']=xlon; projection['area']=area projection['refvalarr']=refvalarr; projection['projarr']=projarr projection['model']=model; projection['param']=lateral_basis return projection
[docs] def get_projection(self,parameter,latitude,longitude,depth_in_km = None, resolution = 0,grid=False): """ Get the projection matrix from a lateral basis to another and for particular depths depth_in_km : depth in km where the projection matrix is needed. If None, returns the projection matrix for the lat/lon and radial basis as a dirac delta. grid: make a grid by repeating (latitude,longitude) by number of depth_in_km """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") if latitude is None and longitude is None and depth_in_km is None: raise ValueError("Need to provide atleast one of latitude, longitude or depth_in_km") # convert to numpy arrays lat = tools.convert2nparray(latitude) lon = tools.convert2nparray(longitude) if depth_in_km is None: dep = None nrows = len(lat) else: dep = tools.convert2nparray(depth_in_km) if grid: nrows = len(lat)*len(dep) else: if not (len(lat) == len(lon) == len(dep)): raise ValueError("latitude, longitude or depth_in_km should be of same length if not making grid = False") if not (lat.ndim == lon.ndim == dep.ndim == 1): raise ValueError("latitude, longitude or depth_in_km should be one-dimensional arrays") nrows = len(lat) if not len(latitude)==len(longitude): raise AssertionError('latitude and longitude should be of same length') # Get the radial projection file kernel = self[resolution]['kernel_set'] # Write to a dictionary # write to a projection file if it does not exist or existing one has different attributes than projection[parameter] projection = {} projection['kernel'] = kernel.name projection['deptharr']=dep projection['xlat']=lat projection['xlon']=lon projection['refstrarr']=parameter # loop through parameter and append the projection for each location horcof, vercof = kernel.evaluate_bases(parameter,lat,lon,dep) # find radial indices of a given physical parameter findrad = kernel.find_radial(parameter) # convert vercof to sparse for easy multiplication vercof = sparse.csr_matrix(vercof) if vercof is not None else np.ones((1,len(findrad))) # initialize the projection matrix indend_all = kernel.metadata['ncoefcum'][findrad['index'][-1]]-1 indstart_all = 0 if findrad['index'][0] == 0 else kernel.metadata['ncoefcum'][findrad['index'][0]-1] ncol = indend_all - indstart_all + 1 proj = sparse.lil_matrix((nrows,ncol), dtype=np.float64) # difference projection matrix based on whether grid is asked or not # loop over all depths for idep in range(1 if dep is None else len(dep)): # loop over all radial kernels that belong to this parameter and add up for ii in np.arange(len(findrad)): if vercof[idep,ii] != 0.: row_start = idep*len(lat) if grid else idep row_end = (idep+1)*len(lat) if grid else idep+1 column_start = ii*horcof.shape[1] column_end = (ii+1)*horcof.shape[1] proj[row_start:row_end,column_start:column_end] = horcof*vercof[idep,ii] if grid else (horcof*vercof[idep,ii])[idep] # store the projection matrix projection['matrix'] = proj.tocsr() # store the lateral and radial parameter of this variable radial_type = [] for rker in kernel.data['radial_basis'][parameter]: radial_type.append(rker.type) if len(np.unique(radial_type)) != 1: raise AssertionError('more than one radial parameterization for '+parameter) projection['radial_basis']=radial_type[0] projection['radial_metadata']=kernel.data['radial_basis'][parameter][0].metadata # same check for lateral parameterization selfmeta = self[resolution] selfkernel = selfmeta['kernel_set'] ivarfind =np.where(selfmeta['varstr']==parameter)[0] if not len(ivarfind) == 1: raise AssertionError('only one parameter can be selected in eval_kernel_set') findvar = selfmeta['varstr'][ivarfind[0]] dt = np.dtype([('index', np.int), ('kernel', np.unicode_,50)]) findrad = np.array([(ii, selfmeta['desckern'][ii]) for ii in np.arange(len(selfmeta['ivarkern'])) if ivarfind[0]+1 == selfmeta['ivarkern'][ii]],dtype=dt) if len(np.unique(selfmeta['ihorpar'][findrad['index']])) != 1: raise AssertionError('more than one lateral parameterization for '+parameter) ihorpar = selfmeta['ihorpar'][findrad['index']][0] projection['lateral_metadata']= selfkernel.data['lateral_basis'][ihorpar-1].metadata projection['lateral_basis']= selfkernel.data['lateral_basis'][ihorpar-1].type return projection
[docs] def projslices(self,projection,variable,depth,resolution=0,realization=0): """ Projection matrix multiplied by model ensemble. Choses the nearest depth available for the projection. """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") modelarr = self.coeff2modelarr(resolution=resolution,realization=realization) # Select appropriate arrays from projection matrix, read from file projarr = projection['projarr']; refstrarr = projection['refstrarr']; deptharr = projection['deptharr'] varindex = np.where(refstrarr==variable)[0] if len(varindex) == 0: raise ValueError('no projection found for variable '+variable) absdiff=abs(deptharr-depth) # Find the nearest depth depindex = absdiff.argmin() if min(absdiff) > 0. : print ("No unique depth found in the projection matrix. Choosing the nearest available depth "+str(deptharr[depindex])) modelselect=projarr[depindex,varindex[0]]*modelarr return modelselect,deptharr[depindex]
[docs] def checktree3D(self,tree,parameter,resolution=0): # check if it is pixel based typehpar = self[resolution]['typehpar'] if not len(typehpar) == 1: raise AssertionError('only one type of horizontal parameterization allowed') for types in typehpar: if not types == 'PIXELS': raise AssertionError('for interpolation with tree3D') # find the radial kernels varindex = np.where(self[resolution]['varstr']==parameter)[0]+1 if len(varindex) != 1: raise AssertionError(str(len(varindex))+' parameters found for '+parameter+'. Only one is allowed') radselect = np.where(self[resolution]['ivarkern']==varindex[0])[0] # if only a single parameterization, simply unravel if len(np.unique(self[resolution]['ihorpar'][radselect])) != 1: raise ValueError('cannot deal with more than one horizontal parameterization') # get the pixel configuration depths = self.getpixeldepths(resolution,parameter) if np.any(depths == None): raise ValueError('depths not found for interpolation for variable '+parameter+' in target file.') #get full path xlapix = self[resolution]['xlapix'][0] xlopix = self[resolution]['xlopix'][0] nlat = len(xlapix) ndep = len(depths) depth_in_km = np.zeros(nlat*ndep) for indx,depth in enumerate(depths): depth_in_km[indx*nlat:(indx+1)*nlat] = depth * np.ones(nlat) xlapix = np.tile(xlapix,len(depths)) xlopix = np.tile(xlopix,len(depths)) radius_in_km = constants.R.to('km').magnitude - depth_in_km if tree.n != len(xlopix): raise AssertionError('tree (knots = '+str(tree.n)+') not compatible with the self instance for variable '+parameter+' (knots = '+str(len(xlopix))+'). Perhaps fewer than necessary depths have been provided; '+str(ndep)+' depths are available now.') # tree stucture is store in spherical coordinates rlatlon = np.column_stack((radius_in_km.flatten(),xlapix.flatten(), xlopix.flatten())) xyz = spher2cart(rlatlon) if not np.allclose(tree.data,xyz): raise ValueError('tree is not compatible with the matrix of the variable '+parameter) return xlapix,xlopix,depth_in_km
[docs] def reparameterize(self, model3d,resolution=0,realization=0,interpolated=False,tree=None,nearest=1, dbs_path=tools.get_filedir()): """ Inverts for new coefficients in self.data from the coefficients in model3d class write : output the reparamterized model as a text file interpolated: assume that it won't be a simple interpolation """ if type(model3d).__name__ != 'Model3D': raise ValueError('input model class should be a Model3D instance') # Get the radial projection file selfmeta = self[resolution] newmeta = model3d[resolution] selfkernel = selfmeta['kernel_set'] newkernel = newmeta['kernel_set'] tree_provided = False if tree == None else True ####################### If both models are pixel based then interpolate########### check1 = selfkernel.metadata['typehpar'] == newkernel.metadata['typehpar'] == 'PIXELS' # they should also have the parameter in common check2 = sorted(np.intersect1d(selfkernel.metadata['varstr'],newkernel.metadata['varstr'])) == sorted(selfkernel.metadata['varstr']) if len(check1) == 1 and check1[0] and check2 and interpolated: # storage of kernel descriptions desckern = np.array([],dtype=newmeta['desckern'].dtype) ivarkern=np.array([],dtype=int); ihorpar=np.array([],dtype=int); ncoeff_layer=np.array([],dtype=int) for indx, variable in enumerate(selfmeta['varstr']): if not tree_provided: tree = self.buildtree3D(resolution=resolution,dbs_path=dbs_path) # check if tree is compatible with modelarr self.checktree3D(tree,parameter=variable,resolution=resolution) # Get modelarr modelarr = self.coeff2modelarr(resolution=resolution,realization=realization,parameter=variable) # queried locations depth_shared = model3d.getpixeldepths(resolution,variable) if np.any(depth_shared == None): raise ValueError('depths not found for interpolation for variable '+variable+' in target file.') xlapix = newmeta['xlapix'][0] xlopix = newmeta['xlopix'][0] nlat = len(xlapix) nlon = len(xlapix) ndep = len(depth_shared) depth_in_km = np.zeros(nlat*ndep) for indx1,depth in enumerate(depth_shared): depth_in_km[indx1*nlat:(indx1+1)*nlat] = depth * np.ones(nlat) xlapix = np.tile(xlapix,len(depth_shared)) xlopix = np.tile(xlopix,len(depth_shared)) # check if the queries is within the bounds of the model checks = self.ifwithinregion(xlapix,xlopix,depth_in_km,resolution) if np.count_nonzero(checks) == 0: final = sparse.csr_matrix((ndep*nlat, 1)) else: xlapix = xlapix[checks];xlopix = xlopix[checks] depth_in_km = depth_in_km[checks] #KDtree evaluate print ('... evaluating KDtree ...') temp,_ = tools.querytree3D(tree=tree,latitude=xlapix,longitude=xlopix,radius_in_km= constants.R.to('km').magnitude - depth_in_km,values=modelarr,nearest=nearest) print ('... done evaluating KDtree.') if np.all(checks): # if all points within region final = temp else: data = temp.toarray().ravel() row = np.where(checks)[0] col = np.zeros_like(row,dtype=int) final = sparse.csr_matrix((data, (row, col)), shape=(ndep*nlat, 1)) # now unwrap to coef shape = [ndep,nlat] if indx == 0: values = final.reshape(shape,order='C') else: values = sparse.vstack([values,final.reshape(shape,order='C')]) # update the kernel descriptions varind = np.where(newmeta['varstr']==variable)[0][0]+1 kerind = np.where(newmeta['ivarkern']==varind) ivarkern = np.concatenate((ivarkern, np.ones(len(newmeta['ivarkern'][kerind]),dtype=int)*(indx+1))) ihorpar = np.concatenate((ihorpar,newmeta['ihorpar'][kerind])) desckern = np.concatenate((desckern,newmeta['desckern'][kerind])) ncoeff_layer = np.concatenate((ncoeff_layer, newmeta['ncoefhor'][newmeta['ihorpar'][kerind]-1])) #update the variable attributes, delete some values as they will be recalculated for field in newmeta['attrs'][variable].keys(): if not field in ['refvalue','average']: selfmeta['attrs'][variable][field] = newmeta['attrs'][variable][field] else: if field in selfmeta['attrs'][variable]: del selfmeta['attrs'][variable][field] # copy over the correct metadata selfmeta['nmodkern'] = len(selfmeta['desckern']) selfmeta['ncoefcum'] = np.cumsum(ncoeff_layer) selfmeta['ivarkern'] = ivarkern selfmeta['ihorpar'] = ihorpar selfmeta['desckern'] = desckern for field in ['hsplfile','xsipix', 'xlapix','ncoefhor', 'xlopix','kerstr','geospatial_lat_resolution','geospatial_lon_resolution']: selfmeta[field] = newmeta[field] try: selfmeta['kernel_set'] = Kernel_set(selfmeta.copy()) except: warnings.warn('Warning: kernel_set could not initialized for '+str(resolution)) pass # make a model3D instance and store coef panda dataframe reparam = copy.deepcopy(self) reparam[resolution] = selfmeta reparam[resolution,realization]['coef'] = pd.DataFrame(values.toarray()) reparam._infile = selfmeta['name']+'.'+selfmeta['kerstr']+'.avni.nc4' ####################### Invert the coefficients ############################## else: if interpolated: warnings.warn('Could not interpolate. Inverting the coefficients...') # get the projection matrix for each variable in self dt = np.dtype([('index', np.int), ('kernel', np.unicode_,50)]) for variable in selfmeta['varstr']: ivarfind =np.where(selfmeta['varstr']==variable)[0] if not len(ivarfind) == 1: raise AssertionError('only one parameter can be selected in eval_kernel_set') findvar = selfmeta['varstr'][ivarfind[0]] findrad = np.array([(ii, selfmeta['desckern'][ii]) for ii in np.arange(len(selfmeta['ivarkern'])) if ivarfind[0]+1 == selfmeta['ivarkern'][ii]],dtype=dt) # Check if the selected radial kernels are boxcar in self for rker in selfkernel.data['radial_basis'][variable]: if rker.type != 'boxcar': raise AssertionError('radial kernel is not boxcar for '+variable) # same check for lateral parameterization for hpar in selfmeta['ihorpar'][findrad['index']]: if selfkernel.data['lateral_basis'][hpar-1].type != 'PIXELS': raise AssertionError('lateral kernel is not PIXELS for '+variable) # find the corresponding radial kernels in newmeta ivarfind2 =np.where(newmeta['varstr']==variable)[0] if len(ivarfind2) != 1: ifselected = [False for x in range(len(newmeta['varstr']))] ivarfind2 = [] ifdone = False while not ifdone: stringout = '' for ii in range(len(newmeta['varstr'])): stringout=stringout+str(ii)+'. '+newmeta['varstr'][ii]+' '+str(ifselected[ii] if ifselected[ii] else '')+'\n' print('') print(stringout) try: x = int(input('Warning: no unique corresponding variable found for '+variable+'. Select one index from above to assign parameterization:')) ifselected[x] = True ivarfind2.append(x) except (ValueError,EOFError): if len(ivarfind2) != 0: ifdone = True # loop over selected variables for ivar in ivarfind2: findvar2 = newmeta['varstr'][ivar] findrad2 = np.array([(ii, newmeta['desckern'][ii]) for ii in np.arange(len(newmeta['ivarkern'])) if newmeta['ivarkern'][ii] == ivar+1],dtype=dt) # calculate the projection matrix for all locations longitude = selfmeta['xlopix'][0]; latitude = selfmeta['xlapix'][0] radialinfo = selfkernel.data['radial_basis'][variable][0].metadata depth_in_km = np.average(np.array([radialinfo['depthtop'],radialinfo['depthbottom']]),axis=0) # loop over depths and append the projection matrices proj = model3d.get_projection(findvar2,latitude,longitude,depth_in_km,resolution=resolution) # get areas for the grid and multiply the proj # invert for the coefficients, if not invertible perform L curve inversion # find optimal fraction of max(GTG_diag) to use for damping by # inflection point # select the sub-array with non-zero values to invert start = newmeta['ncoefcum'][findrad2['index'][0]-1] if findrad2['index'][0] > 0 else 0 end = newmeta['ncoefcum'][findrad2['index'][-1]] GTG= proj['matrix'].T*proj['matrix'] GTG_inv = linalg.inv(GTG[start:end,start:end].todense()) #d = GTG_inv * values pdb.set_trace() if interpolated and not tree_provided: return reparam, tree else: return reparam
[docs] def to_profiles(self,grid=10.,type='pixel',resolution=0,realization=0,model_dir='.',interpolated=True): """ converts a model3d class to profiles class grid: either a grid size of a grid file interpolant: interpolant type either in pixel or nearest model_dir: directory to find reference 1D model """ if type not in ['pixel','nearest']: raise ValueError('only pixel or nearest options allowed for type') # check if we can read 1D model ref1Dfile = self[resolution]['refmodel'] if os.path.isfile(ref1Dfile): try: # try reading the 1D file in card format ref1d = Reference1D(ref1Dfile) depth_in_km = (ref1d.data['depth'].pint.to('km')).data ndep = len(depth_in_km) except: raise IOError('Could not fill some reference values as the 1D reference model file could not be read as Reference1D instance : '+ref1Dfile) else: raise IOError('Could not fill some reference values as the 1D reference model file could not be found : '+ref1Dfile) tree = None # Operations between PintArrays of different unit registry will not work. # We can change the unit registry that will be used in creating new # PintArrays to prevent this issue. pint.PintType.ureg = constants.ureg PA_ = pint.PintArray # loop over reference1d fields and update the corresponding fields common_fields = np.intersect1d(self[resolution]['parameters'],ref1d.metadata['parameters']) missing_fields = sorted(set(ref1d.metadata['parameters'])-set(common_fields)) # copy the 1D model and drop missing fields not specified by 3d model local=copy.deepcopy(ref1d) local.data=local.data.drop(missing_fields,axis=1) local.metadata['parameters'] = common_fields index = [] for field in common_fields:index.append(ref1d.metadata['parameters'].index(field)) local.metadata['units'] = (np.array(ref1d.metadata['units'])[index]).tolist() for field in ['delta','average','contrast']: local.metadata['discontinuities'][field] = local.metadata['discontinuities'][field].drop(missing_fields,axis=1) # loop over variables for xarray Dataset data_vars={};local_profiles = {} profiles = Profiles() profiles._name = self._name profiles._interpolant = type if isinstance(grid,(int,np.int64,float)): profiles._infile = str(grid)+'X'+str(grid) latitude = np.arange(-90+grid/2., 90,grid) longitude = np.arange(0+grid/2., 360,grid) meshgrid = True nlat = len(latitude) nlon = len(longitude) nprofiles = nlat*nlon # index column by longitude profindx = np.arange(nprofiles).reshape((nlat,nlon),order='C') data_vars['index']=(('latitude', 'longitude'), profindx) # deep copy will not modify values of original profile # get the grid sizes stored pixel_array= grid*np.ones((len(latitude),len(longitude))) data_vars['pix_width']=(('latitude', 'longitude'), pixel_array) elif isinstance(grid,string_types): profiles._infile = grid meshgrid = False raise NotImplementedError('needs to be implemented soon') for ii in np.arange(nprofiles): local_profiles[ii]=copy.deepcopy(local) # get the grid sizes stored profiles.data['grid'] = xr.Dataset(data_vars = data_vars, coords = {'latitude':latitude,'longitude':longitude}) tree = None # create the tree the first time arounf for indx,parameter in enumerate(common_fields): print('... evaluating 3D perturbations for parameter # '+str(indx+1)+' / '+str(len(common_fields))+' '+parameter) # Get 3D perturbations, do not add approximate reference stored ins file if interpolated: if tree == None: values3D, tree = self.evaluate_at_location(longitude=longitude,latitude=latitude,depth_in_km=depth_in_km,parameter=parameter,resolution=resolution,realization=realization,grid=meshgrid,interpolated=True,units='absolute',add_reference=False) else: values3D = self.evaluate_at_location(longitude=longitude,latitude=latitude,depth_in_km=depth_in_km,parameter=parameter,resolution=resolution,realization=realization,grid=meshgrid,interpolated=True,tree=tree,units='absolute',add_reference=False) # calculate explicitly else: values3D = self.evaluate_at_location(longitude=longitude,latitude=latitude,depth_in_km=depth_in_km,parameter=parameter,resolution=resolution,realization=realization,grid=meshgrid,interpolated=False,units='absolute',add_reference=False) # Get 1D values values1D = ref1d.evaluate_at_depth(depth_in_km,parameter) # Add the 1D values for idep in np.arange(ndep): values3D[idep,:,:] = values3D[idep,:] + values1D[idep] # loop over profiles and update values if meshgrid: done = [] for ilat in np.arange(nlat): for ilon in np.arange(nlon): index = profiles.data['grid']['index'][ilat,ilon].item() #if not evaluated before if index not in done: target_unit = local_profiles[index].data[parameter].pint.units local_profiles[index].data[parameter][:] = values3D[:,ilat,ilon].to(target_unit) done.append(index) # update name if indx == 0: local_profiles[index]._name = self._name+'_'+ \ local_profiles[index]._name + \ '_profile#' + str(index) else: raise NotImplementedError('needs to be implemented soon') ## Update these values for key in self[resolution].keys(): value = self[resolution][key] if isinstance(value, (list,tuple,np.ndarray,bool,float,int,np.int64,string_types)): profiles.metadata[key] = value profiles.data['profiles'] = local_profiles return profiles
[docs] def get_resolution(self,rescovfile=None,LU2symmetric=True,resolution=0, realization=0): """ Reads Resolution or Covariance matrix created by invwdata_pm64 with option -r. R=inv(ATA+DTD)ATA and the name of file is typically outmodel.Resolution.bin First read the matrix and model file, perform checks and create a sparse matrix. """ # Read metadata from the 3D model refmodel1 = self[resolution]['refmodel'];kerstr1 = self[resolution]['kerstr'] modelarr = self.coeff2modelarr(resolution,realization) # default location of resolution file if rescovfile is None: rescovfile = self._infile+'.Resolution.bin' outfile=rescovfile+'.npz' if (not os.path.isfile(outfile)): if (not os.path.isfile(rescovfile)): raise IOError("Filename ("+rescovfile+") does not exist") # Read the binary covariance matrix, check for metadata refmodel2, kerstr2, ntot, indexrad1, indexrad2, indexhor1, indexhor2, out = \ readResCov(rescovfile,onlymetadata=True) # Checks if refmodel1 != refmodel2: warnings.warn(" refmodel in model3d instance : "+refmodel1+" not equal to the one in the binary file: "+refmodel2) if kerstr1 != kerstr2: raise ValueError("kerstr: "+kerstr1+" not equal to the one in the binary file: "+kerstr2) #if ntot != modelarr.size: raise ValueError("Number of variables: ",str(ntot)," not equal to ",str(modelarr.size)) # Read the binary covariance matrix, check for metadata refmodel2, kerstr2, ntot, indexrad1, indexrad2, indexhor1, indexhor2, out = \ readResCov(rescovfile,onlymetadata=False) # Create the sparse matrix ncoefcum = self[resolution]['ncoefcum'] # cumulative number of coeffients for each radial kernel row=np.zeros(len(indexrad2),dtype=int);col=np.zeros(len(indexrad2),dtype=int) for ii in np.arange(len(indexrad2)): row[ii] = ncoefcum[indexrad2[ii]-2]+(indexhor2[ii]-1) if indexrad2[ii] > 1 else indexhor2[ii]-1 col[ii] = ncoefcum[indexrad1[ii]-2]+(indexhor1[ii]-1) if indexrad1[ii] > 1 else indexhor1[ii]-1 outsparse = sparse.csr_matrix((out, (row, col)), shape=(modelarr.shape[0], modelarr.shape[0])) sparse.save_npz(outfile,outsparse) print(".... written "+outfile) else: print(".... reading "+outfile) outsparse=sparse.load_npz(outfile) # Get the full symmetric matrix if LU2symmetric: outsparse=getLU2symmetric(outsparse) return outsparse,modelarr
[docs] def printsplinefiles(self): """ Prints out the splines knots into a file. Parameters ----------- model3d : The model object from read3dmodelfile """ if self._name == None: raise ValueError("No three-dimensional model has been read into this model3d instance yet") for ii in np.arange(len(self.metadata)): ifindspline=np.where(self[ii]['typehpar']=='SPHERICAL SPLINES')[0] for ifind in ifindspline: filename=self[ii]['hsplfile'][ifind] arr = np.vstack([self[ii]['xlospl'][ifind],self[ii]['xlaspl'][ifind],self[ii]['xraspl'][ifind]]).transpose() np.savetxt(filename,arr, fmt='%7.3f %7.3f %7.3f') print(".... written "+filename) return
#######################################################################################