Skip to content

Utils

Define what laws are written with besides Sage: reductions, pointwise functions, draws, delays, and calls.

advection(expression, velocity)

Return the advection of a field expression by a velocity, its rate of transport, with upwind differences.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field.

required
velocity Sequence[object] | FreeModuleElement

One velocity component per dimension, numbers or expressions.

required

Returns:

Name Type Description
advection Expression

The advection term, to subtract from the rate of the field.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import advection
>>> sorted(str(term) for term in advection(var('u'), [1.0, 2.0]).operands())
['upwind(u, 0, 1.0)', 'upwind(u, 1, 2.0)']
Source code in src/skcomplex/core/laws/utils/_operators.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def advection(expression: object, velocity: Sequence[object] | FreeModuleElement) -> Expression:
    """Return the advection of a field expression by a velocity, its rate of transport, with upwind differences.

    Args:
        expression:
            An expression of the states and parameters of a field.

        velocity:
            One velocity component per dimension, numbers or expressions.

    Returns:
        advection:
            The advection term, to subtract from the rate of the field.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import advection
        >>> sorted(str(term) for term in advection(var('u'), [1.0, 2.0]).operands())
        ['upwind(u, 0, 1.0)', 'upwind(u, 1, 2.0)']
    """
    return sum((upwind(SR(expression), SR(axis), SR(item)) for axis, item in enumerate(velocity)), SR(0))

antisymmetric_part(tensor)

Return the antisymmetric part of a square matrix, (M - M^T) / 2.

Parameters:

Name Type Description Default
tensor Matrix

A square matrix of expressions.

required

Returns:

Name Type Description
part Matrix

The antisymmetric matrix.

Raises:

Type Description
TensorShapeError

The value is not a square matrix.

Examples:

>>> from sage.all__sagemath_symbolics import SR, matrix, var
>>> from skcomplex.core.laws.utils import antisymmetric_part
>>> antisymmetric_part(matrix(SR, [[0, 2 * var('a')], [0, 0]]))
[ 0  a]
[-a  0]
Source code in src/skcomplex/core/laws/utils/_tensors.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def antisymmetric_part(tensor: Matrix) -> Matrix:
    """Return the antisymmetric part of a square matrix, `(M - M^T) / 2`.

    Args:
        tensor:
            A square matrix of expressions.

    Returns:
        part:
            The antisymmetric matrix.

    Raises:
        TensorShapeError:
            The value is not a square matrix.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, matrix, var
        >>> from skcomplex.core.laws.utils import antisymmetric_part
        >>> antisymmetric_part(matrix(SR, [[0, 2 * var('a')], [0, 0]]))
        [ 0  a]
        [-a  0]
    """
    checked = _check_matrix('antisymmetric_part', tensor)
    return (checked - checked.transpose()) / 2

bernoulli(name, p)

Return 1 with a probability and 0 otherwise, drawn at every step.

Parameters:

Name Type Description Default
name str

The name of the draw, a Python identifier.

required
p object

The probability of 1, a number or an expression of states and parameters.

required

Returns:

Name Type Description
draw Expression

The symbolic draw.

Raises:

Type Description
ValidationError

The name is not a Python identifier.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import bernoulli
>>> 1 - bernoulli('flip', var('q'))
-bernoulli_flip(q) + 1
Source code in src/skcomplex/specs/_draw_spec.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def bernoulli(name: str, p: object) -> Expression:
    """Return 1 with a probability and 0 otherwise, drawn at every step.

    Args:
        name:
            The name of the draw, a Python identifier.

        p:
            The probability of 1, a number or an expression of states and parameters.

    Returns:
        draw:
            The symbolic draw.

    Raises:
        ValidationError:
            The name is not a Python identifier.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import bernoulli
        >>> 1 - bernoulli('flip', var('q'))
        -bernoulli_flip(q) + 1
    """
    return _call('bernoulli', name, (p,))

categorical(name, weights)

Return the index of a category drawn with probabilities proportional to weights at every step.

Parameters:

Name Type Description Default
name str

The name of the draw, a Python identifier.

required
weights Sequence[object]

The nonnegative weights of the categories 0 to len(weights) - 1, numbers or expressions of states and parameters.

required

Returns:

Name Type Description
draw Expression

The symbolic draw.

Raises:

Type Description
ValidationError

The name is not a Python identifier, or there are no weights.

Examples:

>>> from skcomplex.core.laws.utils import categorical
>>> categorical('map', [1, 1, 2])
categorical_map(1, 1, 2)
Source code in src/skcomplex/specs/_draw_spec.py
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
def categorical(name: str, weights: Sequence[object]) -> Expression:
    """Return the index of a category drawn with probabilities proportional to weights at every step.

    Args:
        name:
            The name of the draw, a Python identifier.

        weights:
            The nonnegative weights of the categories `0` to `len(weights) - 1`, numbers or expressions of states and
            parameters.

    Returns:
        draw:
            The symbolic draw.

    Raises:
        ValidationError:
            The name is not a Python identifier, or there are no weights.

    Examples:
        >>> from skcomplex.core.laws.utils import categorical
        >>> categorical('map', [1, 1, 2])
        categorical_map(1, 1, 2)
    """
    if isinstance(weights, str) or not len(weights):
        error_msg = f'The categorical draw {name!r} takes a nonempty sequence of weights, but got {weights!r}.'
        raise ValidationError(error_msg)
    return _call('categorical', name, tuple(weights))

circular_mean(angle, over=None, where=None, weights=None, by=None)

Return the circular mean of an angle over the members of a group, arctan2(sum_j sin a_j, sum_j cos a_j).

The mean is the direction of the sum of the unit vectors at the angles, 0 when no member is selected.

Parameters:

Name Type Description Default
angle object

The angle of one member, in radians.

required
over object

The namespace of the group to reduce over, or None for the group the angle reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None
weights object

A constant matrix with one row per reading member and one column per reduced member, or None.

None
by str | None

The name of a state of the group, so every member reduces over the members that share its value of the state, or None for all members. It needs the namespace of the group as over.

None

Returns:

Name Type Description
reduction Expression

The symbolic circular mean, in [-pi, pi].

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import circular_mean
>>> circular_mean(var('theta'))
arctan2(total(sin(theta)), total(cos(theta)))
Source code in src/skcomplex/core/laws/utils/_reductions.py
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
def circular_mean(
    angle: object,
    over: object = None,
    where: object = None,
    weights: object = None,
    by: str | None = None,
) -> Expression:
    """Return the circular mean of an angle over the members of a group, `arctan2(sum_j sin a_j, sum_j cos a_j)`.

    The mean is the direction of the sum of the unit vectors at the angles, 0 when no member is selected.

    Args:
        angle:
            The angle of one member, in radians.

        over:
            The namespace of the group to reduce over, or `None` for the group the angle reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

        weights:
            A constant matrix with one row per reading member and one column per reduced member, or `None`.

        by:
            The name of a state of the group, so every member reduces over the members that share its value of the
            state, or `None` for all members. It needs the namespace of the group as `over`.

    Returns:
        reduction:
            The symbolic circular mean, in `[-pi, pi]`.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import circular_mean
        >>> circular_mean(var('theta'))
        arctan2(total(sin(theta)), total(cos(theta)))
    """
    sines = total(sin(SR(angle)), over, where, weights, by)
    cosines = total(cos(SR(angle)), over, where, weights, by)
    return arctan2(sines, cosines)

clip(x, low=None, high=None)

Return an expression limited to an interval, low below it, high above it, and the expression inside.

Parameters:

Name Type Description Default
x object

The expression to limit.

required
low object

The lower bound, or None for none.

None
high object

The upper bound, or None for none.

None

Returns:

Name Type Description
value Expression

The symbolic limited value.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import clip
>>> x = var('x')
>>> clip(x, 0, 1)
where(x < 0, 0, where(x > 1, 1, x))
>>> [clip(x, 0, 1).subs(x=value) for value in (-2, 0.5, 3)]
[0, 0.5, 1]
Source code in src/skcomplex/core/laws/utils/_pointwise.py
106
107
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
134
135
136
137
def clip(x: object, low: object = None, high: object = None) -> Expression:
    """Return an expression limited to an interval, `low` below it, `high` above it, and the expression inside.

    Args:
        x:
            The expression to limit.

        low:
            The lower bound, or `None` for none.

        high:
            The upper bound, or `None` for none.

    Returns:
        value:
            The symbolic limited value.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import clip
        >>> x = var('x')
        >>> clip(x, 0, 1)
        where(x < 0, 0, where(x > 1, 1, x))
        >>> [clip(x, 0, 1).subs(x=value) for value in (-2, 0.5, 3)]
        [0, 0.5, 1]
    """
    value = SR(x)
    if high is not None:
        value = where(SR(x) > high, high, value)
    if low is not None:
        value = where(SR(x) < low, low, value)
    return value

components(active, over=None, reduce='label')

Return a reduction over the connected components of the active links between the members of a group.

The links are the pairs of neighbours of the neighbourhood, the graph of a network space by default, and a link is active when active, written for one link as in neighbors, is not 0. Inside it namespace.other holds the symbols of the neighbour, namespace those of the member, and the states of a group on the edges of the network those of the edge that joins them, so a link is active by a condition, such as edges.state > 0, or by an edge weight that is not 0. Two members are in one component when a path of active links joins them, read without direction on a directed network, and a member without active links is a component of its own.

The reductions are 'label', the smallest index of a member of the component of every member, 'size', the number of members of the component of every member, 'largest', the number of members of the largest component, the same for every member, and 'giant', 1 for the members of the largest component and 0 for the others, the component of the smallest label among components of equal size.

Parameters:

Name Type Description Default
active object

The activity of one link, an expression of the symbols of namespace.other, namespace, and the edge between them, whose links are active where it is not 0.

required
over BaseNeighborhood | LayerSpec | None

The neighbourhood, or None for the neighbours of the network or lattice the group lives on.

None
reduce str

'label', 'size', 'largest', or 'giant'.

'label'

Returns:

Name Type Description
reduction Expression

The unevaluated reduction, one value per member.

Raises:

Type Description
ValidationError

The reduction or the neighbourhood is unknown.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import components, neighborhoods_in
>>> components(var('open') > 0, reduce='size')
components(open > 0, reduce='size')
>>> [item.kind for item in neighborhoods_in(1 + components(var('open'), reduce='largest'))]
['component-largest']
Source code in src/skcomplex/core/laws/utils/_components.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def components(
    active: object,
    over: BaseNeighborhood | LayerSpec | None = None,
    reduce: str = 'label',
) -> Expression:
    """Return a reduction over the connected components of the active links between the members of a group.

    The links are the pairs of neighbours of the neighbourhood, the graph of a network space by default, and a link is
    active when `active`, written for one link as in `neighbors`, is not 0. Inside it `namespace.other` holds the
    symbols of the neighbour, `namespace` those of the member, and the states of a group on the edges of the network
    those of the edge that joins them, so a link is active by a condition, such as `edges.state > 0`, or by an edge
    weight that is not 0. Two members are in one component when a path of active links joins them, read without
    direction on a directed network, and a member without active links is a component of its own.

    The reductions are `'label'`, the smallest index of a member of the component of every member, `'size'`, the
    number of members of the component of every member, `'largest'`, the number of members of the largest component,
    the same for every member, and `'giant'`, 1 for the members of the largest component and 0 for the others, the
    component of the smallest label among components of equal size.

    Args:
        active:
            The activity of one link, an expression of the symbols of `namespace.other`, `namespace`, and the edge
            between them, whose links are active where it is not 0.

        over:
            The neighbourhood, or `None` for the neighbours of the network or lattice the group lives on.

        reduce:
            `'label'`, `'size'`, `'largest'`, or `'giant'`.

    Returns:
        reduction:
            The unevaluated reduction, one value per member.

    Raises:
        ValidationError:
            The reduction or the neighbourhood is unknown.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import components, neighborhoods_in
        >>> components(var('open') > 0, reduce='size')
        components(open > 0, reduce='size')
        >>> [item.kind for item in neighborhoods_in(1 + components(var('open'), reduce='largest'))]
        ['component-largest']
    """
    if reduce not in COMPONENT_REDUCTIONS:
        error_msg = f'Unknown components reduction {reduce!r}. Use one of {", ".join(COMPONENT_REDUCTIONS)}.'
        raise ValidationError(error_msg)
    return COMPONENTS(SR(active), _neighborhood_symbol(over), COMPONENT_REDUCTIONS.index(reduce))

contraction(tensor, *expressions)

Return the contraction of a tensor with one expression per index after the first, sum_jk T_ijk a_j b_k.

The tensor is a parameter of an interaction declared ParameterSpec(shape=(role,) * k), whose first index runs over the reading members and every other index over the members of a group. Inside the contraction the symbols of the expression of an index stand for the member of that index, so contraction(T, x, x) reads x_j and x_k.

Parameters:

Name Type Description Default
tensor object

The symbol of the shaped parameter of order k.

required
*expressions object

The k - 1 expressions of one member, one per index after the first.

()

Returns:

Name Type Description
contraction Expression

The unevaluated contraction, one value per reading member, which every backend lowers.

Raises:

Type Description
ValidationError

The tensor is not a symbol, or no expression is given.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import contraction, contractions_in
>>> T, x = var('T x')
>>> contraction(T, x, x)
contraction(T, x, x)
>>> [call.operands()[0] for call in contractions_in(x * contraction(T, x, x))]
[T]
Source code in src/skcomplex/core/laws/utils/_contractions.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def contraction(tensor: object, *expressions: object) -> Expression:
    """Return the contraction of a tensor with one expression per index after the first, `sum_jk T_ijk a_j b_k`.

    The tensor is a parameter of an interaction declared `ParameterSpec(shape=(role,) * k)`, whose first index runs
    over the reading members and every other index over the members of a group. Inside the contraction the symbols of
    the expression of an index stand for the member of that index, so `contraction(T, x, x)` reads `x_j` and `x_k`.

    Args:
        tensor:
            The symbol of the shaped parameter of order `k`.

        *expressions:
            The `k - 1` expressions of one member, one per index after the first.

    Returns:
        contraction:
            The unevaluated contraction, one value per reading member, which every backend lowers.

    Raises:
        ValidationError:
            The tensor is not a symbol, or no expression is given.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import contraction, contractions_in
        >>> T, x = var('T x')
        >>> contraction(T, x, x)
        contraction(T, x, x)
        >>> [call.operands()[0] for call in contractions_in(x * contraction(T, x, x))]
        [T]
    """
    if not isinstance(tensor, Expression) or not tensor.is_symbol():
        error_msg = f'The tensor of a contraction must be the symbol of a shaped parameter, not {tensor!r}.'
        raise ValidationError(error_msg)
    if len(expressions) < MIN_FACTORS:
        error_msg = f'The contraction of {tensor} needs one expression per index after the first, but none is given.'
        raise ValidationError(error_msg)
    return CONTRACTION(tensor, *(SR(item) for item in expressions))

