Skip to content

Analysis

Analyse systems, trajectories, and measured data by kind of data: series, paths, fields, agents, and networks.

AlignmentAnalysis(kind='mean-vector', variables=None, *, group=None, vectors=False)

Bases: BaseAnalysis

Alignment of the members of a source at every frame, of one kind.

The members are the agents of a group, the sites of a field, or the columns of a set of series, and each holds an angle or a vector. A trajectory gives the agents of a group with their directions or phases, velocities, and positions, else every frame of a field of angles, or of vectors with vectors=True, else the series of variables or the states with the role 'phase', one column per member and one row per sample.

  • 'mean-vector': the length of the mean unit vector, 1 when every member points the same way and about 1 / sqrt(n) for random directions. It reads vectors when the source has them, and angles otherwise.
  • 'mean-rotation': the mean normalized angular momentum of agents about their centre in a plane, 1 when every agent turns the same way and near 0 for a polarized or disordered group. It reads positions and velocities.
  • 'axial': |<exp(2 i theta)>| of the angles, which ignores the sign of a direction.
  • 'mean-phase': the modulus r and the argument psi of the mean r exp(i psi) = <exp(i theta)> of the angles, the synchrony and the mean phase.
  • 'principal-axis': the eigenvector of the largest eigenvalue of a symmetric tensor at every site, axes_, and the scalar order of the tensor at every site, order_. It reads a field of a symmetric tensor state, or an array of its independent components on the last axis, whose number gives the symmetry and the dimension: 2 and 5 for a symmetric traceless tensor in 2 and 3 dimensions, and 3 and 6 for a symmetric tensor.

Angles are derived from the first two components of vectors when a source has no angles, and a vector of one component points at the angle 0 or pi by its sign. An array of angles holds one snapshot when it has one dimension, and one frame per row of its first axis otherwise, every other axis indexing members. An array of vectors, with vectors=True, holds the components along its last axis, and one snapshot when it has two dimensions. An array for 'mean-rotation' holds the positions followed by the velocities of every agent along its last axis.

Parameters:

Name Type Description Default
kind str

The alignment to compute, one of the kinds above.

'mean-vector'
variables Sequence[str] | None

The field or the series of a trajectory to read, or None for its agents, or else its first field, its states with the role 'phase', or its scalar series.

None
group str | None

The path of the agent group of a trajectory, or None for the first one.

None
vectors bool

Whether the members hold vectors rather than angles.

False

Attributes:

Name Type Description
angles_ Array | None

The angles, frames by members, or None without them.

vectors_ Array | None

The vectors or velocities, frames by members by components, or None without them.

positions_ Array | None

The positions of agents, frames by agents by dimensions, or None without them.

order_ Array | None

The alignment of every frame, between 0 and 1, or the scalar order of every site of every frame.

tensors_ Array | None

The independent components of the tensors, with one frame per row of the first axis, for 'principal-axis'.

axes_ Array | None

The principal axis of every site of every frame, with the components on the last axis, for 'principal-axis'.

phase_ Array | None

The mean phase of every frame, for 'mean-phase'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import AlignmentAnalysis
>>> AlignmentAnalysis().check().prepare([0.0, 0.0, 0.0]).run().order_.tolist()
[1.0]
>>> AlignmentAnalysis('axial').check().prepare([0.0, np.pi]).run().order_.round(6).tolist()
[1.0]
>>> angles = np.linspace(0, 2 * np.pi, 8, endpoint=False)
>>> ring = np.column_stack([np.cos(angles), np.sin(angles)])
>>> mill = np.hstack([ring, np.column_stack([-ring[:, 1], ring[:, 0]])])
>>> AlignmentAnalysis('mean-rotation').check().prepare(mill).run().order_.round(6).tolist()
[1.0]
>>> phases = AlignmentAnalysis('mean-phase').check().prepare([[0.5, 0.5], [0.0, np.pi]]).run()
>>> phases.order_.round(6).tolist(), phases.phase_.round(6)[:1].tolist()
([1.0, 0.0], [0.5])
>>> tensors = AlignmentAnalysis('principal-axis').check().prepare([[0.5, 0.0], [0.0, 0.5]]).run()
>>> tensors.order_.tolist()
[[1.0, 1.0]]
Source code in src/skcomplex/analysis/_alignment.py
126
127
128
129
130
131
132
133
134
135
136
137
def __init__(
    self,
    kind: str = 'mean-vector',
    variables: Sequence[str] | None = None,
    *,
    group: str | None = None,
    vectors: bool = False,
) -> None:
    self.kind = kind
    self.variables = variables
    self.group = group
    self.vectors = vectors

AutomatonAnalysis(kind='injective', *, method='auto', window=None, infinite=False, tries=200, random_state=None, max_radius=2, second=None, configuration=None, site=0, steps=1, windows=None, base=2.0)

Bases: BaseAnalysis

Decisions and measures of a cellular automaton on a finite universe, of a linear automaton, or of a subshift.

The kinds read the automaton, the linear automaton, or the subshift a user builds, such as Automaton, LinearAutomaton, and Subshift of the automata models. The analysis tells them apart by their methods: a linear automaton has laurent_polynomial, a subshift has violations, and an automaton with a rule has local_map.

  • 'injective', 'surjective', and 'pre-injective': whether two different configurations never share their image, whether every configuration has a preimage, which on a finite universe holds exactly when it is injective, and whether two configurations that differ only inside a window never share their image. Small universes are decided by enumerating every configuration, and larger ones by a SAT encoding solved by PySAT. A linear automaton is decided from its matrix, all three holding exactly when the matrix is invertible, or on Z^d from its Laurent polynomial, injective when it is a monomial and surjective and pre-injective when it is not zero.
  • 'garden-of-eden': a pattern on a window that no configuration maps to. Small universes enumerate the images of every configuration, and larger ones draw random patterns and keep the first one whose preimage constraints a SAT solver finds unsatisfiable.
  • 'inverse': an automaton whose composition with a reversible automaton is the identity, with the smallest memory. The search tries the neighbourhoods of every radius up to max_radius, a box of offsets on a lattice or the ball of group elements on a group space, and keeps the first on which the image determines every cell.
  • 'compose': the automaton that applies the automaton and then second on the same space, whose neighbourhood holds the composite t s of every element t read by the second and s read by the first.
  • 'difference': where two runs differ when one starts from configuration and the other from it with the cell at site changed to its next value modulo the number of states.
  • 'entropy': the entropy log |X_F| / |F| of the configurations of a subshift, or of the image of an automaton whose rule and neighbourhood are applied on every window. X_F is the set of admissible configurations of a window F, or the set of images on the sites of F whose cells lie inside it. Along a Følner sequence of windows the values approach the topological entropy.

Parameters:

Name Type Description Default
kind str

The decision or measure, one of the kinds above.

'injective'
method str

'enumeration', 'sat', or 'auto' to enumerate up to MAX_CONFIGURATIONS configurations.

'auto'
window Sequence[int] | None

The sites where configurations may differ, or of the Garden of Eden pattern, or None for every site.

None
infinite bool

Whether to decide a linear automaton on Z^d from its Laurent polynomial instead of on its space.

False
tries int

The number of random patterns tried by SAT.

200
random_state int | Generator | None

The seed or generator of the random patterns.

None
max_radius int

The largest radius of the memory of the inverse.

2
second AutomatonProtocol | None

The automaton applied after the analysed one.

None
configuration ArrayLike | None

The initial values of the sites of the difference pattern.

None
site int

The site of the changed cell.

0
steps int

The number of steps of the difference pattern.

1
windows Sequence[BaseSpace] | None

The windows of the entropy, such as lattices with fixed edges of growing size.

None
base float

The base of the logarithm.

2.0

Attributes:

Name Type Description
automaton_

The automaton, the linear automaton, or the subshift.

injective_

Whether the automaton is injective.

surjective_

Whether the automaton is surjective.

pre_injective_

Whether no two configurations that differ only inside the window share their image.

pattern_

A configuration whose values on the window have no preimage, or None when none is found.

inverse_

The inverse automaton with a table rule, or None when the automaton is not injective or no memory up to the radius determines the cells.

composition_

The automaton of the composition, with a function rule.

differences_

One row per step, the initial one first, true where the runs differ.

entropy_

One entropy per window.

Raises:

Type Description
AnalysisError

The model is not one the kind reads, the method is unknown, enumeration is asked for too many configurations, or composed automata differ in space or number of states.

Examples:

>>> from skcomplex.analysis import AutomatonAnalysis
>>> from skcomplex.specs import TableSpec, TotalisticSpec
>>> from skcomplex.core.spaces import BoxNeighborhood, LatticeNeighborhood, LatticeSpace
>>> from skcomplex.models.computer_science.automata import Automaton, Subshift
>>> shift = Automaton(LatticeSpace(8), LatticeNeighborhood(((1,),)), TableSpec({(1,): 1}))
>>> AutomatonAnalysis().check().prepare(shift).run().injective_
True
>>> majority = Automaton(LatticeSpace(5), BoxNeighborhood(), TotalisticSpec(lambda total: total >= 2))
>>> AutomatonAnalysis('garden-of-eden').check().prepare(majority).run().pattern_.tolist()
[0, 0, 1, 0, 1]
>>> golden = Subshift(LatticeNeighborhood(((0,), (1,))), [(1, 1)])
>>> analysis = AutomatonAnalysis('entropy', windows=[LatticeSpace(20, boundary='fixed')])
>>> analysis.check().prepare(golden).run().entropy_.round(3).tolist()
[0.706]
Source code in src/skcomplex/analysis/_automata.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def __init__(
    self,
    kind: str = 'injective',
    *,
    method: str = 'auto',
    window: Sequence[int] | None = None,
    infinite: bool = False,
    tries: int = 200,
    random_state: int | np.random.Generator | None = None,
    max_radius: int = 2,
    second: AutomatonProtocol | None = None,
    configuration: npt.ArrayLike | None = None,
    site: int = 0,
    steps: int = 1,
    windows: Sequence[BaseSpace] | None = None,
    base: float = 2.0,
) -> None:
    self.kind = kind
    self.method = method
    self.window = window
    self.infinite = infinite
    self.tries = tries
    self.random_state = random_state
    self.max_radius = max_radius
    self.second = second
    self.configuration = configuration
    self.site = site
    self.steps = steps
    self.windows = windows
    self.base = base

BaseAnalysis

Bases: BaseConfig[Source, object], ABC

Base of every analysis: it stores its settings, checks them, prepares on a source, and runs.

A child stores its settings in __init__ and implements four hooks that the verbs call:

  • _check() validates the settings.
  • _get_data_kind() returns the kind of data the analysis reads, such as 'series' or 'field'.
  • _prepare(source) reads the data from the source with the readers of the base and sets the attributes that end in _.
  • _run() computes the results and sets them as attributes that end in _.

The readers _read_series, _read_path, _read_fields, _read_snapshots, and _read_networks take a trajectory, and pass an array or a graph through.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import BaseAnalysis
>>> class MeanAnalysis(BaseAnalysis):
...     def __init__(self, variables=None):
...         self.variables = variables
...
...     def _get_data_kind(self):
...         return 'series'
...
...     def _prepare(self, source):
...         self.series_ = self._read_series(source, self.variables)
...
...     def _run(self, inputs):
...         self.mean_ = self.series_.values.mean(axis=0)
>>> MeanAnalysis().check().prepare(np.array([1.0, 2.0, 3.0])).run().mean_.tolist()
[2.0]

prepare(source=None)

Read the data of the checked analysis from a source.

Parameters:

Name Type Description Default
source Source

The trajectory, array, graph, or system the analysis measures.

None

Returns:

Name Type Description
analysis Self

The analysis itself.

Raises:

Type Description
NotCheckedError

The analysis was not checked with check.

Source code in src/skcomplex/analysis/_base.py
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
def prepare(self, source: Source = None) -> Self:
    """Read the data of the checked analysis from a source.

    Args:
        source:
            The trajectory, array, graph, or system the analysis measures.

    Returns:
        analysis:
            The analysis itself.

    Raises:
        NotCheckedError:
            The analysis was not checked with `check`.
    """
    super().prepare(source)
    return self

run(inputs=None, final=False)

Compute the results of the prepared analysis from the data it read.

Parameters:

Name Type Description Default
inputs object

Nothing: an analysis computes from the source it was prepared on.

None
final bool

Whether this is the last call.

False

Returns:

Name Type Description
analysis Self

The analysis itself, with its results in the attributes that end in _.

Raises:

Type Description
NotPreparedError

The analysis was not prepared with a source.

Source code in src/skcomplex/analysis/_base.py
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
def run(self, inputs: object = None, final: bool = False) -> Self:
    """Compute the results of the prepared analysis from the data it read.

    Args:
        inputs:
            Nothing: an analysis computes from the source it was prepared on.

        final:
            Whether this is the last call.

    Returns:
        analysis:
            The analysis itself, with its results in the attributes that end in `_`.

    Raises:
        NotPreparedError:
            The analysis was not prepared with a source.
    """
    super().run(inputs, final)
    return self

ClusterAnalysis(kind='labels', variables=None, *, periodic=False, diagonal=False, axis=0, radius=None)

Bases: BaseAnalysis

Clusters of the occupied cells of a grid, the components of a graph, or the contacts of points, of one kind.

The grid is a boolean array of any dimension, the last frame of a field of a trajectory, or a graph, the last network of a trajectory. Cells are connected through shared faces, or also corners with diagonal=True.

With a radius, the source holds points instead, an array with one row per point, a stack of such arrays for the kind 'spanning', or the positions of a group of a trajectory in its last frame. Points closer than twice the radius are connected, and a cluster joins two opposite faces along axis when it holds points within the radius of both. The faces are those of the space of a trajectory, and those of the bounding box of an array.

  • 'labels': the label of every cell and the size of every cluster of a grid, in label order, and whether a cluster joins the two opposite edges of axis.
  • 'sizes': the size of every cluster of a grid or of every component of a graph, largest first.
  • 'percolation': the cluster sizes, the strength of the largest cluster, the mean cluster size, and spanning.
  • 'spanning': the share of the grids of an ensemble, stacked on the first axis or the frames of a trajectory, with a cluster joining the opposite edges of axis.

Parameters:

Name Type Description Default
kind str

The measure to compute, one of the kinds above.

'labels'
variables Sequence[str] | None

The field of a trajectory to read, or None for its first field.

None
periodic bool

Whether clusters of a grid wrap around its edges.

False
diagonal bool

Whether cells that share only a corner are connected.

False
axis int

The axis whose two opposite edges a spanning cluster joins.

0
radius float | None

The contact radius of points, or None for grids and graphs.

None

Attributes:

Name Type Description
points_

The arrays of points, one per entry of the first axis, or None for grids and graphs.

grids_

The grids, one per entry of the first axis, or None for a graph.

graph_

The graph, or None for grids.

labels_

The label of every cell, 0 for empty cells and 1 onwards for clusters.

sizes_

The size of every cluster, in label order for 'labels' and largest first otherwise.

strength_

The share of all sites or nodes in the largest cluster.

mean_size_

The mean size of the cluster of an occupied site outside the largest, Σ' s² / Σ' s.

spanning_

Whether a cluster of the grid joins the two opposite edges of axis, always false for graphs.

probability_

The spanning probability of the ensemble.

Examples:

>>> import networkx as nx
>>> from skcomplex.analysis import ClusterAnalysis
>>> ClusterAnalysis('sizes').check().prepare([[1, 1, 0], [0, 0, 1]]).run().sizes_.tolist()
[2, 1]
>>> ClusterAnalysis('sizes').check().prepare(nx.path_graph(3)).run().sizes_.tolist()
[3]
>>> result = ClusterAnalysis('percolation').check().prepare([[1, 1, 0], [1, 0, 1]]).run()
>>> result.strength_, result.mean_size_, result.spanning_
(0.5, 1.0, True)
>>> ClusterAnalysis('sizes', radius=0.6).check().prepare([[0, 0], [1, 0], [5, 5]]).run().sizes_.tolist()
[2, 1]
Source code in src/skcomplex/analysis/_clusters.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def __init__(
    self,
    kind: str = 'labels',
    variables: Sequence[str] | None = None,
    *,
    periodic: bool = False,
    diagonal: bool = False,
    axis: int = 0,
    radius: float | None = None,
) -> None:
    self.kind = kind
    self.variables = variables
    self.periodic = periodic
    self.diagonal = diagonal
    self.axis = axis
    self.radius = radius

CoarseGrainAnalysis(shape=(32, 32), *, box=None, values=None, over=None, state=None)

Bases: BaseAnalysis

Agents coarse-grained into a field on a grid, or members into a density over the values of a state.

An array holds one snapshot, one row per agent in the box. A trajectory gives the positions of its first agent or particle group at every frame, in the box of its space unless box is set, and one grid per frame.

With over, the analysis coarse-grains in state space instead: every member counts at the given value nearest to its value of a state, and the field is the fraction of members at every value, which sums to 1. An array holds the values of one snapshot, one per member, or one row per frame, and a trajectory gives the values of the state state at every frame. The densities are the initial values of a distribution site group with one member per value.

Parameters:

Name Type Description Default
shape Sequence[int]

The number of cells along every side.

(32, 32)
box Sequence[float] | None

The sides of the box, or None for the box of the space of a trajectory.

None
values ArrayLike | None

One value or one row of values per agent of an array, such as unit directions for a polarization field, or None to count agents.

None
over ArrayLike | None

The increasing values of a state to coarse-grain the members over, or None to coarse-grain positions.

None
state str | None

The dotted name of the state whose values a trajectory gives, such as traders.wealth, with over.

None

Attributes:

Name Type Description
positions_

The positions, one row per agent, with one leading axis of frames for a trajectory, or the values of the state of every member with over.

box_

The sides of the box, or None with over.

field_

The grid of counts, or of sums with the value components last, with one grid per frame for a trajectory, ready for field analyses, or the fractions of members at every value of over, one row per frame.

Examples:

>>> from skcomplex.analysis import CoarseGrainAnalysis
>>> positions = [[0.1, 0.1], [0.2, 0.1], [0.9, 0.9]]
>>> CoarseGrainAnalysis([2, 2], box=[1.0, 1.0]).check().prepare(positions).run().field_.tolist()
[[2.0, 0.0], [0.0, 1.0]]
>>> wealth = [-1.0, 0.4, 0.6, 1.5, 9.0]
>>> CoarseGrainAnalysis(over=[0.0, 1.0, 2.0]).check().prepare(wealth).run().field_.tolist()
[0.4, 0.4, 0.2]
Source code in src/skcomplex/analysis/_coarse_grain.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    shape: Sequence[int] = (32, 32),
    *,
    box: Sequence[float] | None = None,
    values: npt.ArrayLike | None = None,
    over: npt.ArrayLike | None = None,
    state: str | None = None,
) -> None:
    self.shape = shape
    self.box = box
    self.values = values
    self.over = over
    self.state = state

CollapseAnalysis(kind='scaling', *, parameters=None, critical=None, nu=None, exponent=0.0, fit=(), waiting=None, lags=None, ratios=None)

Bases: BaseAnalysis

Collapse of curves or distributions of several sizes or ages onto one, of one kind.

The kinds read the curves or the samples of every size or age:

  • 'scaling': the finite-size scaling collapse of curves y_L(p) = L^a f((p - p_c) L^(1/nu)) of every size L, against the control parameters, with the names of fit among 'critical', 'nu', and 'exponent' fitted by minimizing the quality.
  • 'aging': how far correlation curves of several waiting times stay apart against the lag and against tau / t_w, from one curve per waiting time, as the two-time correlation of CorrelationAnalysis gives them.
  • 'distribution': the densities t P(tau, t) of samples of tau at several ages t against tau / t, which collapse under aging.
  • 'crossings': where the curves of consecutive sizes cross against the control parameters, by linear interpolation of their difference, such as the Binder cumulants of several lattice sizes at the critical point.

Parameters:

Name Type Description Default
kind str

The collapse to compute, one of the kinds above.

'scaling'
parameters Sequence[float] | None

The control parameter values, shared by every curve of 'scaling' and 'crossings'.

None
critical float | None

The critical value, or the starting value of a fit.

None
nu float | None

The correlation length exponent, or the starting value of a fit.

