Skip to content

Spaces

Define the spaces that systems live in, the neighbourhoods of their members, and the time of a system.

AdjacencyNeighborhood(*, center=False, layer=None, label='neighborhood')

Bases: BaseNeighborhood

Nodes along the edges of a node of a network, in node order, after the node itself when it is included.

Rules receive the values of the neighbours padded to the largest degree of the graph. On a directed graph the neighbours are the successors.

Parameters:

Name Type Description Default
center bool

Whether the node itself comes first.

False
layer str | None

The label of the layer whose edges join the neighbours, or None for the graph of the network.

None
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import AdjacencyNeighborhood
>>> AdjacencyNeighborhood(layer='work').layer
'work'
Source code in src/skcomplex/core/components/spaces/_adjacency.py
31
32
33
34
def __init__(self, *, center: bool = False, layer: str | None = None, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.center = center
    self.layer = layer

BaseNeighborhood(label)

Bases: BasePart[None, SimpleNamespace]

Base of the neighbourhoods, the parts of a system that give the members a law reads around a member of a space.

A neighbourhood is a part: it stores its arguments in its own __init__, prepare validates them and derives what the engine reads, such as the offsets of a lattice neighbourhood. The space that reads it rejects a neighbourhood of another kind. It contributes no laws.

Parameters:

Name Type Description Default
label str

The label of the neighbourhood.

required
Source code in src/skcomplex/core/components/base.py
59
60
61
62
63
64
65
66
def __init__(self, label: str) -> None:
    """Store the label of the part.

    Args:
        label:
            The label of the part, unique among the parts of its system.
    """
    self.label = label

BaseSpace(label)

Bases: BasePart[None, SimpleNamespace]

Base of the spaces, the parts of a system where members live and which give vector states their dimension.

A space is a part of its system: it stores its arguments in its own __init__, prepare validates them and derives the facts of the space, and _get_laws returns the laws the space contributes, none by default. A space with sites answers the hooks the engine reads: _get_neighbors gives the sites around a site, _index_table the neighbour of every site in every slot of a neighbourhood, _translation_table the sites a group space reads at the products of a site and a group element, _ball the neighbourhood within a radius, _neighbor_pairs the pairs of neighbouring sites, and _default_neighborhood the neighbourhood _get_neighbors reads when it is given none. A space without sites, such as a continuous one, answers none of them.

Parameters:

Name Type Description Default
label str

The label of the space in its system.

required

Attributes:

Name Type Description
size_

The number of sites, or None for a space without sites.

n_dim_

The number of dimensions, the length of the vector states of members of the space.

shape_

The shape of the arrays of the values of the sites, () for a space without sites.

positions_

The position of every site, one row per site, or an empty array of n_dim_ columns without sites.

periodic_

Whether opposite sides of the space join.

default_visual_

The name of the visual of groups on the space when neither the group nor its element sets one, or None.

Source code in src/skcomplex/core/components/base.py
59
60
61
62
63
64
65
66
def __init__(self, label: str) -> None:
    """Store the label of the part.

    Args:
        label:
            The label of the part, unique among the parts of its system.
    """
    self.label = label

BaseTime(label)

Bases: BasePart[None, SimpleNamespace]

Base of the time of a system, the part that decides the laws the system may hold.

A time is a part of its system: it stores its arguments in its own __init__, prepare validates them and sets its schedule, and _get_frames returns the times of the frames of a run up to a time. It contributes no laws.

Parameters:

Name Type Description Default
label str

The label of the time in its system.

required

Attributes:

Name Type Description
schedule_

The schedule of a discrete time, or None.

Source code in src/skcomplex/core/components/base.py
59
60
61
62
63
64
65
66
def __init__(self, label: str) -> None:
    """Store the label of the part.

    Args:
        label:
            The label of the part, unique among the parts of its system.
    """
    self.label = label

BoxNeighborhood(radius=1, *, n_dim=1, label='neighborhood')

Bases: LatticeNeighborhood

Every site within a radius along every dimension, the site itself in place, as one-dimensional automata read.

For radius 1 in one dimension the order is left, centre, right, the order of Wolfram's rule numbers.

Parameters:

Name Type Description Default
radius int

The largest offset along any dimension.

1
n_dim int

The number of dimensions.

1
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import BoxNeighborhood
>>> BoxNeighborhood().prepare().offsets_
((-1,), (0,), (1,))
Source code in src/skcomplex/core/components/spaces/_box.py
34
35
36
37
def __init__(self, radius: int = 1, *, n_dim: int = 1, label: str = 'neighborhood') -> None:
    BaseNeighborhood.__init__(self, label)
    self.radius = radius
    self.n_dim = n_dim

CayleySpace(group, *, ball=None, fill=0.0, max_sites=MAX_SITES, label='space')

Bases: BaseSpace

Cayley graph of a finitely generated group, on a ball of words or a whole finite group, whose sites hold cells.

The group is a native Sage or GAP group: a Sage PermutationGroup or MatrixGroup, or a GAP group from libgap, such as the free group libgap.FreeGroup(['a', 'b']), a finitely presented group free / relators, an automaton group libgap.AutomatonGroup('t = (1, t)(1, 2)') of the AutomGrp package, or the finite quotient of one, such as libgap.PermGroupOnLevel(group, 3). Finitely presented groups are solved by Knuth-Bendix completion with KBMAG.

Sites are the distinct elements of a ball of a radius in the generators and their inverses, numbered by word length and then by order of discovery, so a ball is a prefix of every larger ball. Without a ball the group must be finite, and the sites are all its elements. A cell at the site g reads the site g s for every element s of a memory set, and a neighbour outside a ball, or reached through a site outside it, reads the value fill. A memory set translated by the values of the cells, StateTranslationNeighborhood, reads the site g x(g) s of the group element x(g) that a cell holds as the number of its site, under the same convention.

Parameters:

Name Type Description Default
group object

The group.

required
ball int | None

The radius of the ball, or None for a finite group.

None
fill float

The value read outside a ball.

0.0
max_sites int

The largest number of sites.

MAX_SITES
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
generators_

The names of the generators, their GAP names when these are identifiers, or a, b, and so on.

words_

The shortest word of every site, in site order.

lengths_

The word length of every site, its distance from the identity in the Cayley graph.

ball_sizes_

The number of sites within every radius, from 0 to the radius of the ball or of the finite group.

generator_table_

The site of g s for every site g and letter s, in the order a, a^-1, b, b^-1, ..., and -1 outside.

edges_

The edge from every site g to g s for every generator s inside the space, with the generator's number.

level_

The level of the tree whose action keys the elements of an automaton group, or 0 for other groups.

Examples:

>>> from sage.all__sagemath_gap import PermutationGroup
>>> from skcomplex.core.components.spaces import CayleySpace
>>> space = CayleySpace(PermutationGroup([[(1, 2)], [(1, 2, 3)]]), ball=3).prepare()
>>> space.size_, space.words_[:4], space.ball_sizes_
(6, ['1', 'a', 'b', 'b^-1'], [1, 4, 6, 6])
Source code in src/skcomplex/core/components/spaces/_cayley.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def __init__(
    self,
    group: object,
    *,
    ball: int | None = None,
    fill: float = 0.0,
    max_sites: int = MAX_SITES,
    label: str = 'space',
) -> None:
    super().__init__(label)
    self.group = group
    self.ball = ball
    self.fill = fill
    self.max_sites = max_sites

CellComplexSpace(space, *, faces=None, dimension=LOWEST_DIMENSION, label='space')

Bases: NetworkSpace

Network of the cells of a lattice or a network, with the oriented incidence between consecutive degrees.

Its nodes are the sites or nodes of the space it derives from, its edges the oriented links or edges, and its hyperedges the oriented plaquettes or faces in cyclic node order. Every group, binding, and neighbourhood of a network space applies to it, so groups on its edges and hyperedges hold states on links and plaquettes. It has no layers.

The links of a lattice are numbered by direction and then by site, and point along the positive direction of their axis. The plaquettes are numbered by plane and then by site, with planes (0, 1), (0, 2), ... in increasing order, and turn from the first axis of the plane to the second. Fixed and reflecting edges drop the cells that leave the box. A face of a network is oriented by its node cycle. The faces of a clique complex and the triangles of simplicial hyperedges take their incidence from xgi.linalg.hodge_matrix.boundary_matrix, and the other faces from the sum of the oriented edges around them.

Parameters:

Name Type Description Default
space LatticeSpace | NetworkSpace

The LatticeSpace or the NetworkSpace the cells derive from.

required
faces str | Sequence[Sequence[object]] | None

The faces of a network: None for a minimum cycle basis, 'hyperedges' for the hyperedges of the network, 'cliques' for its cliques up to dimension, or a sequence of node cycles. A lattice takes None.

None
dimension int

The highest degree of the cells. Cells of degree 3 take part in incidence and topology only, since a group lives on sites, edges, or hyperedges.

LOWEST_DIMENSION
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
incidences_

The oriented incidence of every degree k as a SciPy CSR array with entries of 1 and -1, with one row per cell of degree k + 1 and one column per cell of degree k, so incidences_[k + 1] @ incidences_[k] is 0.

cofaces_

The cells of degree k + 1 that contain every cell of degree k, with one row per cell, padded with the number of cells of degree k + 1.

orientations_

The sign of every coface of cofaces_ relative to its cell, padded with 0.

boundaries_

The edges around every face in the cyclic order of its nodes, from the edge that leaves its first node, with one row per face, padded with -1.

boundary_signs_

The sign of the traversal of every edge of boundaries_, 1 along its orientation and -1 against it, padded with 0, so the path-ordered product of link matrices along a row is the holonomy of the face.

stars_

The diagonal of the Hodge star of every degree, the ratio of dual to primal cell volumes on a lattice and 1 on a network.

link_axes_

The axis of every link of a lattice, empty on a network.

face_planes_

The two axes of the plane of every plaquette of a lattice, empty on a network.

Raises:

Type Description
SpaceError

A face uses a pair of nodes that the graph does not join, the network has a loop, or the lattice is not square.

Examples:

>>> import networkx as nx
>>> from skcomplex.core.components.spaces import CellComplexSpace, LatticeSpace, NetworkSpace
>>> square = CellComplexSpace(LatticeSpace((8, 8))).prepare()
>>> [matrix.shape for matrix in square.incidences_]
[(128, 64), (64, 128)]
>>> int((square.incidences_[1] @ square.incidences_[0]).count_nonzero())
0
>>> chorded = nx.cycle_graph(4)
>>> chorded.add_edge(0, 2)
>>> CellComplexSpace(NetworkSpace(chorded)).prepare().hyperedges_
((0, 1, 2), (0, 2, 3))
>>> square.boundaries_[0].tolist(), square.boundary_signs_[0].tolist()
([0, 72, 1, 64], [1.0, 1.0, -1.0, -1.0])
Source code in src/skcomplex/core/components/spaces/_cell_complex.py
109
110
111
112
113
114
115
116
117
118
119
120
def __init__(
    self,
    space: LatticeSpace | NetworkSpace,
    *,
    faces: str | Sequence[Sequence[object]] | None = None,
    dimension: int = LOWEST_DIMENSION,
    label: str = 'space',
) -> None:
    super().__init__(nx.Graph(), label=label)
    self.space = space
    self.faces = faces
    self.dimension = dimension

CofaceNeighborhood(*, label='neighborhood')

Bases: BaseNeighborhood

Cells of the next degree that contain a cell of a cell complex, the faces of an edge or the edges of a site.

A reduction over the neighbourhood runs over those cells. Inside it exterior_derivative of a state of the reading group is the oriented sum of its values around the one coface, which is the holonomy of an abelian connection only, such as an angle or an integer modulo a period. Every coface counts with weight one, so the orientation of a coface relative to the cell enters through that oriented sum alone.

Parameters:

Name Type Description Default
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import CofaceNeighborhood
>>> CofaceNeighborhood().prepare().label
'neighborhood'
Source code in src/skcomplex/core/components/spaces/_coface.py
27
28
def __init__(self, *, label: str = 'neighborhood') -> None:
    super().__init__(label)

ConeNeighborhood(radius, angle, *, direction=None, center=False, label='neighborhood')

Bases: BaseNeighborhood

Members of a continuous space within a distance of a member and inside a cone around its direction.

A neighbour is within the radius of a member and its direction from the member makes an angle of at most angle with the member's direction, so angle = pi is the whole disk and angle = pi / 2 the half ahead. The direction is a vector state of the members, or in two dimensions a scalar state that holds the direction angle.

Parameters:

Name Type Description Default
radius float

The largest distance of a neighbour.

required
angle float

The half-angle of the cone, between 0 and pi.

required
direction str | None

The name of the state that holds the direction, or None for the velocity, the vector state declared as the rate of the position.

None
center bool

Whether a member is its own neighbour.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> import math
>>> from skcomplex.core.components.spaces import ConeNeighborhood
>>> ConeNeighborhood(1.0, math.pi / 2, direction='theta').direction
'theta'
Source code in src/skcomplex/core/components/spaces/_cone.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def __init__(
    self,
    radius: float,
    angle: float,
    *,
    direction: str | None = None,
    center: bool = False,
    label: str = 'neighborhood',
) -> None:
    super().__init__(label)
    self.radius = radius
    self.angle = angle
    self.direction = direction
    self.center = center

ContinuousSpace(extent=(), *, n_dim=None, boundary='open', regions=None, label='space')

Bases: BaseSpace

Continuous space of real positions, a box with a boundary or the whole of R^d.

A box has an extent along every dimension, from 0 to the extent. Its boundary acts on every position state of the members on it: 'open' leaves positions alone, 'periodic' joins opposite sides and measures pair distances by the minimum image, and 'reflecting' mirrors positions back into the box and reverses the velocity across the wall. Without an extent the space is unbounded and has n_dim dimensions. The positions are states of the members, so the space has no sites, and a site has no neighbours in it. A space of two dimensions can hold layers of regions, the polygons of a map in the coordinates of the space, which agents are placed in, moved to, and ask about.

Parameters:

Name Type Description Default
extent Sequence[float]

The side of the box along every dimension, or empty for an unbounded space.

()
n_dim int | None

The number of dimensions of an unbounded space, or None for the number of sides of the box.

None
boundary str

'open', 'periodic', or 'reflecting'.

'open'
regions Mapping[str, Sequence[Polygon]] | None

The polygons of every layer of regions by the name of the layer, or None for no layers.

None
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
extent_

The side of the box along every dimension, or () for an unbounded space.

regions_

The Shapely STRtree of the polygons of every layer of regions by the name of the layer.

Raises:

Type Description
SpaceError

A region is not a valid polygon in two dimensions, or the space has regions and other than two dimensions, raised by prepare.

Examples:

>>> from skcomplex.core.components.spaces import ContinuousSpace
>>> ContinuousSpace(extent=(10.0, 5.0), boundary='periodic').prepare().extent_
(10.0, 5.0)
>>> ContinuousSpace(n_dim=3).prepare().n_dim_
3
>>> from shapely import Point, box
>>> layers = {'parks': [box(0.0, 0.0, 1.0, 1.0), box(2.0, 2.0, 3.0, 3.0)]}
>>> space = ContinuousSpace(extent=(5.0, 5.0), regions=layers).prepare()
>>> space.regions_['parks'].query(Point(2.5, 2.5), predicate='intersects').tolist()
[1]
Source code in src/skcomplex/core/components/spaces/_continuous.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def __init__(
    self,
    extent: Sequence[float] = (),
    *,
    n_dim: int | None = None,
    boundary: str = 'open',
    regions: Mapping[str, Sequence[Polygon]] | None = None,
    label: str = 'space',
) -> None:
    super().__init__(label)
    self.extent = extent
    self.n_dim = n_dim
    self.boundary = boundary
    self.regions = regions

ContinuousTime(*, label='time')

Bases: BaseTime

Continuous time, in which rates, noise, implicit equations, jumps, and crossings change the states.

Parameters:

Name Type Description Default
label str

The label of the time in its system.

'time'

Examples:

>>> from skcomplex.core.components.spaces import ContinuousTime
>>> ContinuousTime().prepare().schedule_ is None
True
Source code in src/skcomplex/core/components/spaces/_continuous_time.py
22
23
def __init__(self, *, label: str = 'time') -> None:
    super().__init__(label)

DiscreteTime(schedule='synchronous', *, label='time')

Bases: BaseTime

Discrete time, in which updates, rules, local rules, rewrites, and samples change the states every step.

Parameters:

Name Type Description Default
schedule str | ScheduleSpec

'synchronous', every update reads the states of the start of the step, 'random-sequential', members update one after another in a new random order, or a ScheduleSpec that a formalism defines, such as the phases of agents.

'synchronous'
label str

The label of the time in its system.

'time'

Examples:

>>> from skcomplex.core.components.spaces import DiscreteTime
>>> DiscreteTime(schedule='random-sequential').prepare().schedule_
'random-sequential'
Source code in src/skcomplex/core/components/spaces/_discrete_time.py
34
35
36
def __init__(self, schedule: str | ScheduleSpec = 'synchronous', *, label: str = 'time') -> None:
    super().__init__(label)
    self.schedule = schedule

GeneratorNeighborhood(generators, *, center=True, inverses=True, label='neighborhood')

Bases: MemoryNeighborhood

Memory set of the generators of a group, after the identity and with their inverses when asked.

Parameters:

Name Type Description Default
generators tuple[str, ...]

The names of the generators of the group.

required
center bool

Whether the identity comes first.

True
inverses bool

Whether every generator is followed by its inverse.

True
label str

The label of the neighbourhood.

'neighborhood'

Attributes:

Name Type Description
elements_

The words of the memory set.

Examples:

>>> from skcomplex.core.components.spaces import GeneratorNeighborhood
>>> GeneratorNeighborhood(['a', 'b']).prepare().elements_
('1', 'a', 'a^-1', 'b', 'b^-1')
Source code in src/skcomplex/core/components/spaces/_generator.py
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(
    self,
    generators: tuple[str, ...],
    *,
    center: bool = True,
    inverses: bool = True,
    label: str = 'neighborhood',
) -> None:
    super().__init__((), label=label)
    self.generators = generators
    self.center = center
    self.inverses = inverses

LatticeNeighborhood(offsets, *, label='neighborhood')

Bases: BaseNeighborhood

Ordered offsets of the sites a local rule reads, with the site itself as the zero offset when included.

Parameters:

Name Type Description Default
offsets tuple[IntTuple, ...]

The offsets, one tuple of integers per neighbour, in the order rules receive their values.

required
label str

The label of the neighbourhood.

'neighborhood'

Attributes:

Name Type Description
offsets_

The offsets as tuples of integers.

Examples:

>>> from skcomplex.core.components.spaces import LatticeNeighborhood
>>> LatticeNeighborhood([(0, 1), (1, 0)]).prepare().offsets_
((0, 1), (1, 0))
Source code in src/skcomplex/core/components/spaces/_lattice_neighborhood.py
34
35
36
def __init__(self, offsets: tuple[IntTuple, ...], *, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.offsets = offsets

LatticeSpace(shape, *, geometry='square', boundary='periodic', fill=0.0, spacing=1.0, neighborhood='moore', capacity=None, label='space')

Bases: BaseSpace

Box of the integer lattice Z^d, or of the triangular lattice in 2D, whose sites hold lattice group members.

Sites are numbered in row-major order, so site (i, j) of a lattice of shape (n, m) is member i * m + j. A triangular site (i, j) keeps its integer coordinates on a skewed grid and sits at (j + i / 2, i sqrt(3) / 2) in the plane, so it has six neighbours at unit distance.

Parameters:

Name Type Description Default
shape Sequence[int] | int

The number of sites along every dimension.

required
geometry str

'square' for the integer lattice or 'triangular' for the triangular lattice in two dimensions.

'square'
boundary str

'periodic' to join opposite edges, 'fixed' to read the value fill outside the box, or 'reflecting' to read the mirror image of the box outside it.

'periodic'
fill float

The value read outside a lattice with fixed edges.

0.0
spacing float | Sequence[float]

The distance between neighbouring sites, or one distance per dimension.

1.0
neighborhood str

The neighbourhood agents on a two-dimensional square lattice query, 'moore' for the 8 surrounding cells or 'von_neumann' for the 4 adjacent ones.

'moore'
capacity int | None

The largest number of agents in a cell, or None for no limit.

None
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
spacings_

The distance between neighbouring sites along every dimension.

Examples:

>>> from skcomplex.core.components.spaces import LatticeSpace
>>> space = LatticeSpace((10, 20)).prepare()
>>> space.size_, space.n_dim_, space.shape_
(200, 2, (10, 20))
>>> LatticeSpace((2, 2), geometry='triangular').prepare().positions_.round(3).tolist()
[[0.0, 0.0], [1.0, 0.0], [0.5, 0.866], [1.5, 0.866]]
Source code in src/skcomplex/core/components/spaces/_lattice.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    shape: Sequence[int] | int,
    *,
    geometry: str = 'square',
    boundary: str = 'periodic',
    fill: float = 0.0,
    spacing: float | Sequence[float] = 1.0,
    neighborhood: str = 'moore',
    capacity: int | None = None,
    label: str = 'space',
) -> None:
    super().__init__(label)
    self.shape = shape
    self.geometry = geometry
    self.boundary = boundary
    self.fill = fill
    self.spacing = spacing
    self.neighborhood = neighborhood
    self.capacity = capacity

MemoryNeighborhood(elements, *, label='neighborhood')

Bases: BaseNeighborhood

Ordered memory set of a cellular automaton on a group space: the group elements whose values a rule reads.

A cell at the element g reads the cell at g s for every element s of the memory set, in its order, so the neighbourhood keeps its orientation. Elements are words in the generators, such as 'a', 'a^-1', or 'a*b', and '1' is the identity, the cell itself.

Parameters:

Name Type Description Default
elements Sequence[str]

The words of the memory set.

required
label str

The label of the neighbourhood.

'neighborhood'

Attributes:

Name Type Description
elements_

The words of the memory set as strings.

Examples:

>>> from skcomplex.core.components.spaces import MemoryNeighborhood
>>> MemoryNeighborhood(['1', 'a', 'a^-1']).prepare().elements_
('1', 'a', 'a^-1')
Source code in src/skcomplex/core/components/spaces/_memory.py
35
36
37
def __init__(self, elements: Sequence[str], *, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.elements = elements

MeshSpace(points, cells, *, label='space')

Bases: BaseSpace

Unstructured mesh of triangles or tetrahedra, whose nodes hold the members of finite element fields.

Build the nodes and cells with scikit-fem, such as skfem.MeshTri.init_lshaped().refined(3), and pass its p and t arrays.

Parameters:

Name Type Description Default
points ArrayLike

The coordinates of the nodes, one column per node and one row per dimension.

required
cells ArrayLike

The node indices of every triangle or tetrahedron, one column per cell.

required
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
mesh_

The scikit-fem mesh of the nodes and cells, a MeshTri in 2D or a MeshTet in 3D.

boundary_nodes_

The indices of the nodes on the boundary of the mesh.

Examples:

>>> import numpy as np
>>> import skfem
>>> from skcomplex.core.components.spaces import MeshSpace
>>> grid = skfem.MeshTri.init_tensor(np.linspace(0, 1, 5), np.linspace(0, 1, 4))
>>> mesh = MeshSpace(grid.p, grid.t).prepare()
>>> mesh.size_, mesh.n_dim_, mesh.positions_.shape
(20, 2, (20, 2))
Source code in src/skcomplex/core/components/spaces/_mesh.py
49
50
51
52
def __init__(self, points: npt.ArrayLike, cells: npt.ArrayLike, *, label: str = 'space') -> None:
    super().__init__(label)
    self.points = points
    self.cells = cells

MooreNeighborhood(radius=1, *, n_dim=2, center=False, label='neighborhood')

Bases: LatticeNeighborhood

Sites within a radius along every dimension, the 8 surrounding cells in two dimensions for radius 1.

The offsets come in lexicographic order, after the site itself when it is included.

Parameters:

Name Type Description Default
radius int

The largest offset along any dimension.

1
n_dim int

The number of dimensions.

2
center bool

Whether the site itself comes first.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import MooreNeighborhood
>>> len(MooreNeighborhood().prepare().offsets_)
8
>>> len(MooreNeighborhood(center=True).prepare().offsets_)
9
Source code in src/skcomplex/core/components/spaces/_moore.py
39
40
41
42
43
def __init__(self, radius: int = 1, *, n_dim: int = 2, center: bool = False, label: str = 'neighborhood') -> None:
    BaseNeighborhood.__init__(self, label)
    self.radius = radius
    self.n_dim = n_dim
    self.center = center

NearestNeighborhood(k, *, center=False, label='neighborhood')

Bases: BaseNeighborhood

The k members of a continuous space closest to a member, the topological neighbourhood.

Distances are measured between the position states of the members, by the minimum image in a periodic box. A group of at most k other members gives every member all the others.

Parameters:

Name Type Description Default
k int

The number of neighbours.

required
center bool

Whether a member is its own neighbour, besides its k nearest others.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import NearestNeighborhood
>>> NearestNeighborhood(7).k
7
Source code in src/skcomplex/core/components/spaces/_nearest.py
32
33
34
35
def __init__(self, k: int, *, center: bool = False, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.k = k
    self.center = center

NetworkSpace(graph, *, layers=(), capacity=None, fill=0.0, hyperedges=(), label='space')

Bases: BaseSpace

Network whose nodes agents occupy or lattice sites sit on, with the neighbours of a node given by its edges.

Lattice groups on a network hold one member per node, in the order of graph.nodes. The graph is the default layer, and layers adds named relations over the same nodes. Edges of a layer are numbered in the order of graph.edges, and every layer lowers to arrays: the source and target node index of every edge, and the adjacency of every node in compressed sparse row form. On an undirected graph the source of an edge is the end that comes first in node order. Build the graph with NetworkX, such as nx.erdos_renyi_graph(100, 0.1, seed=0).

Parameters:

Name Type Description Default
graph Graph

The NetworkX graph, the default layer.

required
layers Sequence[LayerSpec]

The named layers over the nodes of the graph.

()
capacity int | None

The largest number of agents on a node, or None for no limit.

None
fill float

The value lattice rules read in the padding slots of nodes with fewer neighbours than the largest degree.

0.0
hyperedges Sequence[Sequence[object]] | Hypergraph

The hyperedges of the network, which hyperedge interactions and groups on hyperedges read: sequences of at least two nodes, an XGI Hypergraph, or an XGI SimplicialComplex, whose faces of at least two nodes are its hyperedges. The members of an XGI hyperedge follow the order of the nodes of the graph.

()
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
graphs_

The graph of every layer by label, None first for the graph of the network.

edges_

The source and target node index of every edge of every layer by label, in the order of graph.edges.

adjacency_

The neighbours of every node of every layer by label in compressed sparse row form: the start of the neighbours of every node with the number of entries last, the sorted neighbours of every node in turn, the successors on a directed graph, and the edge that joins every node to every neighbour.

hyperedges_

The node index of every member of every hyperedge, in the order of the hyperedges.

incidence_

The sparse incidence matrix of the hyperedges, with one row per node and one column per hyperedge.

memberships_

The indices of the hyperedges every node belongs to, in increasing order.

Examples:

>>> import networkx as nx
>>> from skcomplex.core.components.spaces import NetworkSpace
>>> from skcomplex.core.specs import LayerSpec
>>> NetworkSpace(nx.cycle_graph(5)).prepare().size_
5
>>> space = NetworkSpace(nx.path_graph(3), layers=[LayerSpec('work', nx.complete_graph(3))])
>>> space = space.prepare()
>>> list(space.graphs_), space.graphs_['work'].number_of_edges()
([None, 'work'], 3)
>>> [item.tolist() for item in space.edges_[None]]
[[0, 1], [1, 2]]
>>> [item.tolist() for item in space.adjacency_[None]]
[[0, 1, 3, 4], [1, 0, 2, 1], [0, 0, 1, 1]]
>>> import itertools
>>> import xgi
>>> triangles = xgi.Hypergraph([list(nodes) for nodes in itertools.combinations(range(5), 3)])
>>> space = NetworkSpace(nx.complete_graph(5), hyperedges=triangles).prepare()
>>> len(space.hyperedges_), space.hyperedges_[0], space.memberships_[0]
(10, (0, 1, 2), (0, 1, 2, 3, 4, 5))
Source code in src/skcomplex/core/components/spaces/_network.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def __init__(
    self,
    graph: nx.Graph,
    *,
    layers: Sequence[LayerSpec] = (),
    capacity: int | None = None,
    fill: float = 0.0,
    hyperedges: Sequence[Sequence[object]] | xgi.Hypergraph = (),
    label: str = 'space',
) -> None:
    super().__init__(label)
    self.graph = graph
    self.layers = layers
    self.capacity = capacity
    self.fill = fill
    self.hyperedges = hyperedges

PhaseSpace(position, axes, *, limit=DEFAULT_LIMIT, label='space')

Bases: LatticeSpace

Product of a position lattice and class axes, a lattice whose sites are the points of the product.

The position axes come first and sites are in row-major order, so the sites at one position are contiguous. A class axis has its own cells, of width (high - low) / size, and its own edges, without flux for an interval and joined for a periodic axis. The position axes keep the boundary of the position lattice.

Parameters:

Name Type Description Default
position LatticeSpace

The lattice of the positions.

required
axes Sequence[AxisSpec]

The class axes.

required
limit int

The largest number of sites of the space.

DEFAULT_LIMIT
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
coordinates_

The coordinate of every cell of every class axis, at the cell centres, by the name of the axis.

position_axes_

The indices of the position axes among the axes of the product.

class_axes_

The index of every class axis among the axes of the product, by its name.

position_size_

The number of positions.

axes_periodic_

Whether the edges of every axis of the product are joined.

Raises:

Type Description
PhaseSpaceTooLargeError

The product has more sites than the limit.

Examples:

>>> from skcomplex.core.components.spaces import LatticeSpace, PhaseSpace
>>> from skcomplex.core.specs import AxisSpec
>>> space = PhaseSpace(LatticeSpace((64,), spacing=0.2), [AxisSpec('v', 128, -6.0, 6.0)]).prepare()
>>> space.size_, space.shape_, space.class_axes_
(8192, (64, 128), {'v': 1})
>>> space.coordinates_['v'][:2].round(4).tolist()
[-5.9531, -5.8594]
Source code in src/skcomplex/core/components/spaces/_phase.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def __init__(
    self,
    position: LatticeSpace,
    axes: Sequence[AxisSpec],
    *,
    limit: int = DEFAULT_LIMIT,
    label: str = 'space',
) -> None:
    super().__init__(
        position.shape,
        geometry=position.geometry,
        boundary=position.boundary,
        fill=position.fill,
        spacing=position.spacing,
        label=label,
    )
    self.position = position
    self.axes = axes
    self.limit = limit

PseudoRiemannianSpace(metric=None, coordinates=(), *, curvature=True, label='space')

Bases: ContinuousSpace

Chart of coordinates with a symbolic metric g_(mu nu), from which the connection and the curvature follow.

The metric is a symmetric, nondegenerate Sage matrix whose entries are expressions of the coordinates, numbers, and symbolic functions of the coordinates. A function stands for an unknown of an ansatz, such as the scale factor a(t) of a homogeneous universe, which EinsteinInteraction solves for. The space is an unbounded continuous space with one dimension per coordinate, so a point on it has one component per coordinate, as a test particle of GeodesicInteraction does.

prepare derives, through EinsteinPy, the Christoffel symbols Γ^mu_(alpha beta) = g^(mu lambda) (∂_alpha g_(lambda beta) + ∂_beta g_(lambda alpha) - ∂_lambda g_(alpha beta))/2 and, with curvature, the Riemann tensor R^rho_(sigma mu nu), its contraction the Ricci tensor R_(mu nu) = R^rho_(mu rho nu), the Ricci scalar R = g^(mu nu) R_(mu nu), and the Einstein tensor G_(mu nu) = R_(mu nu) - R g_(mu nu) / 2. The curvature of a metric with many entries, such as Kerr's, takes long to simplify, so it can be left out when only the connection is needed.

Parameters:

Name Type Description Default
metric Matrix | Sequence[Sequence[object]] | None

The metric, a square matrix of expressions, or None for the metric a subclass derives.

None
coordinates Sequence[Expression | str]

The coordinates, Sage variables or their names, in the order of the rows of the metric.

()
curvature bool

Whether prepare derives the curvature tensors besides the Christoffel symbols.

True
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
coordinates_

The coordinates, as Sage variables.

metric_

The metric g_(mu nu).

inverse_metric_

The inverse metric g^(mu nu).

christoffel_

The Christoffel symbols, with christoffel_[mu][alpha][beta] the symbol Γ^mu_(alpha beta).

riemann_

The Riemann tensor, with riemann_[rho][sigma][mu][nu] the component R^rho_(sigma mu nu), or None without curvature.

ricci_

The Ricci tensor R_(mu nu), or None without curvature.

ricci_scalar_

The Ricci scalar, or None without curvature.

einstein_

The Einstein tensor G_(mu nu), or None without curvature.

derived_from_

The metric, the coordinates, and the curvature flag the tensors were derived from, which a later prepare compares by value to skip the derivation when they have not changed.

Raises:

Type Description
ValidationError

The metric is not a symmetric square matrix of one row per coordinate, a coordinate is not a variable, two coordinates coincide, or the metric is degenerate.

Examples:

>>> from sage.all__sagemath_symbolics import SR, matrix, sin
>>> from skcomplex.core.components.spaces import PseudoRiemannianSpace
>>> theta, phi = SR.var('theta phi')
>>> sphere = PseudoRiemannianSpace(matrix(SR, [[1, 0], [0, sin(theta) ** 2]]), (theta, phi)).prepare()
>>> sphere.christoffel_[0][1][1]
-cos(theta)*sin(theta)
>>> sphere.ricci_scalar_
2
Source code in src/skcomplex/core/components/spaces/_pseudo_riemannian.py
 98
 99
100
101
102
103
104
105
106
107
108
109
def __init__(
    self,
    metric: Matrix | Sequence[Sequence[object]] | None = None,
    coordinates: Sequence[Expression | str] = (),
    *,
    curvature: bool = True,
    label: str = 'space',
) -> None:
    super().__init__(label=label)
    self.metric = metric
    self.coordinates = coordinates
    self.curvature = curvature

RadiusNeighborhood(radius, *, center=False, label='neighborhood')

Bases: BaseNeighborhood

Members of a continuous space within a distance of a member, the metric neighbourhood.

Distances are measured between the position states of the members, by the minimum image in a periodic box.

Parameters:

Name Type Description Default
radius float

The largest distance of a neighbour.

required
center bool

Whether a member is its own neighbour.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import RadiusNeighborhood
>>> RadiusNeighborhood(1.5).radius
1.5
Source code in src/skcomplex/core/components/spaces/_radius.py
31
32
33
34
def __init__(self, radius: float, *, center: bool = False, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.radius = radius
    self.center = center

SimplexSpace(position, axes, *, limit=DEFAULT_LIMIT, label='space')

Bases: PhaseSpace

Phase space whose class axes are the free frequencies of a simplex, whose frequencies sum to at most one.

The class axes run over [0, 1] with the same number of cells n, and the last frequency is one minus the sum of the others, so two class axes hold the 2-simplex of three frequencies. The cells whose corner farthest from the origin lies inside the simplex are whole, the cells cut by the slanted face are partial, and the cells beyond it are empty. Every cell has the fraction of its volume inside the simplex, and every face between two cells the fraction of its area inside. Finite-volume fluxes pass only through the open part of every face, and reductions over the class axes weight every cell by its volume.

Parameters:

Name Type Description Default
position LatticeSpace

The lattice of the positions.

required
axes Sequence[AxisSpec]

The class axes, each on [0, 1] with the same number of cells and not periodic.

required
limit int

The largest number of sites of the space.

DEFAULT_LIMIT
label str

The label of the space in its system.

'space'

Attributes:

Name Type Description
volumes_

The fraction of the volume of every cell inside the simplex, with one entry along every position axis.

apertures_

The fraction of the area inside the simplex of every face along every axis of the product, one more face than cells along a class axis, and one entry along the position axes.

Raises:

Type Description
PhaseSpaceTooLargeError

The product has more sites than the limit.

Examples:

>>> from skcomplex.core.components.spaces import LatticeSpace, SimplexSpace
>>> from skcomplex.core.specs import AxisSpec
>>> axes = [AxisSpec('x', 10, 0.0, 1.0), AxisSpec('y', 10, 0.0, 1.0)]
>>> space = SimplexSpace(LatticeSpace((1,)), axes).prepare()
>>> space.shape_, float(space.volumes_.sum())
((1, 10, 10), 50.0)
>>> sorted(set(space.volumes_.ravel().tolist()))
[0.0, 0.5, 1.0]
Source code in src/skcomplex/core/components/spaces/_phase.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def __init__(
    self,
    position: LatticeSpace,
    axes: Sequence[AxisSpec],
    *,
    limit: int = DEFAULT_LIMIT,
    label: str = 'space',
) -> None:
    super().__init__(
        position.shape,
        geometry=position.geometry,
        boundary=position.boundary,
        fill=position.fill,
        spacing=position.spacing,
        label=label,
    )
    self.position = position
    self.axes = axes
    self.limit = limit

StateTranslationNeighborhood(elements=('1',), *, label='neighborhood')

Bases: BaseNeighborhood

Memory set translated by the value of a cell: the cell at g reads the cells at g x(g) s for every word s.

The alphabet is the group of a group space, and a cell holds a group element as the number of its site. A local rule over the neighbourhood reads, at every site g, the cells at the products g x(g) s, where x(g) is the value of the first state the rule reads. The sites read move with the configuration, so on an infinite group the map has no finite memory set, as for x(g · x(g)) of Ceccherini-Silberstein and Coornaert (2023, Example 1.8.2), which is continuous and equivariant but no cellular automaton. A run truncates it to a ball or a finite quotient: on a finite group every product is a site, and on a ball a product outside it, or reached through a site outside it, reads the fill value of the space, as does a value that names no site.

Parameters:

Name Type Description Default
elements Sequence[str]

The words s of the memory set after the translation, '1' for the translated site alone.

('1',)
label str

The label of the neighbourhood.

'neighborhood'

Attributes:

Name Type Description
elements_

The words of the memory set as strings.

Examples:

>>> from skcomplex.core.components.spaces import StateTranslationNeighborhood
>>> StateTranslationNeighborhood().prepare().elements_
('1',)
Source code in src/skcomplex/core/components/spaces/_state_translation.py
39
40
41
def __init__(self, elements: Sequence[str] = ('1',), *, label: str = 'neighborhood') -> None:
    super().__init__(label)
    self.elements = elements

StaticTime(*, label='time')

Bases: BaseTime

No time: the system holds only constraints and objectives, and a run solves it once.

Parameters:

Name Type Description Default
label str

The label of the time in its system.

'time'

Examples:

>>> from skcomplex.core.components.spaces import StaticTime
>>> StaticTime().prepare().schedule_ is None
True
Source code in src/skcomplex/core/components/spaces/_static_time.py
25
26
def __init__(self, *, label: str = 'time') -> None:
    super().__init__(label)

TriangularNeighborhood(*, center=False, label='neighborhood')

Bases: LatticeNeighborhood

Six neighbours of a site of a triangular lattice, counterclockwise from the direction of the second axis.

Direction k points at 60 k degrees in the plane, so direction k + 3 modulo 6 is opposite to direction k.

Parameters:

Name Type Description Default
center bool

Whether the site itself comes first.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import TriangularNeighborhood
>>> TriangularNeighborhood().prepare().offsets_
((0, 1), (1, 0), (1, -1), (0, -1), (-1, 0), (-1, 1))
Source code in src/skcomplex/core/components/spaces/_triangular.py
31
32
33
def __init__(self, *, center: bool = False, label: str = 'neighborhood') -> None:
    BaseNeighborhood.__init__(self, label)
    self.center = center

VonNeumannNeighborhood(radius=1, *, n_dim=2, center=False, label='neighborhood')

Bases: LatticeNeighborhood

Sites within a Manhattan distance, the 4 adjacent cells in two dimensions for radius 1.

The offsets come in lexicographic order, after the site itself when it is included.

Parameters:

Name Type Description Default
radius int

The largest Manhattan distance.

1
n_dim int

The number of dimensions.

2
center bool

Whether the site itself comes first.

False
label str

The label of the neighbourhood.

'neighborhood'

Examples:

>>> from skcomplex.core.components.spaces import VonNeumannNeighborhood
>>> VonNeumannNeighborhood().prepare().offsets_
((-1, 0), (0, -1), (0, 1), (1, 0))
Source code in src/skcomplex/core/components/spaces/_von_neumann.py
37
38
39
40
41
def __init__(self, radius: int = 1, *, n_dim: int = 2, center: bool = False, label: str = 'neighborhood') -> None:
    BaseNeighborhood.__init__(self, label)
    self.radius = radius
    self.n_dim = n_dim
    self.center = center