contractions_in(expression)

Return the outermost contraction calls of an expression, whose operands are the tensor and the expressions.

Parameters:

Name Type Description Default
expression object

A symbolic expression, a number, or a vector of expressions.

required

Returns:

Name Type Description
contractions list[Expression]

The contraction calls, each listed once.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import contraction, contractions_in
>>> T, x = var('T x')
>>> len(contractions_in(1 + contraction(T, x)))
1
Source code in src/skcomplex/core/laws/utils/_contractions.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def contractions_in(expression: object) -> list[Expression]:
    """Return the outermost contraction calls of an expression, whose operands are the tensor and the expressions.

    Args:
        expression:
            A symbolic expression, a number, or a vector of expressions.

    Returns:
        contractions:
            The contraction calls, each listed once.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import contraction, contractions_in
        >>> T, x = var('T x')
        >>> len(contractions_in(1 + contraction(T, x)))
        1
    """
    return _calls_in(expression, {'contraction'})

convolve(expression, kernel, radius=None)

Return the convolution of a field expression with a kernel of the displacement, the sum over sites times volume.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field on a lattice.

required
kernel object

An expression of the symbols displacement(axis) and of parameters.

required
radius object

The distance beyond which the kernel is zero, or None for a kernel over the whole lattice.

None

Returns:

Name Type Description
convolution Expression

The call convolve(expression, kernel, radius).

Examples:

>>> from sage.all__sagemath_symbolics import var, exp
>>> from skcomplex.core.laws.utils import convolve, displacement
>>> convolve(var('u'), exp(-displacement(0) ** 2), 3)
convolve(u, e^(-displacement_0^2), 3)
Source code in src/skcomplex/core/laws/utils/_operators.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def convolve(expression: object, kernel: object, radius: object = None) -> Expression:
    """Return the convolution of a field expression with a kernel of the displacement, the sum over sites times volume.

    Args:
        expression:
            An expression of the states and parameters of a field on a lattice.

        kernel:
            An expression of the symbols `displacement(axis)` and of parameters.

        radius:
            The distance beyond which the kernel is zero, or `None` for a kernel over the whole lattice.

    Returns:
        convolution:
            The call `convolve(expression, kernel, radius)`.

    Examples:
        >>> from sage.all__sagemath_symbolics import var, exp
        >>> from skcomplex.core.laws.utils import convolve, displacement
        >>> convolve(var('u'), exp(-displacement(0) ** 2), 3)
        convolve(u, e^(-displacement_0^2), 3)
    """
    return CONVOLVE(SR(expression), SR(kernel), oo if radius is None else SR(radius))

coordinate(name)

Return the coordinate of the point a law updates along a named class axis of a phase space.

Parameters:

Name Type Description Default
name str

The name of the class axis.

required

Returns:

Name Type Description
coordinate Expression

The symbol of the coordinate, a real number at every point of the space.

Raises:

Type Description
ValidationError

The name is no identifier.

Examples:

>>> from skcomplex.core.laws.utils import coordinate
>>> coordinate('v') ** 2
_coordinate_v^2
Source code in src/skcomplex/core/laws/utils/_phase.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def coordinate(name: str) -> Expression:
    """Return the coordinate of the point a law updates along a named class axis of a phase space.

    Args:
        name:
            The name of the class axis.

    Returns:
        coordinate:
            The symbol of the coordinate, a real number at every point of the space.

    Raises:
        ValidationError:
            The name is no identifier.

    Examples:
        >>> from skcomplex.core.laws.utils import coordinate
        >>> coordinate('v') ** 2
        _coordinate_v^2
    """
    if not isinstance(name, str) or not name.isidentifier():
        error_msg = f'A class axis is named by an identifier, but it was given {name!r}.'
        raise ValidationError(error_msg)
    return SR.var(f'{COORDINATE_PREFIX}{name}', domain='real')

coordinates_of(axes)

Return the coordinates of named class axes.

Parameters:

Name Type Description Default
axes Sequence[str]

The names of the class axes.

required

Returns:

Name Type Description
coordinates list[Expression]

The symbol of the coordinate of every axis.

Raises:

Type Description
ValidationError

No axis is named, or an axis is named by a value that is no identifier.

Examples:

>>> from skcomplex.core.laws.utils import coordinates_of
>>> coordinates_of(('v', 'w'))
[_coordinate_v, _coordinate_w]
Source code in src/skcomplex/core/laws/utils/_phase.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def coordinates_of(axes: Sequence[str]) -> list[Expression]:
    """Return the coordinates of named class axes.

    Args:
        axes:
            The names of the class axes.

    Returns:
        coordinates:
            The symbol of the coordinate of every axis.

    Raises:
        ValidationError:
            No axis is named, or an axis is named by a value that is no identifier.

    Examples:
        >>> from skcomplex.core.laws.utils import coordinates_of
        >>> coordinates_of(('v', 'w'))
        [_coordinate_v, _coordinate_w]
    """
    if isinstance(axes, str) or not axes:
        error_msg = f'Name the class axes by a sequence of names, such as `axes=("v",)`, not {axes!r}.'
        raise ValidationError(error_msg)
    return [coordinate(name) for name in axes]

count(over=None, where=None, by=None)

Return the number of members of a group that a condition selects, or of all members.

Parameters:

Name Type Description Default
over object

The namespace of the group to count, or None for the group the condition reads, or else the group of the law.

None
where object

A condition on the counted member, or None for all members.

None
by str | None

The name of a state of the group, so every member reduces over the members that share its value of the state, or None for all members. It needs the namespace of the group as over.

None

Returns:

Name Type Description
reduction Expression

The unevaluated count.

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol, or a key has no group namespace.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import count
>>> x = var('x')
>>> count(where=x > 0.5)
count(where=x > 0.5)
>>> from types import SimpleNamespace
>>> members = SimpleNamespace(cell=var('cell'), other=SimpleNamespace(cell=var('cell_other')))
>>> count(over=members, by='cell')
count(over=cell, where=cell_other == cell)
Source code in src/skcomplex/core/laws/utils/_reductions.py
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def count(over: object = None, where: object = None, by: str | None = None) -> Expression:
    """Return the number of members of a group that a condition selects, or of all members.

    Args:
        over:
            The namespace of the group to count, or `None` for the group the condition reads, or else the group of the
            law.

        where:
            A condition on the counted member, or `None` for all members.

        by:
            The name of a state of the group, so every member reduces over the members that share its value of the
            state, or `None` for all members. It needs the namespace of the group as `over`.

    Returns:
        reduction:
            The unevaluated count.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol, or a key has no group namespace.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import count
        >>> x = var('x')
        >>> count(where=x > 0.5)
        count(where=x > 0.5)
        >>> from types import SimpleNamespace
        >>> members = SimpleNamespace(cell=var('cell'), other=SimpleNamespace(cell=var('cell_other')))
        >>> count(over=members, by='cell')
        count(over=cell, where=cell_other == cell)
    """
    return _reduce('count', 1, over, where, None, by=by)

delay1(value, time)

Return the outflow of a first-order material delay of an inflow, one hidden stock emptied at stock / time.

Parameters:

Name Type Description Default
value object

The symbolic inflow.

required
time object

The mean delay time, a positive number or an expression of parameters.

required

Returns:

Name Type Description
output Expression

The symbolic outflow.

Raises:

Type Description
ValueError

The delay time is a number that is not positive.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import delay1
>>> delay1(var('inflow'), 4)
material_delay1(inflow, 4)
Source code in src/skcomplex/core/laws/utils/_delays.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def delay1(value: object, time: object) -> Expression:
    """Return the outflow of a first-order material delay of an inflow, one hidden stock emptied at `stock / time`.

    Args:
        value:
            The symbolic inflow.

        time:
            The mean delay time, a positive number or an expression of parameters.

    Returns:
        output:
            The symbolic outflow.

    Raises:
        ValueError:
            The delay time is a number that is not positive.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import delay1
        >>> delay1(var('inflow'), 4)
        material_delay1(inflow, 4)
    """
    return distributed_delay(value, time, 1)

delay3(value, time)

Return the outflow of a third-order material delay, three hidden stocks in series each with a third of the time.

The response to a pulse is the Erlang distribution of order 3 with the mean time.

Parameters:

Name Type Description Default
value object

The symbolic inflow.

required
time object

The mean delay time, a positive number or an expression of parameters.

required

Returns:

Name Type Description
output Expression

The symbolic outflow.

Raises:

Type Description
ValueError

The delay time is a number that is not positive.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import delay3
>>> delay3(var('inflow'), 6)
material_delay3(inflow, 6)
Source code in src/skcomplex/core/laws/utils/_delays.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def delay3(value: object, time: object) -> Expression:
    """Return the outflow of a third-order material delay, three hidden stocks in series each with a third of the time.

    The response to a pulse is the Erlang distribution of order 3 with the mean `time`.

    Args:
        value:
            The symbolic inflow.

        time:
            The mean delay time, a positive number or an expression of parameters.

    Returns:
        output:
            The symbolic outflow.

    Raises:
        ValueError:
            The delay time is a number that is not positive.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import delay3
        >>> delay3(var('inflow'), 6)
        material_delay3(inflow, 6)
    """
    return distributed_delay(value, time, 3)

delay_parts(name)

Return whether a delay function conserves its input, and its order, from its name, or None for no delay.

Parameters:

Name Type Description Default
name str

The name of a function.

required

Returns:

Name Type Description
parts tuple[bool, int] | None

Whether the delay is material, and its order.

Examples:

>>> from skcomplex.core.laws.utils import delay_parts
>>> delay_parts('material_delay3'), delay_parts('sin')
((True, 3), None)
Source code in src/skcomplex/core/laws/utils/_delays.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def delay_parts(name: str) -> tuple[bool, int] | None:
    """Return whether a delay function conserves its input, and its order, from its name, or `None` for no delay.

    Args:
        name:
            The name of a function.

    Returns:
        parts:
            Whether the delay is material, and its order.

    Examples:
        >>> from skcomplex.core.laws.utils import delay_parts
        >>> delay_parts('material_delay3'), delay_parts('sin')
        ((True, 3), None)
    """
    match = DELAY_NAME.match(name)
    return None if match is None else (match['kind'] == 'material', int(match['order']))

displacement(axis)

Return the symbol of the displacement along an axis, the variable a convolution kernel is written in.

Parameters:

Name Type Description Default
axis int

The axis of the lattice.

required

Returns:

Name Type Description
displacement Expression

The real symbol displacement_<axis>.

Examples:

>>> from skcomplex.core.laws.utils import displacement
>>> displacement(0) ** 2
displacement_0^2
Source code in src/skcomplex/core/laws/utils/_operators.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def displacement(axis: int) -> Expression:
    """Return the symbol of the displacement along an axis, the variable a convolution kernel is written in.

    Args:
        axis:
            The axis of the lattice.

    Returns:
        displacement:
            The real symbol `displacement_<axis>`.

    Examples:
        >>> from skcomplex.core.laws.utils import displacement
        >>> displacement(0) ** 2
        displacement_0^2
    """
    return SR.var(f'displacement_{axis}', domain='real')

distribute_operators(expression)

Return an expression with the linear field operators distributed over sums and out of numeric factors.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field.

required

Returns:

Name Type Description
expression Expression

The same field, in which every operator reads a product of symbols, such as laplacian(-a) as -laplacian(a).

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import distribute_operators, laplacian
>>> a = var('a')
>>> distribute_operators(laplacian(-a) + laplacian(a))
0
Source code in src/skcomplex/core/laws/utils/_tensors.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def distribute_operators(expression: object) -> Expression:
    """Return an expression with the linear field operators distributed over sums and out of numeric factors.

    Args:
        expression:
            An expression of the states and parameters of a field.

    Returns:
        expression:
            The same field, in which every operator reads a product of symbols, such as `laplacian(-a)` as
            `-laplacian(a)`.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import distribute_operators, laplacian
        >>> a = var('a')
        >>> distribute_operators(laplacian(-a) + laplacian(a))
        0
    """
    item = SR(expression)
    operator = item.operator()
    if operator is None:
        return item
    operands = [distribute_operators(operand) for operand in item.operands()]
    return _distribute_call(item, operands) if _is_linear_call(item) else operator(*operands)

distributed_delay(value, time, order=1, *, conserved=True)

Return the output of a distributed delay of a value: hidden stages in series, each with a share of the time.

A conserved delay passes its input through the stages like a material, so its output is the outflow of the last stage, stage / (time / order), and its response to a pulse is the Erlang distribution of the order with the mean time. A delay that does not conserve its input smooths it, each stage adjusting to the previous one at gap / (time / order), and its output starts at the value.

Parameters:

Name Type Description Default
value object

The symbolic input.

required
time object

The mean delay time, a positive number or an expression of parameters.

required
order int

The number of stages.

1
conserved bool

Whether the delay conserves its input, as a material delay, or smooths it, as an information delay.

True

Returns:

Name Type Description
output Expression

The symbolic output.

Raises:

Type Description
ValueError