None
exponent float

The exponent a of the scaled curves y L^-a, such as gamma / nu for a susceptibility, or the starting value of a fit.

0.0
fit Sequence[str]

The names among 'critical', 'nu', and 'exponent' to fit.

()
waiting Sequence[float] | None

The waiting times of the curves of 'aging'.

None
lags Sequence[float] | None

The lags shared by every curve of 'aging'.

None
ratios Sequence[float] | None

The edges of the bins of tau / t, or None for 20 logarithmic bins from 0.01 to 10.

None

Attributes:

Name Type Description
values_

The curve of every size, the curves of every waiting time, or the samples of every age.

labels_

The size, waiting time, or age of every curve.

critical_

The critical value used or fitted.

nu_

The correlation length exponent used or fitted.

exponent_

The exponent a used or fitted.

quality_

The mean squared relative distance between the scaled curves, 0 for a perfect collapse.

time_spread_

The largest difference between the curves against the lag, 0 without aging.

ratio_spread_

The largest difference between the curves against tau / t_w, 0 under simple aging.

ratios_

The centres of the bins of tau / t.

densities_

One row per age of t P(tau, t) on the bins.

spread_

The largest relative difference between the rows where every row is positive.

crossings_

The first crossing of the curves of every pair of consecutive sizes, nan where they do not cross.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import CollapseAnalysis
>>> grid = np.linspace(-1, 1, 401)
>>> curves = {size: np.tanh(grid * size) for size in (8, 16, 32)}
>>> collapse = CollapseAnalysis(parameters=grid, critical=0.2, nu=1.5, fit=('critical', 'nu'))
>>> round(collapse.check().prepare(curves).run().nu_, 1)
1.0
>>> lags = np.linspace(1, 100, 100)
>>> curves = [np.exp(-lags / tw) for tw in (10, 100)]
>>> aging = CollapseAnalysis('aging', waiting=[10, 100], lags=lags).check().prepare(curves).run()
>>> round(aging.ratio_spread_, 2), aging.time_spread_ > 0.5
(0.0, True)
>>> curves = {8: [0.6, 0.5, 0.2], 16: [0.65, 0.5, 0.1]}
>>> CollapseAnalysis('crossings', parameters=[1.0, 2.0, 3.0]).check().prepare(curves).run().crossings_.tolist()
[2.0]
Source code in src/skcomplex/analysis/_collapse.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def __init__(
    self,
    kind: str = 'scaling',
    *,
    parameters: Sequence[float] | None = None,
    critical: float | None = None,
    nu: float | None = None,
    exponent: float = 0.0,
    fit: Sequence[str] = (),
    waiting: Sequence[float] | None = None,
    lags: Sequence[float] | None = None,
    ratios: Sequence[float] | None = None,
) -> None:
    self.kind = kind
    self.parameters = parameters
    self.critical = critical
    self.nu = nu
    self.exponent = exponent
    self.fit = fit
    self.waiting = waiting
    self.lags = lags
    self.ratios = ratios

ComplexityAnalysis(variables=None, *, dimension=6, delay=1, ties='order', random_state=None)

Bases: BaseAnalysis

Place of every series of a source, or of every frame of a field, on Rosso's complexity-entropy plane.

The entropy is the normalized permutation entropy of the ordinal patterns, and the complexity is that entropy times the Jensen-Shannon divergence of the patterns from uniform ones, computed with ordpy. Noise has entropy near 1 and complexity near 0, regular series both near 0, and chaos lies in between with high complexity.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series, or the first field for a field.

None
dimension int | tuple[int, int]

The length of the patterns of a series, or the window (columns, rows) of the patterns of a field.

6
delay int

The number of steps between the values of a pattern.

1
ties str

'order' to rank equal values by position, or 'random' to rank them at random.

'order'
random_state int | None

The seed of the random ranks of equal values.

None

Attributes:

Name Type Description
values_

The series, one per column, or the frames of the field.

labels_

The label of every series, or of the field.

entropy_

The normalized permutation entropy of every series or frame.

complexity_

The statistical complexity of every series or frame.

minimum_

The curve of least complexity, one row of entropy and complexity per point.

maximum_

The curve of largest complexity, one row of entropy and complexity per point.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import ComplexityAnalysis
>>> noise = np.random.default_rng(0).standard_normal(50_000)
>>> plane = ComplexityAnalysis(dimension=4).check().prepare(noise).run()
>>> plane.entropy_.round(2).tolist(), plane.complexity_.round(2).tolist()
([1.0], [0.0])
Source code in src/skcomplex/analysis/_complexity.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    dimension: int | tuple[int, int] = 6,
    delay: int = 1,
    ties: str = 'order',
    random_state: int | None = None,
) -> None:
    self.variables = variables
    self.dimension = dimension
    self.delay = delay
    self.ties = ties
    self.random_state = random_state

CorrelationAnalysis(kind='auto', variables=None, *, max_lag=None, dt=1.0, waiting=None, lags=None, connected=False, max_distance=None, method='moment', spacing=1.0, frames=False)

Bases: BaseAnalysis

Correlation of every series of a source, of an ensemble at two times, or of a field in space, of one kind.

The kinds read series, ensembles, or fields:

  • 'auto': the autocorrelation of every series at lags 0 to max_lag, computed with the FFT, and its integrated autocorrelation time dt (1/2 + Σ r(k)), summed to the first negative correlation.
  • 'two-time': the two-time correlation C(t_w + tau, t_w) = <x(t_w) x(t_w + tau)> of an ensemble with one row per run and one column per frame, any further axes, such as sites, averaged over.
  • 'function': the radially averaged spatial correlation of a field on a periodic grid, 1 at distance 0.
  • 'length': the correlation length of a field. The 'moment' method fits the Ornstein-Zernike form 1 / S(q) ∝ 1 + xi² Σ 4 sin²(q_i / 2) over the modes with q̂² xi² <= 1, and the 'fit' method fits C(r) ~ r^-a exp(-r / xi). Several frames are taken as fluctuations about their common mean.
  • 'overlap': the overlap q = <s_i t_i> of two configurations s and t of the same sites, ±1 for identical or reversed configurations of ±1 values and near 0 for independent ones, per frame for frames. It reads a pair of trajectories, whose fields it compares at every frame, or two arrays stacked on the first axis.

The kinds 'function' and 'length' read the last frame of a trajectory, or every frame when frames is true.

Parameters:

Name Type Description Default
kind str

The correlation to compute, one of the kinds above.

'auto'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series, or the first field for a field.

None
max_lag int | None

The largest lag in steps, or None for half the series length.

None
dt float

The time between samples.

1.0
waiting Sequence[int] | None

The waiting times t_w in frames.

None
lags Sequence[int] | None

The lags tau in frames.

None
connected bool

Whether to subtract the product of the means at the two times.

False
max_distance int | None

The largest distance in cells, or None for half the smallest side.

None
method str

The method of the correlation length, 'moment' or 'fit'.

'moment'
spacing float

The distance between neighbouring sites.

1.0
frames bool

Whether the first axis of an array indexes frames, and whether to read every frame of a trajectory.

False

Attributes:

Name Type Description
values_

The series, one per column, the ensemble, the field, or the two configurations.

labels_

The label of every series, of the ensemble or field, or of the two configurations.

lags_

The lags in time of 'auto'.

correlation_

One autocorrelation per series for 'auto', one row per waiting time and one column per lag for 'two-time', and the correlation at every distance for 'function'.

time_

The integrated autocorrelation time of every series, for 'auto'.

distances_

The distances of 'function'.

length_

The correlation length, for 'length'.

overlap_

The overlap of the two configurations, one per frame for frames.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import CorrelationAnalysis
>>> analysis = CorrelationAnalysis(max_lag=1).check().prepare([1.0, -1.0, 1.0, -1.0]).run()
>>> analysis.correlation_.round(2).tolist()
[[1.0, -0.75]]
>>> two_time = CorrelationAnalysis('two-time', waiting=[2], lags=[0, 3])
>>> two_time.check().prepare(np.ones((4, 10))).run().correlation_.tolist()
[[1.0, 1.0]]
>>> CorrelationAnalysis('overlap').check().prepare([[1, -1, 1, 1], [1, -1, -1, 1]]).run().overlap_
0.5
Source code in src/skcomplex/analysis/_correlation.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def __init__(
    self,
    kind: str = 'auto',
    variables: Sequence[str] | None = None,
    *,
    max_lag: int | None = None,
    dt: float = 1.0,
    waiting: Sequence[int] | None = None,
    lags: Sequence[int] | None = None,
    connected: bool = False,
    max_distance: int | None = None,
    method: str = 'moment',
    spacing: float = 1.0,
    frames: bool = False,
) -> None:
    self.kind = kind
    self.variables = variables
    self.max_lag = max_lag
    self.dt = dt
    self.waiting = waiting
    self.lags = lags
    self.connected = connected
    self.max_distance = max_distance
    self.method = method
    self.spacing = spacing
    self.frames = frames

DefectAnalysis(period=np.pi, variables=None, *, periodic=True)

Bases: BaseAnalysis

Point defects of a two-dimensional field of angles, with their topological charges, at every frame.

The winding around a plaquette of four neighbouring sites is the sum of the differences of the angles along its edges, each wrapped to one period around 0, over 2 pi. A plaquette with a winding that is not 0 holds a defect of that charge. The charges of a periodic lattice sum to 0.

An array of two dimensions holds one snapshot, and an array of three dimensions one frame per row of its first axis.

Parameters:

Name Type Description Default
period float

The period of the angles, np.pi for axes without head and tail and 2 * np.pi for vectors.

pi
variables Sequence[str] | None

The field of a trajectory to read, or None for its first field.

None
periodic bool

Whether the lattice joins its opposite edges, or has plaquettes only between its sites.

True

Attributes:

Name Type Description
angles_

The angles, with one frame per row of the first axis.

positions_ list[Array]

The centres of the plaquettes with a defect at every frame, each an array of defects by dimensions.

charges_ list[Array]

The charges of the defects at every frame, each an array in the order of positions_.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import DefectAnalysis
>>> rows, columns = np.meshgrid(np.arange(8.0), np.arange(8.0), indexing='ij')
>>> vortex = np.arctan2(rows - 3.5, columns - 3.5)
>>> defects = DefectAnalysis(2 * np.pi, periodic=False).check().prepare(vortex).run()
>>> defects.positions_[0].tolist(), defects.charges_[0].tolist()
([[3.5, 3.5]], [1.0])
Source code in src/skcomplex/analysis/_defects.py
61
62
63
64
65
66
67
68
69
70
def __init__(
    self,
    period: float = np.pi,
    variables: Sequence[str] | None = None,
    *,
    periodic: bool = True,
) -> None:
    self.period = period
    self.variables = variables
    self.periodic = periodic

DiscoveryAnalysis

Bases: BaseAnalysis

Analyses that apply to a trajectory, a system, or an array, listed under the kind of data they read.

Preparing reads the kinds of data the source holds. Running builds every analysis of the library that takes no required setting, once per kind of a family, asks each for its data kind, and keeps those whose kind the source holds. A one-dimensional array is one series, and an array of more dimensions is one field and a set of series, one per column.

Attributes:

Name Type Description
kinds_

The data kinds of the source, in the order agents, field, network, path, series.

readings_

How the analyses of every data kind read the source, by data kind.

analyses_

The unprepared analyses that apply to the source, by data kind.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import DiscoveryAnalysis
>>> discovery = DiscoveryAnalysis().check().prepare(np.zeros((16, 16))).run()
>>> discovery.kinds_
['field', 'series']
>>> discovery.readings_['series']
'every column of the array as a series'
>>> any(type(item).__name__ == 'EntropyAnalysis' for item in discovery.analyses_['series'])
True

DisplacementAnalysis(group=None, *, dt=1.0, lags=None, fit_range=None)

Bases: BaseAnalysis

Mean squared displacement of particles over all lags, its scaling exponent, and the diffusion coefficient.

The displacement of every particle is computed for all lags at once with the FFT of tidynamics, so positions must be unwrapped across periodic boundaries, as a trajectory gives them.

Parameters:

Name Type Description Default
group str | None

The path of the agent group of a trajectory, or None for the first one.

None
dt float

The time between frames.

1.0
lags Sequence[int] | None

The lags in frames to keep, or None for every lag from 1.

None
fit_range tuple[float, float] | None

The smallest and largest lags in time of the fits, or None for one frame to a quarter of the run.

None

Attributes:

Name Type Description
positions_

The unwrapped positions, frames by particles by dimensions.

lags_

The lags in time.

msd_

The mean squared displacement at every lag.

exponent_

The slope of log msd against log lag over the fit range, 1 for normal diffusion, below 1 for subdiffusion, and above 1 for superdiffusion.

diffusion_

The coefficient D of msd = 2 d D lag fitted over the fit range in d dimensions.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import DisplacementAnalysis
>>> walks = np.cumsum(np.random.default_rng(0).standard_normal((2000, 500)), axis=0)
>>> displacement = DisplacementAnalysis().check().prepare(walks).run()
>>> round(displacement.exponent_, 1), round(displacement.diffusion_, 1)
(1.0, 0.5)
Source code in src/skcomplex/analysis/_displacement.py
68
69
70
71
72
73
74
75
76
77
78
79
def __init__(
    self,
    group: str | None = None,
    *,
    dt: float = 1.0,
    lags: Sequence[int] | None = None,
    fit_range: tuple[float, float] | None = None,
) -> None:
    self.group = group
    self.dt = dt
    self.lags = lags
    self.fit_range = fit_range

DistributionAnalysis(kind='histogram', variables=None, *, group=None, bins=30, log=False, absolute=False, orders=(4, 2), tail=10)

Bases: BaseAnalysis

Distribution of the values of a source, of one kind.

The source gives samples as columns: the series of a trajectory or an array, or the speeds of the agents of a trajectory, one sample per frame. The first kinds pool every sample into one:

  • 'histogram': the edges and the probability density of bins bins, equally spaced in the logarithm of the values with log=True for heavy tails.
  • 'ccdf': the complementary cumulative distribution, the share of samples at or above every distinct value.
  • 'gini': the Gini coefficient of nonnegative values, 0 for equality and close to 1 when one holds everything.
  • 'tail-index': the Hill estimate of the tail index from the tail largest positive samples, the exponent alpha - 1 of the survival function of a density p(x) ∝ x^-alpha.

The other kinds describe every sample:

  • 'moments': the mean, the variance, the skewness, and the excess kurtosis of every sample, or of its absolute values with absolute=True.
  • 'moment-ratio': the ratio <x^p> / <x^q>^(p / q) of the moments of the even orders (p, q), and the cumulant 1 - ratio / ratio_gauss, normalized by the ratio of a centred Gaussian so that it is 0 for Gaussian samples. For the orders 4 and 2 the ratio is the kurtosis about 0 and the cumulant 1 - <x^4> / (3 <x^2>^2), 2/3 for samples at ±1.
  • 'dispersion-index': the variance over the mean of every sample of counts, 1 for Poisson counts, above 1 for overdispersed and below 1 for underdispersed counts.
  • 'records': the number of records of every series, the values larger than every value before them, about the harmonic number of the length for independent values.
  • 'total-variation': the total variation distance 1/2 Σ_u |a_u / A - b_u / B| between the distributions of two groups across units, from one row per item with its group and its unit, 0 for identical distributions and 1 for disjoint ones.

Parameters:

Name Type Description Default
kind str

The description to compute, one of the kinds above.

'histogram'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series.

None
group str | None

The path of the agent or particle group whose speeds are read, or None for the series of a trajectory, or the first group of a trajectory without series.

None
bins int

The number of bins of the histogram.

30
log bool

Whether the bins of the histogram are equally spaced in the logarithm of the values, which needs positive values.

False
absolute bool

Whether the moments are those of the absolute values.

False
orders tuple[int, int]

The positive even orders (p, q) of the moment ratio.

(4, 2)
tail int

The number of largest samples of the Hill estimate.

10

Attributes:

Name Type Description
values_

The pooled sample, or the samples, one per column.

labels_

The label of every sample.

edges_

The bin edges of the histogram.

density_

The probability density in every bin of the histogram.

support_

The sorted distinct values of the complementary cumulative distribution.

survival_

The share of samples at or above every distinct value, P(X >= x).

mean_

The mean of every sample.

variance_

The variance of every sample.

skewness_

The skewness of every sample.

kurtosis_

The excess kurtosis of every sample, 0 for Gaussian samples.

ratio_

The moment ratio of every sample.

cumulant_

The normalized cumulant of every sample.

index_

The dispersion index of every sample.

gini_

The Gini coefficient.

tail_index_

The Hill estimate of the tail index.

records_

The number of records of every series, the first value included.

distance_

The total variation distance between the two groups.

Examples:

>>> from skcomplex.analysis import DistributionAnalysis
>>> histogram = DistributionAnalysis('histogram', bins=3, log=True).check().prepare([1, 2, 4, 8]).run()
>>> histogram.edges_.round(2).tolist()
[1.0, 2.0, 4.0, 8.0]
>>> DistributionAnalysis('ccdf').check().prepare([1, 1, 2, 4]).run().survival_.tolist()
[1.0, 0.5, 0.25]
>>> DistributionAnalysis('gini').check().prepare([0, 0, 0, 4]).run().gini_
0.75
>>> DistributionAnalysis('moment-ratio').check().prepare([1.0, -1.0, 1.0]).run().cumulant_.round(4).tolist()
[0.6667]
>>> DistributionAnalysis('dispersion-index').check().prepare([1, 3, 1, 3]).run().index_.tolist()
[0.5]
>>> DistributionAnalysis('total-variation').check().prepare([[0, 0], [0, 0], [1, 1], [1, 1]]).run().distance_
1.0
Source code in src/skcomplex/analysis/_distribution.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def __init__(
    self,
    kind: str = 'histogram',
    variables: Sequence[str] | None = None,
    *,
    group: str | None = None,
    bins: int = 30,
    log: bool = False,
    absolute: bool = False,
    orders: tuple[int, int] = (4, 2),
    tail: int = 10,
) -> None:
    self.kind = kind
    self.variables = variables
    self.group = group
    self.bins = bins
    self.log = log
    self.absolute = absolute
    self.orders = orders
    self.tail = tail

DomainAnalysis(kind='size', variables=None, *, method='moment', spacing=1.0, fit_range=None, times=None, attribute='state')

Bases: BaseAnalysis

Domains of every frame of a field, or of the states on a network, of one kind.

The kinds read frames of a field on a periodic grid:

  • 'size': the characteristic domain size, 2 pi / <k> over the structure factor with method='moment', or the first zero of the correlation function with method='zero'.
  • 'growth': the domain size of every frame and the exponent of its growth in time, the slope of log size against log time over the fit range, 1/2 for non-conserved and 1/3 for conserved order parameters.
  • 'wavelength': the dominant wavelength and wavenumber of a pattern, the peak of its structure factor, and the nematic order of the wave vectors near the peak, 1 for parallel stripes and near 0 for labyrinths or spots.
  • 'interface-density': the share of neighbouring pairs, on a periodic grid without corners or along the edges of a network, whose states differ, 0 for one domain and 1/2 for random binary states.

A trajectory gives the frames of a lattice state and their times. An array is one frame, or one frame per entry of times along its first axis. A graph carries the states on its nodes for 'interface-density'.

Parameters:

Name Type Description Default
kind str

The domain measure to compute, one of the kinds above.

'size'
variables Sequence[str] | None

The lattice state of a trajectory to read, or None for the first one.

None
method str

The domain size method, 'moment' or 'zero'.

'moment'
spacing float

The distance between neighbouring sites.

1.0
fit_range tuple[float, float] | None

The first and last times of the growth fit, or None for every positive time.

None
times Sequence[float] | None

The time of every frame of an array, or None for an array of one frame.

None
attribute str

The node attribute of the states of a graph.

'state'

Attributes:

Name Type Description
frames_

The frames of the field, or the graph.

times_

The time of every frame.

size_

The domain size of every frame.

exponent_

The growth exponent of the domain size.

wavelength_

The wavelength of the peak of every frame.

wavenumber_

The wavenumber of the peak of every frame.

orientation_

The orientation order of the wave vectors near the peak of every frame.

density_

