Source code for pyunicorn.timeseries.inter_system_recurrence_network

# This file is part of pyunicorn.
# Copyright (C) 2008--2024 Jonathan F. Donges and pyunicorn authors
# URL: <https://www.pik-potsdam.de/members/donges/software-2/software>
# License: BSD (3-clause)
#
# Please acknowledge and cite the use of this software and its authors
# when results are used in publications or published elsewhere.
#
# You can use the following reference:
# J.F. Donges, J. Heitzig, B. Beronov, M. Wiedermann, J. Runge, Q.-Y. Feng,
# L. Tupikina, V. Stolbova, R.V. Donner, N. Marwan, H.A. Dijkstra,
# and J. Kurths, "Unified functional network and nonlinear time series analysis
# for complex systems science: The pyunicorn package"

"""
Provides classes for the analysis of dynamical systems and time series based
on recurrence plots, including measures of recurrence quantification
analysis (RQA) and recurrence network analysis.
"""

# array object and fast numerics
import numpy as np

from ..core import InteractingNetworks
from .recurrence_plot import RecurrencePlot
from .cross_recurrence_plot import CrossRecurrencePlot


#
#  Class definitions
#

[docs] class InterSystemRecurrenceNetwork(InteractingNetworks): """ Generating and quantitatively analyzing inter-system recurrence networks. For a inter-system recurrence network, time series x and y do not need to have the same length! Formally, nodes are identified with state vectors in the common phase space of both time series. Hence, the time series need to have the same number of dimensions and identical physical units. Undirected links are added to describe recurrences within x and y as well as cross-recurrences between x and y. Self-loops are excluded in this undirected network representation. More information on the theory and applications of inter system recurrence networks can be found in [Feldhoff2012]_. **Examples:** - Create an instance of InterSystemRecurrenceNetwork with fixed recurrence thresholds and without embedding:: InterSystemRecurrenceNetwork(x, y, threshold=(0.1, 0.2, 0.1)) - Create an instance of InterSystemRecurrenceNetwork at a fixed recurrence rate and using time delay embedding:: InterSystemRecurrenceNetwork( x, y, dim=3, tau=(2, 1), recurrence_rate=(0.05, 0.05, 0.02)) """ # # Internal methods #
[docs] def __init__(self, x, y, metric="supremum", normalize=False, silence_level=0, **kwds): """ Initialize an instance of InterSystemRecurrenceNetwork (ISRN). .. note:: For an inter system recurrence network, time series x and y need to have the same number of dimensions! Creates an embedding of the given time series x and y, calculates a inter system recurrence matrix from the embedding and then creates an InteractingNetwork object from this matrix, interpreting the inter system recurrence matrix as the adjacency matrix of an undirected complex network. Either recurrence thresholds ``threshold`` or recurrence rates ``recurrence_rate`` have to be given as keyword arguments. Embedding is only supported for scalar time series. If embedding dimension ``dim`` and delay ``tau`` are **both** given as keyword arguments, embedding is applied. Multidimensional time series are processed as is by default. :type x: 2D Numpy array (time, dimension) :arg x: The time series x to be analyzed, can be scalar or multi-dimensional. :type y: 2D Numpy array (time, dimension) :arg y: The time series y to be analyzed, can be scalar or multi-dimensional. :type metric: tuple of string :arg metric: The metric for measuring distances in phase space ("manhattan", "euclidean", "supremum"). :arg bool normalize: Decide whether to normalize the time series to zero mean and unit standard deviation. :arg int silence_level: The inverse level of verbosity of the object. :arg kwds: Additional options. :type threshold: tuple of number (three numbers) :keyword threshold: The recurrence threshold keyword for generating the recurrence plot using fixed thresholds. Give for each time series and the cross recurrence plot separately. :type recurrence_rate: tuple of number (three numbers) :keyword recurrence_rate: The recurrence rate keyword for generating the recurrence plot using a fixed recurrence rate. Give separately for each time series. :keyword int dim: The embedding dimension. Must be the same for both time series. :type tau: tuple of int :keyword tau: The embedding delay. Give separately for each time series. """ # Store time series self.x = x.copy().astype("float32") """The time series x.""" self.y = y.copy().astype("float32") """The time series y.""" # Reshape time series self.x.shape = (self.x.shape[0], -1) self.y.shape = (self.y.shape[0], -1) # Get embedding dimension and delay from **kwds dim = kwds.get("dim") tau = kwds.get("tau") # Check for consistency if self.x.shape[1] == self.y.shape[1]: # Set silence_level self.silence_level = silence_level """The inverse level of verbosity of the object.""" # Get number of nodes in subnetwork x self.N_x = self.x.shape[0] """Number of nodes in subnetwork x.""" # Get number of nodes in subnetwork y self.N_y = self.y.shape[0] """Number of nodes in subnetwork y.""" # Get total number of nodes of ISRN self.N = self.N_x + self.N_y """Total number of nodes of ISRN.""" # Store type of metric self.metric = metric """The metric used for measuring distances in phase space.""" # Normalize time series if normalize: RecurrencePlot.normalize_time_series(self.x) RecurrencePlot.normalize_time_series(self.y) # Embed time series if required self.dim = dim if dim is not None and tau is not None and self.x.shape[1] == 1: self.x_embedded = \ RecurrencePlot.embed_time_series(self.x, dim, tau[0]) """The embedded time series x.""" self.y_embedded = \ RecurrencePlot.embed_time_series(self.y, dim, tau[1]) """The embedded time series y.""" else: self.x_embedded = self.x self.y_embedded = self.y # Get threshold or recurrence rate from **kwds, construct # ISRN accordingly threshold = kwds.get("threshold") recurrence_rate = kwds.get("recurrence_rate") self.threshold = threshold if threshold is not None: # Calculate the ISRN using the radius of neighborhood # threshold ISRM = self.set_fixed_threshold(threshold) elif recurrence_rate is not None: # Calculate the ISRN using a fixed recurrence rate ISRM = self.set_fixed_recurrence_rate(recurrence_rate) else: raise NameError("Please give either threshold or \ recurrence_rate to construct the joint \ recurrence plot!") InteractingNetworks.__init__(self, adjacency=ISRM, directed=False, silence_level=self.silence_level) # No treatment of missing values yet! self.missing_values = False else: raise ValueError("Both time series x and y need to have the same \ dimension!")
[docs] def __str__(self): """ Returns a string representation. """ return ("InterSystemRecurrenceNetwork: " f"time series shapes {self.x.shape}, {self.y.shape}.\n" f"Embedding dimension {self.dim if self.dim else 0}\n" f"Threshold {self.threshold}, {self.metric} metric.\n" f"{InteractingNetworks.__str__(self)}")
# # Service methods #
[docs] def clear_cache(self): """ Clean up memory by deleting information that can be recalculated from basic data. Extends the clean up methods of the parent classes. """ # Call clean up of RecurrencePlot objects self.rp_x.clear_cache() self.rp_y.clear_cache() # Call clean up of CrossRecurrencePlot object self.crp_xy.clear_cache() # Call clean up of InteractingNetworks InteractingNetworks.clear_cache(self)
# # Methods to handle inter system recurrence networks #
[docs] def inter_system_recurrence_matrix(self): """ Return the current inter system recurrence matrix :math:`ISRM`. :rtype: 2D square Numpy array :return: the current inter system recurrence matrix :math:`ISRM`. """ # Shortcuts N = self.N N_x = self.N_x N_y = self.N_y # Init ISRM = np.zeros((N, N)) # Combine to inter system recurrence matrix ISRM[:N_x, :N_x] = self.rp_x.recurrence_matrix() ISRM[:N_x, N_x:N] = self.crp_xy.recurrence_matrix() ISRM[N_x:N, :N_x] = self.crp_xy.recurrence_matrix().transpose() ISRM[N_x:N, N_x:N] = self.rp_y.recurrence_matrix() return ISRM
[docs] def set_fixed_threshold(self, threshold): """ Create a inter system recurrence network at fixed thresholds. :type threshold: tuple of number (three numbers) :arg threshold: The three threshold parameters. Give for each time series and the cross recurrence plot separately. """ # Compute recurrence matrices of x and y self.rp_x = RecurrencePlot(time_series=self.x_embedded, threshold=threshold[0], metric=self.metric, silence_level=self.silence_level) self.rp_y = RecurrencePlot(time_series=self.y_embedded, threshold=threshold[1], metric=self.metric, silence_level=self.silence_level) # Compute cross-recurrence matrix of x and y self.crp_xy = CrossRecurrencePlot(x=self.x_embedded, y=self.y_embedded, threshold=threshold[2], metric=self.metric, silence_level=self.silence_level) # Get combined ISRM ISRM = self.inter_system_recurrence_matrix() # Set diagonal of ISRM to zero to avoid self-loops ISRM.flat[::self.N + 1] = 0 return ISRM
[docs] def set_fixed_recurrence_rate(self, density): """ Create a inter system recurrence network at fixed link densities ( recurrence rates). :type density: tuple of number (three numbers) :arg density: The three recurrence rate parameters. Give for each time series and the cross recurrence plot separately. """ # Compute recurrence matrices of x and y self.rp_x = RecurrencePlot(time_series=self.x_embedded, recurrence_rate=density[0], metric=self.metric, silence_level=self.silence_level) self.rp_y = RecurrencePlot(time_series=self.y_embedded, recurrence_rate=density[1], metric=self.metric, silence_level=self.silence_level) # Compute cross-recurrence matrix of x and y self.crp_xy = CrossRecurrencePlot(x=self.x_embedded, y=self.y_embedded, recurrence_rate=density[2], metric=self.metric, silence_level=self.silence_level) # Get combined ISRM ISRM = self.inter_system_recurrence_matrix() # Set diagonal of ISRM to zero to avoid self-loops ISRM.flat[::self.N + 1] = 0 return ISRM
# # Methods to quantify inter system recurrence networks #
[docs] def internal_recurrence_rates(self): """ Return internal recurrence rates of subnetworks x and y. :rtype: tuple of number (float) :return: the internal recurrence rates of subnetworks x and y. """ return (self.rp_x.recurrence_rate(), self.rp_y.recurrence_rate())
[docs] def cross_recurrence_rate(self): """ Return cross recurrence rate between subnetworks x and y. :rtype: number (float) :return: the cross recurrence rate between subnetworks x and y. """ return self.crp_xy.cross_recurrence_rate()
[docs] def cross_global_clustering_xy(self): """ Return cross global clustering of x with respect to y. See [Feldhoff2012]_ for definition, further explanation and applications. :rtype: number (float) :return: the cross global clustering of x with respect to y. """ return self.cross_global_clustering(np.arange(self.N_x), np.arange(self.N_x, self.N))
[docs] def cross_global_clustering_yx(self): """ Return cross global clustering of y with respect to x. See [Feldhoff2012]_ for definition, further explanation and applications. :rtype: number (float) :return: the cross global clustering of y with respect to x. """ return self.cross_global_clustering(np.arange(self.N_x, self.N), np.arange(self.N_x))
[docs] def cross_transitivity_xy(self): """ Return cross transitivity of x with respect to y. See [Feldhoff2012]_ for definition, further explanation and applications. :rtype: number (float) :return: the cross transitivity of x with respect to y. """ return self.cross_transitivity(np.arange(self.N_x), np.arange(self.N_x, self.N))
[docs] def cross_transitivity_yx(self): """ Return cross transitivity of y with respect to x. See [Feldhoff2012]_ for definition, further explanation and applications. :rtype: number (float) :return: the cross transitivity of y with respect to x. """ return self.cross_transitivity(np.arange(self.N_x, self.N), np.arange(self.N_x))