The order is not a positive integer, or the delay time is a number that is not positive.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import distributed_delay
>>> distributed_delay(var('orders'), 4, order=3)
material_delay3(orders, 4)
>>> distributed_delay(var('sales'), 2, conserved=False)
smoothing_delay1(sales, 2)
Source code in src/skcomplex/core/laws/utils/_delays.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def distributed_delay(value: object, time: object, order: int = 1, *, conserved: bool = True) -> Expression:
    """Return the output of a distributed delay of a value: hidden stages in series, each with a share of the time.

    A conserved delay passes its input through the stages like a material, so its output is the outflow of the last
    stage, `stage / (time / order)`, and its response to a pulse is the Erlang distribution of the order with the mean
    `time`. A delay that does not conserve its input smooths it, each stage adjusting to the previous one at
    `gap / (time / order)`, and its output starts at the value.

    Args:
        value:
            The symbolic input.

        time:
            The mean delay time, a positive number or an expression of parameters.

        order:
            The number of stages.

        conserved:
            Whether the delay conserves its input, as a material delay, or smooths it, as an information delay.

    Returns:
        output:
            The symbolic output.

    Raises:
        ValueError:
            The order is not a positive integer, or the delay time is a number that is not positive.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import distributed_delay
        >>> distributed_delay(var('orders'), 4, order=3)
        material_delay3(orders, 4)
        >>> distributed_delay(var('sales'), 2, conserved=False)
        smoothing_delay1(sales, 2)
    """
    if isinstance(order, bool) or not isinstance(order, int) or order < 1:
        error_msg = f'A distributed delay has a positive integer order, but got {order!r}.'
        raise ValueError(error_msg)
    name = f'{"material" if conserved else "smoothing"}_delay{order}'
    if SR(time).is_numeric() and float(SR(time)) <= 0:
        error_msg = f'The delay time of {name} must be positive, but got {time}.'
        raise ValueError(error_msg)
    return function(name, nargs=2, eval_func=_eval_delay)(value, time)

divergence(components)

Return the divergence of a vector of field expressions, the sum of the derivative of each along its dimension.

Parameters:

Name Type Description Default
components Sequence[object] | FreeModuleElement

One expression per dimension, such as a flux.

required

Returns:

Name Type Description
divergence Expression

The divergence.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import divergence
>>> u = var('u')
>>> sorted(str(term) for term in divergence([u, 2 * u]).operands())
['partial(2*u, 1)', 'partial(u, 0)']
Source code in src/skcomplex/core/laws/utils/_operators.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def divergence(components: Sequence[object] | FreeModuleElement) -> Expression:
    """Return the divergence of a vector of field expressions, the sum of the derivative of each along its dimension.

    Args:
        components:
            One expression per dimension, such as a flux.

    Returns:
        divergence:
            The divergence.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import divergence
        >>> u = var('u')
        >>> sorted(str(term) for term in divergence([u, 2 * u]).operands())
        ['partial(2*u, 1)', 'partial(u, 0)']
    """
    return sum((partial(SR(item), SR(axis)) for axis, item in enumerate(components)), SR(0))

draw(name, distribution, **parameters)

Return a value drawn from a distribution of scipy.stats at every step or renewal.

The compiler checks the distribution name against scipy.stats and the keywords against the distribution's shape parameters, loc, and scale, and samples with the distribution's rvs and the simulation's random stream.

Parameters:

Name Type Description Default
name str

The name of the draw, a Python identifier.

required
distribution str

The name of a distribution of scipy.stats, such as 'pareto', 'lomax', or 'levy_stable'.

required
**parameters object

The shape parameters, loc, and scale of the distribution, numbers or expressions of states and parameters.

{}

Returns:

Name Type Description
draw Expression

The symbolic draw.

Raises:

Type Description
ValidationError

The name is not a Python identifier, or the distribution is not given by its name.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import draw, draws_in
>>> wait = draw('wait', 'pareto', b=var('alpha'), scale=2)
>>> wait
pareto_wait_b_scale(alpha, 2)
>>> draws_in(wait)[0].parameters
{'b': alpha, 'scale': 2}
Source code in src/skcomplex/specs/_draw_spec.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
235
236
def draw(name: str, distribution: str, **parameters: object) -> Expression:
    """Return a value drawn from a distribution of `scipy.stats` at every step or renewal.

    The compiler checks the distribution name against `scipy.stats` and the keywords against the distribution's shape
    parameters, `loc`, and `scale`, and samples with the distribution's `rvs` and the simulation's random stream.

    Args:
        name:
            The name of the draw, a Python identifier.

        distribution:
            The name of a distribution of `scipy.stats`, such as `'pareto'`, `'lomax'`, or `'levy_stable'`.

        **parameters:
            The shape parameters, `loc`, and `scale` of the distribution, numbers or expressions of states and
            parameters.

    Returns:
        draw:
            The symbolic draw.

    Raises:
        ValidationError:
            The name is not a Python identifier, or the distribution is not given by its name.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import draw, draws_in
        >>> wait = draw('wait', 'pareto', b=var('alpha'), scale=2)
        >>> wait
        pareto_wait_b_scale(alpha, 2)
        >>> draws_in(wait)[0].parameters
        {'b': alpha, 'scale': 2}
    """
    if not isinstance(distribution, str):
        error_msg = f'The draw {name!r} takes the name of a scipy.stats distribution, but got {distribution!r}.'
        raise ValidationError(error_msg)
    return _call('draw', name, tuple(parameters.values()), distribution, tuple(parameters))

draws_in(expression)

Return every draw of an expression with its arguments, including draws in the arguments of other draws.

Parameters:

Name Type Description Default
expression object

A symbolic expression, a number, a vector of expressions, or a sequence of them.

required

Returns:

Name Type Description
draws list[DrawSpec]

The draws, each call listed once, outer draws before the draws in their arguments.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import bernoulli, draws_in, uniform
>>> x = var('x')
>>> [item.name for item in draws_in(x * uniform('u') + bernoulli('flip', uniform('u')))]
['u', 'flip']
Source code in src/skcomplex/specs/_draw_spec.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def draws_in(expression: object) -> list[DrawSpec]:
    """Return every draw of an expression with its arguments, including draws in the arguments of other draws.

    Args:
        expression:
            A symbolic expression, a number, a vector of expressions, or a sequence of them.

    Returns:
        draws:
            The draws, each call listed once, outer draws before the draws in their arguments.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import bernoulli, draws_in, uniform
        >>> x = var('x')
        >>> [item.name for item in draws_in(x * uniform('u') + bernoulli('flip', uniform('u')))]
        ['u', 'flip']
    """
    items = list(expression) if isinstance(expression, FreeModuleElement | list | tuple) else [expression]
    stack = [SR(item) for item in items if isinstance(item, Expression | int | float)]
    found: dict[str, DrawSpec] = {}
    while stack:
        item = stack.pop()
        operator = item.operator()
        if operator is None:
            continue
        name = getattr(operator, 'name', lambda: '')()
        if name in DRAWS:
            known = DRAWS[name]
            found.setdefault(
                str(item),
                DrawSpec(known.kind, known.name, known.distribution, known.keywords, tuple(item.operands()), item),
            )
        stack.extend(reversed(item.operands()))
    return list(found.values())

gradient(expression, n_dim=2)

Return the gradient of a field expression, the derivative along every dimension of a lattice.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field.

required
n_dim int

The number of dimensions of the lattice.

2

Returns:

Name Type Description
gradient FreeModuleElement

One derivative per dimension.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import gradient
>>> gradient(var('u'))
(partial(u, 0), partial(u, 1))
Source code in src/skcomplex/core/laws/utils/_operators.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def gradient(expression: object, n_dim: int = 2) -> FreeModuleElement:
    """Return the gradient of a field expression, the derivative along every dimension of a lattice.

    Args:
        expression:
            An expression of the states and parameters of a field.

        n_dim:
            The number of dimensions of the lattice.

    Returns:
        gradient:
            One derivative per dimension.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import gradient
        >>> gradient(var('u'))
        (partial(u, 0), partial(u, 1))
    """
    return vector(SR, [partial(SR(expression), SR(axis)) for axis in range(n_dim)])

has_operator(expression)

Check whether an expression holds a local operator, which makes it an operator rather than a scalar.

Parameters:

Name Type Description Default
expression object

A Sage expression, or any other value.

required

Returns:

Name Type Description
has_operator bool

Whether a local operator appears in the expression.

Examples:

>>> from sage.all__sagemath_symbolics import SR, var
>>> from skcomplex.core.laws.utils import has_operator, local_operator
>>> has_operator(2 * local_operator(SR.symbol('q'), 'z')), has_operator(var('J'))
(True, False)
Source code in src/skcomplex/core/laws/utils/_local_operators.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def has_operator(expression: object) -> bool:
    """Check whether an expression holds a local operator, which makes it an operator rather than a scalar.

    Args:
        expression:
            A Sage expression, or any other value.

    Returns:
        has_operator:
            Whether a local operator appears in the expression.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, var
        >>> from skcomplex.core.laws.utils import has_operator, local_operator
        >>> has_operator(2 * local_operator(SR.symbol('q'), 'z')), has_operator(var('J'))
        (True, False)
    """
    return isinstance(expression, Expression) and expression.has(LOCAL_OPERATOR(SR.wild(0), SR.wild(1)))

hill(x, half, n=1)

Return the Hill function x^n / (half^n + x^n), which rises from 0 to 1 and is one half at x = half.

The order n = 1 gives the saturating function x / (half + x), and 1 - hill(x, half, n) is the decreasing Hill function.

Parameters:

Name Type Description Default
x object

The expression the function reads, nonnegative.

required
half object

The value of x where the function is one half.

required
n object

The Hill coefficient, the steepness of the rise.

1

Returns:

Name Type Description
value Expression

The symbolic value.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import hill
>>> x = var('x')
>>> hill(x, 2, 3)
x^3/(x^3 + 8)
>>> float(hill(x, 2, 3).subs(x=2))
0.5
Source code in src/skcomplex/core/laws/utils/_pointwise.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def hill(x: object, half: object, n: object = 1) -> Expression:
    """Return the Hill function `x^n / (half^n + x^n)`, which rises from 0 to 1 and is one half at `x = half`.

    The order `n = 1` gives the saturating function `x / (half + x)`, and `1 - hill(x, half, n)` is the decreasing
    Hill function.

    Args:
        x:
            The expression the function reads, nonnegative.

        half:
            The value of `x` where the function is one half.

        n:
            The Hill coefficient, the steepness of the rise.

    Returns:
        value:
            The symbolic value.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import hill
        >>> x = var('x')
        >>> hill(x, 2, 3)
        x^3/(x^3 + 8)
        >>> float(hill(x, 2, 3).subs(x=2))
        0.5
    """
    return SR(x) ** n / (SR(half) ** n + SR(x) ** n)

hyperedges(expression, reduce='sum', size=None, weight=None)

Return the reduction of an expression over the hyperedges around every member of a group on a network.

The expression is written for one hyperedge around the member that reads the reduction: the namespaces namespace.others hold the symbols of the other members of the hyperedge in its order, namespace.members those of all its members, which total, mean, count, maximum, and minimum reduce with over=namespace.members, and namespace those of the reading member. So sin(s.others[0].theta + s.others[1].theta - 2 * s.theta) with size=3 is the three-body phase coupling over the triangles around a member. The hyperedges are those of the network space of the group, and a reduction over no hyperedges is 0.

The reductions are those of neighbors with the weight w_e of every hyperedge e of the value f_e: 'sum', 'mean', 'count', 'max', 'min', 'normalized-mean', 'argmax', and 'circular-mean'. Without a size, the reduction runs over the hyperedges of every size, which the sum, the mean, and the count reduce, and the expression reads namespace.members rather than namespace.others.

Parameters:

Name Type Description Default
expression object

The value of one hyperedge, an expression of the symbols of namespace.others, namespace.members, and namespace, or a vector of them.

required
reduce str

'sum', 'mean', 'count', 'max', 'min', 'normalized-mean', 'argmax', or 'circular-mean'.

'sum'
size int | None

The number of members of the hyperedges, or None for every size.

None
weight object

The weight of one hyperedge, an expression of the same symbols, or None for 1.

None

Returns:

Name Type Description
reduction Expression | FreeModuleElement

The unevaluated reduction, one per component of a vector expression.

Raises:

Type Description
ValidationError

The reduction is unknown, or the size is not a whole number of at least two.

Examples:

>>> from sage.all__sagemath_symbolics import sin, var
>>> from skcomplex.core.laws.utils import hyperedges
>>> theta, first, second = var('theta first second')
>>> hyperedges(sin(first + second - 2 * theta), size=3)
hyperedges(sin(first + second - 2*theta), reduce='sum', size=3)
>>> hyperedges(var('x'), reduce='mean')
hyperedges(x, reduce='mean')
Source code in src/skcomplex/core/laws/utils/_reductions.py
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
def hyperedges(
    expression: object,
    reduce: str = 'sum',
    size: int | None = None,
    weight: object = None,
) -> Expression | FreeModuleElement:
    """Return the reduction of an expression over the hyperedges around every member of a group on a network.

    The expression is written for one hyperedge around the member that reads the reduction: the namespaces
    `namespace.others` hold the symbols of the other members of the hyperedge in its order, `namespace.members` those
    of all its members, which `total`, `mean`, `count`, `maximum`, and `minimum` reduce with `over=namespace.members`,
    and `namespace` those of the reading member. So `sin(s.others[0].theta + s.others[1].theta - 2 * s.theta)` with
    `size=3` is the three-body phase coupling over the triangles around a member. The hyperedges are those of the
    network space of the group, and a reduction over no hyperedges is 0.

    The reductions are those of `neighbors` with the weight `w_e` of every hyperedge `e` of the value `f_e`: `'sum'`,
    `'mean'`, `'count'`, `'max'`, `'min'`, `'normalized-mean'`, `'argmax'`, and `'circular-mean'`. Without a size, the
    reduction runs over the hyperedges of every size, which the sum, the mean, and the count reduce, and the
    expression reads `namespace.members` rather than `namespace.others`.

    Args:
        expression:
            The value of one hyperedge, an expression of the symbols of `namespace.others`, `namespace.members`, and
            `namespace`, or a vector of them.

        reduce:
            `'sum'`, `'mean'`, `'count'`, `'max'`, `'min'`, `'normalized-mean'`, `'argmax'`, or `'circular-mean'`.

        size:
            The number of members of the hyperedges, or `None` for every size.

        weight:
            The weight of one hyperedge, an expression of the same symbols, or `None` for 1.

    Returns:
        reduction:
            The unevaluated reduction, one per component of a vector expression.

    Raises:
        ValidationError:
            The reduction is unknown, or the size is not a whole number of at least two.

    Examples:
        >>> from sage.all__sagemath_symbolics import sin, var
        >>> from skcomplex.core.laws.utils import hyperedges
        >>> theta, first, second = var('theta first second')
        >>> hyperedges(sin(first + second - 2 * theta), size=3)
        hyperedges(sin(first + second - 2*theta), reduce='sum', size=3)
        >>> hyperedges(var('x'), reduce='mean')
        hyperedges(x, reduce='mean')
    """
    if size is not None and (
        isinstance(size, bool) or not isinstance(size, numbers.Integral) or size < MIN_HYPEREDGE_SIZE
    ):
        error_msg = f'A hyperedge reduction takes a size of 2 or more, or None for every size, not {size!r}.'
        raise ValidationError(error_msg)
    if reduce == 'circular-mean':
        sines = hyperedges(sin(SR(expression)), 'sum', size, weight)
        return arctan2(sines, hyperedges(cos(SR(expression)), 'sum', size, weight))
    if reduce not in NEIGHBOR_REDUCTIONS:
        error_msg = (
            f'Unknown hyperedge reduction {reduce!r}. Use one of {", ".join((*NEIGHBOR_REDUCTIONS, "circular-mean"))}.'
        )
        raise ValidationError(error_msg)
    code = SR(NEIGHBOR_REDUCTIONS.index(reduce))
    weighted = SR(1) if weight is None else SR(weight)
    sized = SR(0 if size is None else int(size))
    if isinstance(expression, FreeModuleElement):
        return vector(SR, [HYPEREDGES(SR(item), weighted, sized, code) for item in expression])
    return HYPEREDGES(SR(expression), weighted, sized, code)