The interface density of every frame.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import DomainAnalysis
>>> x = np.arange(256)
>>> stripes = np.sign(np.cos(2 * np.pi * x / 16))
>>> DomainAnalysis(method='zero').check().prepare(stripes[:64]).run().size_.tolist()
[4.0]
>>> stack = [np.sign(np.cos(2 * np.pi * x / (8 * t))) for t in (1, 2, 4)]
>>> growth = DomainAnalysis('growth', method='zero', times=[1, 2, 4]).check().prepare(stack).run()
>>> round(growth.exponent_, 2)
1.0
>>> DomainAnalysis('interface-density').check().prepare([[1, 1, -1, -1]] * 4).run().density_.tolist()
[0.25]
Source code in src/skcomplex/analysis/_domains.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def __init__(
    self,
    kind: str = 'size',
    variables: Sequence[str] | None = None,
    *,
    method: str = 'moment',
    spacing: float = 1.0,
    fit_range: tuple[float, float] | None = None,
    times: Sequence[float] | None = None,
    attribute: str = 'state',
) -> None:
    self.kind = kind
    self.variables = variables
    self.method = method
    self.spacing = spacing
    self.fit_range = fit_range
    self.times = times
    self.attribute = attribute

EmbeddingAnalysis(kind='delay', variables=None, *, delay=None, dimension=None, threshold=0.01, max_delay=100, max_dimension=10, bins=16, ratio=10.0, escape=2.0)

Bases: BaseAnalysis

Delay embedding of one series, with the delay and the dimension chosen from the data unless given, of one kind.

The kinds read one series:

  • 'delay': the delay vectors (x_t, x_{t+delay}, ..., x_{t+(dimension-1) delay}). Without a delay, the delay is the first minimum of the auto mutual information. Without a dimension, the dimension is the first one whose fraction of false nearest neighbours lies below threshold.
  • 'false-nearest-neighbors': Kennel's fraction of false nearest neighbours of the embeddings of dimensions 1 to max_dimension. A nearest neighbour in dimension d is false when the next coordinate moves it away by more than ratio times their distance, or by more than escape times the standard deviation of the series.

Parameters:

Name Type Description Default
kind str

The embedding measure to compute, one of the kinds above.

'delay'
variables Sequence[str] | None

The one variable of a trajectory to read, or None for the only scalar series.

None
delay int | None

The delay in steps, or None for the first minimum of the auto mutual information.

None
dimension int | None

The dimension, or None for the first one with a fraction of false nearest neighbours below threshold.

None
threshold float

The fraction of false nearest neighbours accepted.

0.01
max_delay int

The largest delay tried.

100
max_dimension int

The largest dimension tried.

10
bins int

The number of equally populated bins of the auto mutual information.

16
ratio float

The threshold on the relative growth of the distance of a false neighbour.

10.0
escape float

The threshold on the distance of a false neighbour in the next dimension, in standard deviations.

2.0

Attributes:

Name Type Description
values_

The series, as one column.

labels_

The label of the series.

delay_

The delay in steps.

dimension_

The dimension of 'delay'.

points_

One row per delay vector of 'delay'.

mutual_information_

The auto mutual information at every delay from 0, or None when the delay is given.

false_neighbors_

