Source code for pycif.plugins.controlvects.standard.utils.build_hcorr

import os
from logging import debug, info

import numpy as np
import scipy.sparse as sparse

from .....utils.geometry.dist_matrix import dist_matrix
from .....utils.path import init_dir
from .....utils.check.errclass import CifIOError


[docs] def build_hcorrelations( hresol, hresoldim, zlat, zlon, lsm, landseamask, sigma_land, sigma_sea, evalmin=0., dump=False, dir_dump="", projection="gps", is_lbc=False, crop_chi=False, tracer=None, glob_err=None, use_sparse=False, sparse_crop_threshold=0.1, **kwargs ): """Build horizontal correlation matrix based on distance between grid cells. For cells i and j, the corresponding correlation is: c(i,j) = exp(-dist(i, j) / sigma) sigma depends on the land-sea mask: land and sea cells are assumed un-correlated Args: zlat (np.array): 2D array of latitudes zlon (np.array): 2D array of longitudes file_lsm (str): path to NetCDF file with land-sea mask (grid must be consistent with LMDZ grid); the land-sea mask is assumed to be stored in the varible 'lsm' sigma_land (float): decay distance for correlation between land cells sigma_sea (float): idem for sea evalmin (float): flag out all eigenvalues below this value. Default is 0.5 dump (bool): dumps computed correlations if True dir_dump (str): directory where correlation matrices are stored projection (str): the projection used for the longitudes and latitudes is_lbc (bool): True if boundary of a 2D domain use_sparse (bool): True if convert correlation matrix to a sparse matrix sparse_crop_threshold (float): Threshold on correlation to exclude values from the sparse array Return: tuple with: - square roots of eigenvalues - eigenvectors """ # Define domain dimensions nlat, nlon = zlat.shape hresoldim = tracer.hresoldim # Try reading existing file try: evalues, evectors = read_hcorr( hresol, hresoldim, nlon, nlat, sigma_sea, sigma_land, dir_dump, is_lbc ) # Else build correlations except IOError as e: # Dumping exception causing to recompute debug(e.__str__()) info(f"Computing hcorr for {nlat}/{nlon} domain") # No correlation between land and sea if lsm = True if lsm: land_grid = landseamask.flatten() >= 0.5 sea_grid = landseamask.flatten() < 0.5 sigma = ( sigma_land * land_grid[:, np.newaxis] * land_grid[np.newaxis, :] + sigma_sea * sea_grid[:, np.newaxis] * sea_grid[np.newaxis, :] ) # Unmask arrays if np.ma.isMaskedArray(sigma): sigma = sigma.data # Otherwise, isotropic correlation # Takes sigma_land else: sigma = sigma_land # Compute matrix of distance dx = dist_matrix(zlat, zlon, projection) # Compute the correlation matrix itself corr = np.exp( - np.divide( dx, sigma, out=np.full_like(dx, np.inf), where=sigma != 0) ).astype(np.float64) # Component analysis debug("Computing the eigen decomposition of the correlation matrix") if use_sparse: # Use sparse matrix mask_corr = np.abs(corr) > 0.1 masked_corr = np.ma.masked_array(data=corr, mask=mask_corr) sparse_corr = sparse.csgraph.csgraph_from_masked(masked_corr) debug(f"Using sparse matrix of size {mask_corr.sum()}") evalues, evectors = sparse.linalg.eigsh( sparse_corr, k=zlon.size - 2) # Fill missing eigen values evalues = np.concatenate([evalues, np.zeros(2)]) evectors = np.concatenate( [evectors, np.zeros((zlon.size, 2))], axis=1) else: evalues, evectors = np.linalg.eigh(corr) # Re-ordering values # (not necessary in principle in recent numpy versions) index = np.argsort(evalues)[::-1] evalues = evalues[index] evectors = evectors[:, index] # Dumping to a binary file if dump: dump_hcorr( hresol, hresoldim, nlon, nlat, sigma_sea, sigma_land, evalues, evectors, f"{tracer.workdir}/controlvect/correlations/" ) except Exception as e: raise e # Truncating values < evalmin mask = evalues >= evalmin if crop_chi: return evalues[mask] ** 0.5, evectors[:, mask] else: evalues[~mask] = 0 return evalues ** 0.5, evectors
[docs] def dump_hcorr(hresol, hresoldim, nlon, nlat, sigma_sea, sigma_land, evalues, evectors, dir_dump, is_lbc=False, overwrite=False): """Dumps eigenvalues and vectors to a binary file. The default file format is: f"{dir_dump}/horcor_{hresol}_{hresoldim}_{nlon}x{nlat}_cs{sigma_sea}_cl{sigma_land}{'_lbc' if is_lbc else ''}.bin" """ ncell = evalues.size file_dump = f"{dir_dump}/horcor_{hresol}_{hresoldim}_{nlon}x{nlat}_cs{sigma_sea}_cl{sigma_land}{'_lbc' if is_lbc else ''}.bin" if os.path.isfile(file_dump) and overwrite: raise CifIOError( f"Warning: {file_dump} already exists. I don't want to overwrite it" ) datasave = np.concatenate((evalues[np.newaxis, :], evectors), axis=0) # Creating path if does not exist if not os.path.isdir(os.path.dirname(file_dump)): init_dir(os.path.dirname(file_dump)) # Saving data np.array(datasave).tofile(file_dump)
[docs] def read_hcorr(hresol, hresoldim, nlon, nlat, sigma_sea, sigma_land, dir_dump, is_lbc=False): """Reads horizontal correlations from existing text file Args: nlon, nlat (ints): dimensions of the domain sigma_land, sigma_sea (floats): horizontal correlation distances dir_dump (str): where the horizontal correlations have been stored """ file_dump = f"{dir_dump}/horcor_{hresol}_{hresoldim}_{nlon}x{nlat}_cs{sigma_sea}_cl{sigma_land}{'_lbc' if is_lbc else ''}.bin" if not os.path.isfile(file_dump): raise CifIOError( f"{file_dump} does not exist. Please compute correlations from scratch" ) debug(f"Reading horizontal correlations from {file_dump}") data = np.fromfile(file_dump).reshape((-1, nlon * nlat)) evalues = data[0] evectors = data[1:] evalues[evalues < 0] = 0.0 return evalues, evectors