hyperedges_in(expression)

Return the outermost hyperedge reductions of an expression.

Parameters:

Name Type Description Default
expression object

A symbolic expression, a number, or a vector of expressions.

required

Returns:

Name Type Description
reductions list[HyperedgeReductionSpec]

The hyperedge reductions, each listed once.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import hyperedges, hyperedges_in
>>> [(item.kind, item.size) for item in hyperedges_in(hyperedges(var('x'), reduce='mean', size=3))]
[('mean', 3)]
Source code in src/skcomplex/core/laws/utils/_reductions.py
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
def hyperedges_in(expression: object) -> list[HyperedgeReductionSpec]:
    """Return the outermost hyperedge reductions of an expression.

    Args:
        expression:
            A symbolic expression, a number, or a vector of expressions.

    Returns:
        reductions:
            The hyperedge reductions, each listed once.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import hyperedges, hyperedges_in
        >>> [(item.kind, item.size) for item in hyperedges_in(hyperedges(var('x'), reduce='mean', size=3))]
        [('mean', 3)]
    """
    reductions = []
    for call in _calls_in(expression, {'hyperedges'}):
        argument, weight, size, kind = call.operands()
        reductions.append(
            HyperedgeReductionSpec(
                call=call,
                kind=NEIGHBOR_REDUCTIONS[int(SR(kind))],
                argument=argument,
                weight=None if _is_placeholder(weight, 1) else weight,
                size=None if _is_placeholder(size, 0) else int(SR(size)),
            ),
        )
    return reductions

inverse_helmholtz(expression, screening, boundary=None)

Return the solution psi of the modified-Helmholtz equation (laplacian - screening**2) psi = expression.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field.

required
screening object

The screening, a non-negative expression of the parameters.

required
boundary object

The boundary condition of the solution, 'periodic', 'zero-flux', or ('fixed', value), or None for the boundary condition of the states of the expression.

None

Returns:

Name Type Description
solution Expression

The call inverse_helmholtz(expression, screening), followed for a given boundary by its position in INVERSION_BOUNDARIES and its fixed value, 0 for the other boundaries.

Raises:

Type Description
ValidationError

The boundary is not one of INVERSION_BOUNDARIES or a fixed boundary with its value.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import inverse_helmholtz
>>> inverse_helmholtz(var('q'), 2)
inverse_helmholtz(q, 2)
>>> inverse_helmholtz(var('q'), 2, ('fixed', 0.5))
inverse_helmholtz(q, 2, 2, 0.5)
Source code in src/skcomplex/core/laws/utils/_operators.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
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
134
135
136
def inverse_helmholtz(expression: object, screening: object, boundary: object = None) -> Expression:
    """Return the solution `psi` of the modified-Helmholtz equation `(laplacian - screening**2) psi = expression`.

    Args:
        expression:
            An expression of the states and parameters of a field.

        screening:
            The screening, a non-negative expression of the parameters.

        boundary:
            The boundary condition of the solution, `'periodic'`, `'zero-flux'`, or `('fixed', value)`, or `None` for
            the boundary condition of the states of the expression.

    Returns:
        solution:
            The call `inverse_helmholtz(expression, screening)`, followed for a given boundary by its position in
            `INVERSION_BOUNDARIES` and its fixed value, 0 for the other boundaries.

    Raises:
        ValidationError:
            The boundary is not one of `INVERSION_BOUNDARIES` or a fixed boundary with its value.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import inverse_helmholtz
        >>> inverse_helmholtz(var('q'), 2)
        inverse_helmholtz(q, 2)
        >>> inverse_helmholtz(var('q'), 2, ('fixed', 0.5))
        inverse_helmholtz(q, 2, 2, 0.5)
    """
    if boundary is None:
        return INVERSE_HELMHOLTZ(SR(expression), SR(screening))
    kind, *values = boundary if isinstance(boundary, tuple) else (boundary,)
    if kind not in INVERSION_BOUNDARIES or (kind == 'fixed') != bool(values) or len(values) > 1:
        error_msg = (
            f"The boundary of the inversion is {boundary!r}. Use 'periodic', 'zero-flux', or ('fixed', value), or None."
        )
        raise ValidationError(error_msg)
    code = SR(INVERSION_BOUNDARIES.index(kind))
    return INVERSE_HELMHOLTZ(SR(expression), SR(screening), code, SR(values[0] if values else 0))

inverse_laplacian(expression)

Return the solution of the Poisson equation laplacian(psi) = expression, the unscreened inverse Helmholtz.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field.

required

Returns:

Name Type Description
solution Expression

The call inverse_helmholtz(expression, 0).

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import inverse_laplacian
>>> inverse_laplacian(var('q'))
inverse_helmholtz(q, 0)
Source code in src/skcomplex/core/laws/utils/_operators.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def inverse_laplacian(expression: object) -> Expression:
    """Return the solution of the Poisson equation `laplacian(psi) = expression`, the unscreened inverse Helmholtz.

    Args:
        expression:
            An expression of the states and parameters of a field.

    Returns:
        solution:
            The call `inverse_helmholtz(expression, 0)`.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import inverse_laplacian
        >>> inverse_laplacian(var('q'))
        inverse_helmholtz(q, 0)
    """
    return INVERSE_HELMHOLTZ(SR(expression), SR(0))

leray_projection(field)

Return the divergence-free part of a vector of field expressions, v - grad(inverse_laplacian(div(v))).

Parameters:

Name Type Description Default
field Sequence[object] | FreeModuleElement

One expression per dimension.

required

Returns:

Name Type Description
projection FreeModuleElement

The divergence-free vector.

Raises:

Type Description
TensorShapeError

The value is not a vector.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import leray_projection
>>> len(leray_projection([var('u'), var('v'), var('w')]))
3
Source code in src/skcomplex/core/laws/utils/_tensors.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def leray_projection(field: Sequence[object] | FreeModuleElement) -> FreeModuleElement:
    """Return the divergence-free part of a vector of field expressions, `v - grad(inverse_laplacian(div(v)))`.

    Args:
        field:
            One expression per dimension.

    Returns:
        projection:
            The divergence-free vector.

    Raises:
        TensorShapeError:
            The value is not a vector.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import leray_projection
        >>> len(leray_projection([var('u'), var('v'), var('w')]))
        3
    """
    components = _check_vector('leray_projection', field)
    potential = inverse_laplacian(divergence(components))
    return components - gradient(potential, len(components))

local_operator(subsystem, name)

Return the operator of a name of the HilbertSpec of a subsystem, acting on that subsystem alone.

Parameters:

Name Type Description Default
subsystem object

The symbol of the state of the subsystem, such as namespace.psi.

required
name object

The name of the operator in the HilbertSpec, such as 'z', or its symbol.

required

Returns:

Name Type Description
operator Expression

The unevaluated local operator.

Examples:

>>> from sage.all__sagemath_symbolics import SR
>>> from skcomplex.core.laws.utils import local_operator
>>> local_operator(SR.symbol('q'), 'z')
local_operator(q, z)
Source code in src/skcomplex/core/laws/utils/_local_operators.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def local_operator(subsystem: object, name: object) -> Expression:
    """Return the operator of a name of the `HilbertSpec` of a subsystem, acting on that subsystem alone.

    Args:
        subsystem:
            The symbol of the state of the subsystem, such as `namespace.psi`.

        name:
            The name of the operator in the `HilbertSpec`, such as `'z'`, or its symbol.

    Returns:
        operator:
            The unevaluated local operator.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR
        >>> from skcomplex.core.laws.utils import local_operator
        >>> local_operator(SR.symbol('q'), 'z')
        local_operator(q, z)
    """
    return LOCAL_OPERATOR(subsystem, SR.symbol(name) if isinstance(name, str) else name)

log_sum_exp(expression, over=None, where=None)

Return the logarithm of the sum of the exponentials of an expression over the selected members of a group.

Parameters:

Name Type Description Default
expression object

The expression of one member, a logarithm of the terms of the sum.

required
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None

Returns:

Name Type Description
reduction Expression

The unevaluated reduction, 0 when no member is selected.

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import log_sum_exp
>>> log_sum_exp(-var('E') / var('T'))
log_sum_exp(-E/T)
Source code in src/skcomplex/core/laws/utils/_reductions.py
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
def log_sum_exp(expression: object, over: object = None, where: object = None) -> Expression:
    """Return the logarithm of the sum of the exponentials of an expression over the selected members of a group.

    Args:
        expression:
            The expression of one member, a logarithm of the terms of the sum.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

    Returns:
        reduction:
            The unevaluated reduction, 0 when no member is selected.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import log_sum_exp
        >>> log_sum_exp(-var('E') / var('T'))
        log_sum_exp(-E/T)
    """
    return _reduce('log_sum_exp', expression, over, where, None)

logistic(x, center=0, width=1)

Return the logistic sigmoid 1 / (1 + exp(-(x - center) / width)), which rises from 0 to 1 through center.

Parameters:

Name Type Description Default
x object

The expression the function reads.

required
center object

The value of x where the function is one half.

0
width object

The scale of the rise, the inverse of its gain, negative for a falling sigmoid.

1

Returns:

Name Type Description
value Expression

The symbolic value.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import logistic
>>> x = var('x')
>>> logistic(x, 1, 2)
1/(e^(-1/2*x + 1/2) + 1)
>>> float(logistic(x, 1, 2).subs(x=1))
0.5
Source code in src/skcomplex/core/laws/utils/_pointwise.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def logistic(x: object, center: object = 0, width: object = 1) -> Expression:
    """Return the logistic sigmoid `1 / (1 + exp(-(x - center) / width))`, which rises from 0 to 1 through `center`.

    Args:
        x:
            The expression the function reads.

        center:
            The value of `x` where the function is one half.

        width:
            The scale of the rise, the inverse of its gain, negative for a falling sigmoid.

    Returns:
        value:
            The symbolic value.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import logistic
        >>> x = var('x')
        >>> logistic(x, 1, 2)
        1/(e^(-1/2*x + 1/2) + 1)
        >>> float(logistic(x, 1, 2).subs(x=1))
        0.5
    """
    return 1 / (1 + exp(-(SR(x) - center) / width))

lookup(value, points)

Return a table function, the piecewise-linear interpolation of a value between points, flat outside them.

Parameters:

Name Type Description Default
value object

The symbolic input of the table.

required
points Sequence[Sequence[float]]

The points (x, y) of the table, with increasing x.

required

Returns:

Name Type Description
output Expression

The symbolic output, nested conditions that every backend lowers.

Raises:

Type Description
ValueError

The points are empty or their inputs do not increase.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import lookup
>>> food = var('food')
>>> fertility = lookup(food, [(0, 0.0), (1, 2.0), (2, 3.0)])
>>> [float(fertility.subs({food: value})) for value in (-1, 0.5, 1.5, 5)]
[0.0, 1.0, 2.5, 3.0]
Source code in src/skcomplex/core/laws/utils/_lookups.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def lookup(value: object, points: Sequence[Sequence[float]]) -> Expression:
    """Return a table function, the piecewise-linear interpolation of a value between points, flat outside them.

    Args:
        value:
            The symbolic input of the table.

        points:
            The points `(x, y)` of the table, with increasing `x`.

    Returns:
        output:
            The symbolic output, nested conditions that every backend lowers.

    Raises:
        ValueError:
            The points are empty or their inputs do not increase.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import lookup
        >>> food = var('food')
        >>> fertility = lookup(food, [(0, 0.0), (1, 2.0), (2, 3.0)])
        >>> [float(fertility.subs({food: value})) for value in (-1, 0.5, 1.5, 5)]
        [0.0, 1.0, 2.5, 3.0]
    """
    table = [(float(x), float(y)) for x, y in points]
    return series(SR(value), [x for x, _ in table], [y for _, y in table])

matches(model, label='match')

Return the pairs a matching law stored for the current step.

Parameters:

Name Type Description Default
model ModelViewProtocol

The model view a rule receives.

required
label str

The label of the matching law.

'match'

Returns:

Name Type Description
pairs list[tuple[object, object]]

The pairs of agents, or none before the law runs.

Source code in src/skcomplex/core/laws/utils/_agents.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def matches(model: ModelViewProtocol, label: str = 'match') -> list[tuple[object, object]]:
    """Return the pairs a matching law stored for the current step.

    Args:
        model:
            The model view a rule receives.

        label:
            The label of the matching law.

    Returns:
        pairs:
            The pairs of agents, or none before the law runs.
    """
    pairs = model.storage.get(f'match:{label}', ())
    return list(pairs) if isinstance(pairs, list | tuple) else []

maximum(*expressions, over=None, where=None)

Return the largest value of an expression over the selected members of a group, or the largest of expressions.

With one expression it reduces over the members of a group, as max over an array. With several it compares them member by member, as max over its arguments, with no group or condition.

Parameters:

Name Type Description Default
*expressions object

The expression of one member, or several expressions to compare member by member.