The fraction of false nearest neighbours in every dimension from 1, or None when 'delay' is given the dimension.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import EmbeddingAnalysis
>>> EmbeddingAnalysis(delay=2, dimension=2).check().prepare([0, 1, 2, 3, 4]).run().points_.tolist()
[[0.0, 2.0], [1.0, 3.0], [2.0, 4.0]]
>>> embedding = EmbeddingAnalysis(delay=31).check().prepare(np.sin(np.arange(5000) * 0.05)).run()
>>> embedding.delay_, embedding.dimension_
(31, 2)
Source code in src/skcomplex/analysis/_embedding.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def __init__(
    self,
    kind: str = 'delay',
    variables: Sequence[str] | None = None,
    *,
    delay: int | None = None,
    dimension: int | None = None,
    threshold: float = 0.01,
    max_delay: int = 100,
    max_dimension: int = 10,
    bins: int = 16,
    ratio: float = 10.0,
    escape: float = 2.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.delay = delay
    self.dimension = dimension
    self.threshold = threshold
    self.max_delay = max_delay
    self.max_dimension = max_dimension
    self.bins = bins
    self.ratio = ratio
    self.escape = escape

EmergenceAnalysis(kind='causal', variables=None, *, partition=None, weighting='uniform', search='greedy', lag=1, history=1, estimator='plugin', k=3, base=2.0)

Bases: BaseAnalysis

Emergence of a macro scale over a micro scale, of one kind.

The kinds read a transition matrix or the series of micro variables:

  • 'effective-information': the effective information of a transition matrix, H(mean row) - mean H(row) over the states with observed transitions, split into determinism and degeneracy. A matrix of counts is normalised by row first.
  • 'causal': the causal emergence of a transition matrix, the gain in effective information of the macro matrix of a given or searched partition over the micro matrix. The macro matrix sums the micro transitions from every block to every block, weighting the micro states of a block uniformly, as Hoel, Albantakis, and Tononi (2013) do, or by the stationary distribution, as Klein and Hoel (2020) do. The greedy search of Klein and Hoel (2020) merges the pair of blocks with the largest macro effective information while that grows, so it can miss the best partition. The exhaustive search tries every partition of at most 10 states.
  • 'criteria': the criteria Ψ, Δ, and Γ of Rosas et al. (2020) for the macro feature of the micro series given by partition, estimated lag steps ahead with the minimum mutual information redundancy.
  • 'closure': how well the macro past and the micro past of the macro series given by partition predict the macro future. The coarse-graining is closed when the gap is zero up to the bias of the plug-in estimator.

Parameters:

Name Type Description Default
kind str

The emergence to measure, one of the kinds above.

'causal'
variables Sequence[str] | None

The micro variables of a trajectory to read, or None for every scalar series.

None
partition Partition | None

The macro label of every integer micro state, numbered 0, 1, and so on, or a function from an array of micro states, or from the micro series with one column per variable, to their macro labels. None to search a partition of the matrix.

None
weighting str

'uniform' or 'stationary', the weight of the micro states within a block.

'uniform'
search str

'greedy' or 'exhaustive', the search when no partition is given.

'greedy'
lag int

The number of steps between the present and the future of the criteria.

1
history int

The number of past steps the closure conditions on.

1
estimator str

'plugin' for the frequencies of symbols, 'gaussian' for the closed form of a fitted Gaussian, or 'knn' for nearest-neighbour conditional mutual informations.

'plugin'
k int

The number of neighbours of the 'knn' estimator.

3
base float

The base of the logarithm, 2 for bits.

2.0

Attributes:

Name Type Description
matrix_

The micro transition matrix, for 'effective-information' and 'causal'.

values_

The micro series, one column per micro variable, for 'criteria' and 'closure'.

labels_

The label of every micro series.

information_

The effective information in bits, for 'effective-information'.

determinism_

log2 n minus the mean entropy of the rows, high when every state has one successor.

degeneracy_

log2 n minus the entropy of the mean row, high when many states share a successor.

states_

The states with observed transitions, the rows the effective information uses.

partition_

The macro label of every micro state, for 'causal'.

macro_matrix_

The macro transition matrix of the partition, every row summing to one.

micro_information_

The effective information of the micro matrix.

macro_information_

The effective information of the macro matrix.

gain_

The macro minus the micro effective information in bits.

psi_

I(V_t ; V_t') - Σ_j I(X_t^j ; V_t'), positive when the macro feature predicts its own future beyond what every micro variable does, a sufficient condition for causal emergence.

delta_

max_j [I(V_t ; X_t'^j) - Σ_i I(X_t^i ; X_t'^j)], positive when the macro feature predicts a micro variable beyond the micro variables, a sufficient condition for downward causation.

gamma_

max_j I(V_t ; X_t'^j), zero for a causally decoupled feature when psi_ is positive.

macro_uncertainty_

H(V_t | V_past), the entropy of the macro future given the macro past.

micro_uncertainty_

H(V_t | X_past), the entropy of the macro future given the micro past.

gap_

The information the micro past adds about the macro future, zero for a closed coarse-graining.

Raises:

Type Description
AnalysisError

The matrix is not square or has no observed transition, the partition is not valid, or the exhaustive search gets more than 10 states.

Warns:

Type Description
UnobservedStatesWarning

Some rows of the micro matrix hold no transition, and the warning names their states.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import EmergenceAnalysis
>>> EmergenceAnalysis('effective-information').check().prepare(np.eye(4)).run().information_
2.0
>>> matrix = np.zeros((4, 4))
>>> matrix[:3, :3], matrix[3, 3] = 1 / 3, 1.0
>>> found = EmergenceAnalysis('causal').check().prepare(matrix).run()
>>> found.partition_.tolist(), round(found.gain_, 3)
([0, 0, 0, 1], 0.189)
>>> micro = np.random.default_rng(0).integers(0, 4, 5000)
>>> round(EmergenceAnalysis('closure', partition=[0, 0, 1, 1]).check().prepare(micro).run().gap_, 2)
0.0
Source code in src/skcomplex/analysis/_emergence.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def __init__(
    self,
    kind: str = 'causal',
    variables: Sequence[str] | None = None,
    *,
    partition: Partition | None = None,
    weighting: str = 'uniform',
    search: str = 'greedy',
    lag: int = 1,
    history: int = 1,
    estimator: str = 'plugin',
    k: int = 3,
    base: float = 2.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.partition = partition
    self.weighting = weighting
    self.search = search
    self.lag = lag
    self.history = history
    self.estimator = estimator
    self.k = k
    self.base = base

EntropyAnalysis(kind='shannon', variables=None, *, order=2.0, base=2.0, probabilities=False, dimension=3, delay=1, tolerance=0.2, scales=tuple(range(1, 11)), normalize=True, dt=1.0, measure='shannon', ties='order', random_state=None, reference=None)

Bases: BaseAnalysis

Entropy of every series of a source, or of every frame of a field, of one kind.

The kinds read symbols, patterns, or real values:

  • 'shannon', 'renyi', and 'tsallis': the entropy of the distribution of the symbols, or of a probability vector with probabilities=True, the Rényi order and the Tsallis index given by order.
  • 'block', 'rate', and 'excess': the block entropies S_0 to S_m, the entropy rate, and the excess entropy of the blocks of up to dimension symbols, or of square patches of a field.
  • 'lempel-ziv': the number of new phrases of the LZ76 parsing.
  • 'permutation': the entropy of the ordinal patterns of length dimension, or of the windows (columns, rows) of a field, measured with measure.
  • 'sample' and 'approximate': the regularity of patterns of length dimension, close within tolerance standard deviations.
  • 'multiscale': the sample entropy of the means of non-overlapping windows of every scale of scales.
  • 'spectral': the Shannon entropy of the normalized periodogram.
  • 'ensemble': the Shannon entropy in bits of the outcomes of an ensemble at every frame, and their Kullback-Leibler divergence from the reference law, as in the H-theorem. The ensemble has one row per run and one column per frame of integer outcomes, such as ExperimentResult.ensemble returns.

Parameters:

Name Type Description Default
kind str

The entropy to compute, one of the kinds above.

'shannon'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series, or the first field for a field.

None
order float

The order of the Rényi entropy or the index of the Tsallis entropy.

2.0
base float

The base of the logarithm, 2 for bits.

2.0
probabilities bool

Whether every series is already a probability vector.

False
dimension int | tuple[int, int]

The length of patterns or the largest block length, or the window (columns, rows) of a field.

3
delay int

The number of steps between the values of an ordinal pattern.

1
tolerance float

The largest distance of close patterns, as a fraction of the standard deviation.

0.2
scales Sequence[int]

The window lengths of the multiscale entropy.

tuple(range(1, 11))
normalize bool

Whether to divide by the largest value, so the entropy lies between 0 and 1.

True
dt float

The time between samples of the spectral entropy.

1.0
measure str

The entropy of the ordinal patterns: 'shannon', 'renyi', or 'tsallis'.

'shannon'
ties str

'order' to rank equal values by position, or 'random' to rank them at random.

'order'
random_state int | None

The seed of the random ranks of equal values.

None
reference ArrayLike | None

The probability of every outcome under the reference law of an ensemble, such as the stationary law.

None

Attributes:

Name Type Description
values_

The series, one per column, or the frames of the field.

labels_

The label of every series, or of the field.

entropy_

One entropy per series or frame, with one column per block length for 'block' and per scale for 'multiscale'.

divergence_

The divergence in bits of the outcomes of every frame of an ensemble from the reference law.

error_

The expected divergence of a sample of the reference law of the ensemble's size, (k - 1) / (2 n ln 2) for k outcomes and n runs, below which a divergence is sampling noise.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import EntropyAnalysis
>>> EntropyAnalysis().check().prepare([0, 1, 0, 1]).run().entropy_.tolist()
[1.0]
>>> EntropyAnalysis('excess', dimension=4).check().prepare([0, 0, 1, 1] * 100).run().entropy_.round(3).tolist()
[2.0]
Source code in src/skcomplex/analysis/_entropy.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def __init__(
    self,
    kind: str = 'shannon',
    variables: Sequence[str] | None = None,
    *,
    order: float = 2.0,
    base: float = 2.0,
    probabilities: bool = False,
    dimension: int | tuple[int, int] = 3,
    delay: int = 1,
    tolerance: float = 0.2,
    scales: Sequence[int] = tuple(range(1, 11)),
    normalize: bool = True,
    dt: float = 1.0,
    measure: str = 'shannon',
    ties: str = 'order',
    random_state: int | None = None,
    reference: npt.ArrayLike | None = None,
) -> None:
    self.kind = kind
    self.variables = variables
    self.order = order
    self.base = base
    self.probabilities = probabilities
    self.dimension = dimension
    self.delay = delay
    self.tolerance = tolerance
    self.scales = scales
    self.normalize = normalize
    self.dt = dt
    self.measure = measure
    self.ties = ties
    self.random_state = random_state
    self.reference = reference

EpsilonMachineAnalysis(variables=None, *, max_length=3, significance=0.001)

Bases: BaseAnalysis

Epsilon-machine of a discrete series reconstructed by causal-state splitting (CSSR).

Histories up to max_length symbols join the state whose law of the next symbol they share by a chi-square test, and states are then split until their transitions are deterministic.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variable of a trajectory to read, a list of one name, or None for the only scalar series.

None
max_length int

The longest history, kept well below the logarithm of the series length in the alphabet size.

3
significance float

The significance level of the test that splits a history from a state.

0.001

Attributes:

Name Type Description
values_

The series of symbols.

labels_

The label of the series.

alphabet_

The distinct symbols, whose positions are the codes of the histories and of the columns.

histories_

The histories of every causal state, as codes of their symbols, oldest first.

transitions_

The state reached from every state on every symbol, -1 for symbols a state never emits.

probabilities_

The probability of every symbol from every state.

stationary_

The fraction of time spent in every state.

statistical_complexity_

The entropy of the stationary distribution of the causal states in bits.

entropy_rate_

The mean entropy of the next symbol given the causal state in bits.

Examples:

>>> from skcomplex.analysis import EpsilonMachineAnalysis
>>> machine = EpsilonMachineAnalysis(max_length=3).check().prepare([0, 0, 1] * 300).run()
>>> len(machine.histories_), round(machine.statistical_complexity_, 3), round(machine.entropy_rate_, 3)
(3, 1.585, 0.0)
Source code in src/skcomplex/analysis/_computational_mechanics.py
69
70
71
72
73
74
75
76
77
78
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    max_length: int = 3,
    significance: float = 0.001,
) -> None:
    self.variables = variables
    self.max_length = max_length
    self.significance = significance

ErgodicityAnalysis(kind='breaking', variables=None, *, start=0.01, dt=1.0, bins=None, value_range=None, edges=None, samples=100)

Bases: BaseAnalysis

Ergodicity of an ensemble of runs, or invariant density of a map or a flow, of one kind.

The ensemble kinds read one row per run and one column per time, such as ExperimentResult.ensemble returns:

  • 'breaking': the variance across runs of the time averages over the variance of all the values, near 0 for ergodic runs longer than their relaxation time and near 1 when every run is frozen in its own state.
  • 'metric': the fluctuation metric of Thirumalai and Mountain, the mean square deviation of the running time averages from their ensemble mean, which decays as 1 / t for ergodic runs and levels off for frozen ones.
  • 'distance': the median total-variation distance between the values one run visits over time and the values of all runs, after Lesmana et al. (2026), near 0 for ergodic runs and near 1 - p when every run stays in its own state of ensemble weight p.

The density kinds read one coordinate of a long run, or a one-dimensional map:

  • 'invariant-density': the histogram density of the values of a long run.
  • 'ulam': the Markov chain of a map coarse-grained on the cells between edges by Ulam's method, and the density of its stationary vector.

Parameters:

Name Type Description Default
kind str

The diagnostic, one of the kinds above.

'breaking'
variables Sequence[str] | None

The variable of a trajectory whose values 'invariant-density' reads, or None for the first one.

None
start float

The fraction of the run after which the exponent of the metric is fitted, at 50 log-spaced times.

0.01
dt float

The time between the columns of the runs.

1.0
bins int | ArrayLike | None

The number of bins or their edges, or None for 20 for 'distance' and 100 for 'invariant-density'.

None
value_range tuple[float, float] | None

The range of the bins, or None for the range of the values.

None
edges ArrayLike | None

The increasing edges of the cells of 'ulam'.

None
samples int

The number of evenly spaced points per cell of 'ulam'.

100

Attributes:

Name Type Description
values_

The runs, one row per run, or the values of the run.

mapping_

The map of 'ulam'.

ergodicity_breaking_

The ergodicity-breaking parameter, of 'breaking'.

times_

The times of the columns, of 'metric'.

metric_

The fluctuation metric at every time, of 'metric'.

exponent_

The slope of log metric against log time at log-spaced later times, -1 for ergodic runs and 0 for frozen ones, of 'metric'.

ergodicity_distance_

The median distance between the law of every run and that of the ensemble, of 'distance'.

edges_

The edges of the bins or of the cells, of the density kinds.

density_

The density in every bin or cell, of the density kinds.

transition_matrix_

The probability that a point of every cell lands in every cell, of 'ulam'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import ErgodicityAnalysis
>>> runs = [[1.0, -1.0, 1.0, -1.0], [-1.0, 1.0, -1.0, 1.0]]
>>> ErgodicityAnalysis().check().prepare(runs).run().ergodicity_breaking_
0.0
>>> ErgodicityAnalysis('distance').check().prepare([[0.0, 0.0], [1.0, 1.0]]).run().ergodicity_distance_
0.5
>>> ulam = ErgodicityAnalysis('ulam', edges=np.linspace(0, 1, 5))
>>> ulam.check().prepare(lambda x: np.mod(2 * x, 1)).run().density_.round(2).tolist()
[1.0, 1.0, 1.0, 1.0]
Source code in src/skcomplex/analysis/_ergodicity.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def __init__(
    self,
    kind: str = 'breaking',
    variables: Sequence[str] | None = None,
    *,
    start: float = 0.01,
    dt: float = 1.0,
    bins: int | npt.ArrayLike | None = None,
    value_range: tuple[float, float] | None = None,
    edges: npt.ArrayLike | None = None,
    samples: int = 100,
) -> None:
    self.kind = kind
    self.variables = variables
    self.start = start
    self.dt = dt
    self.bins = bins
    self.value_range = value_range
    self.edges = edges
    self.samples = samples

EventAnalysis(kind='detect', variables=None, *, threshold=0.0, window=None, start=0.0, end=None, max_step=40, dt=1.0)

Bases: BaseAnalysis

Events of one series or of event times, of one kind.

The kinds read one series or the times of events:

  • 'detect': the events of a series, every maximal run of consecutive values above threshold, with their starts, sizes, durations, peaks, and the waiting times between them. For avalanches recorded as topplings per drive step, an event is an avalanche and its size is the total number of topplings.
  • 'counts': the number of events in consecutive windows of length window, from the times of the events, or from the jump times of a discrete state of a trajectory.
  • 'laminar-durations': the durations of the laminar phases of a series, the complete runs of values within threshold of 0 between two bursts, such as the distance from an invariant manifold of on-off intermittency.
  • 'excursions': the excursions of a series above threshold, every maximal run of values above it, with the profile of every excursion, its values minus the threshold, its size, the sum of the profile, and its duration, the number of its samples. A sequence of records with a size and a duration, such as the cascade records of a lattice, holds excursions already.
  • 'branching': the branching ratio of the series by the multistep regression estimator of mrestimator, which fits r_k ∝ m^k to the regression slopes r_k of the values k steps apart, and stays unbiased when only a fraction of the units is observed. Every series is one trial.

Times are the times of a trajectory, and the step indices of an array.

Parameters:

Name Type Description Default
kind str

The events to measure, one of the kinds above.

'detect'
variables Sequence[str] | None

The one variable of a trajectory to read, or None for the only scalar series or discrete state.

None
threshold float

The value an event must exceed, or the largest absolute value of a laminar phase.

0.0
window float | None

The length of every window of 'counts'.

None
start float

The start of the first window.

0.0
end float | None

The end of the last full window, or None for the last event.

None
max_step int

The largest step k of the regression of the branching ratio.

40
dt float

The duration of a step of the branching ratio.

1.0

Attributes:

Name Type Description
times_

The time of every value, or of every event for 'counts'.

values_

The series, as one column, or one column per trial for 'branching', or None for records.

labels_

The label of the series or the state.

starts_

The time at which every event starts.

sizes_

The sum of the values of every event.

durations_

The time every event lasts for 'detect', every laminar phase lasts for 'laminar-durations', or the number of samples of every excursion for 'excursions'.

shapes_

The profile of every excursion of a series, empty for records.

peaks_

The largest value of every event.

waiting_times_

The time between the end of every event and the start of the next.

counts_

The number of events in every window.

branching_ratio_

The branching ratio m, 1 at criticality.

Examples:

>>> from skcomplex.analysis import EventAnalysis
>>> events = EventAnalysis().check().prepare([0, 3, 1, 0, 0, 2, 0]).run()
>>> events.sizes_.tolist(), events.durations_.tolist(), events.waiting_times_.tolist()
([4.0, 2.0], [2.0, 1.0], [2.0])
>>> EventAnalysis('counts', window=1.0, end=3.0).check().prepare([0.1, 0.2, 1.5, 2.2]).run().counts_.tolist()
[2.0, 1.0, 1.0]
>>> laminar = EventAnalysis('laminar-durations', threshold=0.5)
>>> laminar.check().prepare([1, 0, 0, 1, 0, 1]).run().durations_.tolist()
[2.0, 1.0]
>>> excursions = EventAnalysis('excursions').check().prepare([0, 1, 2, 1, 0, 3, 0]).run()
>>> excursions.sizes_.tolist(), excursions.durations_.tolist()
([4.0, 3.0], [3.0, 1.0])
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> activity = np.zeros(20_000)
>>> activity[0] = 100
>>> for t in range(1, activity.size):
...     activity[t] = rng.poisson(0.9 * activity[t - 1] + 10)
>>> round(EventAnalysis('branching').check().prepare(activity).run().branching_ratio_, 1)
0.9
Source code in src/skcomplex/analysis/_events.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def __init__(
    self,
    kind: str = 'detect',
    variables: Sequence[str] | None = None,
    *,
    threshold: float = 0.0,
    window: float | None = None,
    start: float = 0.0,
    end: float | None = None,
    max_step: int = 40,
    dt: float = 1.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.threshold = threshold
    self.window = window
    self.start = start
    self.end = end
    self.max_step = max_step
    self.dt = dt

FitAnalysis(law='power', variables=None, *, group=None, minimum=None, maximum=None, discrete=False, bootstrap=100, candidates=50, compare_with=ALTERNATIVES, block=None, fixed=None, simulations=199, random_state=0)

Bases: BaseAnalysis

Law fitted to the values of a source, pooled into one sample.

The source gives the series of a trajectory or an array, or the speeds of the agents of a trajectory at every frame.

The laws are:

  • 'power': a power law p(x) ∝ x^-alpha fitted to the tail of the positive samples by maximum likelihood, with Clauset, Shalizi, and Newman's method. The lower cutoff minimises the Kolmogorov-Smirnov distance between the tail and the fit, unless minimum fixes it. The goodness of fit is the share of bootstrap samples whose distance exceeds the data's, and the power law is compared with every alternative of compare_with by a likelihood-ratio test. Discrete samples use the exact discrete likelihood. The warnings of powerlaw about its optimiser are silenced during the run.
  • 'stable': the Lévy stable law fitted by McCulloch's quantile method, in SciPy's S1 parameterization.
  • 'extreme-value': the generalized extreme-value law fitted by maximum likelihood to the maxima of blocks of block samples.
  • 'exponential', 'normal', 'lognormal', 'gamma', 'maxwell', 'rayleigh', and 'uniform': the named law with its free parameters fitted, tested with a Monte Carlo Kolmogorov-Smirnov test that accounts for the fit.

Parameters:

Name Type Description Default
law str

The law to fit, one of the laws above.

'power'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series.

None
group str | None

The path of the agent or particle group whose speeds are read, or None for the series of a trajectory, or the first group of a trajectory without series.

None
minimum float | None

A fixed lower cutoff of the power law, or None to choose it.

None
maximum float | None

A fixed upper cutoff of the power law, or None for none.

None
discrete bool

Whether the samples of the power law are integers, such as avalanche sizes.

False
bootstrap int

The number of bootstrap samples for the p-value of the power law, 0 for none.

100
candidates int

The largest number of lower cutoffs tried.

50
compare_with Sequence[str]

The alternatives the power law is compared with, among 'exponential', 'lognormal', 'stretched_exponential', and 'truncated_power_law'.

ALTERNATIVES
block int | None

The number of samples per block of the extreme-value law.

None
fixed Mapping[str, float] | None

The parameters of a named law held fixed by SciPy name, such as {'loc': 0}, or None to fit all.

None
simulations int

The number of Monte Carlo samples of the null distribution of a named law.

199
random_state int | None

The seed of the bootstrap of the power law and of the Monte Carlo samples of a named law.

0

Attributes:

Name Type Description
values_

The pooled sample.

labels_

The label of every series.

exponent_

The exponent alpha of the density of the power law.

minimum_

The lower cutoff of the power law.

maximum_

The upper cutoff of the power law, or None without one.

statistic_

The Kolmogorov-Smirnov distance between the tail and the power law, or between the sample and a named law.

p_value_

For the power law, the share of bootstrap samples from the fit whose distance exceeds statistic_, or None without bootstrap, values above 0.1 not rejecting it. For a named law, the Monte Carlo p-value, values below the significance level rejecting it.

tail_

The number of samples between the cutoffs of the power law.

comparisons_

For every alternative of compare_with, the log-likelihood ratio of the power law against it, positive when the power law fits better, and its p-value.

index_

The stability index of the stable law, 2 for the normal law.

skewness_

The skewness of the stable law.

shape_

The shape xi of the extreme-value law, 0 for the Gumbel class, positive for the Fréchet class of heavy tails, and negative for the Weibull class of bounded tails.

location_

The location of the stable or the extreme-value law.

scale_

The scale of the stable or the extreme-value law.

maxima_

The maximum of every block of the extreme-value law.

parameters_

The fitted and fixed parameters of a named law.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import FitAnalysis
>>> samples = (1 - np.random.default_rng(0).random(5000)) ** (-1 / 1.5)
>>> round(FitAnalysis('power', minimum=1.0, bootstrap=0).check().prepare(samples).run().exponent_, 1)
2.5
>>> fit = FitAnalysis('exponential', fixed={'loc': 0}).check()
>>> fit = fit.prepare(np.random.default_rng(0).exponential(2.0, 1000)).run()
>>> round(fit.parameters_['scale'], 0), fit.p_value_ > 0.05
(2.0, True)
Source code in src/skcomplex/analysis/_fit.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def __init__(
    self,
    law: str = 'power',
    variables: Sequence[str] | None = None,
    *,
    group: str | None = None,
    minimum: float | None = None,
    maximum: float | None = None,
    discrete: bool = False,
    bootstrap: int = 100,
    candidates: int = 50,
    compare_with: Sequence[str] = ALTERNATIVES,
    block: int | None = None,
    fixed: Mapping[str, float] | None = None,
    simulations: int = 199,
    random_state: int | None = 0,
) -> None:
    self.law = law
    self.variables = variables
    self.group = group
    self.minimum = minimum
    self.maximum = maximum
    self.discrete = discrete
    self.bootstrap = bootstrap
    self.candidates = candidates
    self.compare_with = compare_with
    self.block = block
    self.fixed = fixed
    self.simulations = simulations
    self.random_state = random_state

InformationAnalysis(kind='mutual', variables=None, *, symbols=None, bins=2, order=3, delay=1, history=1, lag=1, max_delay=100, dimension=8, estimator='plugin', k=3, base=2.0)

Bases: BaseAnalysis

Information among the series of a source, of one kind.

The kinds measure pairs of series, every series, or all series together:

  • 'mutual': the mutual information H(X) + H(Y) - H(X, Y) of every pair of series, by plug-in counts.
  • 'transfer': the transfer entropy H(Y_t | Y_past) - H(Y_t | Y_past, X_{t-lag}) from every series to every series, the target conditioned on history past values, by plug-in counts or by the Kraskov-Stögbauer-Grassberger nearest-neighbour estimate of k neighbours.
  • 'kl-divergence': the Kullback-Leibler divergence of every series, a probability vector, from every series.
  • 'auto-mutual': the mutual information of every series with itself delayed by 0 to max_delay steps.
  • 'active-storage': the information the past history values of every series hold about its next value.
  • 'correlation-information': Lindgren's correlation information k_m = ΔS_{m-1} - ΔS_m of every series for block lengths 2 to dimension.
  • 'total-correlation', 'dual-total-correlation', 'o-information': the information all series share at any order, the information two or more of them share, and their difference, positive for redundancy and negative for synergy, by plug-in counts with dit, the closed form of a fitted Gaussian, or chains of neighbour estimates.
  • 'o-information-gradients': the O-information every series adds to the others.

Every kind but 'kl-divergence' reads the series as symbols, or first turns them into symbols with symbols: 'bins' for bins bins of equal width, 'quantiles' for bins equally populated bins, or 'ordinal' for the rank pattern of every order values spaced by delay steps.

Parameters:

Name Type Description Default
kind str

The information to compute, one of the kinds above.

'mutual'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series.

None
symbols str | None

None to read the series as symbols, or 'bins', 'quantiles', or 'ordinal' to symbolize them.

None
bins int

The number of symbols of bins and quantiles.

2
order int

The length of ordinal patterns.

3
delay int

The number of steps between the values of an ordinal pattern.

1
history int

The number of past values of the target of the transfer entropy, or of the series of the storage.

1
lag int

The delay of the source of the transfer entropy.

1
max_delay int

The largest delay of the auto mutual information.

100
dimension int

The largest block length of the correlation information.

8
estimator str

'plugin' for counts of symbols, 'gaussian' for a fitted Gaussian, or 'knn' for nearest neighbours.

'plugin'
k int

The number of neighbours of the 'knn' estimator.

3
base float

The base of the logarithm, 2 for bits.

2.0

Attributes:

Name Type Description
values_

The series, one per column.

labels_

The label of every series.

symbols_

The symbols the kinds measure, one column per series, the series themselves without symbols.

information_

A matrix with the measure from the series of every row to the series of every column for 'mutual', 'transfer', and 'kl-divergence', nan from a series to itself for 'transfer'; one value per series, with one column per delay or block length for 'auto-mutual' and 'correlation-information'; one value per series for 'o-information-gradients'; and a single value for the other kinds of all series together.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import InformationAnalysis
>>> x = np.random.default_rng(0).integers(0, 2, 10000)
>>> transfer = InformationAnalysis('transfer').check().prepare(np.column_stack([x, np.roll(x, 1)])).run()
>>> transfer.information_.round(1).tolist()
[[nan, 1.0], [0.0, nan]]
>>> xor = [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
>>> round(InformationAnalysis('o-information').check().prepare(xor).run().information_, 6)
-1.0
Source code in src/skcomplex/analysis/_information.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def __init__(
    self,
    kind: str = 'mutual',
    variables: Sequence[str] | None = None,
    *,
    symbols: str | None = None,
    bins: int = 2,
    order: int = 3,
    delay: int = 1,
    history: int = 1,
    lag: int = 1,
    max_delay: int = 100,
    dimension: int = 8,
    estimator: str = 'plugin',
    k: int = 3,
    base: float = 2.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.symbols = symbols
    self.bins = bins
    self.order = order
    self.delay = delay
    self.history = history
    self.lag = lag
    self.max_delay = max_delay
    self.dimension = dimension
    self.estimator = estimator
    self.k = k
    self.base = base

InvariantAnalysis(kind='conservation', *, expressions=(), symmetry=None, samples=16, random_state=None)

Bases: BaseAnalysis

Poisson brackets, conserved quantities, and symmetries of a system, with the Noether charge of a symmetry.

The canonical pairs and the Hamiltonian H are those of the system's HamiltonianInteractions, whose Hamiltonians sum, and the Lagrangian L is the sum of those of its EulerLagrangeInteractions. The expressions are ObservableSpecs whose expression is a function of the namespace of the system, where s.<element>.<state> is the symbol of a state, or a symbolic expression of those symbols. The kinds:

  • 'brackets': the Poisson bracket {f, g} = sum df/dq dg/dp - df/dp dg/dq of every pair of expressions, summed over the canonical pairs.
  • 'conservation': the rate of change df/dt of every expression, which is diff(f, t) + {f, H} for a system whose dynamics all come from Hamiltonian interactions, and the Lie derivative along the vector field of its equations otherwise. An expression is conserved when its rate simplifies to zero, or, when simplification does not decide, when the rate vanishes within a tolerance at random states with the parameters at their values.
  • 'symmetry': whether the Hamiltonian, or else the Lagrangian, is unchanged to first order by the variation of a SymmetrySpec, and the Noether charge of the symmetry, sum p dq for a Hamiltonian and sum dL/dq' dq for a Lagrangian, with the variation of the velocities dq' = sum_j d(dq)/dq_j q_j'.

Parameters:

Name Type Description Default
kind str

The analysis to run, one of the kinds above.

'conservation'
expressions Sequence[ObservableSpec]

The expressions to bracket or to check.

()
symmetry SymmetrySpec | None

The symmetry to check.

None
samples int

The number of random states of the numeric check.

16
random_state int | Generator | None

The seed or generator of the random states.

None

Attributes:

Name Type Description
problem_

The compiled problem.

system_

The system.

dynamics_

The first-order equations of the system, with the values of its parameters.

namespace_

The namespace of the symbols of the states and parameters of the system.

brackets_

A mapping from every pair of labels to the Poisson bracket of their expressions.

rates_

A mapping from every label to the rate of change of its expression.

conserved_

A mapping from every label to whether its expression is conserved.

methods_

A mapping from every label to the method that decided, 'symbolic' or 'numeric'.

invariant_

Whether the Hamiltonian or the Lagrangian is unchanged to first order by the symmetry.

remainder_

The first-order change of the Hamiltonian or the Lagrangian under the symmetry, zero when it is invariant.

charge_

The Noether charge of the symmetry.

Raises:

Type Description
AnalysisError

The source is not a system, the system is not an ODE, an expression or the symmetry is missing or invalid, or the system has no Hamiltonian interaction for brackets, or neither a Hamiltonian nor an Euler-Lagrange interaction for a symmetry.

Examples:

>>> from typing import Annotated
>>> from skcomplex.analysis import InvariantAnalysis
>>> from skcomplex.core.elements import Element
>>> from skcomplex.core.interactions import HamiltonianInteraction
>>> from skcomplex.core.system import System
>>> from skcomplex.specs import ObservableSpec, StateSpec
>>> class OscillatorElement(Element):
...     def __init__(self, label, q: Annotated[float, StateSpec()] = 1.0, p: Annotated[float, StateSpec()] = 0):
...         super().__init__(label)
...         self.q = q
...         self.p = p
>>> energy = lambda s: (s.target.p**2 + s.target.q**2) / 2
>>> system = System(
...     elements=[OscillatorElement('mass')],
...     interactions=[HamiltonianInteraction('spring', target='mass', hamiltonian=energy)],
... )
>>> expressions = [
...     ObservableSpec('energy', lambda s: s.mass.q**2 + s.mass.p**2),
...     ObservableSpec('position', lambda s: s.mass.q),
... ]
>>> analysis = InvariantAnalysis(expressions=expressions).check().prepare(system).run()
>>> analysis.conserved_
{'energy': True, 'position': False}
>>> analysis.rates_['position']
p_mass
Source code in src/skcomplex/analysis/_invariants.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def __init__(
    self,
    kind: str = 'conservation',
    *,
    expressions: Sequence[ObservableSpec] = (),
    symmetry: SymmetrySpec | None = None,
    samples: int = 16,
    random_state: int | np.random.Generator | None = None,
) -> None:
    self.kind = kind
    self.expressions = expressions
    self.symmetry = symmetry
    self.samples = samples
    self.random_state = random_state

IrreversibilityAnalysis(kind='entropy-production', variables=None, *, rates=None, duration=None, drive=1.0, intensity=1.0, dt=1.0, n_states=None, significance=0.05, bins=40, entropy=None, resamples=200, random_state=None, unperturbed=None, field=1.0, correlation=None)

Bases: BaseAnalysis

Irreversibility of jump and Langevin dynamics, of one kind.

The kinds read a rate matrix, jump paths, Langevin paths, samples, or ensembles:

  • 'entropy-production': the stationary entropy production rate of a jump process from its rate matrix, Σ_{i<j} (p_i k_ij - p_j k_ji) ln(p_i k_ij / (p_j k_ji)), 0 under detailed balance.
  • 'jump-entropy-production': the entropy every jump path produces in the medium, Σ ln(k_ij / k_ji) over its jumps with the rates, and the mean rate over the duration of the paths.
  • 'langevin-entropy-production': the entropy every overdamped Langevin path produces, the work of the drive times the intensity, b ∫ F(x) ∘ dx, and the mean rate.
  • 'count-entropy-production': the entropy production rate of a discrete path from its transition counts alone, Σ_{i<j} (N_ij - N_ji) ln(N_ij / N_ji) per unit time.
  • 'detailed-balance': the chi-squared test of detailed balance on the transition counts of discrete paths.
  • 'fluctuation-theorem': the log ratio of the probabilities of producing s and -s from samples of the entropy, and its slope against s, 1 when the theorem holds.
  • 'uncertainty-relation': the squared relative fluctuation Var(J) / ⟨J⟩² of samples of an integrated current times the mean entropy produced over the same duration, at least 2 (Barato and Seifert 2015).
  • 'response': the integrated response chi(t) = (<x(t)>_h - <x(t)>_0) / h of an ensemble with the field against the unperturbed ensemble.
  • 'fluctuation-dissipation': the response, and the slope of chi / b against C(t_w, t_w) - C(t, t_w) of the two-time correlation over the same lags, 1 in equilibrium and below 1 in an aging system.

Parameters:

Name Type Description Default
kind str

The measure to compute, one of the kinds above.

'entropy-production'
variables Sequence[str] | None

The discrete state or the position of a trajectory to read, or None for the first one.

None
rates ArrayLike | None

The rate of every jump, one row per origin.

None
duration float | None

The duration of every jump path, or None for the duration of a trajectory.

None
drive Callable[[Array], Array] | float

The drift along a Langevin path, a function of position or a constant.

1.0
intensity float

The factor b of the work of the drive in the entropy, and one over the factor of the response in the fluctuation-dissipation ratio, as in the acceptance exp(-intensity * change) of sampling.

1.0
dt float

The time between frames of Langevin paths, or between records of a discrete path.

1.0
n_states int | None

The number of states, or None for the largest state plus one.

None
significance float

The level below which the p-value rejects detailed balance.

0.05
bins int

The number of bins of entropy, symmetric about 0.

40
entropy float | None

The mean entropy produced along a path over the duration of the currents.

None
resamples int

The number of bootstrap resamples of the standard error of the uncertainty product.

200
random_state int | None

The seed of the bootstrap.

None
unperturbed ArrayLike | None

The ensemble without the field, one row per run and one column per frame.

None
field float

The strength of the field.

1.0
correlation ArrayLike | None

The two-time correlation over lags from 0.

None

Attributes:

Name Type Description
values_

The rate matrix, the paths one row per path, or the samples the kind reads.

duration_

The duration of the jump paths, the setting or the duration of a trajectory.

entropy_production_

The entropy production per unit time in units of k_B, of the entropy production kinds.

entropies_

The entropy produced along every path, of 'jump-entropy-production' and 'langevin-entropy-production'.

currents_

The net number of transitions from every state to every other per step, of 'detailed-balance'.

statistic_

The statistic Σ (N_ij - N_ji)² / (N_ij + N_ji) over pairs of states, of 'detailed-balance'.

p_value_

The chi-squared p-value of the statistic under detailed balance, of 'detailed-balance'.

balanced_

Whether the p-value exceeds the significance level, of 'detailed-balance'.

centres_

The centres of the bins of entropy whose mirror bin is also populated, of 'fluctuation-theorem'.

log_ratios_

ln P(s) / P(-s) at every centre, of 'fluctuation-theorem'.

slope_

The slope of the log ratio against the entropy, of 'fluctuation-theorem'.

mean_

The mean of the current over the paths, of 'uncertainty-relation'.

variance_

The variance of the current over the paths, of 'uncertainty-relation'.

product_

The squared relative fluctuation of the current times the entropy, of 'uncertainty-relation'.

error_

The standard error of the product from bootstrap resamples of the paths, of 'uncertainty-relation'.

holds_

Whether the product is at least 2 within two standard errors, of 'uncertainty-relation'.

response_

The integrated response at every frame, of 'response' and 'fluctuation-dissipation'.

fluctuation_dissipation_ratio_

The fluctuation-dissipation ratio, of 'fluctuation-dissipation'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import IrreversibilityAnalysis
>>> ring = 2 * np.roll(np.eye(5), 1, axis=1) + np.roll(np.eye(5), -1, axis=1)
>>> production = IrreversibilityAnalysis().check().prepare(ring).run().entropy_production_
>>> bool(abs(production - np.log(2)) < 1e-12)
True
>>> jumps = IrreversibilityAnalysis('jump-entropy-production', rates=[[0, 2], [1, 0]], duration=3.0)
>>> jumps.check().prepare([0, 1, 0, 1]).run().entropies_.round(3).tolist()
[0.693]
>>> IrreversibilityAnalysis('detailed-balance').check().prepare([0, 1, 2] * 1000).run().balanced_
False
Source code in src/skcomplex/analysis/_irreversibility.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def __init__(
    self,
    kind: str = 'entropy-production',
    variables: Sequence[str] | None = None,
    *,
    rates: npt.ArrayLike | None = None,
    duration: float | None = None,
    drive: Callable[[Array], Array] | float = 1.0,
    intensity: float = 1.0,
    dt: float = 1.0,
    n_states: int | None = None,
    significance: float = 0.05,
    bins: int = 40,
    entropy: float | None = None,
    resamples: int = 200,
    random_state: int | None = None,
    unperturbed: npt.ArrayLike | None = None,
    field: float = 1.0,
    correlation: npt.ArrayLike | None = None,
) -> None:
    self.kind = kind
    self.variables = variables
    self.rates = rates
    self.duration = duration
    self.drive = drive
    self.intensity = intensity
    self.dt = dt
    self.n_states = n_states
    self.significance = significance
    self.bins = bins
    self.entropy = entropy
    self.resamples = resamples
    self.random_state = random_state
    self.unperturbed = unperturbed
    self.field = field
    self.correlation = correlation

KramersMoyalAnalysis(variables=None, *, dt=1.0, bins=50, bandwidth=None)

Bases: BaseAnalysis

Kramers-Moyal drift and diffusion of one series, estimated with kernels from its increments by kramersmoyal.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variable of a trajectory to read, or None for the only scalar series.

None
dt float

The time between samples.

1.0
bins int

The number of bins of values.

50
bandwidth float | None

The width of the Epanechnikov kernel, or None for Silverman's rule.

None

Attributes:

Name Type Description
values_

The series.

positions_

The centres of the bins of values.

drift_

The first coefficient <dx | x> / dt in every bin.

diffusion_

The second coefficient <dx² | x> / (2 dt) in every bin, so dx = a dt + sqrt(2 D) dW gives D.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import KramersMoyalAnalysis
>>> rng = np.random.default_rng(0)
>>> x = np.zeros(100_000)
>>> for t in range(1, x.size):
...     x[t] = x[t - 1] - x[t - 1] * 0.01 + np.sqrt(0.02) * rng.standard_normal()
>>> analysis = KramersMoyalAnalysis(dt=0.01, bins=21).check().prepare(x).run()
>>> round(float(analysis.diffusion_[10]), 1)
1.0
Source code in src/skcomplex/analysis/_kramers_moyal.py
57
58
59
60
61
62
63
64
65
66
67
68
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    dt: float = 1.0,
    bins: int = 50,
    bandwidth: float | None = None,
) -> None:
    self.variables = variables
    self.dt = dt
    self.bins = bins
    self.bandwidth = bandwidth

LargeDeviationAnalysis(kind='tilted', variables=None, *, weights=None, tilts=1.0, values=None, block=10, dt=1.0, observable=None, population=1000, interval=1.0, until=20.0, random_state=None, simulation=None, bins=41, value_range=None, log_weights=None)

Bases: BaseAnalysis

Large deviations of a time-additive observable or of an order parameter, of one kind.

The kinds estimate the scaled cumulant generating function lambda(k) at the tilts, or the rate function I:

  • 'tilted': exactly, for a counting observable of a jump process that adds weights[i, j] at every jump from i to j, as the largest eigenvalue of the tilted generator rates * exp(k weights) - diag(escape rates) of the rate matrix. A diagonal rate counts jumps that keep the state, such as the events of a Poisson process.
  • 'block': from blocks of block steps of one long run of the increments of the observable, as ln <exp(k A)> / (block dt).
  • 'cloning': by the fixed-population cloning of Giardinà, Kurchan, and Peliti, which advances every clone of a system by interval through the simulation's steppers, weights it by exp(k A) for the increment A of the observable, and resamples the population by the weights. The system is compiled once, and every segment restarts the clock at zero, so the laws must not depend on time explicitly. A warning names a population whose effective size falls below a tenth of the clones or below 10 clones.
  • 'histogram': the rate function of P_N(m) ≍ exp(-N I(m)) from the samples of an order parameter at several sizes N, fitting -ln P_N linearly in N in every bin.
  • 'reweight': samples reweighted to another law by the logarithms of the likelihood ratios, such as beta dh M.

With values, the generating function kinds also give the rate function, its Legendre transform I(a) = max_k (k a - lambda(k)) over the tilts.

Parameters:

Name Type Description Default
kind str

The estimate, one of the kinds above.

'tilted'
variables Sequence[str] | None

The series of a trajectory whose values are the increments of 'block', or None for the first one.

None
weights ArrayLike | None

The increment of the observable at every jump of 'tilted'.

None
tilts float | ArrayLike

The values of the tilt k.

1.0
values ArrayLike | None

The values a of the observable per unit time of the rate function, or None for none.

None
block int

The number of steps of every block.

10
dt float

The time of one step.

1.0
observable str | Callable[[Trajectory], float] | None

The dotted name of a state whose increment over each interval is the observable, such as a counter, or a function of the trajectory of one interval that returns the increment.

None
population int

The number of clones.

1000
interval float

The time between two selection steps.

1.0
until float

The simulated time of every clone, a multiple of the interval.

20.0
random_state int | None

The seed of the clones and of the selection.

None
simulation Mapping[str, object] | None

Other settings of Simulation, such as solver or dt.

None
bins int | ArrayLike

The number of bins or their edges of 'histogram'.

41
value_range tuple[float, float] | None

The range of the bins, or None for the range of all samples.

None
log_weights ArrayLike | None

The logarithm of the ratio of the new to the old probability of every sample.

None

Attributes:

Name Type Description
rates_

The rate matrix of 'tilted'.

increments_

The increments of 'block'.

system_

The system or its compiled problem of 'cloning'.

samples_

The samples at every size of 'histogram'.

observations_

The samples of 'reweight'.

tilts_

The tilts of the generating function.

scgf_

The scaled cumulant generating function at every tilt.

effective_population_

The smallest effective number of clones over the selection steps at every tilt, (Σ w)² / Σ w².

values_

The values of the rate function, the centres of the bins of 'histogram'.

rate_function_

The rate function at every value, 0 at its minimum and nan where a size left the bin empty for 'histogram'.

weights_

The normalized weight of every sample of 'reweight'.

mean_

The weighted mean of the samples of 'reweight'.

effective_size_

The effective number of samples 1 / Σ w² of 'reweight'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import LargeDeviationAnalysis
>>> tilted = LargeDeviationAnalysis(weights=[[1.0]], tilts=[0.0, 1.0])
>>> tilted.check().prepare([[2.0]]).run().scgf_.round(4).tolist()
[0.0, 3.4366]
>>> tilts = np.linspace(-3, 3, 6001)
>>> rate = LargeDeviationAnalysis(weights=[[1.0]], tilts=tilts, values=[3.0])
>>> round(float(rate.check().prepare([[2.0]]).run().rate_function_[0]), 4)
0.2164
>>> LargeDeviationAnalysis('reweight', log_weights=[0.0, 0.0]).check().prepare([-1.0, 1.0]).run().mean_
0.0
Source code in src/skcomplex/analysis/_large_deviations.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def __init__(
    self,
    kind: str = 'tilted',
    variables: Sequence[str] | None = None,
    *,
    weights: npt.ArrayLike | None = None,
    tilts: float | npt.ArrayLike = 1.0,
    values: npt.ArrayLike | None = None,
    block: int = 10,
    dt: float = 1.0,
    observable: str | Callable[[Trajectory], float] | None = None,
    population: int = 1000,
    interval: float = 1.0,
    until: float = 20.0,
    random_state: int | None = None,
    simulation: Mapping[str, object] | None = None,
    bins: int | npt.ArrayLike = 41,
    value_range: tuple[float, float] | None = None,
    log_weights: npt.ArrayLike | None = None,
) -> None:
    self.kind = kind
    self.variables = variables
    self.weights = weights
    self.tilts = tilts
    self.values = values
    self.block = block
    self.dt = dt
    self.observable = observable
    self.population = population
    self.interval = interval
    self.until = until
    self.random_state = random_state
    self.simulation = simulation
    self.bins = bins
    self.value_range = value_range
    self.log_weights = log_weights

LinearizationAnalysis(controls=None, outputs=None, at=None, *, state_weights=None, control_weights=None)

Bases: BaseAnalysis

Linear state-space model x' = A x + B u, y = C x + D u of a system near an operating point.

The matrices are the Jacobians of the rates and of the outputs, derived symbolically and evaluated at the operating point. The controls are adaptive parameters, named by their dotted names, and the outputs are states or controls. The system is controllable when the rank of [B, AB, ..., A^(n-1) B] equals the number of states, and observable when the rank of [C; CA; ...; C A^(n-1)] does. With weights of the states and the controls, the gain K of the linear-quadratic regulator u = -K x minimises ∫ (x' Q x + u' R u) dt over an infinite horizon, from the continuous algebraic Riccati equation that SciPy solves.

Parameters:

Name Type Description Default
controls Sequence[str] | None

The dotted names of the adaptive parameters that are inputs, or None for the bounded ones.

None
outputs Sequence[str] | None

The dotted names of the states and controls that are outputs, or None for every state.

None
at Mapping[str, float] | None

The values of states and controls at the operating point, by dotted name, or None for the initial values.

None
state_weights ArrayLike | None

The weight Q of the states in the cost of the regulator, a symmetric positive semidefinite matrix, or None for no regulator.

None
control_weights ArrayLike | None

The weight R of the controls in the cost of the regulator, a symmetric positive definite matrix, or None for no regulator.

None

Attributes:

Name Type Description
problem_

The compiled problem.

A_

The state matrix, the Jacobian of the rates by the states.

B_

The input matrix, the Jacobian of the rates by the controls.

C_

The output matrix, the Jacobian of the outputs by the states.

D_

The feedthrough matrix, the Jacobian of the outputs by the controls.

states_

The names of the state components, in the order of the rows of A_.

controls_

The names of the controls, in the order of the columns of B_.

outputs_

The names of the outputs, in the order of the rows of C_.

controllability_

The rank of the controllability matrix.

observability_

The rank of the observability matrix.

gain_

The gain of the regulator, one row per control and one column per state, or None without weights.

Examples:

>>> from typing import Annotated
>>> from skcomplex.analysis import LinearizationAnalysis
>>> from skcomplex.core.elements import Element
>>> from skcomplex.core.laws import RateLaw
>>> from skcomplex.specs import ParameterSpec, StateSpec
>>> from skcomplex.core.system import System
>>> class CartElement(Element):
...     def __init__(
...         self,
...         label,
...         x: Annotated[float, StateSpec()] = 0.0,
...         v: Annotated[float, StateSpec()] = 0.0,
...         force: Annotated[float, ParameterSpec(adaptive=True, bounds=(-1, 1))] = 0.0,
...     ):
...         super().__init__(label)
...         self.x = x
...         self.v = v
...         self.force = force
...
...     def _get_laws(self, namespace):
...         return [RateLaw(namespace.x, namespace.v), RateLaw(namespace.v, namespace.force)]
>>> analysis = LinearizationAnalysis().check().prepare(System(elements=[CartElement('cart')])).run()
>>> analysis.B_.tolist(), analysis.controllability_
([[0.0], [1.0]], 2)
Source code in src/skcomplex/analysis/_linearization.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def __init__(
    self,
    controls: Sequence[str] | None = None,
    outputs: Sequence[str] | None = None,
    at: Mapping[str, float] | None = None,
    *,
    state_weights: npt.ArrayLike | None = None,
    control_weights: npt.ArrayLike | None = None,
) -> None:
    self.controls = controls
    self.outputs = outputs
    self.at = at
    self.state_weights = state_weights
    self.control_weights = control_weights

LocalInformationAnalysis(kind='entropy', variables=None, *, history=1, lag=1, estimator='plugin', base=2.0)

Bases: BaseAnalysis

Information of the series of a source sample by sample, whose mean over the samples is the global measure.

The kinds measure every series or every pair of series:

  • 'entropy': the local entropy -log p(x) of every sample of every series.
  • 'mutual': the local mutual information log p(x, y) / (p(x) p(y)) of every sample of every pair of series, negative where the series mislead about each other.
  • 'transfer': the local transfer entropy log p(y_t | y_past, x_{t-lag}) / p(y_t | y_past) from every series to every series at every time from max(history, lag) on.

The probabilities are the frequencies of symbols, or the densities of a fitted Gaussian.

Parameters:

Name Type Description Default
kind str

The local information to compute, one of the kinds above.

'entropy'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series.

None
history int

The number of past target values of the transfer entropy.

1
lag int

The delay of the source of the transfer entropy.

1
estimator str

'plugin' for the frequencies of symbols, or 'gaussian' for the densities of a fitted Gaussian.

'plugin'
base float

The base of the logarithm, 2 for bits.

2.0

Attributes:

Name Type Description
values_

The series, one per column.

labels_

The label of every series.

information_

One row of local entropies per series for 'entropy', and for 'mutual' and 'transfer' one row of local values from the series of every first index to the series of every second index, nan from a series to itself.

Examples:

>>> from skcomplex.analysis import LocalInformationAnalysis
>>> local = LocalInformationAnalysis('mutual').check().prepare([[0, 0], [0, 0], [1, 1], [1, 0]]).run()
>>> local.information_[0, 1].round(3).tolist()
[0.415, 0.415, 1.0, -0.585]
Source code in src/skcomplex/analysis/_local_information.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def __init__(
    self,
    kind: str = 'entropy',
    variables: Sequence[str] | None = None,
    *,
    history: int = 1,
    lag: int = 1,
    estimator: str = 'plugin',
    base: float = 2.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.history = history
    self.lag = lag
    self.estimator = estimator
    self.base = base

LoopAnalysis(kind='loops', *, at=None, frame=-1, quantity=None)

Bases: BaseAnalysis

Feedback loops of the signed dependency graph of a system, their dominance in time, or its flow matrix.

The signed dependency graph of a system has the state variables as nodes and an edge from x_j to x_i when the rate of x_i depends on x_j, with the sign of the partial derivative df_i/dx_j, the entry of the Jacobian. The rate of a map is the change of its state in one step, its next value minus itself. When a system declares flows, the flows are nodes too: an edge goes from each state to every flow whose amount depends on it, with the sign of the partial derivative, and from each flow to its sink with sign + and to its source with sign -, so the graph shows the path of every influence through the flows. Rate laws of single elements give the edges into the states they change, and the Jacobian, as StabilityAnalysis derives it with the parameters at their values, gives the edges into every other state, such as the members of a group or the velocity of a state of second order. Delays and smoothings pass their input with gain one.

The kinds read a system or a trajectory:

  • 'loops': the signed dependency graph and its feedback loops, the elementary cycles of the graph found with Johnson's algorithm. The gain of a loop is the product of the partial derivatives along it, and its polarity the sign of the gain.
  • 'dominance': the gains of the loops along a trajectory, at every frame, and the dominant loop, with the largest absolute gain, at every frame.
  • 'flow-matrix': the transaction-flow matrix of one frame of a trajectory, with flows as rows and holders as columns. Every row holds the outflow of its flow at the source, negative, and the inflow at the sink, positive, so every row sums to zero. Flows of a group sum over its members. The matrix books the amounts the flows move between holders, which the Jacobian does not hold, so it is defined only for systems that declare flows.

Parameters:

Name Type Description Default
kind str

The analysis of the loops, one of the kinds above.

'loops'
at ArrayLike | None

The state vector the signs of 'loops' are evaluated at, or None for the initial state.

None
frame int

The index of the frame of the flow matrix.

-1
quantity str | None

The quantity whose flows form the rows of the flow matrix, or None for every flow.

None

Attributes:

Name Type Description
problem_

The compiled problem.

trajectory_

The trajectory of 'dominance' and 'flow-matrix'.

graph_

The signed dependency graph from each state or flow to every node whose amount or rate depends on it, with the edge attributes sign and gain, for 'loops'. The members of a group and the components of a vector that the Jacobian describes are nodes of their own, named like species.x[0].

loops_

The nodes of every loop, in the order of their influence, starting at its first state by name, or at its first node by name when it holds no state.

polarities_

The polarity of every loop, at the first frame for 'dominance': 'reinforcing' for a positive product of edge signs, 'balancing' for a negative one, or 'neutral' when an edge has no influence.

gains_

The gain of every loop, with one row per frame for 'dominance'.

times_

The time of every frame of 'dominance'.

dominant_

The index of the loop with the largest absolute gain at every frame of 'dominance'.

flows_

The paths of the flows of the flow matrix.

holders_

The states and environments the flows connect.

matrix_

The outflow, negative, and the inflow, positive, of every flow for every holder.

Examples:

>>> from skcomplex.analysis import LoopAnalysis
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models import create
>>> LoopAnalysis().check().prepare(create('bass_diffusion')).run().polarities_
['reinforcing', 'balancing']
>>> trajectory = Simulation(create('bass_diffusion'), dt=1).check().prepare().run(2).trajectory_
>>> LoopAnalysis('dominance').check().prepare(trajectory).run().gains_.shape
(3, 2)
Source code in src/skcomplex/analysis/_loops.py
273
274
275
276
277
278
279
280
281
282
283
284
def __init__(
    self,
    kind: str = 'loops',
    *,
    at: npt.ArrayLike | None = None,
    frame: int = -1,
    quantity: str | None = None,
) -> None:
    self.kind = kind
    self.at = at
    self.frame = frame
    self.quantity = quantity

LyapunovAnalysis(kind='spectrum', variables=None, *, n_exponents=None, until=None, dt=None, transient=None, parameters=None, dimension=3, delay=1, min_separation=None, trajectory_length=20, skip=0)

Bases: BaseAnalysis

Lyapunov exponents of a system from its tangent dynamics, or the largest exponent of recorded series.

The kinds read a system or data:

  • 'spectrum': the largest n_exponents exponents of a flow or a map from the system's initial state. The tangent dynamics use the symbolic Jacobian, in which abs(u) has the derivative sign(u), and the compiled functions take the parameters as an argument, so a sweep over parameter values compiles them once. Tangent vectors are re-orthonormalised by QR after every step, as in the Benettin algorithm, and the exponents are the average logarithmic growth of the diagonal of R. The Kaplan-Yorke dimension and the Kolmogorov-Sinai entropy, by Pesin's identity the sum of the positive exponents, follow from the spectrum. An array of exponents passes through as the spectrum.
  • 'largest': Rosenstein's estimate of the largest exponent of every series, from the divergence of neighbours of its delay embedding.

A positive largest exponent indicates chaos.

Parameters:

Name Type Description Default
kind str

The exponents to compute, one of the kinds above.

'spectrum'
variables Sequence[str] | None

The variables of a trajectory to read for 'largest', or None for every scalar series.

None
n_exponents int | None

The number of exponents of the spectrum, or None for all of them.

None
until float | None

The time averaged over, or the number of iterations of a map, or None for 1000 for flows and 10,000 for maps.

None
dt float | None

The fixed step of the RK4 integration of flows, or the time between samples of a series, or None for 0.01 and 1.

None
transient float | None

The time, or the number of iterations, run before averaging, or None for a tenth of until.

None
parameters Mapping[str, float] | None

Values of parameters by dotted path that replace the system's values.

None
dimension int

The dimension of the delay embedding.

3
delay int

The delay of the embedding in steps.

1
min_separation int | None

The fewest steps between neighbours, or None for the mean period from the spectrum.

None
trajectory_length int

The number of steps over which the divergence of neighbours is followed and fitted.

20
skip int

The number of first steps of the divergence left out of the fit, while neighbours align with the unstable direction.

0

Attributes:

Name Type Description
problem_

The compiled problem of the spectrum of a system, or None for an array of exponents.

exponents_

The exponents of the spectrum in decreasing order, per unit of time for flows and per iteration for maps.

kaplan_yorke_dimension_

The Kaplan-Yorke dimension of the spectrum, with j the largest number of exponents whose sum is nonnegative.

kolmogorov_sinai_entropy_

The entropy rate of the dynamics in nats per unit time, the sum of the positive exponents.

values_

The series of 'largest', one per column.

labels_

The label of every series of 'largest'.

largest_exponent_

The largest exponent per unit time of every series of 'largest'.

Examples:

>>> from skcomplex.analysis import LyapunovAnalysis
>>> from skcomplex.models.mathematics.dynamical_systems import logistic_map
>>> analysis = LyapunovAnalysis(until=20000).check().prepare(logistic_map(r=4.0)).run()
>>> float(analysis.exponents_[0].round(2))
0.69
>>> round(LyapunovAnalysis().check().prepare([0.906, 0.0, -14.572]).run().kaplan_yorke_dimension_, 2)
2.06
Source code in src/skcomplex/analysis/_lyapunov.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def __init__(
    self,
    kind: str = 'spectrum',
    variables: Sequence[str] | None = None,
    *,
    n_exponents: int | None = None,
    until: float | None = None,
    dt: float | None = None,
    transient: float | None = None,
    parameters: Mapping[str, float] | None = None,
    dimension: int = 3,
    delay: int = 1,
    min_separation: int | None = None,
    trajectory_length: int = 20,
    skip: int = 0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.n_exponents = n_exponents
    self.until = until
    self.dt = dt
    self.transient = transient
    self.parameters = parameters
    self.dimension = dimension
    self.delay = delay
    self.min_separation = min_separation
    self.trajectory_length = trajectory_length
    self.skip = skip

MarkovAnalysis(kind='transition-matrix', variables=None, *, matrix=None, n_states=None, lag=1, lags=(1,), n_timescales=1, n_metastable=None, reversible=True, dt=1.0)

Bases: BaseAnalysis

Markov chain of discrete paths, or of a transition or rate matrix, of one kind.

The kinds read the integer states of one path, or one row per path, or a matrix with matrix:

  • 'transition-matrix': the transitions after lag steps counted along the paths and normalized by row.
  • 'stationary': the stationary law pi of the matrix, pi P = pi or pi Q = 0 for rates.
  • 'spectral-gap': the gap 1 - |lambda_2|, or -Re lambda_2 for rates, and the relaxation time of every mode.
  • 'state-model': the maximum-likelihood Markov state model of the largest connected set, with its metastable sets found by PCCA+.
  • 'timescales': the slowest implied timescales of Markov state models at every lag of lags, which do not depend on the lag for a Markovian discretization.

Parameters:

Name Type Description Default
kind str

The quantity to compute, one of the kinds above.

'transition-matrix'
variables Sequence[str] | None

The discrete state of a trajectory to read, or None for the first one.

None
matrix str | None

None to read paths, 'transition' to read the source as a transition matrix, or 'rate' to read it as the rate matrix of a continuous-time chain, whose diagonal is ignored.

None
n_states int | None

The number of states of the paths, or None for the largest state plus one.

None
lag int

The lag time in steps.

1
lags Sequence[int]

The lag times in steps of the implied timescales.

(1,)
n_timescales int

The number of implied timescales.

1
n_metastable int | None

The number of metastable sets PCCA+ groups the states into, or None for none.

None
reversible bool

Whether the state models obey detailed balance, which PCCA+ needs.

True
dt float

The time of one step.

1.0

Attributes:

Name Type Description
path_

The integer states of the paths, one row per path, read without a matrix.

matrix_

The matrix read with matrix.

counts_

The transition counts, one row per origin, of 'transition-matrix'.

transition_matrix_

The probability of every destination after one lag from every origin, of 'transition-matrix' and 'state-model'.

stationary_

The stationary law, of 'stationary' and 'state-model'.

spectral_gap_

The spectral gap, of 'spectral-gap'.

relaxation_times_

-dt / ln |lambda|, or -1 / Re lambda for rates, of every mode slowest first, of 'spectral-gap'.

states_

The states of the largest connected set, which index the rows of the matrix, of 'state-model'.

timescales_

The implied timescales -lag dt / ln |lambda_i| slowest first, of 'state-model', and one row per lag of 'timescales'.

metastable_sets_

The states of every metastable set, empty without a number of sets, of 'state-model'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import MarkovAnalysis
>>> MarkovAnalysis().check().prepare([0, 1, 0, 1, 1]).run().transition_matrix_.tolist()
[[0.0, 1.0], [0.5, 0.5]]
>>> stationary = MarkovAnalysis('stationary', matrix='transition')
>>> stationary.check().prepare([[0.5, 0.5], [0.25, 0.75]]).run().stationary_.round(3).tolist()
[0.333, 0.667]
>>> path = np.random.default_rng(0).integers(0, 2, 10_000)
>>> MarkovAnalysis('state-model').check().prepare(path).run().transition_matrix_.round(1).tolist()
[[0.5, 0.5], [0.5, 0.5]]
Source code in src/skcomplex/analysis/_markov.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def __init__(
    self,
    kind: str = 'transition-matrix',
    variables: Sequence[str] | None = None,
    *,
    matrix: str | None = None,
    n_states: int | None = None,
    lag: int = 1,
    lags: Sequence[int] = (1,),
    n_timescales: int = 1,
    n_metastable: int | None = None,
    reversible: bool = True,
    dt: float = 1.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.matrix = matrix
    self.n_states = n_states
    self.lag = lag
    self.lags = lags
    self.n_timescales = n_timescales
    self.n_metastable = n_metastable
    self.reversible = reversible
    self.dt = dt

ModeDecompositionAnalysis(variables=None, *, rank=None, dictionary=None, validation=0.2, horizon=0, dt=1.0, label='surrogate')

Bases: BaseAnalysis

Linear system fitted to series by dynamic mode decomposition, computed with PyDMD.

The decomposition fits the exact DMD of the one-sample map g(t + dt) = A g(t) to the pairs of consecutive samples of every run. With a dictionary, g holds the observables the dictionary computes from the variables, which is extended DMD, and the dictionary should keep the variables among its observables. The fitted system follows the rate law dg/dt = L g with L = Phi diag(ln(mu) / dt) Phi^+ under continuous time and starts at the first sample of the first run. The forecast sums the modes from the last sample of the last run.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of trajectories, or None for every scalar state or observable.

None
rank float | None

The rank of the truncated singular value decomposition: a number of modes, 0 for the optimal hard threshold, a share of the energy below 1, or None to keep every singular value.

None
dictionary TransformerMixin | None

A scikit-learn transformer that computes the observables, such as PolynomialFeatures(2), or None for the variables themselves.

None
validation float

The share of the samples of every run held out at its end to measure the validation error.

0.2
horizon int

The number of samples to forecast after the last sample of the last run.

0
dt float

The time between the rows of an array.

1.0
label str

The label of the surrogate element.

'surrogate'

Attributes:

Name Type Description
samples_

The series of every run, the names of the variables, and the step, set by prepare.

system_

The system of one surrogate element under continuous time, whose states follow dg/dt = L g.

labels_

The variables, or the observables of the dictionary, in the order of the states.

states_

The names of the states of the surrogate element.

last_state_

The values of the states at the last sample of the last run, where the forecast starts.

dt_

The time between the samples it was fitted to.

training_error_

The relative error of the predicted increments over one sample on the training samples.

validation_error_

The relative error on the held-out samples, NaN when none are held out.

eigenvalues_

The continuous eigenvalues ln(mu) / dt of the modes, from the eigenvalues mu of the one-sample map.

modes_

The modes, one column per mode and one row per state.

amplitudes_

The amplitudes of the modes at the last sample of the last run.

generator_

The matrix L of the rate law, one row and one column per state.

forecast_

The states at the horizon samples after the last one, one row per sample.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import ModeDecompositionAnalysis
>>> times = np.arange(0.0, 10.0, 0.1)
>>> values = np.column_stack([np.cos(times), np.sin(times)])
>>> analysis = ModeDecompositionAnalysis(['x', 'y'], horizon=5, dt=0.1).check().prepare(values).run()
>>> np.sort(analysis.eigenvalues_.imag).round(6).tolist()
[-1.0, 1.0]
>>> bool(np.allclose(analysis.forecast_[:, 0], np.cos(times[-1] + 0.1 * np.arange(1, 6))))
True
Source code in src/skcomplex/analysis/_mode_decomposition.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    rank: float | None = None,
    dictionary: TransformerMixin | None = None,
    validation: float = 0.2,
    horizon: int = 0,
    dt: float = 1.0,
    label: str = 'surrogate',
) -> None:
    self.variables = variables
    self.rank = rank
    self.dictionary = dictionary
    self.validation = validation
    self.horizon = horizon
    self.dt = dt
    self.label = label

MotifAnalysis(kind='counts', *, size=3, null='switch', samples=100, random_state=None)

Bases: BaseAnalysis

Motifs of three or four nodes of the network of every frame of a trajectory, or of a graph.

The kinds count or score motifs:

  • 'counts': the number of connected induced subgraphs of every kind. Directed triads carry the names of the triadic census, such as '030T' for the feed-forward loop. Undirected motifs are named by their shape, and directed motifs of four nodes by their smallest adjacency code.
  • 'z-scores': the counts against random graphs that keep the degrees, (count - null_mean) / null_std of every motif, nan where the null count never varies.

Parameters:

Name Type Description Default
kind str

'counts' or 'z-scores'.

'counts'
size int

The number of nodes of the motifs, 3 or 4.

3
null str

'switch' for degree-preserving edge switches, or 'configuration' for the configuration model with self-loops and multiple edges removed.

'switch'
samples int

The number of random graphs.

100
random_state int | None

The seed of the ensemble.

None

Attributes:

Name Type Description
graphs_

The graph of every frame.

counts_

The count of every motif of every frame.

null_mean_

The mean count of every motif over the null ensemble of every frame.

null_std_

The standard deviation of every count over the null ensemble of every frame.

z_scores_

The z-score of every motif of every frame.

Examples:

>>> import networkx as nx
>>> from skcomplex.analysis import MotifAnalysis
>>> MotifAnalysis().check().prepare(nx.complete_graph(4)).run().counts_
[{'path': 0, 'triangle': 4}]
>>> MotifAnalysis(size=4).check().prepare(nx.complete_graph(4)).run().counts_
[{'clique': 1}]
>>> graph = nx.gnm_random_graph(30, 60, seed=0)
>>> sorted(MotifAnalysis('z-scores', samples=10, random_state=0).check().prepare(graph).run().z_scores_[0])
['path', 'triangle']
Source code in src/skcomplex/analysis/_motifs.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def __init__(
    self,
    kind: str = 'counts',
    *,
    size: int = 3,
    null: str = 'switch',
    samples: int = 100,
    random_state: int | None = None,
) -> None:
    self.kind = kind
    self.size = size
    self.null = null
    self.samples = samples
    self.random_state = random_state

NeighborAnalysis(kind='distances', group=None, *, variables=None, box=None, bins=100, r_max=None, attribute='state', empty=None, diagonal=True)

Bases: BaseAnalysis

Neighbours of agents in space, or of cells of a grid or nodes of a network, of one kind.

The kinds read positions, or states on grids and networks:

  • 'distances': the distance from every agent to its nearest neighbour, across periodic edges in a box.
  • 'clark-evans': the mean nearest-neighbour distance over its value for uniform random points in a periodic box, 1 for random points, below 1 for clustered ones, and up to 2.15 for a triangular lattice in a plane.
  • 'radial-distribution': the radial distribution function g(r) in a periodic box, averaged over frames, 1 for an ideal gas.
  • 'like-share': the mean share of an agent's occupied neighbours that share its state, on a periodic grid or along the edges of a network, near 1 when segregated.

An array of positions holds one snapshot, one row per agent, or one snapshot per frame along its first axis. A trajectory gives the positions of an agent group with the sides of its periodic space as the box, or its lattice frames for 'like-share'. A grid array is one frame of states, and a graph carries the states on its nodes.

Parameters:

Name Type Description Default
kind str

The neighbour statistic to compute, one of the kinds above.

'distances'
group str | None

The path of the agent group of a trajectory, or None for the first one.

None
variables Sequence[str] | None

The lattice state of a trajectory whose frames 'like-share' reads, or None for the first one.

None
box Sequence[float] | None

The sides of a periodic box, or None for the periodic space of a trajectory or for open space.

None
bins int

The number of distance bins of g(r).

100
r_max float | None

The largest distance of g(r), or None for half the smallest side.

None
attribute str

The node attribute of the states of a graph.

'state'
empty float | None

The state of empty cells, left out as agents and as neighbours, or None when every cell is occupied.

None
diagonal bool

Whether grid cells that share only a corner are neighbours, as in Schelling's Moore neighbourhood.

True

Attributes:

Name Type Description
positions_

The positions, frames by agents by dimensions, for the agent kinds.

box_

The sides of the periodic box, or None for open space.

frames_

The grids of states, or the graphs, of 'like-share'.

distances_

The nearest-neighbour distances, frames by agents, of 'distances'.

ratio_

One Clark-Evans ratio per frame.

radii_

The bin centres of g(r).

distribution_

g(r) at every bin centre.

share_

One like share per frame.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import NeighborAnalysis
>>> positions = [[0.5, 0.0], [9.5, 0.0]]
>>> NeighborAnalysis(box=[10.0, 10.0]).check().prepare(positions).run().distances_.tolist()
[[1.0, 1.0]]
>>> points = np.random.default_rng(0).random((5000, 2))
>>> NeighborAnalysis('clark-evans', box=[1.0, 1.0]).check().prepare(points).run().ratio_.round(1).tolist()
[1.0]
>>> NeighborAnalysis('like-share', diagonal=False).check().prepare([[1, 1, 2, 2]] * 4).run().share_.tolist()
[0.75]
Source code in src/skcomplex/analysis/_neighbors.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def __init__(
    self,
    kind: str = 'distances',
    group: str | None = None,
    *,
    variables: Sequence[str] | None = None,
    box: Sequence[float] | None = None,
    bins: int = 100,
    r_max: float | None = None,
    attribute: str = 'state',
    empty: float | None = None,
    diagonal: bool = True,
) -> None:
    self.kind = kind
    self.group = group
    self.variables = variables
    self.box = box
    self.bins = bins
    self.r_max = r_max
    self.attribute = attribute
    self.empty = empty
    self.diagonal = diagonal

NetworkAnalysis(kind='summary', *, orders=(1, 2), attribute=None, numeric=False, measure='degree', method='louvain', reference=None, random_state=None)

Bases: BaseAnalysis

Measures of the network of every frame of a trajectory, or of a graph, of one kind.

The kinds read the graph of every frame:

  • 'degrees': the degree of every node, the moments <k^n> of the degree distribution for every order of orders, and the fitted exponent of a heavy-tailed degree distribution, nan below 50 nodes or for a narrow one.
  • 'assortativity': the assortativity by degree, or by a node attribute, positive when linked nodes are alike.
  • 'centrality': the degree, betweenness, closeness, eigenvector, or PageRank centrality of every node.
  • 'communities': the communities by Louvain modularity maximization or label propagation, their modularity, and their agreement with a reference partition.
  • 'neighbor-degree': the mean degree of the neighbours of nodes against their degree, rising in assortative networks.
  • 'summary': the size, mean degree, clustering, path length, diameter, and components, with the path measures on the largest component.

Parameters:

Name Type Description Default
kind str

The measure to compute, one of the kinds above.

'summary'
orders Sequence[int]

The orders n of the degree moments.

(1, 2)
attribute str | None

The node attribute of the assortativity, or None for the degree.

None
numeric bool

Whether the attribute is a number compared by correlation rather than a category compared by equality.

False
measure str

The centrality: 'degree', 'betweenness', 'closeness', 'eigenvector', or 'pagerank'.

'degree'
method str

The community detection: 'louvain' or 'label_propagation'.

'louvain'
reference Sequence[object] | str | None

The reference community of every node in node order, the name of a node attribute that holds it, or None.

None
random_state int | None

The seed of the Louvain method.

None

Attributes:

Name Type Description
graphs_

The graph of every frame.

degrees_

The degree of every node of every frame.

moments_

The degree moments, frames by orders.

exponent_

The fitted degree exponent of every frame.

assortativity_

The assortativity of every frame.

centrality_

A mapping from node to its centrality for every frame.

labels_

The community of every node of every frame.

modularity_

The modularity of every frame.

agreement_

The normalized mutual information with the reference of every frame, or None without a reference.

distinct_degrees_

The distinct degrees of every frame.

neighbor_degree_

The mean neighbour degree of the nodes of every distinct degree of every frame.

nodes_, (edges_, mean_degree_, clustering_, path_length_, diameter_)

The summary measures of every frame.

components_ (edges_, mean_degree_, clustering_, path_length_, diameter_)

The sizes of the connected components of every frame, largest first.

Examples:

>>> import networkx as nx
>>> from skcomplex.analysis import NetworkAnalysis
>>> summary = NetworkAnalysis().check().prepare(nx.cycle_graph(10)).run()
>>> summary.mean_degree_.tolist(), summary.clustering_.tolist(), summary.diameter_.tolist()
([2.0], [0.0], [5])
>>> NetworkAnalysis('degrees').check().prepare(nx.star_graph(3)).run().moments_.tolist()
[[1.5, 3.0]]
>>> centrality = NetworkAnalysis('centrality', measure='betweenness').check().prepare(nx.star_graph(3)).run()
>>> centrality.centrality_[0][0]
1.0
Source code in src/skcomplex/analysis/_networks.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def __init__(
    self,
    kind: str = 'summary',
    *,
    orders: Sequence[int] = (1, 2),
    attribute: str | None = None,
    numeric: bool = False,
    measure: str = 'degree',
    method: str = 'louvain',
    reference: Sequence[object] | str | None = None,
    random_state: int | None = None,
) -> None:
    self.kind = kind
    self.orders = orders
    self.attribute = attribute
    self.numeric = numeric
    self.measure = measure
    self.method = method
    self.reference = reference
    self.random_state = random_state

OperatorAnalysis(kind='spectrum', *, n_states=6, parameters=None, sector=None, subsystems=None)

Bases: BaseAnalysis

Spectrum and ground state of an operator system, or the purity and entanglement of the states of its trajectory.

The kinds read a system or a trajectory:

  • 'spectrum': the lowest eigenvalues and eigenstates of the generator, by exact diagonalisation. QuTiP diagonalises small spaces densely and larger ones with a sparse eigensolver. A sector restricts the basis to the states with a given eigenvalue of a conserved operator that is diagonal in the product basis, such as the total magnetisation.
  • 'ground-state': the lowest eigenvalue and eigenstate of the generator.
  • 'purity': the purity Tr(rho^2) of the state of a trajectory at every frame, 1 for pure states.
  • 'entanglement': the von Neumann entropy of the reduced state of some subsystems at every frame of a trajectory, in nats.

Parameters:

Name Type Description Default
kind str

The quantity to compute, one of the kinds above.

'spectrum'
n_states int

The number of eigenvalues of the spectrum.

6
parameters Mapping[str, object] | None

Parameter values by dotted name that replace the initial ones.

None
sector SectorSpec | None

The sector of a conserved diagonal operator whose basis states the spectrum keeps, or None for every basis state.

None
subsystems Sequence[str] | None

The keys of the subsystems to keep, such as chain[0], or the path of a whole element or group.

None

Attributes:

Name Type Description
problem_

The compiled operator problem.

trajectory_

The trajectory of 'purity' and 'entanglement'.

energies_

The eigenvalues of the spectrum in increasing order.

states_

The eigenstates of the spectrum as columns over the whole space.

energy_

The ground state energy.

state_

The ground state vector.

purity_

The purity at every frame.

entropy_

The entanglement entropy at every frame.

Raises:

Type Description
AnalysisError

The system or the trajectory holds no operators, the sector operator is not diagonal or lacks the eigenvalue, or a subsystem is not part of the trajectory.

ParameterError

A parameter name is not part of the system.

Examples:

>>> from typing import Annotated
>>> import numpy as np
>>> from sage.all__sagemath_symbolics import CC, SR
>>> from skcomplex.analysis import OperatorAnalysis
>>> from skcomplex.core.elements import Element, GroupElement
>>> from skcomplex.core.laws import GeneratorLaw
>>> from skcomplex.core.laws.utils import local_operator
>>> from skcomplex.specs import HilbertSpec, StateSpec
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.compile import OperatorCompiler
>>> from skcomplex.engine.runtime import Simulation
>>> operators = {'z': (np.diag([1, -1]), True), 'minus': (np.array([[0, 0], [1, 0]]), False)}
>>> QUBIT = HilbertSpec(2, operators, basis={'up': 0, 'down': 1})
>>> class TwoLevelElement(Element):
...     def __init__(
...         self, label, psi: Annotated[object, StateSpec(domain=CC**2)] = 'up', hilbert=QUBIT
...     ):
...         super().__init__(label)
...         self.psi = psi
...         self.hilbert = hilbert
...
...     def _get_laws(self, s):
...         return [GeneratorLaw(local_operator(s.psi, SR.symbol('z')))]
>>> system = System(elements=[TwoLevelElement('q')])
>>> OperatorAnalysis(n_states=2).check().prepare(system).run().energies_.round(6).tolist()
[-1.0, 1.0]
>>> singlet = np.array([0, 1, -1, 0]) / np.sqrt(2)
>>> pair = System(elements=[GroupElement('pair', element=TwoLevelElement, size=2)])
>>> trajectory = Simulation(
...     pair, compiler=OperatorCompiler(), dt=1.0, initial_state={'system.state': singlet},
... ).check().prepare().run(until=1.0).trajectory_
>>> analysis = OperatorAnalysis('entanglement', subsystems=['pair[0]']).check().prepare(trajectory).run()
>>> bool(np.allclose(analysis.entropy_, np.log(2)))
True
Source code in src/skcomplex/analysis/_operator.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def __init__(
    self,
    kind: str = 'spectrum',
    *,
    n_states: int = 6,
    parameters: Mapping[str, object] | None = None,
    sector: SectorSpec | None = None,
    subsystems: Sequence[str] | None = None,
) -> None:
    self.kind = kind
    self.n_states = n_states
    self.parameters = parameters
    self.sector = sector
    self.subsystems = subsystems

PartialInformationAnalysis(variables=None, *, redundancy='mmi', base=2.0)

Bases: BaseAnalysis

Partial information decomposition of what two series of symbols tell about a third, the target.

The source holds three series: the first source, the second source, and the target. The mutual information of the two sources together with the target splits into the information both give, the information only one of them gives, and the information only the two together give, with a redundancy measure of dit.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of a trajectory to read, the two sources and the target, or None for every scalar series.

None
redundancy str

The redundancy measure of dit: 'mmi' for the minimum mutual information, 'williams_beer' for I_min, 'broja', 'ccs', 'pm', or 'dep'.

'mmi'
base float

The base of the logarithm, 2 for bits.

2.0

Attributes:

Name Type Description
values_

The symbols of the two sources and the target, one per column.

labels_

The label of every series.

redundancy_

The information both sources give.

unique_first_

The information only the first source gives.

unique_second_

The information only the second source gives.

synergy_

The information only the two sources together give.

total_

The mutual information of the two sources together with the target, the sum of the four parts.

Examples:

>>> from skcomplex.analysis import PartialInformationAnalysis
>>> xor = [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]
>>> parts = PartialInformationAnalysis().check().prepare(xor).run()
>>> round(parts.synergy_, 6), round(parts.redundancy_, 6)
(1.0, 0.0)
Source code in src/skcomplex/analysis/_partial_information.py
76
77
78
79
def __init__(self, variables: Sequence[str] | None = None, *, redundancy: str = 'mmi', base: float = 2.0) -> None:
    self.variables = variables
    self.redundancy = redundancy
    self.base = base

PassageAnalysis(kind='first-passage', variables=None, *, threshold=None, target=None, origin=None, direction='up', level=0.0, lag=1, dt=1.0)

Bases: BaseAnalysis

Passages of paths to a level or a set of states, or of a Markov chain between two sets, of one kind.

The kinds read paths, one row per path on common times, or a transition matrix:

  • 'first-passage': the first time every path reaches threshold, from below for direction='up' and from above for 'down', or enters the target states.
  • 'hitting': the share of paths that reach each of the absorbing target states before the others.
  • 'return-times': the times between successive returns of a series to level, by visit or by crossing.
  • 'committor': the forward committor of a transition matrix, the probability that the chain reaches the target states before the origin states.
  • 'mean-first-passage': the mean first-passage time of a transition matrix at lag from the origin states, started in their stationary weights, to the target states.

The matrix kinds estimate the transition matrix at lag of the jump path of a trajectory.

Parameters:

Name Type Description Default
kind str

The passage to measure, one of the kinds above.

'first-passage'
variables Sequence[str] | None

The discrete state of a trajectory to read, or the series for 'return-times', or None for the first.

None
threshold float | None

The level a first passage reaches.

None
target Sequence[float] | None

The states that end a passage, the absorbing states, or the target set of the chain.

None
origin Sequence[float] | None

The origin set of the chain.

None
direction str

'up' to reach values at or above the threshold, 'down' at or below it.

'up'
level float

The level of the returns.

0.0
lag int

The lag of the transition matrix in steps.

1
dt float

The time of one step of an array, while a trajectory gives its own times.

1.0

Attributes:

Name Type Description
values_

The paths, one row per path, or the series of 'return-times'.

times_

The common times of the steps.

matrix_

The transition matrix of the matrix kinds.

passage_times_

The time from the start to the first passage of every path, nan for paths that never pass, of 'first-passage'.

hitting_probabilities_

The share of paths that hit every target state first, of 'hitting'.

return_times_

The time between every return and the next, of 'return-times'.

committor_

The committor of every state, 0 on the origin and 1 on the target, of 'committor'.

mean_first_passage_time_

The mean first-passage time, of 'mean-first-passage'.

Examples:

>>> from skcomplex.analysis import PassageAnalysis
>>> passage = PassageAnalysis(threshold=2).check().prepare([[0, 1, 2, 3], [0, 0, 0, 0]]).run()
>>> passage.passage_times_.tolist()
[2.0, nan]
>>> PassageAnalysis('return-times').check().prepare([0, 1, 0, -1, -2, -1, 0]).run().return_times_.tolist()
[2.0, 4.0]
>>> walk = [[0.5, 0.5, 0.0], [0.25, 0.5, 0.25], [0.0, 0.5, 0.5]]
>>> PassageAnalysis('committor', origin=[0], target=[2]).check().prepare(walk).run().committor_.tolist()
[0.0, 0.5, 1.0]
Source code in src/skcomplex/analysis/_passage.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def __init__(
    self,
    kind: str = 'first-passage',
    variables: Sequence[str] | None = None,
    *,
    threshold: float | None = None,
    target: Sequence[float] | None = None,
    origin: Sequence[float] | None = None,
    direction: str = 'up',
    level: float = 0.0,
    lag: int = 1,
    dt: float = 1.0,
) -> None:
    self.kind = kind
    self.variables = variables
    self.threshold = threshold
    self.target = target
    self.origin = origin
    self.direction = direction
    self.level = level
    self.lag = lag
    self.dt = dt

RecurrenceAnalysis(variables=None, *, radius=None, rate=0.05, min_line=2, theiler=1, random_state=0)

Bases: BaseAnalysis

Recurrence quantification of the points of a source: rate, determinism, laminarity, lines, and entropy.

Every row of the series of a source is a point, and every series a coordinate, so an embedded series or the states of a trajectory give a point set. Two points recur when they lie within the radius of each other.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of a trajectory to read as coordinates, or None for every scalar series.

None
radius float | None

The recurrence radius, or None to choose the radius that gives the recurrence rate.

None
rate float

The share of recurrent pairs when the radius is not given.

0.05
min_line int

The shortest diagonal or vertical line counted.

2
theiler int

The number of diagonals about the main one left out, 1 to leave out the main diagonal alone.

1
random_state int | None

The seed of the sample of pairs that sets the radius of large sets.

0

Attributes:

Name Type Description
values_

The points, one per row.

labels_

The label of every coordinate.

radius_

The recurrence radius.

matrix_

The symmetric sparse boolean recurrence matrix, with a true diagonal.

rate_

The share of recurrent pairs off the Theiler window.

determinism_

The share of recurrent points on diagonal lines of at least the minimum length.

laminarity_

The share of recurrent points on vertical lines of at least the minimum length.

mean_diagonal_

The mean length of those diagonal lines.

longest_diagonal_

The length of the longest diagonal line.

trapping_time_

The mean length of those vertical lines.

entropy_

The Shannon entropy in nats of the distribution of those diagonal line lengths.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import EmbeddingAnalysis, RecurrenceAnalysis
>>> sine = EmbeddingAnalysis(delay=15, dimension=2).check().prepare(np.sin(np.arange(2000) * 0.1)).run()
>>> bool(RecurrenceAnalysis().check().prepare(sine.points_).run().determinism_ > 0.95)
True
>>> recurrence = RecurrenceAnalysis(radius=0.5).check().prepare([0.0, 0.1, 5.0, 5.1]).run()
>>> recurrence.matrix_.toarray().astype(int).tolist()
[[1, 1, 0, 0], [1, 1, 0, 0], [0, 0, 1, 1], [0, 0, 1, 1]]
Source code in src/skcomplex/analysis/_recurrence.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    radius: float | None = None,
    rate: float = 0.05,
    min_line: int = 2,
    theiler: int = 1,
    random_state: int | None = 0,
) -> None:
    self.variables = variables
    self.radius = radius
    self.rate = rate
    self.min_line = min_line
    self.theiler = theiler
    self.random_state = random_state

ReservoirAnalysis(variables=None, *, kind='next_generation', delay=2, order=2, units=300, spectral_radius=0.9, leak=1.0, input_scaling=0.5, ridge=0.0001, warmup=None, validation=0.2, horizon=0, dt=1.0, random_state=0, label='surrogate')

Bases: BaseAnalysis

Reservoir system whose ridge readout gives the increments of the variables over one sample, from ReservoirPy.

The reservoir is driven by the standardized variables, and a ridge regression from its features to the increments of the variables over one sample is fitted on the training samples. A next-generation reservoir takes as features the variables at delay consecutive samples and their monomials of degree order. An echo state network takes the activities of units leaky tanh units with random recurrent weights. The fitted system runs under discrete time, one step per sample, and starts at the last sample of the last run with the memory the runs left, so a run of the system is the autonomous forecast.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of trajectories, or None for every scalar state or observable.

None
kind str

The reservoir, 'next_generation' or 'echo_state'.

'next_generation'
delay int

The number of consecutive samples of a next-generation reservoir.

2
order int

The degree of the monomials of a next-generation reservoir.

2
units int

The number of units of an echo state network.

300
spectral_radius float

The spectral radius of the recurrent weights of an echo state network.

0.9
leak float

The leak rate of the units of an echo state network, 1 for no leak.

1.0
input_scaling float

The scale of the input weights of an echo state network.

0.5
ridge float

The strength of the ridge penalty of the readout.

0.0001
warmup int | None

The number of first samples of every series left out of the fit while the memory fills, or None for delay with a next-generation reservoir and 100 with an echo state network.

None
validation float

The share of the samples of every run held out at its end to measure the validation error.

0.2
horizon int

The number of samples to forecast after the last sample of the last run.

0
dt float

The time between the rows of an array.

1.0
random_state int | None

The seed of the random weights of an echo state network.

0
label str

The label of the surrogate element.

'surrogate'

Attributes:

Name Type Description
samples_

The series of every run, the names of the variables, and the step, set by prepare.

system_

The system of one surrogate element under discrete time, one step per sample, whose states are the variables and then the memory of the reservoir.

labels_

The variables the surrogate was fitted to.

states_

The names of the states of the surrogate element, the variables first.

last_state_

The variables at the last sample of the last series and the memory the series left, where forecasts start.

dt_

The time between the samples it was fitted to, which one step of the system stands for.

training_error_

The relative error of the predicted increments on the training samples, with the reservoir driven by the series.

validation_error_

The relative error on the held-out samples, NaN when none are held out.

step_

The map from the states to the states one step later, which the update law calls.

forecast_

The variables at the horizon samples after the last one, one row per sample.

Raises:

Type Description
AnalysisError

The reservoir kind is unknown, or no training sample is left after the warmup.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import ReservoirAnalysis
>>> times = np.arange(400.0)
>>> analysis = ReservoirAnalysis(['x'], horizon=10).check().prepare(np.sin(0.1 * times)).run()
>>> analysis.states_
['x', 'memory_0', 'memory_1']
>>> bool(abs(analysis.forecast_[-1, 0] - np.sin(0.1 * 409)) < 0.01)
True
Source code in src/skcomplex/analysis/_reservoirs.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    kind: str = 'next_generation',
    delay: int = 2,
    order: int = 2,
    units: int = 300,
    spectral_radius: float = 0.9,
    leak: float = 1.0,
    input_scaling: float = 0.5,
    ridge: float = 1e-4,
    warmup: int | None = None,
    validation: float = 0.2,
    horizon: int = 0,
    dt: float = 1.0,
    random_state: int | None = 0,
    label: str = 'surrogate',
) -> None:
    self.variables = variables
    self.kind = kind
    self.delay = delay
    self.order = order
    self.units = units
    self.spectral_radius = spectral_radius
    self.leak = leak
    self.input_scaling = input_scaling
    self.ridge = ridge
    self.warmup = warmup
    self.validation = validation
    self.horizon = horizon
    self.dt = dt
    self.random_state = random_state
    self.label = label

RollingAnalysis(kind='variance', variables=None, *, window=None, detrend=False)

Bases: BaseAnalysis

Statistic of every series of a source in rolling windows, of one kind, and its trend.

Every window holds the window values that end at a sample, possibly with a linear trend removed first, and the statistic of the window is set at that sample. The trend of the statistic is Kendall's tau against time.

  • 'variance': the variance of the window.
  • 'autocorrelation': the lag-one autocorrelation of the window.
  • 'skewness': the skewness of the window.

Parameters:

Name Type Description Default
kind str

The statistic to compute, one of the kinds above.

'variance'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series.

None
window int | None

The number of values in every rolling window.

None
detrend bool

Whether to remove a linear trend in every window first.

False

Attributes:

Name Type Description
values_

The series, one per column.

labels_

The label of every series.

statistic_

The statistic of every series in every window, one row per series, nan before the first full window.

trend_

Kendall's tau of the statistic of every series against time, positive when it rises.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import RollingAnalysis
>>> rng = np.random.default_rng(0)
>>> x = np.zeros(2000)
>>> for t in range(1, 2000):
...     x[t] = (0.2 + 0.79 * t / 2000) * x[t - 1] + rng.standard_normal()
>>> rolling = RollingAnalysis('autocorrelation', window=200, detrend=True).check().prepare(x).run()
>>> bool(rolling.trend_[0] > 0.5)
True
Source code in src/skcomplex/analysis/_rolling.py
69
70
71
72
73
74
75
76
77
78
79
80
def __init__(
    self,
    kind: str = 'variance',
    variables: Sequence[str] | None = None,
    *,
    window: int | None = None,
    detrend: bool = False,
) -> None:
    self.kind = kind
    self.variables = variables
    self.window = window
    self.detrend = detrend

ScalingAnalysis(kind='detrended-fluctuation', variables=None, *, scales=None, order=1, fit_sums=(0.0001, 0.01), random_state=0, against='mean', parameters=None, critical=None, above=False)

Bases: BaseAnalysis

Scaling exponent of every series of a source, fractal dimension of a set of cells or points, or scaling law.

The kinds read series, fields, points, or samples at several sizes:

  • 'detrended-fluctuation': the slope of the log fluctuation of the integrated series, detrended in windows by a polynomial of degree order, against the log window size. It is 0.5 for white noise, the Hurst exponent H for fractional Gaussian noise, and H + 1 for its integral, such as 1.5 for a random walk.
  • 'rescaled-range': the Hurst exponent by the rescaled range, 0.5 for uncorrelated noise, above 0.5 for persistent and below 0.5 for anti-persistent noise.
  • 'box-counting': the box-counting dimension of the occupied cells of a field, the slope of the log box count against the log box size, read from the last frame of a trajectory.
  • 'correlation-dimension': the Grassberger-Procaccia correlation dimension of a point set, one row per point, such as the columns of an embedded series, from tree counts of close pairs.
  • 'variance-mean': the mean and the standard deviation of a quantity at every scale, and the slope of log deviation against log mean with against='mean', or of log (deviation / mean) against log size with against='size'. It reads the counts of a density field in square boxes of growing side over every frame, where the slope against the mean is 1/2 for independent particles and above for giant fluctuations, or a mapping from size to samples, where the slope against the size is -1/2 for the sums of independent parts.
  • 'power': the exponent b of the power law y ~ |p - p_c|^b of every series against the distance of the control parameters to the critical value, fitted on the side above or below it where y is positive, such as the exponent beta of an order parameter.

Parameters:

Name Type Description Default
kind str

The scaling to measure, one of the kinds above.

'detrended-fluctuation'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series, or the first field for a field.

None
scales Sequence[float] | None

The window sizes, the box sides in cells, or the radii, or None for windows spaced logarithmically up to a tenth of the series, powers of 2 up to an eighth of the smallest side, or a quarter of it for 'variance-mean', or 30 radii spaced logarithmically over the range of pair distances.

None
order int

The order of the detrending polynomial.

1
fit_sums tuple[float, float]

The smallest and largest correlation sums of the scaling region the slope is fitted in.

(0.0001, 0.01)
random_state int | None

The seed of the sample of pairs that sets the default radii.

0
against str

What the variance-mean law is fitted against, 'mean' or 'size'.

'mean'
parameters Sequence[float] | None

The control parameter value of every row of the series of 'power'.

None
critical float | None

The value of the control parameter the distance is measured to.

None
above bool

Whether the fit uses the values above the critical value, as for couplings, rather than below it, as for temperatures.

False

Attributes:

Name Type Description
values_

The series, one per column, the field or its frames, or the points, and an empty array for samples by size.

samples_

The samples by size, or None for a source of series, points, or a field.

labels_

The label of every series, of the field, or of every size.

scales_

The window sizes, the box sides, the radii, or the sizes.

fluctuations_

The fluctuation, or the rescaled range, of every series at every window size.

counts_

The number of occupied boxes of every side.

sums_

The share of pairs of points closer than every radius.

means_

The mean of the quantity at every scale.

deviations_

The standard deviation of the quantity at every scale.

fit_range_

The smallest and largest radii of the fit of the correlation dimension.

exponent_ float | Array

The exponent of every series for the fluctuation kinds and 'power', the slope of the variance-mean law, and the dimension for the other kinds.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import ScalingAnalysis
>>> walk = np.cumsum(np.random.default_rng(0).standard_normal(5000))
>>> ScalingAnalysis().check().prepare(walk).run().exponent_.round(1).tolist()
[1.5]
>>> round(ScalingAnalysis('box-counting').check().prepare(np.ones((256, 256))).run().exponent_, 2)
2.0
>>> points = np.random.default_rng(0).random((5000, 2))
>>> round(ScalingAnalysis('correlation-dimension').check().prepare(points).run().exponent_, 1)
2.0
>>> counts = np.random.default_rng(0).poisson(5.0, (20, 64, 64))
>>> round(ScalingAnalysis('variance-mean').check().prepare(counts).run().exponent_, 1)
0.5
>>> power = ScalingAnalysis('power', parameters=[0.9, 0.99], critical=1.0)
>>> power.check().prepare([0.1**0.5, 0.01**0.5]).run().exponent_.round(6).tolist()
[0.5]
Source code in src/skcomplex/analysis/_scaling.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def __init__(
    self,
    kind: str = 'detrended-fluctuation',
    variables: Sequence[str] | None = None,
    *,
    scales: Sequence[float] | None = None,
    order: int = 1,
    fit_sums: tuple[float, float] = (1e-4, 1e-2),
    random_state: int | None = 0,
    against: str = 'mean',
    parameters: Sequence[float] | None = None,
    critical: float | None = None,
    above: bool = False,
) -> None:
    self.kind = kind
    self.variables = variables
    self.scales = scales
    self.order = order
    self.fit_sums = fit_sums
    self.random_state = random_state
    self.against = against
    self.parameters = parameters
    self.critical = critical
    self.above = above

SectionAnalysis(kind='poincare', variables=None, *, state=None, value=0.0, direction=1, until=1000.0, transient=None, period=None, n_points=1000, solver=None, random_state=None)

Bases: BaseAnalysis

Section of a system: the states where a flow crosses a value, or at multiples of a period.

The kinds run the system:

  • 'poincare': the states where the scalar state state crosses value in a direction, located like an event.
  • 'stroboscopic': the states of a periodically forced flow at multiples of the forcing period.

Parameters:

Name Type Description Default
kind str

The section to take, one of the kinds above.

'poincare'
variables Sequence[str] | None

The dotted names of the states whose values to read at the points of a section, or None for every state.

None
state str | None

The dotted name of the scalar state that defines a Poincaré section, such as oscillator.x.

None
value float

The value of the state on the Poincaré section.

0.0
direction int

1 for crossings upward, -1 for crossings downward, and 0 for both.

1
until float

The time a Poincaré section runs until.

1000.0
transient float | None

The time before which crossings are discarded, or the number of periods discarded before the first stroboscopic point. None gives 0 and 100 periods.

None
period float | None

The period of the forcing, such as 2 * pi / omega.

None
n_points int

The number of points of a stroboscopic section.

1000
solver Solver | None

The solver, as in Simulation.

None
random_state int | None

The seed of stochastic solvers.

None

Attributes:

Name Type Description
problem_

The compiled problem of a Poincaré or stroboscopic section.

times_

The time of every point of a section.

states_

One state vector per point of a section.

values_

A mapping from the name of every read state to its values at the points of a section, one row per point for a vector state.

Examples:

>>> from skcomplex.analysis import SectionAnalysis
>>> from skcomplex.models.physics.mechanics import harmonic_oscillator
>>> section = SectionAnalysis(state='mass.x', until=20.0).check().prepare(harmonic_oscillator()).run()
>>> len(section.times_) > 2
True
Source code in src/skcomplex/analysis/_sections.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def __init__(
    self,
    kind: str = 'poincare',
    variables: Sequence[str] | None = None,
    *,
    state: str | None = None,
    value: float = 0.0,
    direction: int = 1,
    until: float = 1000.0,
    transient: float | None = None,
    period: float | None = None,
    n_points: int = 1000,
    solver: Solver | None = None,
    random_state: int | None = None,
) -> None:
    self.kind = kind
    self.variables = variables
    self.state = state
    self.value = value
    self.direction = direction
    self.until = until
    self.transient = transient
    self.period = period
    self.n_points = n_points
    self.solver = solver
    self.random_state = random_state

SignatureAnalysis

Bases: BaseAnalysis

Default analysis of every kind of data of a trajectory, with its curves, axis labels, title, and values.

A series gives its power spectrum with the autocorrelation time, or its return map for a map. A jump path gives the survival function of its waiting times with the dispersion index of jumps per frame. A field gives the structure factor of its last frame with the peak wavelength or the correlation length. Agents give their polar and milling orders over time, or their nearest-neighbour distances against the Poisson law. A network gives its degree distribution.

Attributes:

Name Type Description
kinds_

The data kinds of the trajectory, in the order agents, field, network, path, series.

snapshots_

The agents of the first group at every frame, for the agents kind.

frames_

The frames of the first lattice state, for the field kind.

graphs_

The network of every frame, for the network kind.

path_

The jump path of the first discrete state, for the path kind.

jumps_

The size of the jumps of that state between frames, for the path kind.

series_

The scalar series, for the series kind.

discrete_

Whether the trajectory is that of a map, whose series give a return map.

signatures_

One signature per data kind, in the order of kinds_, each with its kind, title, axis labels and scales, curves, and stated values.

Examples:

>>> from skcomplex.analysis import SignatureAnalysis
>>> from skcomplex.models import run
>>> trajectory = run('lotka_volterra', settings={'until': 20.0}).trajectory_
>>> [signature.title for signature in SignatureAnalysis().check().prepare(trajectory).run().signatures_]
['Power spectrum']

SparseIdentificationAnalysis(variables=None, *, degree=2, threshold=0.1, alpha=0.05, validation=0.2, horizon=0, dt=1.0, label='surrogate')

Bases: BaseAnalysis

Sparse symbolic system fitted to series by sparse identification of nonlinear dynamics, computed with PySINDy.

SINDy writes the derivative of every variable as a sparse sum of the monomials of the variables up to a degree. It estimates the derivatives by centred finite differences and selects the terms by sequentially thresholded least squares. The fitted system holds one rate law per variable under continuous time and starts at the first sample of the first run, and the forecast simulates it from the last sample of the last run.

Parameters:

Name Type Description Default
variables Sequence[str] | None

The variables of trajectories, or None for every scalar state or observable.

None
degree int

The highest degree of the candidate monomials.

2
threshold float

The smallest magnitude of a kept coefficient.

0.1
alpha float

The strength of the ridge penalty of every least-squares fit.

0.05
validation float

The share of the samples of every run held out at its end to measure the validation error.

0.2
horizon int

The number of samples to forecast after the last sample of the last run.

0
dt float

The time between the rows of an array.

1.0
label str

The label of the surrogate element.

'surrogate'

Attributes:

Name Type Description
samples_

The series of every run, the names of the variables, and the step, set by prepare.

system_

The system of one surrogate element under continuous time, with one rate law per variable.

labels_

The variables the system was fitted to, in the order of its states.

states_

The names of the states of the surrogate element.

last_state_

The values of the states at the last sample of the last run, where the forecast starts.

dt_

The time between the samples it was fitted to.

training_error_

The relative error of the predicted derivatives on the training samples.

validation_error_

The relative error on the held-out samples, NaN when none are held out.

features_

The candidate monomials of the states, such as x y.

coefficients_

The coefficient of every monomial in the rate of every state, one row per state and one column per monomial, zero where the term is absent.

forecast_

The variables at the horizon samples after the last one, one row per sample.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import SparseIdentificationAnalysis
>>> times = np.arange(0.0, 2.0, 0.001)
>>> analysis = SparseIdentificationAnalysis(['x'], degree=1, dt=0.001)
>>> analysis = analysis.check().prepare(np.exp(-2 * times)).run()
>>> analysis.features_, analysis.coefficients_.round(3).tolist()
(['1', 'x'], [[0.0, -2.0]])
Source code in src/skcomplex/analysis/_sparse_identification.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def __init__(
    self,
    variables: Sequence[str] | None = None,
    *,
    degree: int = 2,
    threshold: float = 0.1,
    alpha: float = 0.05,
    validation: float = 0.2,
    horizon: int = 0,
    dt: float = 1.0,
    label: str = 'surrogate',
) -> None:
    self.variables = variables
    self.degree = degree
    self.threshold = threshold
    self.alpha = alpha
    self.validation = validation
    self.horizon = horizon
    self.dt = dt
    self.label = label

SpectrumAnalysis(kind='power', variables=None, *, dt=1.0, spacing=1.0, frames=False)

Bases: BaseAnalysis

Spectrum of every series of a source, or of a field.

The kinds read series or fields:

  • 'power': the power spectral density of every series by Welch's method.
  • 'structure-factor': the radially averaged structure factor S(k) of a field on a periodic grid of any dimension, in shells of width 2 pi / (side spacing), averaged over frames when frames is true.

A structure factor reads the last frame of a trajectory, or every frame when frames is true.

Parameters:

Name Type Description Default
kind str

The spectrum to compute, one of the kinds above.

'power'
variables Sequence[str] | None

The variables of a trajectory to read, or None for every scalar series, or the first field for a field.

None
dt float

The time between samples.

1.0
spacing float

The distance between neighbouring sites.

1.0
frames bool

Whether the first axis of an array indexes frames, and whether to read every frame of a trajectory.

False

Attributes:

Name Type Description
values_

The series, one per column, or the field.

labels_

The label of every series, or of the field.

frequencies_

The frequencies of 'power'.

power_

One density per series and frequency for 'power', and the mean power in every shell for 'structure-factor'.

wavenumbers_

The wavenumbers of the shells, without 0, for 'structure-factor'.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import SpectrumAnalysis
>>> times = np.arange(4096) * 0.01
>>> spectrum = SpectrumAnalysis(dt=0.01).check().prepare(np.sin(2 * np.pi * 5 * times)).run()
>>> round(float(spectrum.frequencies_[spectrum.power_[0].argmax()]))
5
>>> stripes = np.cos(2 * np.pi * np.arange(64) / 8)[:, None] * np.ones(64)
>>> factor = SpectrumAnalysis('structure-factor').check().prepare(stripes).run()
>>> round(float(2 * np.pi / factor.wavenumbers_[factor.power_.argmax()]), 1)
8.0
Source code in src/skcomplex/analysis/_spectrum.py
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def __init__(
    self,
    kind: str = 'power',
    variables: Sequence[str] | None = None,
    *,
    dt: float = 1.0,
    spacing: float = 1.0,
    frames: bool = False,
) -> None:
    self.kind = kind
    self.variables = variables
    self.dt = dt
    self.spacing = spacing
    self.frames = frames

StabilityAnalysis(kind='fixed-points', parameters=None, points=None, *, at=None)

Bases: BaseAnalysis

Linear stability of a system, of one kind.

  • 'fixed-points': the fixed points of a flow or a map, their linear stability, and the Jacobian, nullclines, and divergence.
  • 'dispersion': the growth rate of every Fourier mode of a system of one field, a site group on a periodic lattice whose laws are rate laws. The laws are linearised symbolically around a uniform state, and every Laplacian, nested ones included, becomes its eigenvalue on a Fourier mode of the lattice, Σ (2 cos(k h) - 2) / h² for finite differences and -|k|² for a spectral field. Without at, the uniform state is the fixed point of the laws nearest the mean initial values.

The fixed points of a flow are where every rate is 0, and those of a map where every state equals its next value. Sage solves the equations symbolically, and when it finds no closed form, roots are searched numerically from a grid of starting points with the parameters at their values. The stability of every point comes from the eigenvalues of the Jacobian there, evaluated at the parameter values.

Parameters:

Name Type Description Default
kind str

The stability to derive, one of the kinds above.

'fixed-points'
parameters Mapping[str, float] | None

Values of parameters by dotted path that replace the system's values. They are substituted before solving, and without them the symbolic fixed points keep the parameters as symbols.

None
points Sequence[Point] | None

The points whose stability to analyse, each a mapping from state component name to its value, symbolic or numeric, or None for every fixed point found.

None
at Mapping[str, float] | None

The uniform state of the dispersion relation by state name, or None for the nearest fixed point.

None

Attributes:

Name Type Description
problem_

The compiled problem.

names_

The state component names, in the order of the rows of the Jacobian.

jacobian_

The Sage matrix of the partial derivatives of the right sides by the states.

divergence_

The trace of the Jacobian, 0 for volume-preserving flows.

nullclines_

A mapping from state component name to its nullcline as a Sage relation, where its rate is 0 or where it is its own next value.

fixed_points_

One mapping from state component name to its value per point, the fixed points found or the given points.

eigenvalues_

The eigenvalues of the Jacobian at every point.

classifications_

The class of every point: stable node, stable focus, unstable node, unstable focus, saddle, centre, or marginal for flows, and stable, unstable, saddle, or marginal for maps.

system_

The system of one field, for 'dispersion'.

wavenumbers_

The wave vector of every mode, one row per mode, for 'dispersion'.

magnitudes_

The length of every wave vector.

growth_rates_

The largest real part of the eigenvalues of the linearisation at every mode.

frequencies_

The imaginary part of the leading eigenvalue at every mode, nonzero for travelling or oscillating modes.

uniform_

The uniform state the model is linearised around, by state name.

band_

The lengths of the wave vectors whose modes grow.

Examples:

>>> from skcomplex.analysis import StabilityAnalysis
>>> from skcomplex.models.biology.ecology import logistic_growth
>>> analysis = StabilityAnalysis().check().prepare(logistic_growth()).run()
>>> sorted(str(point['population.n']) for point in analysis.fixed_points_)
['0', 'K_growth']
>>> analysis = StabilityAnalysis(points=[{'population.n': 100.0}]).check().prepare(logistic_growth())
>>> analysis.run().classifications_
['stable node']
>>> from skcomplex.models.chemistry.pattern_formation import brusselator
>>> relation = StabilityAnalysis('dispersion').check().prepare(brusselator((64,), b=1.9, Dv=8.0)).run()
>>> bool(relation.growth_rates_.max() > 0), bool(relation.growth_rates_[0] < 0)
(True, True)
Source code in src/skcomplex/analysis/_stability.py
137
138
139
140
141
142
143
144
145
146
147
148
def __init__(
    self,
    kind: str = 'fixed-points',
    parameters: Mapping[str, float] | None = None,
    points: Sequence[Point] | None = None,
    *,
    at: Mapping[str, float] | None = None,
) -> None:
    self.kind = kind
    self.parameters = parameters
    self.points = points
    self.at = at

SurrogateTestAnalysis(analysis, attribute, variables=None, *, n=99, alternative='two-sided', random_state=None)

Bases: BaseAnalysis

Test of an analysis of many variables against surrogates that shuffle every variable independently.

The analysis runs on the data and on n surrogates, and the attribute it sets gives the value of each. The p-value is the fraction of surrogates, with the data counted among them, at least as extreme as the data.

Parameters:

Name Type Description Default
analysis BaseAnalysis

The unprepared analysis of the variables, cloned for the data and for every surrogate.

required
attribute str

The scalar attribute of the analysis that measures the dependence, such as 'information_'.

required
variables Sequence[str] | None

The variables of a trajectory, or None for every scalar state or observable.

None
n int

The number of surrogates.

99
alternative str

'greater', 'less', or 'two-sided', the direction in which the data differ from independence.

'two-sided'
random_state int | None

The seed of the shuffles.

None

Attributes:

Name Type Description
values_

The data, one row per sample and one column per variable.

value_

The measure on the data.

surrogates_

The measure on every surrogate.

p_value_

The p-value of the data against the surrogates.

Examples:

>>> import numpy as np
>>> from skcomplex.analysis import InformationAnalysis, SurrogateTestAnalysis
>>> bit = np.random.default_rng(0).integers(0, 2, 500)
>>> test = SurrogateTestAnalysis(InformationAnalysis('o-information'), 'information_', n=19, random_state=0)
>>> test = test.check().prepare(np.column_stack([bit, bit, bit])).run()
>>> round(test.value_, 1), test.p_value_
(1.0, 0.05)
Source code in src/skcomplex/analysis/_surrogates.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __init__(
    self,
    analysis: BaseAnalysis,
    attribute: str,
    variables: Sequence[str] | None = None,
    *,
    n: int = 99,
    alternative: str = 'two-sided',
    random_state: int | None = None,
) -> None:
    self.analysis = analysis
    self.attribute = attribute
    self.variables = variables
    self.n = n
    self.alternative = alternative
    self.random_state = random_state

SymbolicAnalysis(kind='equations', *, use_initial_state=False)

Bases: BaseAnalysis

Symbolic form of a system: its equations, their exact solution, its Hamiltonian, or its boundary flows.

The kinds read the equations of a system:

  • 'equations': one equation per state component, a Sage relation. Each state component is a function of time t. Continuous forms give its time derivative, the drift for stochastic systems, and discrete maps give its value at t + 1. States of higher order give their highest derivative, diff(x(t), t, k), with their companions written as derivatives, and lagged states read their past values as x(t - j). The equations of a group hold for every member and are named with the member index i. Components driven by a callable law or by a sum over members or pairs are left out.
  • 'solve': the exact solution of the equations of an ODE system as functions of time, with Maxima through Sage. One equation of order 1 or 2 is solved with desolve, one of higher order with desolve_laplace, and several with desolve_system after reduction to first order, which solves linear systems with constant coefficients. A result that still contains unevaluated transforms, integrals, or the unknown functions is not a solution, and raises the error. Parameters give Maxima their sign from their domain, or from their value when their domain is real. One linear fractional relaxation D^alpha x = c x has the solution x(0) E_alpha(c t^alpha), written with the symbolic function mittag_leffler(alpha, beta, z).
  • 'hamiltonian': the Hamiltonian of an operator system, the sum of the operators of its generator laws, whose subsystems are the symbols of the states of the elements, named <state>_<path>, and whose parameters are named <parameter>_<path>.
  • 'conservation': the net inflow from the environment of every quantity of a system with a transfer network, after checking that its internal flows cancel, so the total changes only through the boundary.

Parameters:

Name Type Description Default
kind str

The symbolic form to derive, one of the kinds above.

'equations'
use_initial_state bool

Whether 'solve' uses the initial state of the system, with the initial values of every derivative of states of higher order, as initial conditions at t = 0. Without them, the solution keeps its integration constants.

False

Attributes:

Name Type Description
problem_

The compiled problem.

equations_

A mapping from state component name to its equation.

solutions_

A mapping from state component name to its solution, a relation f(t) == solution. States of higher order appear once, without their derivatives.

hamiltonian_

The Hamiltonian, a Sage expression of local operators.

inflows_

The symbolic rate, or amount per step, at which each quantity enters the system from the environment.

Raises:

Type Description
AnalysisError

The system has no form of the kind, Maxima cannot solve its equations, or needs the sign of a parameter.

ConservationError

The internal flows of a quantity do not cancel.

Examples:

>>> from skcomplex.analysis import SymbolicAnalysis
>>> from skcomplex.core.system import System
>>> from skcomplex.models.biology.common.populations import ExponentialGrowthInteraction, PopulationElement
>>> system = System(
...     elements=[PopulationElement('a', n=2)],
...     interactions=[ExponentialGrowthInteraction('g', target='a', r=0.5)],
... )
>>> SymbolicAnalysis().check().prepare(system).run().equations_['a.n']
diff(n_a(t), t) == r_g*n_a(t)
>>> SymbolicAnalysis('solve', use_initial_state=True).check().prepare(system).run().solutions_['a.n']
n_a(t) == 2*e^(r_g*t)
Source code in src/skcomplex/analysis/_symbolic.py
164
165
166
def __init__(self, kind: str = 'equations', *, use_initial_state: bool = False) -> None:
    self.kind = kind
    self.use_initial_state = use_initial_state