#!/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
#######################################################################################