()
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None

Returns:

Name Type Description
value Expression

The unevaluated reduction, 0 when no member is selected, or the largest of the expressions.

Raises:

Type Description
ValidationError

No expression is given, several expressions come with a group or a condition, or the group is not a namespace or a symbol.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import maximum
>>> x, y = var('x y')
>>> maximum(x)
maximum(x)
>>> maximum(x, y)
where(y > x, y, x)
>>> maximum(x, y).subs(x=1, y=2)
2
Source code in src/skcomplex/core/laws/utils/_reductions.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def maximum(*expressions: object, over: object = None, where: object = None) -> Expression:
    """Return the largest value of an expression over the selected members of a group, or the largest of expressions.

    With one expression it reduces over the members of a group, as `max` over an array. With several it compares
    them member by member, as `max` over its arguments, with no group or condition.

    Args:
        *expressions:
            The expression of one member, or several expressions to compare member by member.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

    Returns:
        value:
            The unevaluated reduction, 0 when no member is selected, or the largest of the expressions.

    Raises:
        ValidationError:
            No expression is given, several expressions come with a group or a condition, or the group is not a
            namespace or a symbol.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import maximum
        >>> x, y = var('x y')
        >>> maximum(x)
        maximum(x)
        >>> maximum(x, y)
        where(y > x, y, x)
        >>> maximum(x, y).subs(x=1, y=2)
        2
    """
    if len(expressions) == 1:
        return _reduce('maximum', expressions[0], over, where, None)
    if not expressions or over is not None or where is not None:
        error_msg = (
            'maximum takes one expression to reduce over a group, or several expressions to compare member by member '
            f'without a group or a condition, but got {len(expressions)} expressions.'
        )
        raise ValidationError(error_msg)
    return _largest(expressions)

maximum_entropy(expression, axes, order=2)

Return the density with the largest entropy whose moments up to an order equal those of an expression.

The density is exp(a + b c) for the order 1 and exp(a + b c + d |c|^2) for the order 2, with c the coordinates of the named axes, and its parameters are found at every position so that its discrete moments equal those of the expression to round-off.

Parameters:

Name Type Description Default
expression object

An expression of the states of a field on a phase space and the coordinates.

required
axes Sequence[str]

The names of the class axes of the density.

required
order int

The highest order of the moments that are kept, 0, 1, or 2.

2

Returns:

Name Type Description
density Expression

The call maximum_entropy(expression, order, *coordinates).

Raises:

Type Description
ValidationError

The order is not 0, 1, or 2.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import maximum_entropy
>>> maximum_entropy(var('f'), ('v',))
maximum_entropy(f, 2, _coordinate_v)
Source code in src/skcomplex/core/laws/utils/_phase.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def maximum_entropy(expression: object, axes: Sequence[str], order: int = 2) -> Expression:
    """Return the density with the largest entropy whose moments up to an order equal those of an expression.

    The density is `exp(a + b c)` for the order 1 and `exp(a + b c + d |c|^2)` for the order 2, with `c` the coordinates
    of the named axes, and its parameters are found at every position so that its discrete moments equal those of the
    expression to round-off.

    Args:
        expression:
            An expression of the states of a field on a phase space and the coordinates.

        axes:
            The names of the class axes of the density.

        order:
            The highest order of the moments that are kept, 0, 1, or 2.

    Returns:
        density:
            The call `maximum_entropy(expression, order, *coordinates)`.

    Raises:
        ValidationError:
            The order is not 0, 1, or 2.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import maximum_entropy
        >>> maximum_entropy(var('f'), ('v',))
        maximum_entropy(f, 2, _coordinate_v)
    """
    if order not in {0, 1, 2}:
        error_msg = f'The order of a maximum-entropy density is 0, 1, or 2, not {order!r}.'
        raise ValidationError(error_msg)
    return MAXIMUM_ENTROPY(SR(expression), SR(order), *coordinates_of(axes))

mean(expression, over=None, where=None, weights=None, by=None)

Return the mean of an expression over the selected members of a group, the total divided by their count.

Parameters:

Name Type Description Default
expression object

The expression of one member.

required
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None
weights object

A constant matrix with one row per reading member and one column per reduced member, or None.

None
by str | None

The name of a state of the group, so every member reduces over the members that share its value of the state, or None for all members. It needs the namespace of the group as over.

None

Returns:

Name Type Description
reduction Expression

The unevaluated reduction, 0 when no member is selected.

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

Examples:

>>> from sage.all__sagemath_symbolics import cos, var
>>> from skcomplex.core.laws.utils import mean
>>> mean(cos(var('theta')))
mean(cos(theta))
Source code in src/skcomplex/core/laws/utils/_reductions.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
def mean(
    expression: object,
    over: object = None,
    where: object = None,
    weights: object = None,
    by: str | None = None,
) -> Expression:
    """Return the mean of an expression over the selected members of a group, the total divided by their count.

    Args:
        expression:
            The expression of one member.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

        weights:
            A constant matrix with one row per reading member and one column per reduced member, or `None`.

        by:
            The name of a state of the group, so every member reduces over the members that share its value of the
            state, or `None` for all members. It needs the namespace of the group as `over`.

    Returns:
        reduction:
            The unevaluated reduction, 0 when no member is selected.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

    Examples:
        >>> from sage.all__sagemath_symbolics import cos, var
        >>> from skcomplex.core.laws.utils import mean
        >>> mean(cos(var('theta')))
        mean(cos(theta))
    """
    return _reduce('mean', expression, over, where, weights, by=by)

minimum(*expressions, over=None, where=None)

Return the smallest value of an expression over the selected members of a group, or the smallest of expressions.

With one expression it reduces over the members of a group, as min over an array. With several it compares them member by member, as min over its arguments, with no group or condition.

Parameters:

Name Type Description Default
*expressions object

The expression of one member, or several expressions to compare member by member.

()
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None

Returns:

Name Type Description
value Expression

The unevaluated reduction, 0 when no member is selected, or the smallest of the expressions.

Raises:

Type Description
ValidationError

No expression is given, several expressions come with a group or a condition, or the group is not a namespace or a symbol.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import minimum
>>> x, y = var('x y')
>>> minimum(x)
minimum(x)
>>> minimum(x, y)
where(y < x, y, x)
>>> minimum(x, y).subs(x=1, y=2)
1
Source code in src/skcomplex/core/laws/utils/_reductions.py
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def minimum(*expressions: object, over: object = None, where: object = None) -> Expression:
    """Return the smallest value of an expression over the selected members of a group, or the smallest of expressions.

    With one expression it reduces over the members of a group, as `min` over an array. With several it compares
    them member by member, as `min` over its arguments, with no group or condition.

    Args:
        *expressions:
            The expression of one member, or several expressions to compare member by member.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

    Returns:
        value:
            The unevaluated reduction, 0 when no member is selected, or the smallest of the expressions.

    Raises:
        ValidationError:
            No expression is given, several expressions come with a group or a condition, or the group is not a
            namespace or a symbol.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import minimum
        >>> x, y = var('x y')
        >>> minimum(x)
        minimum(x)
        >>> minimum(x, y)
        where(y < x, y, x)
        >>> minimum(x, y).subs(x=1, y=2)
        1
    """
    if len(expressions) == 1:
        return _reduce('minimum', expressions[0], over, where, None)
    if not expressions or over is not None or where is not None:
        error_msg = (
            'minimum takes one expression to reduce over a group, or several expressions to compare member by member '
            f'without a group or a condition, but got {len(expressions)} expressions.'
        )
        raise ValidationError(error_msg)
    return _smallest(expressions)

moment(expression, order, coordinates=None, *, over=None, normalized=False, central=False, axes=None)

Return a moment of the distribution that a group holds over a grid of coordinates, one point per member.

The raw moment of order r is sum_j c_j^r f_j, where member j holds the density f_j at the coordinate c_j. The order-0 moment is the density. A normalized moment divides by the density, so the normalized order-1 moment is the mean coordinate. A central moment is normalized and taken about the mean, so the central order-2 moment is the variance.

With axes, the moment reduces a field on a phase space over those class axes at every position, by the sum over the cells of the coordinate raised to the order times the cell widths, and it is a field over position that laws read broadcast along the reduced axes. Over several axes the coordinate is the length of the vector of coordinates.

Parameters:

Name Type Description Default
expression object

The expression of one member, usually the density state.

required
order int

The order r, a nonnegative integer.

required
coordinates object

The coordinate of every member, such as the activity values of a grid, or None with axes.

None
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
normalized bool

Whether to divide by the density.

False
central bool

Whether to take the moment about the mean, which also normalizes it.

False
axes Sequence[str] | None

The names of the class axes of a phase space to reduce over, or None to reduce over every member.

None

Returns:

Name Type Description
moment Expression

The moment as an expression of reductions, which laws read and observables record.

Raises:

Type Description
ValidationError

The order is not a nonnegative integer, the coordinates are not a nonempty vector, both coordinates and axes are given, or an odd order is taken over several axes.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import moment, reductions_in
>>> f = var('f')
>>> moment(f, 0, [0.0, 1.0, 2.0])
total(f)
>>> sorted(item.weights is None for item in reductions_in(moment(f, 1, [0.0, 1.0, 2.0], normalized=True)))
[False, True]
>>> moment(f, 2, axes=('v',))
reduce_axes(_coordinate_v^2*f, _coordinate_v)
Source code in src/skcomplex/core/laws/utils/_moments.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def moment(
    expression: object,
    order: int,
    coordinates: object = None,
    *,
    over: object = None,
    normalized: bool = False,
    central: bool = False,
    axes: Sequence[str] | None = None,
) -> Expression:
    """Return a moment of the distribution that a group holds over a grid of coordinates, one point per member.

    The raw moment of order `r` is `sum_j c_j^r f_j`, where member `j` holds the density `f_j` at the coordinate `c_j`.
    The order-0 moment is the density. A normalized moment divides by the density, so the normalized order-1 moment
    is the mean coordinate. A central moment is normalized and taken about the mean, so the central order-2 moment is
    the variance.

    With `axes`, the moment reduces a field on a phase space over those class axes at every position, by the sum over
    the cells of the coordinate raised to the order times the cell widths, and it is a field over position that laws
    read broadcast along the reduced axes. Over several axes the coordinate is the length of the vector of coordinates.

    Args:
        expression:
            The expression of one member, usually the density state.

        order:
            The order `r`, a nonnegative integer.

        coordinates:
            The coordinate of every member, such as the activity values of a grid, or `None` with `axes`.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        normalized:
            Whether to divide by the density.

        central:
            Whether to take the moment about the mean, which also normalizes it.

        axes:
            The names of the class axes of a phase space to reduce over, or `None` to reduce over every member.

    Returns:
        moment:
            The moment as an expression of reductions, which laws read and observables record.

    Raises:
        ValidationError:
            The order is not a nonnegative integer, the coordinates are not a nonempty vector, both coordinates and
            axes are given, or an odd order is taken over several axes.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import moment, reductions_in
        >>> f = var('f')
        >>> moment(f, 0, [0.0, 1.0, 2.0])
        total(f)
        >>> sorted(item.weights is None for item in reductions_in(moment(f, 1, [0.0, 1.0, 2.0], normalized=True)))
        [False, True]
        >>> moment(f, 2, axes=('v',))
        reduce_axes(_coordinate_v^2*f, _coordinate_v)
    """
    if isinstance(order, bool) or not isinstance(order, int) or order < 0:
        error_msg = f'The order of a moment is a nonnegative integer, not {order!r}.'
        raise ValidationError(error_msg)
    if axes is not None:
        if coordinates is not None:
            error_msg = f'A moment over the axes {axes} reads the coordinates of the axes. Give no other coordinates.'
            raise ValidationError(error_msg)
        return _combined(lambda power: _raw_over_axes(expression, power, axes), order, normalized, central)
    values = np.asarray(coordinates, dtype=float)
    if values.ndim != 1 or not values.size:
        error_msg = f'The coordinates of a moment are a nonempty vector with one value per member, not {coordinates!r}.'
        raise ValidationError(error_msg)
    return _combined(lambda power: _raw(expression, power, values, over), order, normalized, central)

neighborhoods_in(expression)

Return the outermost neighbourhood reductions of an expression, the components reductions included.

A components reduction has the kind 'component-' followed by its reduction, and the activity of one link as its argument.

Parameters:

Name Type Description Default
expression object

A symbolic expression, a number, or a vector of expressions.

required

Returns:

Name Type Description
reductions list[NeighborReductionSpec]

The neighbourhood reductions, each listed once.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import neighbors, neighborhoods_in
>>> [item.kind for item in neighborhoods_in(neighbors(var('x'), reduce='mean'))]
['mean']
Source code in src/skcomplex/core/laws/utils/_reductions.py
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
def neighborhoods_in(expression: object) -> list[NeighborReductionSpec]:
    """Return the outermost neighbourhood reductions of an expression, the components reductions included.

    A components reduction has the kind `'component-'` followed by its reduction, and the activity of one link as its
    argument.

    Args:
        expression:
            A symbolic expression, a number, or a vector of expressions.

    Returns:
        reductions:
            The neighbourhood reductions, each listed once.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import neighbors, neighborhoods_in
        >>> [item.kind for item in neighborhoods_in(neighbors(var('x'), reduce='mean'))]
        ['mean']
    """
    reductions = []
    for call in _calls_in(expression, {'neighbors', 'components'}):
        if call.operator().name() == 'components':
            reductions.append(_component_reduction(call))
            continue
        argument, weight, over, kind = call.operands()
        reductions.append(
            NeighborReductionSpec(
                call=call,
                kind=NEIGHBOR_REDUCTIONS[int(SR(kind))],
                argument=argument,
                weight=None if _is_placeholder(weight, 1) else weight,
                over=None if _is_placeholder(over, 0) else NEIGHBORHOODS[str(over)],
            ),
        )
    return reductions

neighbors(expression, over=None, reduce='sum', weight=None)

Return the reduction of an expression over the neighbours of every member of a group.

The expression is written for one neighbour: the namespace namespace.other holds the symbols of the neighbour, and namespace those of the member that reads the reduction, so sin(namespace.other.theta - namespace.theta) is the phase difference to one neighbour. The neighbourhood is the graph of a network space by default, AdjacencyNeighborhood(), a layer of the network, AdjacencyNeighborhood(layer=label) or the LayerSpec itself, a lattice neighbourhood, or in a continuous space the members within a distance, RadiusNeighborhood(r), the nearest members, NearestNeighborhood(k), or the members within a distance inside a cone around a direction, ConeNeighborhood(r, angle). A reduction over no neighbours is 0.

