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

import os

import numpy as np
import pandas as pd

from logging import info

from pycif.utils.path import init_dir
from .....utils.check.errclass import CifError, CifIOError


[docs] def build_tcorrelations( period, subperiod, dates, sigma_t, sigma_type, evalmin=0.5, dump=False, dir_dump="", crop_chi=False, corr_plg=None, tracer=None, **kwargs ): """Build temporal correlation matrix based on timedelta between periods. For period i and j, the corresponding correlation is: c(i,j) = exp(-timedelta(i, j) / sigma) Args: period (int): period duration subperiod (int): sub-period duration dates (np.array): dates sub-dividing the control vector periods sigma_t (float): decay distance for correlation between periods (in days) 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 Return: tuple with: - square roots of eigenvalues - eigenvectors """ # Check ambiguous units if sigma_t[-1].lower() == "m": raise CifError( "WARNING! Your `sigma_t` unit end by 'm' which is an ambiguous unit. \n" "if you mean a temporal correlation with monthly unit, please convert it " "into days unit. If you want minute units, please state the full `min` unit." ) # Try reading existing file try: evalues, evectors = read_tcorr(period, subperiod, dates, sigma_t, sigma_type, dir_dump) # Else build correlations except IOError: info("Computing temporal correlations") temp_distance = np.abs( pd.DatetimeIndex(dates).values[:, np.newaxis] - pd.DatetimeIndex(dates).values[np.newaxis, :] ) corr = np.eye(dates.size) if sigma_type == "isotrope": # Compute matrix of distance dt = temp_distance / pd.to_timedelta(sigma_t) # Compute the correlation matrix itself corr = np.exp(-dt) elif sigma_type == "frequency": freq = pd.to_timedelta(corr_plg.freq).to_numpy() mask = np.mod(temp_distance, freq) == np.timedelta64(0) corr[mask] = np.exp( -np.abs(temp_distance[mask] / pd.to_timedelta(sigma_t))) elif sigma_type == "category": scale = corr_plg.scale if scale == "hourofday": category = pd.DatetimeIndex(dates).hour.values ntimes = 24 dist_scale = np.timedelta64(1, "h") elif scale == "dayofweek": category = pd.DatetimeIndex(dates).dayofweek.values ntimes = 7 dist_scale = np.timedelta64(1, "D") elif scale == "monthofyear": category = pd.DatetimeIndex(dates).month.values ntimes = 12 dist_scale = np.timedelta64(30, "D") else: raise CifError( f"Scale for 'category' temporal correlations unknown:{scale}") # Compute and loop distance dist = category[:, np.newaxis] - category[np.newaxis, :] dist = (dist + ntimes / 2) % ntimes - ntimes / 2 dist = dist.astype(int) * dist_scale corr = np.exp(-np.abs(dist / pd.to_timedelta(sigma_t))) else: raise CifError( f"Type for temporal correlations is not recognized:{sigma_type}") # Component analysis 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 txt file if dump: dump_tcorr(period, subperiod, dates, sigma_t, sigma_type, 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_tcorr(period, subperiod, dates, sigma_t, sigma_type, evalues, evectors, dir_dump, overwrite=False): """Dumps eigenvalues and vectors to a bin file. The default file format is: f"{dir_dump}/tempcor_{datei.strftime('%Y%m%d%H%M')}_{datef.strftime('%Y%m%d%H%M')}_per{period}-{subperiod}_ct{sigma_t}_{sigma_type}.bin" Args: period (int): period duration subperiod (int): subperiod duration dates (np.array): dates sub-dividing the control vector periods sigma_t (float): decay distance for correlation between periods (in days) """ datei = dates[0] datef = dates[-1] file_dump = f"{dir_dump}/tempcor_{datei.strftime('%Y%m%d%H%M')}_{datef.strftime('%Y%m%d%H%M')}_per{period}-{subperiod}_ct{sigma_t}_{sigma_type}.bin" if os.path.isfile(file_dump) and overwrite: raise CifIOError( f"Warning: {file_dump} already exists. I don't want to overwrite it" ) # Initiate path if needed if not os.path.isdir(dir_dump): init_dir(dir_dump) datasave = np.concatenate((evalues[np.newaxis, :], evectors), axis=0) datasave.tofile(file_dump)
[docs] def read_tcorr(period, subperiod, dates, sigma_t, sigma_type, dir_dump): """Reads temporal correlations from existing bin file Args: period (int): period duration subperiod (int): subperiod duration dates (np.array): dates sub-dividing the control vector periods sigma_t (float): decay distance for correlation between periods (in days) dir_dump (str): where the horizontal correlations have been stored """ datei = dates[0] datef = dates[-1] file_dump = f"{dir_dump}/tempcor_{datei.strftime('%Y%m%d%H%M')}_{datef.strftime('%Y%m%d%H%M')}_per{period}-{subperiod}_ct{sigma_t}_{sigma_type}.bin" if not os.path.isfile(file_dump): raise CifIOError( f"{file_dump} does not exist. Please compute correlations from scratch" ) data = np.fromfile(file_dump).reshape((-1, len(dates))) evalues = data[0] evectors = data[1:] evalues[evalues < 0] = 0.0 return evalues, evectors