The reductions with the weight w_j of every neighbour j of the value f_j are 'sum', sum_j w_j f_j, 'mean', the sum divided by the number of neighbours, 'count', sum_j w_j [f_j != 0], 'max' and 'min', the largest and smallest w_j f_j, 'normalized-mean', sum_j w_j f_j / sum_j w_j, and 'argmax', the value f_j of the neighbour whose weight w_j is largest, the first of them in neighbour order. The circular mean of an angle, 'circular-mean', is arctan2(sum_j w_j sin f_j, sum_j w_j cos f_j), 0 over no neighbours. On a lattice neighbourhood, reduce='each' gives the values of a lattice state at every offset, in neighbourhood order, without reducing them.

Parameters:

Name Type Description Default
expression object

The value of one neighbour, an expression of the symbols of namespace.other and namespace, or a vector of them.

required
over BaseNeighborhood | LayerSpec | None

The neighbourhood, or None for the neighbours of the network or lattice the group lives on.

None
reduce str

'sum', 'mean', 'count', 'max', 'min', 'normalized-mean', 'argmax', or 'circular-mean', or 'each' for the values at every offset of a lattice neighbourhood or a memory.

'sum'
weight object

The weight of one neighbour, an expression of the same symbols, or None for 1. The 'argmax' reduction picks the neighbour whose weight is largest.

None

Returns:

Name Type Description
reduction Expression | FreeModuleElement

The unevaluated reduction, one per component of a vector expression, or with reduce='each' the values of a lattice state at the offsets of a lattice neighbourhood.

Raises:

Type Description
ValidationError

The reduction or the neighbourhood is unknown.

Examples:

>>> from sage.all__sagemath_symbolics import sin, var
>>> from skcomplex.core.laws.utils import neighbors
>>> from skcomplex.core.spaces import RadiusNeighborhood, VonNeumannNeighborhood
>>> neighbors(sin(var('theta_other')), over=RadiusNeighborhood(1.0), reduce='mean')
neighbors(sin(theta_other), over=RadiusNeighborhood(radius=1.0), reduce='mean')
>>> neighbors(var('strategy_other'), reduce='argmax', weight=var('payoff_other'))
neighbors(strategy_other, reduce='argmax', weight=payoff_other)
>>> values = neighbors(var('spin'), VonNeumannNeighborhood(), reduce='each')
>>> len(values), values[0]
(4, neighbor(spin, 0))
Source code in src/skcomplex/core/laws/utils/_reductions.py
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
def neighbors(
    expression: object,
    over: BaseNeighborhood | LayerSpec | None = None,
    reduce: str = 'sum',
    weight: object = None,
) -> Expression | FreeModuleElement:
    """Return the reduction of an expression over the neighbours of every member of a group.

    The expression is written for one neighbour: the namespace `namespace.other` holds the symbols of the neighbour,
    and `namespace` those of the member that reads the reduction, so `sin(namespace.other.theta - namespace.theta)` is
    the phase difference to one neighbour. The neighbourhood is the graph of a network space by default,
    `AdjacencyNeighborhood()`, a layer of the network, `AdjacencyNeighborhood(layer=label)` or the `LayerSpec` itself, a
    lattice neighbourhood, or in a continuous space the members within a distance, `RadiusNeighborhood(r)`, the nearest
    members, `NearestNeighborhood(k)`, or the members within a distance inside a cone around a direction,
    `ConeNeighborhood(r, angle)`. A reduction over no neighbours is 0.

    The reductions with the weight `w_j` of every neighbour `j` of the value `f_j` are `'sum'`, `sum_j w_j f_j`,
    `'mean'`, the sum divided by the number of neighbours, `'count'`, `sum_j w_j [f_j != 0]`, `'max'` and `'min'`, the
    largest and smallest `w_j f_j`, `'normalized-mean'`, `sum_j w_j f_j / sum_j w_j`, and `'argmax'`, the value `f_j` of
    the neighbour whose weight `w_j` is largest, the first of them in neighbour order. The circular mean of an angle,
    `'circular-mean'`, is `arctan2(sum_j w_j sin f_j, sum_j w_j cos f_j)`, 0 over no neighbours. On a lattice
    neighbourhood, `reduce='each'` gives the values of a lattice state at every offset, in neighbourhood order, without
    reducing them.

    Args:
        expression:
            The value of one neighbour, an expression of the symbols of `namespace.other` and `namespace`, or a vector
            of them.

        over:
            The neighbourhood, or `None` for the neighbours of the network or lattice the group lives on.

        reduce:
            `'sum'`, `'mean'`, `'count'`, `'max'`, `'min'`, `'normalized-mean'`, `'argmax'`, or `'circular-mean'`, or
            `'each'` for the values at every offset of a lattice neighbourhood or a memory.

        weight:
            The weight of one neighbour, an expression of the same symbols, or `None` for 1. The `'argmax'` reduction
            picks the neighbour whose weight is largest.

    Returns:
        reduction:
            The unevaluated reduction, one per component of a vector expression, or with `reduce='each'` the values of
            a lattice state at the offsets of a lattice neighbourhood.

    Raises:
        ValidationError:
            The reduction or the neighbourhood is unknown.

    Examples:
        >>> from sage.all__sagemath_symbolics import sin, var
        >>> from skcomplex.core.laws.utils import neighbors
        >>> from skcomplex.core.spaces import RadiusNeighborhood, VonNeumannNeighborhood
        >>> neighbors(sin(var('theta_other')), over=RadiusNeighborhood(1.0), reduce='mean')
        neighbors(sin(theta_other), over=RadiusNeighborhood(radius=1.0), reduce='mean')
        >>> neighbors(var('strategy_other'), reduce='argmax', weight=var('payoff_other'))
        neighbors(strategy_other, reduce='argmax', weight=payoff_other)
        >>> values = neighbors(var('spin'), VonNeumannNeighborhood(), reduce='each')
        >>> len(values), values[0]
        (4, neighbor(spin, 0))
    """
    if reduce == 'each':
        if isinstance(over, LatticeNeighborhood):
            size = len(over.check().prepare().offsets_)
        elif isinstance(over, MemoryNeighborhood):
            size = len(over.check().prepare().elements_)
        else:
            error_msg = f"The reduction 'each' reads a lattice or a memory neighbourhood, not {over!r}."
            raise ValidationError(error_msg)
        return vector(SR, [NEIGHBOR(expression, index) for index in range(size)])
    if reduce == 'circular-mean':
        sines = neighbors(sin(SR(expression)), over, 'sum', weight)
        return arctan2(sines, neighbors(cos(SR(expression)), over, 'sum', weight))
    if reduce not in NEIGHBOR_REDUCTIONS:
        error_msg = (
            'Unknown neighbourhood reduction '
            f'{reduce!r}. Use one of {", ".join((*NEIGHBOR_REDUCTIONS, "circular-mean", "each"))}.'
        )
        raise ValidationError(error_msg)
    over_symbol = _neighborhood_symbol(over)
    code = SR(NEIGHBOR_REDUCTIONS.index(reduce))
    weighted = SR(1) if weight is None else SR(weight)
    if isinstance(expression, FreeModuleElement):
        return vector(SR, [NEIGHBORS(SR(item), weighted, over_symbol, code) for item in expression])
    return NEIGHBORS(SR(expression), weighted, over_symbol, code)

normal(name, mean=0, deviation=1)

Return a value drawn from a normal distribution at every step.

Parameters:

Name Type Description Default
name str

The name of the draw, a Python identifier.

required
mean object

The mean, a number or an expression of states and parameters.

0
deviation object

The standard deviation.

1

Returns:

Name Type Description
draw Expression

The symbolic draw.

Raises:

Type Description
ValidationError

The name is not a Python identifier.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import normal
>>> var('x') + normal('step', 0, 1)
x + normal_step(0, 1)
Source code in src/skcomplex/specs/_draw_spec.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def normal(name: str, mean: object = 0, deviation: object = 1) -> Expression:
    """Return a value drawn from a normal distribution at every step.

    Args:
        name:
            The name of the draw, a Python identifier.

        mean:
            The mean, a number or an expression of states and parameters.

        deviation:
            The standard deviation.

    Returns:
        draw:
            The symbolic draw.

    Raises:
        ValidationError:
            The name is not a Python identifier.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import normal
        >>> var('x') + normal('step', 0, 1)
        x + normal_step(0, 1)
    """
    return _call('normal', name, (mean, deviation))

pulse(t, start, width, height=1.0)

Return a rectangular pulse of a width and a height that starts at a time.

Parameters:

Name Type Description Default
t Expression

The time symbol, s.t in laws.

required
start float

The time the pulse starts.

required
width float

The duration of the pulse.

required
height float

The height of the pulse.

1.0

Returns:

Name Type Description
input Expression

The symbolic input.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import pulse
>>> t = var('t')
>>> [float(pulse(t, 2, 1, 3).subs({t: time})) for time in (1.5, 2.5, 3.5)]
[0.0, 3.0, 0.0]
Source code in src/skcomplex/core/laws/utils/_signals.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def pulse(t: Expression, start: float, width: float, height: float = 1.0) -> Expression:
    """Return a rectangular pulse of a width and a height that starts at a time.

    Args:
        t:
            The time symbol, `s.t` in laws.

        start:
            The time the pulse starts.

        width:
            The duration of the pulse.

        height:
            The height of the pulse.

    Returns:
        input:
            The symbolic input.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import pulse
        >>> t = var('t')
        >>> [float(pulse(t, 2, 1, 3).subs({t: time})) for time in (1.5, 2.5, 3.5)]
        [0.0, 3.0, 0.0]
    """
    return where(both(t >= start, t < start + width), height, 0)

pulse_train(t, period, width, height=1.0, start=0.0, *, count=None)

Return a periodic train of rectangular pulses, each of a width, starting at start.

Parameters:

Name Type Description Default
t Expression

The time symbol, s.t in laws.

required
period float

The time between the starts of pulses.

required
width float

The duration of every pulse.

required
height float

The height of every pulse.

1.0
start float

The time of the first pulse.

0.0
count int | None

The number of pulses, or None for a train that never ends.

None

Returns:

Name Type Description
input Expression

The symbolic input.

Raises:

Type Description
ValidationError

The count is not a positive integer.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import pulse_train
>>> t = var('t')
>>> [float(pulse_train(t, 10, 1).subs({t: time})) for time in (0.5, 5, 20.5)]
[1.0, 0.0, 1.0]
>>> [float(pulse_train(t, 10, 1, count=2).subs({t: time})) for time in (10.5, 20.5)]
[1.0, 0.0]
Source code in src/skcomplex/core/laws/utils/_signals.py
118
119
120
121
122
123
124
125
126
127
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def pulse_train(
    t: Expression,
    period: float,
    width: float,
    height: float = 1.0,
    start: float = 0.0,
    *,
    count: int | None = None,
) -> Expression:
    """Return a periodic train of rectangular pulses, each of a width, starting at `start`.

    Args:
        t:
            The time symbol, `s.t` in laws.

        period:
            The time between the starts of pulses.

        width:
            The duration of every pulse.

        height:
            The height of every pulse.

        start:
            The time of the first pulse.

        count:
            The number of pulses, or `None` for a train that never ends.

    Returns:
        input:
            The symbolic input.

    Raises:
        ValidationError:
            The count is not a positive integer.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import pulse_train
        >>> t = var('t')
        >>> [float(pulse_train(t, 10, 1).subs({t: time})) for time in (0.5, 5, 20.5)]
        [1.0, 0.0, 1.0]
        >>> [float(pulse_train(t, 10, 1, count=2).subs({t: time})) for time in (10.5, 20.5)]
        [1.0, 0.0]
    """
    if count is None:
        return where(both(t >= start, mod(t - start, period) < width), height, 0)
    if isinstance(count, bool) or not isinstance(count, int) or count < 1:
        error_msg = f'A pulse train takes a positive integer count of pulses or None, but got {count!r}.'
        raise ValidationError(error_msg)
    return where(both(t >= start, t < start + count * period, mod(t - start, period) < width), height, 0)

ramp(t, start, end, height=1.0)

Return a ramp input, 0 before start, rising linearly to height at end, and constant after.

Parameters:

Name Type Description Default
t Expression

The time symbol, s.t in laws.

required
start float

The time the ramp starts.

required
end float

The time the ramp ends.

required
height float

The value after the ramp.

1.0

Returns:

Name Type Description
input Expression

The symbolic input.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import ramp
>>> t = var('t')
>>> [float(ramp(t, 0, 10, 5).subs({t: time})) for time in (-1, 4, 20)]
[0.0, 2.0, 5.0]
Source code in src/skcomplex/core/laws/utils/_signals.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def ramp(t: Expression, start: float, end: float, height: float = 1.0) -> Expression:
    """Return a ramp input, 0 before `start`, rising linearly to `height` at `end`, and constant after.

    Args:
        t:
            The time symbol, `s.t` in laws.

        start:
            The time the ramp starts.

        end:
            The time the ramp ends.

        height:
            The value after the ramp.

    Returns:
        input:
            The symbolic input.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import ramp
        >>> t = var('t')
        >>> [float(ramp(t, 0, 10, 5).subs({t: time})) for time in (-1, 4, 20)]
        [0.0, 2.0, 5.0]
    """
    return where(t < start, 0, where(t < end, height * (t - start) / (end - start), height))

random_pairing(left, right, rng, capacity)

Return pairs drawn uniformly at random, each member in at most capacity pairs and never paired with itself.

Parameters:

Name Type Description Default
left Sequence[object]

The members of the first group.

required
right Sequence[object]

The members of the second group, the same sequence as left for pairs within one group.

required
rng Generator

The random generator.

required
capacity int

The largest number of pairs of every member.

required

Returns:

Name Type Description
pairs list[tuple[object, object]]

The pairs of members.

Examples:

>>> import numpy as np
>>> from skcomplex.core.laws.utils import random_pairing
>>> pairs = random_pairing(list(range(5)), list('abc'), np.random.default_rng(0), 1)
>>> len(pairs)
3
Source code in src/skcomplex/core/laws/utils/_agents.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def random_pairing(
    left: Sequence[object],
    right: Sequence[object],
    rng: np.random.Generator,
    capacity: int,
) -> list[tuple[object, object]]:
    """Return pairs drawn uniformly at random, each member in at most `capacity` pairs and never paired with itself.

    Args:
        left:
            The members of the first group.

        right:
            The members of the second group, the same sequence as `left` for pairs within one group.

        rng:
            The random generator.

        capacity:
            The largest number of pairs of every member.

    Returns:
        pairs:
            The pairs of members.

    Examples:
        >>> import numpy as np
        >>> from skcomplex.core.laws.utils import random_pairing
        >>> pairs = random_pairing(list(range(5)), list('abc'), np.random.default_rng(0), 1)
        >>> len(pairs)
        3
    """
    pairs: list[tuple[object, object]] = []
    seen: set[tuple[int, int]] = set()
    if left is right:
        for _ in range(capacity):
            order = rng.permutation(len(left))
            for first, second in zip(order[::2], order[1::2], strict=False):
                key = (min(first, second), max(first, second))
                if key not in seen:
                    seen.add(key)
                    pairs.append((left[first], right[second]))
        return pairs
    firsts = rng.permutation(np.repeat(np.arange(len(left)), capacity))
    seconds = rng.permutation(np.repeat(np.arange(len(right)), capacity))
    for first, second in zip(firsts, seconds, strict=False):
        if (first, second) not in seen:
            seen.add((first, second))
            pairs.append((left[first], right[second]))
    return pairs

reduce_axes(expression, axes)

Return the integral of an expression over named class axes at every position of a phase space.

Parameters:

Name Type Description Default
expression object

An expression of the states of a field on a phase space and the coordinates.

required
axes Sequence[str]

The names of the class axes to integrate over, each cell weighted by its width.

required

Returns:

Name Type Description
integral Expression

The call reduce_axes(expression, *coordinates), which laws read broadcast along the reduced axes.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import reduce_axes
>>> reduce_axes(var('f'), ('v',))
reduce_axes(f, _coordinate_v)
Source code in src/skcomplex/core/laws/utils/_phase.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def reduce_axes(expression: object, axes: Sequence[str]) -> Expression:
    """Return the integral of an expression over named class axes at every position of a phase space.

    Args:
        expression:
            An expression of the states of a field on a phase space and the coordinates.

        axes:
            The names of the class axes to integrate over, each cell weighted by its width.

    Returns:
        integral:
            The call `reduce_axes(expression, *coordinates)`, which laws read broadcast along the reduced axes.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import reduce_axes
        >>> reduce_axes(var('f'), ('v',))
        reduce_axes(f, _coordinate_v)
    """
    return REDUCE_AXES(SR(expression), *coordinates_of(axes))

reduction(function, expression, over=None, where=None)

Return a reduction over a group by a function of the array of member values.

The function runs in Python, so a law that reads it falls back to Python on the Numba backend. The name of the function stands in the expression and the function waits in a module-level mapping, for the same reason as the draws: a Sage symbolic function holds only a name and arguments.

Parameters:

Name Type Description Default
function Callable[[AnyArray], object]

The function of the one-dimensional array of the values of the members that where selects.

required
expression object

The value of one member, a symbolic expression.

required
over object

The group to reduce over, or None for the group the expression reads.

None
where object

A condition on each member, or None for every member.

None

Returns:

Name Type Description
reduction Expression

The reduction, a symbolic expression.

Examples:

>>> import numpy as np
>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import reduction, reductions_in
>>> x = var('x')
>>> [item.kind for item in reductions_in(reduction(np.median, x))]
['function']
Source code in src/skcomplex/core/laws/utils/_reductions.py
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def reduction(
    function: Callable[[AnyArray], object],
    expression: object,
    over: object = None,
    where: object = None,
) -> Expression:
    """Return a reduction over a group by a function of the array of member values.

    The function runs in Python, so a law that reads it falls back to Python on the Numba backend. The name of the
    function stands in the expression and the function waits in a module-level mapping, for the same reason as the
    draws: a Sage symbolic function holds only a name and arguments.

    Args:
        function:
            The function of the one-dimensional array of the values of the members that `where` selects.

        expression:
            The value of one member, a symbolic expression.

        over:
            The group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on each member, or `None` for every member.

    Returns:
        reduction:
            The reduction, a symbolic expression.

    Examples:
        >>> import numpy as np
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import reduction, reductions_in
        >>> x = var('x')
        >>> [item.kind for item in reductions_in(reduction(np.median, x))]
        ['function']
    """
    if not callable(function):
        error_msg = f'A reduction takes a function of the array of member values, but got {function!r}.'
        raise ValidationError(error_msg)
    name = f'_reduction_{id(function)}'
    FUNCTIONS[name] = function
    condition = SR(1) if where is None else SR(where)
    return REDUCTIONS['reduction'](SR(expression), condition, var(name), _over_symbol(over))

reductions_in(expression)

Return the outermost reduction calls of an expression, which may hold further reductions in their arguments.

Parameters:

Name Type Description Default
expression object

A symbolic expression, a number, or a vector of expressions.

required

Returns:

Name Type Description
reductions list[ReductionSpec]

The reductions, each listed once.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import mean, reductions_in, total
>>> x = var('x')
>>> [item.kind for item in reductions_in(total(x * mean(x)))]
['sum']
Source code in src/skcomplex/core/laws/utils/_reductions.py
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
def reductions_in(expression: object) -> list[ReductionSpec]:
    """Return the outermost reduction calls of an expression, which may hold further reductions in their arguments.

    Args:
        expression:
            A symbolic expression, a number, or a vector of expressions.

    Returns:
        reductions:
            The reductions, each listed once.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import mean, reductions_in, total
        >>> x = var('x')
        >>> [item.kind for item in reductions_in(total(x * mean(x)))]
        ['sum']
    """
    reductions = []
    for call in _calls_in(expression, set(KINDS)):
        argument, condition, weights, over = call.operands()
        reductions.append(
            ReductionSpec(
                call=call,
                kind=KINDS[call.operator().name()],
                argument=argument,
                condition=None if _is_placeholder(condition, 1) else condition,
                weights=(
                    None
                    if _is_placeholder(weights, 0) or str(weights) in FUNCTIONS
                    else WEIGHTS.get(str(weights), weights)
                ),
                over=None if _is_placeholder(over, 0) else over,
                function=FUNCTIONS.get(str(weights)),
            ),
        )
    return reductions

series(t, times, values, kind='linear')

Return an input that follows data, interpolated linearly or held from each time to the next.

Before the first time the input takes the first value, and after the last time the last value.

Parameters:

Name Type Description Default
t Expression

The time symbol, s.t in laws.

required
times Sequence[float]

The increasing times of the data.

required
values Sequence[float]

The values at those times.

required
kind str

'linear' for linear interpolation, or 'previous' to hold every value until the next time.

'linear'

Returns:

Name Type Description
input Expression

The symbolic input, a balanced search of conditions on time that every backend lowers.

Raises:

Type Description
ValueError

The times do not increase, the lengths differ, or the kind is unknown.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import series
>>> t = var('t')
>>> data = series(t, [0, 10, 20], [0.0, 5.0, 1.0])
>>> [float(data.subs({t: time})) for time in (-5, 5, 15, 30)]
[0.0, 2.5, 3.0, 1.0]
Source code in src/skcomplex/core/laws/utils/_signals.py
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
207
208
209
210
211
212
213
214
def series(t: Expression, times: Sequence[float], values: Sequence[float], kind: str = 'linear') -> Expression:
    """Return an input that follows data, interpolated linearly or held from each time to the next.

    Before the first time the input takes the first value, and after the last time the last value.

    Args:
        t:
            The time symbol, `s.t` in laws.

        times:
            The increasing times of the data.

        values:
            The values at those times.

        kind:
            `'linear'` for linear interpolation, or `'previous'` to hold every value until the next time.

    Returns:
        input:
            The symbolic input, a balanced search of conditions on time that every backend lowers.

    Raises:
        ValueError:
            The times do not increase, the lengths differ, or the kind is unknown.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import series
        >>> t = var('t')
        >>> data = series(t, [0, 10, 20], [0.0, 5.0, 1.0])
        >>> [float(data.subs({t: time})) for time in (-5, 5, 15, 30)]
        [0.0, 2.5, 3.0, 1.0]
    """
    points, levels = [float(item) for item in times], [float(item) for item in values]
    if len(points) != len(levels) or not points or any(b <= a for a, b in itertools.pairwise(points)):
        error_msg = 'A series takes increasing times and as many values.'
        raise ValueError(error_msg)
    if kind not in {'linear', 'previous'}:
        error_msg = f"Unknown series kind {kind!r}. Use 'linear' or 'previous'."
        raise ValueError(error_msg)
    return where(t < points[0], levels[0], _search(t, points, levels, kind, (0, len(points) - 1)))

shift_member(expression, offset, *, boundary='periodic', fill=0.0)

Return an expression of a group's members read at the member a number of places along the group order.

The order of a group is the member index, so member i reads the expression at member i + offset. Past the ends, 'periodic' wraps around to close a ring, 'reflecting' mirrors the order so the end member reads itself for an offset of 1, and 'fixed' reads fill. The compiler lowers the read to an index table of the group.

Parameters:

Name Type Description Default
expression Expression

An expression of the member symbols of one group.

required
offset int

The number of members along the group order, negative to read members behind.

required
boundary str

'periodic', 'reflecting', or 'fixed'.

'periodic'
fill float

The value read past the ends of a fixed boundary.

0.0

Returns:

Name Type Description
shifted Expression

The symbolic read at the offset member.

Raises:

Type Description
ValidationError

The offset is not an integer or the boundary is unknown.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import shift_member
>>> x = var('x')
>>> shift_member(x, 1) - x
-x + shift_member(x, 1, 0, 0.0)
>>> shift_member(x, 0)
x
Source code in src/skcomplex/core/laws/utils/_reductions.py
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
def shift_member(expression: Expression, offset: int, *, boundary: str = 'periodic', fill: float = 0.0) -> Expression:
    """Return an expression of a group's members read at the member a number of places along the group order.

    The order of a group is the member index, so member `i` reads the expression at member `i + offset`. Past the
    ends, `'periodic'` wraps around to close a ring, `'reflecting'` mirrors the order so the end member reads itself
    for an offset of 1, and `'fixed'` reads `fill`. The compiler lowers the read to an index table of the group.

    Args:
        expression:
            An expression of the member symbols of one group.

        offset:
            The number of members along the group order, negative to read members behind.

        boundary:
            `'periodic'`, `'reflecting'`, or `'fixed'`.

        fill:
            The value read past the ends of a fixed boundary.

    Returns:
        shifted:
            The symbolic read at the offset member.

    Raises:
        ValidationError:
            The offset is not an integer or the boundary is unknown.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import shift_member
        >>> x = var('x')
        >>> shift_member(x, 1) - x
        -x + shift_member(x, 1, 0, 0.0)
        >>> shift_member(x, 0)
        x
    """
    if boundary not in BOUNDARIES or isinstance(offset, bool) or not isinstance(offset, numbers.Integral):
        error_msg = (
            f'shift_member takes a whole number of members and a boundary among {", ".join(BOUNDARIES)}, but '
            f'got the offset {offset!r} and the boundary {boundary!r}.'
        )
        raise ValidationError(error_msg)
    return SHIFT_MEMBER(expression, int(offset), BOUNDARIES.index(boundary), float(fill))

shifted(expression, offset, order=1)

Return a field expression read at a physical displacement from every site, one component per axis.

Parameters:

Name Type Description Default
expression object

An expression of the states and parameters of a field on a lattice.

required
offset Sequence[object]

The displacement along every axis, numbers or expressions of the states and parameters.

required
order int

The order of the spline that interpolates between sites, 1 for linear or 3 for cubic.

1

Returns:

Name Type Description
read Expression

The call shifted(expression, *offset), followed by the order when it is 3.

Raises:

Type Description
ValidationError

The order is not 1 or 3.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import shifted
>>> shifted(var('u'), [var('v'), 1])
shifted(u, v, 1)
>>> shifted(var('u'), [var('v'), 1], order=3)
shifted(u, v, 1, 3)
Source code in src/skcomplex/core/laws/utils/_operators.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
234
235
236
237
def shifted(expression: object, offset: Sequence[object], order: int = 1) -> Expression:
    """Return a field expression read at a physical displacement from every site, one component per axis.

    Args:
        expression:
            An expression of the states and parameters of a field on a lattice.

        offset:
            The displacement along every axis, numbers or expressions of the states and parameters.

        order:
            The order of the spline that interpolates between sites, 1 for linear or 3 for cubic.

    Returns:
        read:
            The call `shifted(expression, *offset)`, followed by the order when it is 3.

    Raises:
        ValidationError:
            The order is not 1 or 3.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import shifted
        >>> shifted(var('u'), [var('v'), 1])
        shifted(u, v, 1)
        >>> shifted(var('u'), [var('v'), 1], order=3)
        shifted(u, v, 1, 3)
    """
    if order not in ORDERS:
        error_msg = f'The order of a shifted read is {order!r}. Use 1 for linear or 3 for cubic interpolation.'
        raise ValidationError(error_msg)
    orders = [SR(order)] if order != 1 else []
    return SHIFTED(SR(expression), *[SR(item) for item in offset], *orders)

sign(x)

Return the sign of an expression, 1 above 0, -1 below 0, and 0 at 0.

Parameters:

Name Type Description Default
x object

The expression.

required

Returns:

Name Type Description
value Expression

The symbolic sign.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import sign
>>> x = var('x')
>>> sign(x)
where(x > 0, 1, where(x < 0, -1, 0))
>>> [sign(x).subs(x=value) for value in (-2, 0, 3)]
[-1, 0, 1]
Source code in src/skcomplex/core/laws/utils/_pointwise.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def sign(x: object) -> Expression:
    """Return the sign of an expression, 1 above 0, -1 below 0, and 0 at 0.

    Args:
        x:
            The expression.

    Returns:
        value:
            The symbolic sign.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import sign
        >>> x = var('x')
        >>> sign(x)
        where(x > 0, 1, where(x < 0, -1, 0))
        >>> [sign(x).subs(x=value) for value in (-2, 0, 3)]
        [-1, 0, 1]
    """
    return where(SR(x) > 0, 1, where(SR(x) < 0, -1, 0))

smooth(value, time)

Return the first-order exponential smoothing of a value, a hidden stock that adjusts at gap / time.

Parameters:

Name Type Description Default
value object

The symbolic value.

required
time object

The smoothing time, a positive number or an expression of parameters.

required

Returns:

Name Type Description
output Expression

The symbolic smoothed value, which starts at the value.

Raises:

Type Description
ValueError

The smoothing time is a number that is not positive.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import smooth
>>> smooth(var('signal'), 2)
smoothing_delay1(signal, 2)
Source code in src/skcomplex/core/laws/utils/_delays.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def smooth(value: object, time: object) -> Expression:
    """Return the first-order exponential smoothing of a value, a hidden stock that adjusts at `gap / time`.

    Args:
        value:
            The symbolic value.

        time:
            The smoothing time, a positive number or an expression of parameters.

    Returns:
        output:
            The symbolic smoothed value, which starts at the value.

    Raises:
        ValueError:
            The smoothing time is a number that is not positive.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import smooth
        >>> smooth(var('signal'), 2)
        smoothing_delay1(signal, 2)
    """
    return distributed_delay(value, time, 1, conserved=False)

step(t, at, height=1.0)

Return a step input, 0 before a time and height from it on.

Parameters:

Name Type Description Default
t Expression

The time symbol, s.t in laws.

required
at float

The time of the step.

required
height float

The height of the step.

1.0

Returns:

Name Type Description
input Expression

The symbolic input.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import step
>>> t = var('t')
>>> [float(step(t, 5, 2).subs({t: time})) for time in (4, 5)]
[0.0, 2.0]
Source code in src/skcomplex/core/laws/utils/_signals.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def step(t: Expression, at: float, height: float = 1.0) -> Expression:
    """Return a step input, 0 before a time and `height` from it on.

    Args:
        t:
            The time symbol, `s.t` in laws.

        at:
            The time of the step.

        height:
            The height of the step.

    Returns:
        input:
            The symbolic input.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import step
        >>> t = var('t')
        >>> [float(step(t, 5, 2).subs({t: time})) for time in (4, 5)]
        [0.0, 2.0]
    """
    return where(t >= at, height, 0)

symmetric_part(tensor)

Return the symmetric part of a square matrix, (M + M^T) / 2.

Parameters:

Name Type Description Default
tensor Matrix

A square matrix of expressions.

required

Returns:

Name Type Description
part Matrix

The symmetric matrix.

Raises:

Type Description
TensorShapeError

The value is not a square matrix.

Examples:

>>> from sage.all__sagemath_symbolics import SR, matrix, var
>>> from skcomplex.core.laws.utils import symmetric_part
>>> symmetric_part(matrix(SR, [[0, 2 * var('a')], [0, 0]]))
[0 a]
[a 0]
Source code in src/skcomplex/core/laws/utils/_tensors.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def symmetric_part(tensor: Matrix) -> Matrix:
    """Return the symmetric part of a square matrix, `(M + M^T) / 2`.

    Args:
        tensor:
            A square matrix of expressions.

    Returns:
        part:
            The symmetric matrix.

    Raises:
        TensorShapeError:
            The value is not a square matrix.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, matrix, var
        >>> from skcomplex.core.laws.utils import symmetric_part
        >>> symmetric_part(matrix(SR, [[0, 2 * var('a')], [0, 0]]))
        [0 a]
        [a 0]
    """
    checked = _check_matrix('symmetric_part', tensor)
    return (checked + checked.transpose()) / 2

tensor_divergence(tensor)

Return the divergence of a rank-2 tensor, the vector of the sums of partial(T_ij, j) over j.

Parameters:

Name Type Description Default
tensor Matrix

A square matrix of expressions.

required

Returns:

Name Type Description
divergence FreeModuleElement

One expression per row of the tensor.

Raises:

Type Description
TensorShapeError

The value is not a square matrix.

Examples:

>>> from sage.all__sagemath_symbolics import SR, identity_matrix, var
>>> from skcomplex.core.laws.utils import tensor_divergence
>>> tensor_divergence(var('p') * identity_matrix(SR, 2))
(partial(p, 0), partial(p, 1))
Source code in src/skcomplex/core/laws/utils/_tensors.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def tensor_divergence(tensor: Matrix) -> FreeModuleElement:
    """Return the divergence of a rank-2 tensor, the vector of the sums of `partial(T_ij, j)` over `j`.

    Args:
        tensor:
            A square matrix of expressions.

    Returns:
        divergence:
            One expression per row of the tensor.

    Raises:
        TensorShapeError:
            The value is not a square matrix.

    Examples:
        >>> from sage.all__sagemath_symbolics import SR, identity_matrix, var
        >>> from skcomplex.core.laws.utils import tensor_divergence
        >>> tensor_divergence(var('p') * identity_matrix(SR, 2))
        (partial(p, 0), partial(p, 1))
    """
    rows = _check_matrix('tensor_divergence', tensor).rows()
    return vector(SR, [sum((_derivative(item, axis) for axis, item in enumerate(row)), SR(0)) for row in rows])

total(expression, over=None, where=None, weights=None, by=None)

Return the sum of an expression over the members of a group, sum_j a_j, or sum_j W_ij a_j with weights.

Inside the reduction, the symbols of a member stand for the reduced member, and outside it for the reading member.

Parameters:

Name Type Description Default
expression object

The expression of one member, such as namespace.x or cos(namespace.members.theta).

required
over object

The namespace of the group to reduce over, such as namespace.members, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None
weights object

A constant matrix, a NumPy array or a SciPy sparse matrix, with one row per reading member and one column per reduced member, a vector with one weight per reduced member that every reading member shares, or None.

None
by str | None

The name of a state of the group, so every member reduces over the members that share its value of the state, or None for all members. It needs the namespace of the group as over.

None

Returns:

Name Type Description
reduction Expression

The unevaluated reduction, which every backend lowers.

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

Examples:

>>> from sage.all__sagemath_symbolics import tanh, var
>>> from skcomplex.core.laws.utils import total
>>> x = var('x')
>>> -x + tanh(total(x))
-x + tanh(total(x))
>>> total(x, where=x > 0.5)
total(x, where=x > 0.5)
Source code in src/skcomplex/core/laws/utils/_reductions.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def total(
    expression: object,
    over: object = None,
    where: object = None,
    weights: object = None,
    by: str | None = None,
) -> Expression:
    """Return the sum of an expression over the members of a group, `sum_j a_j`, or `sum_j W_ij a_j` with weights.

    Inside the reduction, the symbols of a member stand for the reduced member, and outside it for the reading member.

    Args:
        expression:
            The expression of one member, such as `namespace.x` or `cos(namespace.members.theta)`.

        over:
            The namespace of the group to reduce over, such as `namespace.members`, or `None` for the group the
            expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

        weights:
            A constant matrix, a NumPy array or a SciPy sparse matrix, with one row per reading member and one column
            per reduced member, a vector with one weight per reduced member that every reading member shares, or
            `None`.

        by:
            The name of a state of the group, so every member reduces over the members that share its value of the
            state, or `None` for all members. It needs the namespace of the group as `over`.

    Returns:
        reduction:
            The unevaluated reduction, which every backend lowers.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol, the weights are not a matrix, or a key has no group namespace.

    Examples:
        >>> from sage.all__sagemath_symbolics import tanh, var
        >>> from skcomplex.core.laws.utils import total
        >>> x = var('x')
        >>> -x + tanh(total(x))
        -x + tanh(total(x))
        >>> total(x, where=x > 0.5)
        total(x, where=x > 0.5)
    """
    return _reduce('total', expression, over, where, weights, by=by)

transfer(source, sink, amount, state, sink_state=None)

Move an amount of a quantity from one agent to another at once, inside a rule.

It is the imperative form of TransferLaw for agent rules. Both holdings change together, so the total of the quantity over all agents is unchanged, and a negative holding is an amount the agent owes.

Parameters:

Name Type Description Default
source object

The agent the amount leaves, as the rule receives it.

required
sink object

The agent the amount enters.

required
amount float

The amount moved, negative to move it the other way.

required
state str

The state of the source that holds the quantity.

required
sink_state str | None

The state of the sink that holds the quantity, or None for state.

None

Raises:

Type Description
ValidationError

An agent has no such state.

Examples:

>>> from types import SimpleNamespace
>>> from skcomplex.core.laws.utils import transfer
>>> first, second = SimpleNamespace(level=10.0), SimpleNamespace(level=0.0)
>>> transfer(first, second, 4.0, 'level')
>>> first.level, second.level
(6.0, 4.0)
Source code in src/skcomplex/core/laws/utils/_agents.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def transfer(source: object, sink: object, amount: float, state: str, sink_state: str | None = None) -> None:
    """Move an amount of a quantity from one agent to another at once, inside a rule.

    It is the imperative form of `TransferLaw` for agent rules. Both holdings change together, so the total of the
    quantity over all agents is unchanged, and a negative holding is an amount the agent owes.

    Args:
        source:
            The agent the amount leaves, as the rule receives it.

        sink:
            The agent the amount enters.

        amount:
            The amount moved, negative to move it the other way.

        state:
            The state of the source that holds the quantity.

        sink_state:
            The state of the sink that holds the quantity, or `None` for `state`.

    Raises:
        ValidationError:
            An agent has no such state.

    Examples:
        >>> from types import SimpleNamespace
        >>> from skcomplex.core.laws.utils import transfer
        >>> first, second = SimpleNamespace(level=10.0), SimpleNamespace(level=0.0)
        >>> transfer(first, second, 4.0, 'level')
        >>> first.level, second.level
        (6.0, 4.0)
    """
    target = state if sink_state is None else sink_state
    for agent, name in ((source, state), (sink, target)):
        if not hasattr(agent, name):
            error_msg = f'The transfer of {amount} reads the state {name!r}, which the agent {agent!r} does not have.'
            raise ValidationError(error_msg)
    if source is sink and state == target:
        return
    source_value, sink_value = getattr(source, state), getattr(sink, target)
    setattr(source, state, source_value - amount)
    setattr(sink, target, sink_value + amount)

uniform(name, low=0, high=1)

Return a value drawn uniformly between two bounds at every step.

Every use of the same name in one element reads the same value within a step, and every member of a group draws its own value.

Parameters:

Name Type Description Default
name str

The name of the draw, a Python identifier.

required
low object

The lower bound, a number or an expression of states and parameters.

0
high object

The upper bound.

1

Returns:

Name Type Description
draw Expression

The symbolic draw.

Raises:

Type Description
ValidationError

The name is not a Python identifier.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import uniform
>>> x = var('x')
>>> uniform('u', 0, x) + uniform('u', 0, x)
2*uniform_u(0, x)
Source code in src/skcomplex/specs/_draw_spec.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def uniform(name: str, low: object = 0, high: object = 1) -> Expression:
    """Return a value drawn uniformly between two bounds at every step.

    Every use of the same name in one element reads the same value within a step, and every member of a group draws
    its own value.

    Args:
        name:
            The name of the draw, a Python identifier.

        low:
            The lower bound, a number or an expression of states and parameters.

        high:
            The upper bound.

    Returns:
        draw:
            The symbolic draw.

    Raises:
        ValidationError:
            The name is not a Python identifier.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import uniform
        >>> x = var('x')
        >>> uniform('u', 0, x) + uniform('u', 0, x)
        2*uniform_u(0, x)
    """
    return _call('uniform', name, (low, high))

variance(expression, over=None, where=None)

Return the population variance of an expression over the selected members of a group.

Parameters:

Name Type Description Default
expression object

The expression of one member.

required
over object

The namespace of the group to reduce over, or None for the group the expression reads.

None
where object

A condition on the reduced member that selects the members, or None for all members.

None

Returns:

Name Type Description
reduction Expression

The unevaluated reduction, 0 when no member is selected.

Raises:

Type Description
ValidationError

The group is not a namespace or a symbol.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import variance
>>> variance(var('x'))
variance(x)
Source code in src/skcomplex/core/laws/utils/_reductions.py
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
def variance(expression: object, over: object = None, where: object = None) -> Expression:
    """Return the population variance of an expression over the selected members of a group.

    Args:
        expression:
            The expression of one member.

        over:
            The namespace of the group to reduce over, or `None` for the group the expression reads.

        where:
            A condition on the reduced member that selects the members, or `None` for all members.

    Returns:
        reduction:
            The unevaluated reduction, 0 when no member is selected.

    Raises:
        ValidationError:
            The group is not a namespace or a symbol.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import variance
        >>> variance(var('x'))
        variance(x)
    """
    return _reduce('variance', expression, over, where, None)

vector_gradient(velocity)

Return the gradient of a vector, the matrix of the derivatives partial(v_i, j).

Parameters:

Name Type Description Default
velocity Sequence[object] | FreeModuleElement

One expression per dimension.

required

Returns:

Name Type Description
gradient Matrix

The matrix of the derivative of every component along every dimension.

Raises:

Type Description
TensorShapeError

The value is not a vector.

Examples:

>>> from sage.all__sagemath_symbolics import var
>>> from skcomplex.core.laws.utils import vector_gradient
>>> vector_gradient([var('u'), var('v')])
[partial(u, 0) partial(u, 1)]
[partial(v, 0) partial(v, 1)]
Source code in src/skcomplex/core/laws/utils/_tensors.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
def vector_gradient(velocity: Sequence[object] | FreeModuleElement) -> Matrix:
    """Return the gradient of a vector, the matrix of the derivatives `partial(v_i, j)`.

    Args:
        velocity:
            One expression per dimension.

    Returns:
        gradient:
            The matrix of the derivative of every component along every dimension.

    Raises:
        TensorShapeError:
            The value is not a vector.

    Examples:
        >>> from sage.all__sagemath_symbolics import var
        >>> from skcomplex.core.laws.utils import vector_gradient
        >>> vector_gradient([var('u'), var('v')])
        [partial(u, 0) partial(u, 1)]
        [partial(v, 0) partial(v, 1)]
    """
    components = _check_vector('vector_gradient', velocity)
    size = len(components)
    return matrix(SR, [[_derivative(item, axis) for axis in range(size)] for item in components])