Skip to content

API Reference

core

Module containing core components of the SigAlg library.

Domain

Bases: Index

A class representing domains of functions and measurable spaces.

Parameters:

Name Type Description Default
indices IndexLike | None

The object from which to construct the Domain.

None
variable_names list[Hashable] | None

A list of variable names for the dimensions of the domain. If None, defaults will be generated.

None
name Hashable | None

Name identifier for the domain. If None, a default name will be generated.

None

Examples:

>>> from sigalg.core import Domain

Build a Domain from a list of indices.

>>> X = Domain([1, 2, 3])
>>> print(X)
Domain 'X':
 x
 1
 2
 3

make_measurable_space

make_measurable_space(sig_alg=None)

Convert this domain to a measurable space by adding a sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra | None

Sigma-algebra to use. If None, a power set sigma-algebra will be created.

None

Returns:

Name Type Description
measurable_space MeasurableSpace

A MeasurableSpace object with this domain.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a domain.

>>> X = Domain(indices=["s0", "s1", "s2", "s3"], name="X")

Promote to a MeasurableSpace with default power-set sigma-algebra.

>>> measurable_space = X.make_measurable_space()
>>> print(measurable_space)
Measurable space (X, R)
=======================

* Domain 'X':
  x
 s0
 s1
 s2
 s3

* Sigma algebra 'R':
     R
 x
s0  s0
s1  s1
s2  s2
s3  s3

Create a custom sigma-algebra, and promote to a MeasurableSpace with this custom object.

>>> F = SigmaAlgebra(domain=X, mapping={"s0": 0, "s1": 0, "s2": 1, "s3": 1})
>>> measurable_space = X.make_measurable_space(sig_alg=F)
>>> print(measurable_space)
Measurable space (X, F)
=======================

* Domain 'X':
  x
 s0
 s1
 s2
 s3

* Sigma algebra 'F':
     F
x
s0   0
s1   0
s2   1
s3   1

make_measure_space

make_measure_space(sig_alg=None, measure=None)

Convert this domain to a measure space by adding a sigma-algebra and measure.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra | None

Sigma-algebra to use. If None, a power set sigma-algebra will be created.

None
measure Measure | None

Measure to use. If None, the counting measure will be created.

None

Returns:

Name Type Description
measure_space MeasureSpace

A MeasureSpace object with this domain.

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra

Define a domain.

>>> X = Domain(indices=["a", "b", "c"])

Promote to a MeasureSpace with default power set sigma-algebra and counting measure.

>>> measure_space = X.make_measure_space()
>>> print(measure_space)
Measure space (X, R, C)
=======================

* Domain 'X':
 x
 a
 b
 c

* Sigma algebra 'R':
   R
x
a  a
b  b
c  c

* Measure 'C':
        C
x
a       1
b       1
c       1

Create a custom sigma-algebra and measure, and promote to a MeasureSpace with these custom objects.

>>> F = SigmaAlgebra(domain=X, mapping=
...     {
...         "a": 0,
...         "b": 1,
...         "c": 1,
...     },
... )
>>> mu = Measure(domain=F, mapping=
...     {
...         0: 2,
...         1: 8,
...     },
... )
>>> measure_space = X.make_measure_space(sig_alg=F, measure=mu)
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x
 a
 b
 c

* Sigma algebra 'F':
   F
x
a  0
b  1
c  1

* Measure 'mu':
        mu
F
0        2
1        8

to_function

to_function(
    domain_name="Omega",
    domain_variable_name="omega",
    index_kind="Index",
    index_name=None,
    name="f",
)

Convert the current domain to a Function instance.

Parameters:

Name Type Description Default
domain_name Hashable

Name of the domain of the function.

"Omega"
domain_variable_name Hashable

Name of the domain variable.

"omega"
index_kind Literal['Index', 'Time']

Kind of the index.

"Index"
index_name Hashable | None

Name of the index.

None
name Hashable

Name of the function.

"f"

Returns:

Name Type Description
function Function

A new Function object representing the current domain.

Examples:

>>> from sigalg.core import Domain

Define a 2-dimensional domain.

>>> X = Domain.cartesian_product([["a", "b"], [1, 2]], variable_names=["letter", "number"])

Convert to a function with default domain parameter values.

>>> f = X.to_function()
>>> print(f)
Function 'f':
i     letter  number
omega
0          a       1
1          a       2
2          b       1
3          b       2

Convert to a function with custom domain parameters values.

>>> g = X.to_function(name="g", domain_variable_name="s", domain_name="S")
>>> print(g)
Function 'g':
i letter  number
s
0      a       1
1      a       2
2      b       1
3      b       2

to_sample_space

to_sample_space()

Copy this domain and return it as an instance of SampleSpace.

Returns:

Name Type Description
sample_space SampleSpace

A new SampleSpace object with the same underlying data as this domain.

Examples:

>>> from sigalg.core import Domain

Define a domain.

>>> X = Domain(indices=["a", "b", "c"], name="X")

Promote to a SampleSpace.

>>> sample_space = X.to_sample_space()
>>> print(sample_space)
Sample space 'X':
 x
 a
 b
 c

Filtration

A class representing a filtration of sigma-algebras.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
sig_algs FiltrationLike | None

A list of sigma-algebras or a pd.DataFrame representing the filtration.

None
variable_names dict[Hashable, list[Hashable]] | None

A dictionary mapping each index to a list of variable names for the corresponding sigma-algebra.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain of the sigma-algebras.

"Domain"
domain_name Hashable | None

The name of the domain of the sigma-algebras. If None, a default will be generated.

None
index IndexLike | None

The index of the filtration. If None, a default will be generated.

None
index_name Hashable | None

The name of the index of the filtration. If None, a default will be generated.

None
name Hashable

A name for the filtration.

"F"

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Generate a domain and three sigma-algebras, one a sub-sigma-algebra of the next.

>>> X = Domain.from_sequence(size=5)
>>> C = SigmaAlgebra.from_rand(domain=X, num_atoms=4, name="C", random_state=rng)
>>> B = SigmaAlgebra.from_rand(super=C, num_atoms=3, name="B", random_state=rng)
>>> A = SigmaAlgebra.from_rand(super=B, num_atoms=2, name="A", random_state=rng)

Build the filtration and print it.

>>> F = Filtration(sig_algs=[A, B, C])
>>> print(F)
Filtration 'F'
==============

* Time 'T':
 time
    0
    1
    2

* At index 0:
Sigma algebra 'F_0':
   F_0
x
0    0
1    0
2    1
3    0
4    1

* At index 1:
Sigma algebra 'F_1':
   F_1
x
0    0
1    0
2    2
3    1
4    2

* At index 2:
Sigma algebra 'F_2':
   F_2
x
0    2
1    3
2    0
3    1
4    0
Notes

A \(\sigma\)-algebra \(\mathcal{F}\) on a nonempty set \(X\) is called a filtered \(\sigma\)-algebra if it is equipped with a collection \(\{\mathcal{F}_t\}_{t\in T}\) of \(\sigma\)-algebras on \(X\), indexed by some linearly ordered set \(T\), such that \(\mathcal{F}_t \subset \mathcal{F}\) for every \(t\in T\), and \(\mathcal{F}_s \subset \mathcal{F}_t\) for all \(s,t\in T\) with \(s\leq t\). The collection \(\{\mathcal{F}_t\}_{t\in T}\) is called a filtration.

at property

at

Get an indexer for accessing sigma-algebras at specific times.

The difference between F.at[time] and F[time] is that F.at[time] will return the sigma-algebra at the nearest time in the filtration, while F[time] will raise a ValueError if the specified time is not in the index of the filtration.

Returns:

Name Type Description
indexer _FiltrationIndexer

An indexer for accessing sigma-algebras at specific times.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra, Time
>>> rng = np.random.default_rng(42)
>>> X = Domain.from_sequence(size=5)
>>> C = SigmaAlgebra.from_rand(domain=X, num_atoms=4, name="C", random_state=rng)
>>> B = SigmaAlgebra.from_rand(super=C, num_atoms=3, name="B", random_state=rng)
>>> A = SigmaAlgebra.from_rand(super=B, num_atoms=2, name="A", random_state=rng)
>>> T = Time.continuous(start=0.0, stop=1.5, num_points=3)
>>> F = Filtration(sig_algs=[A, B, C], index=T)
>>> print(F)
Filtration 'F'
==============

* Time 'T':
   t
0.00
0.75
1.50

* At index 0.0:
Sigma algebra 'F_0.0':
   F_0.0
x
0      0
1      0
2      1
3      0
4      1

* At index 0.75:
Sigma algebra 'F_0.75':
   F_0.75
x
0       0
1       0
2       2
3       1
4       2

* At index 1.5:
Sigma algebra 'F_1.5':
   F_1.5
x
0      2
1      3
2      0
3      1
4      0

Access sigma algebra at time 0.0.

>>> print(F.at[0.0])
Sigma algebra 'F_0.0':
   F_0.0
x
0      0
1      0
2      1
3      0
4      1

Access sigma algebra at time 0.5 (returns the same as at time 0.75).

>>> print(F.at[0.5])
Sigma algebra 'F_0.75':
   F_0.75
x
0       0
1       0
2       2
3       1
4       2

Access sigma algebra at time 0.75.

>>> print(F.at[0.75])
Sigma algebra 'F_0.75':
   F_0.75
x
0       0
1       0
2       2
3       1
4       2

Access sigma algebra at time 1.2 (returns the same as at time 1.5).

>>> print(F.at[1.2])
Sigma algebra 'F_1.5':
   F_1.5
x
0      2
1      3
2      0
3      1
4      0

Access sigma algebra at time 1.5.

>>> print(F.at[1.5])
Sigma algebra 'F_1.5':
   F_1.5
x
0      2
1      3
2      0
3      1
4      0

coarsest cached property

coarsest

Get the coarsest sigma-algebra in the filtration.

Returns:

Name Type Description
coarsest SigmaAlgebra | None

The coarsest sigma-algebra in the filtration.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra
>>> rng = np.random.default_rng(42)
>>> X = Domain.from_sequence(size=5)
>>> C = SigmaAlgebra.from_rand(domain=X, num_atoms=4, name="C", random_state=rng)
>>> B = SigmaAlgebra.from_rand(super=C, num_atoms=3, name="B", random_state=rng)
>>> A = SigmaAlgebra.from_rand(super=B, num_atoms=2, name="A", random_state=rng)
>>> F = Filtration(sig_algs=[A, B, C])
>>> print(F.coarsest)
Sigma algebra 'F_0':
       F_0
x
0        0
1        0
2        1
3        0
4        1

domain property

domain

Get the domain over which the sigma-algebras are defined.

Returns:

Name Type Description
domain Domain | None

The domain of the sigma-algebras.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Generate a domain and three sigma-algebras, one a sub-sigma-algebra of the next.

>>> X = Domain.from_sequence(size=5)
>>> C = SigmaAlgebra.from_rand(domain=X, num_atoms=4, name="C", random_state=rng)
>>> B = SigmaAlgebra.from_rand(super=C, num_atoms=3, name="B", random_state=rng)
>>> A = SigmaAlgebra.from_rand(super=B, num_atoms=2, name="A", random_state=rng)

Build the filtration and print the domain.

>>> F = Filtration([A, B, C])
>>> print(F.domain)
Domain 'X':
 x
 0
 1
 2
 3
 4

finest cached property

finest

Get the finest sigma-algebra in the filtration.

Returns:

Name Type Description
finest SigmaAlgebra | None

The finest sigma-algebra in the filtration.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra
>>> rng = np.random.default_rng(42)
>>> X = Domain.from_sequence(size=5)
>>> C = SigmaAlgebra.from_rand(domain=X, num_atoms=4, name="C", random_state=rng)
>>> B = SigmaAlgebra.from_rand(super=C, num_atoms=3, name="B", random_state=rng)
>>> A = SigmaAlgebra.from_rand(super=B, num_atoms=2, name="A", random_state=rng)
>>> F = Filtration(sig_algs=[A, B, C])
>>> print(F.finest)
Sigma algebra 'F_2':
       F_2
x
0        2
1        3
2        0
3        1
4        0

variable_names property

variable_names

Get the variable names for the sigma-algebras in the filtration.

Returns:

Name Type Description
variable_names dict[Hashable, list[Hashable]] | None

A dictionary mapping each index to a list of variable names for the corresponding sigma-algebra, or None if the variable names are not set.

Examples:

>>> import numpy as np
>>> import pandas as pd
>>> from sigalg.core import Domain, Filtration, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Explicitly define a filtration from two instances of SigmaAlgebra with custom variable names.

>>> X = Domain.from_sequence(size=5)
>>> B = SigmaAlgebra.from_rand(
...     domain=X,
...     num_atoms=4,
...     dim=2,
...     variable_names=["x", "y"],
...     name="B",
...     random_state=rng,
... )
>>> A = SigmaAlgebra.from_rand(
...     super=B,
...     num_atoms=2,
...     variable_names=["u"],
...     name="A",
...     random_state=rng,
... )
>>> F = Filtration([A, B], index=[1, 2])

Leaving the variable_names parameter as None will automatically extract the variable names from the sigma-algebras in the filtration.

>>> print(F.variable_names)
{1: ['u'], 2: ['x', 'y']}

Passing in a custom dictionary for variable_names to the Filtration constructor will override the variable names from the sigma-algebras. Note that when we index into the filtration to retrieve a specific sigma-algebra, the variable names for that sigma-algebra will reflect the custom names provided in the variable_names dictionary and not the original names from the sigma-algebras.

>>> G = Filtration([A, B], index=[1, 2], name="G", variable_names={1: ["v"], 2: ["a", "b"]})
>>> print(repr(G[2]))
SigmaAlgebra(domain=X, num_atoms=4, variable_names=['a', 'b'], name=G_2)

Instead of generating a filtration from a list of SigmaAlgebra instances, we can also create a filtration directly from a pd.DataFrame.

>>> sig_algs = pd.DataFrame(
...     [
...         (0, (0, 0)),
...         (0, (3, 0)),
...         (0, (0, 0)),
...         (1, (2, 2)),
...         (1, (1, 0)),
...     ],
...     columns=[1, 2],
... )
>>> H = Filtration(sig_algs, name="H")

If the variable_names parameter is not provided, the variable names for each sigma-algebra in the filtration will default to None.

>>> print(H.variable_names)
{1: None, 2: None}

However, this does not mean that the sigma-algebras themselves have no variable names. When we index into the filtration to retrieve a specific sigma-algebra, the variable names for that sigma-algebra will reflect defaults from the SigmaAlgebra constructor.

>>> print(repr(H[2]))
SigmaAlgebra(domain=X, num_atoms=4, variable_names=['H_2_0', 'H_2_1'], name=H_2)

Finally, if we construct a filtration from a pd.DataFrame and provide a custom dictionary for variable_names, the variable names for each sigma-algebra in the filtration will reflect the custom names provided in the variable_names dictionary.

>>> K = Filtration(sig_algs, name="K", variable_names={1: ["u"], 2: ["x", "y"]})
>>> print(K.variable_names)
{1: ['u'], 2: ['x', 'y']}
>>> print(repr(K[2]))
SigmaAlgebra(domain=X, num_atoms=4, variable_names=['x', 'y'], name=K_2)

Function

Bases: OperatorsMethods

A class representing a function.

Mathematically, a function requires three items: A domain set, a codomain set, and a rule defining the function. For instances of Function:

  • The domain of the function is passed as the parameter domain, but this parameter is not required. This allows for the creation of functions whose domains are supposed to be continuous.
  • The codomain of an instance of Function is always assumed to be a Cartesian power of the real line.
  • The rule defining the function may be passed into the constructor as the parameter mapping.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the function.

None
mapping MappingLike | None

The underlying rule defining the function. If a Callable, its parameters must either all be keyword-only, or all positional only.

None
kind Literal['any', 'measure', 'probability', 'param_measure', 'param_probability']

The kind of outputs of the function.

"any"
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
multi_dim_outputs bool

Whether the outputs of the function are 1-dimensional or multi-dimensional.

True
parameter_names list[Hashable] | None

A list of parameter names of the function.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None
**kwargs

Additional keyword arguments passed to subclasses.

{}

Examples:

>>> import pandas as pd
>>> from sigalg.core import Domain, Function

Define a Function with an explicit domain and a mapping expressed as a lambda function. Note that the parameters to the lambda function are keyword-only.

>>> X = Domain([(1, 2), (2, 3), (1, 4)], variable_names=["x", "y"])
>>> f = Function(domain=X, mapping=lambda *, x, y: 2 * x + y**2)
>>> print(f)
Function 'f':
      f
x y
1 2   6
2 3  13
1 4  18

Define a function from a pd.Series object and an explicit domain.

>>> mapping = pd.Series([6, 13, 18], index=X.data)
>>> g = Function(domain=X, mapping=mapping, name="g")
>>> print(g)
Function 'g':
      g
x y
1 2   6
2 3  13
1 4  18

Define a function from a dictionary and an explicit domain.

>>> mapping = {(1, 2): 6, (2, 3): 13, (1, 4): 18}
>>> h = Function(domain=X, mapping=mapping, name="h")
>>> print(h)
Function 'h':
      h
x y
1 2   6
2 3  13
1 4  18

Define a function from a lambda function without an explicit domain. We no longer can print the range of the function, but we can evaluate the function.

>>> k = Function(mapping=lambda *, x, y: x * 2 + y, name="k")
>>> print(k)
Function(parameters=(x, y), name=k)
>>> print(k(x=2, y=1))
5

component_names property writable

component_names

Get the names of the component functions of the function.

Returns:

Name Type Description
component_names dict[Hashable] | None

A dictionary of the names of the component functions of the function if it has multi-dimensional outputs, or None.

Examples:

>>> from sigalg.core import Domain, Function, Index
>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping=lambda x: (x, x**2),
...     index=J,
...     multi_dim_outputs=True,
... )
>>> f.component_names
{1: 'f_1', 2: 'f_2'}

components cached property

components

Get the component functions of the function.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
components list[Function] | None

A list of the component functions of the function.

Examples:

>>> from sigalg.core import Domain, Function

Define a function.

>>> X = Domain.from_sequence(size=3)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 4),
...         1: (2, 5),
...         2: (3, 6),
...     }
... )
>>> print(f)
Function 'f':
i   0  1
x
0   1  4
1   2  5
2   3  6

Extract the component functions of the function.

>>> for component in f.components:
...     print(component)
Function 'f_0':
   f_0
x
0    1
1    2
2    3
Function 'f_1':
   f_1
x
0    4
1    5
2    6
Notes

If \(f: X \to \mathbb{R}^d\) is a function, then for each \(x \in X\) we may write

\[ f(x) = (f_1(x),f_2(x),\ldots, f_d(x)) \]

where \(f_j: X \to \mathbb{R}\) is the \(j\)-th component function of \(f\).

dimension property

dimension

Get the dimension of the outputs of the function.

Returns:

Name Type Description
dim int | None

The dimension of the outputs of the function, or None if the underlying data of the function is not a pd.Series or pd.DataFrame.

Examples:

>>> from sigalg.core import Domain, Function, Index
>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping=lambda x: (x, x**2),
...     index=J,
...     multi_dim_outputs=True,
... )
>>> print(f)
Function 'f':
j  1  2
x
0  0  0
1  1  1
>>> f.dimension
2
>>> g = Function(domain=X, mapping=lambda x: x**2)
>>> g.dimension
1

domain property

domain

Get the domain of the function.

Returns:

Name Type Description
domain Domain | None

The domain of the function if defined, otherwise None.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain([(1, 2), (2, 3), (1, 4)], variable_names=["x", "y"])
>>> f = Function(domain=X, mapping=lambda *, x, y: 2 * x + y**2)
>>> print(f)
Function 'f':
      f
x y
1 2   6
2 3  13
1 4  18
>>> print(f.domain)
Domain 'X':
 x  y
 1  2
 2  3
 1  4

generated_sig_alg cached property

generated_sig_alg

Get the sigma-algebra generated by the function.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
sig_alg SigmaAlgebra | None

The sigma-algebra induced by the function.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Function,
... )

Extract the generated sigma-algebra from a function with 2-dimensional outputs. Note that the atom identifiers are exactly the values of the function.

>>> X = Domain.from_sequence(size=3)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...     },
... )
>>> print(f.generated_sig_alg)
Sigma algebra 'sigma(f)':
i      0    1
x
0      1    2
1      3    4
2      3    4
Notes

A function \(f: X \to \mathbb{R}^d\) generates a \(\sigma\)-algebra on \(X\) denoted \(\sigma(f)\). On a finite domain \(X\), this \(\sigma\)-algebra is determined by its atoms, which are the nonempty preimages

\[ \{ x \in X : f(x) = y\}, \]

for \(y\in \mathbb{R}^d\). The atom identifiers may thus be taken as the vectors \(y\in \mathbb{R}^d\) in the range of \(f\).

index property

index

Get the index of the function.

Returns:

Name Type Description
index Index | None

The index of the function if defined, otherwise None.

Examples:

>>> from sigalg.core import Domain, Function, Index
>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping=lambda x: (x, x**2),
...     index=J,
...     multi_dim_outputs=True,
... )
>>> print(f)
Function 'f':
j  1  2
x
0  0  0
1  1  1
>>> print(f.index)
Index 'J':
 j
 1
 2

lattice cached property

lattice

Get the (upward) lattice of sigma-algebras containing the sigma-algebra generated by this function.

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define three sigma-algebras on a domain.

>>> X = Domain.from_sequence(size=4, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 0,
...     },
...     name="G",
... )
>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 0,
...         3: 0,
...     },
...     name="H",
... )

Define a function with 2-dimensional outputs.

>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (1, 2),
...     },
... )
>>> print(f)
Function 'f':
i  0  1
x
0  1  2
1  3  4
2  3  4
3  1  2

We may test whether the function is measurable with respect to a sigma-algebra by using the in operator with the lattice attribute.

>>> F in f.lattice
True

We may get the unique values of the function on the atoms of the sigma-algebra by calling the get_atom_data method.

>>> print(f.lattice.get_atom_data(F))
i    0    1
F
0    1    2
1    3    4
2    1    2

Whenever a measurability check is executed, and the result is True, the sigma-algebra is added to the internal lattice.

>>> f.lattice
Lattice(base=sigma(f), type=upward, num_sig_algs=2)

Perform another measurability check, inspect the lattice attribute to see the updated list of contents, and print the atom data.

>>> G in f.lattice
True
>>> f.lattice
Lattice(base=sigma(f), type=upward, num_sig_algs=3)
>>> print(f.lattice.get_atom_data(G))
i    0    1
G
0    1    2
1    3    4

Notice that the function is not measurable with respect to the third sigma-algebra. The measurability check accordingly returns False, and the contents of lattice is not changed.

>>> H in f.lattice
False
>>> f.lattice
Lattice(base=sigma(f), type=upward, num_sig_algs=3)

num_variables property

num_variables

Get the number of variables of the function.

Returns:

Name Type Description
num_variables int | None

The number of variables of the function if defined, otherwise None.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain([(1, 2), (2, 3), (1, 4)], variable_names=["x", "y"])
>>> f = Function(domain=X, mapping=lambda *, x, y: 2 * x + y**2)
>>> print(f)
Function 'f':
      f
x y
1 2   6
2 3  13
1 4  18
>>> print(f.num_variables)
2

range cached property

range

Return the range of the function.

Returns:

Name Type Description
range Domain | None

The range of the function as an instance of Domain, or None if the underlying data is not a pd.Series or pd.DataFrame.

Examples:

>>> from sigalg.core import Domain, Index, Function
>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> X = Domain.from_sequence(size=3)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (2, 3),
...     },
...     index=J,
...     multi_dim_outputs=True,
... )
>>> print(f.range)
Domain 'f_range':
 f_1  f_2
   1    2
   2    3
>>> Y = Domain([-2, -1, 0, 1, 2], variable_names=["y"], name="Y")
>>> g = Function(domain=Y, mapping=lambda y: y**2, name="g")
>>> print(g)
Function 'g':
    g
y
-2  4
-1  1
 0  0
 1  1
 2  4
>>> print(g.range)
Domain 'g_range':
 g
 0
 1
 4

signature property

signature

Get the signature of the function.

Returns:

Name Type Description
signature Signature

The signature of the function.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain([(1, 2), (2, 3), (1, 4)], variable_names=["x", "y"])
>>> f = Function(domain=X, mapping=lambda *, x, y: 2 * x + y**2)
>>> print(f)
Function 'f':
        f
x y
1 2   6
2 3  13
1 4  18
>>> print(f.signature)
(*, x, y)

variable_names property

variable_names

Get the variable names of the function.

Returns:

Name Type Description
variable_names list[Hashable] | None

The variable names of the function if defined, otherwise None.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain([(1, 2), (2, 3), (1, 4)], variable_names=["x", "y"])
>>> f = Function(domain=X, mapping=lambda *, x, y: 2 * x + y**2)
>>> print(f)
Function 'f':
      f
x y
1 2   6
2 3  13
1 4  18
>>> print(f.variable_names)
['x', 'y']
>>> g = Function(mapping=lambda *, x: x**2, name="g")
>>> print(g.variable_names)
['x']

add

add(
    other,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Add another Function instance or scalar to this function.

Parameters:

Name Type Description Default
other Function | Real

The object to add to the current function.

required
domain_name Hashable | None

The name of the domain on which the sum is defined. If None, a default will be generated.

None
index Index | None

An optional custom index for the sum, provided that the dimensions of the functions are > 1. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the sum, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

"Index"
index_name Hashable | None

The name of the index of the sum, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

None
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
sum Function

The sum of the function and the object.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Function, Index, Time
>>> rng = np.random.default_rng(42)

Define a 2-dimensional domain, and two functions with 2-dimensional outputs and custom indices.

>>> X = Domain([(1, 2), (3, 4), (5, 6)], variable_names=["u", "v"])
>>> f = Function.from_rand(domain=X, dim=2, random_state=rng)
>>> print(f)
Function 'f':
i    0  1
u v
1 2  0  7
3 4  6  4
5 6  4  8
>>> g = Function.from_rand(domain=X, dim=2, name="g", random_state=rng)
>>> print(g)
Function 'g':
i    0  1
u v
1 2  0  6
3 4  2  0
5 6  5  9

The domains and indices of the functions are fully aligned and the functions have the same dimension, so adding the functions produces the expected result.

>>> print(f.add(g))
Function '(f + g)':
i    0   1
u v
1 2  0  13
3 4  8   4
5 6  9  17

If the functions had different indices, but still the same dimension, it is still possible to add them. The index of the result will reset to a default.

>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> f = Function.from_rand(domain=X, dim=2, index=J, random_state=rng)
>>> print(f)
Function 'f':
j    1  2
u v
1 2  7  7
3 4  7  7
5 6  5  1
>>> K = Index([3, 4], variable_names=["k"], name="K")
>>> g = Function.from_rand(domain=X, dim=2, index=K, name="g", random_state=rng)
>>> print(g)
Function 'g':
k    3  4
u v
1 2  8  4
3 4  5  3
5 6  1  9
>>> print(f.add(g))
Function '(f + g)':
i     0   1
u v
1 2  15  11
3 4  12  10
5 6   6  10

One may also pass in a custom index.

>>> T = Time.discrete(length=1, start=5)
>>> print(f.add(g, index=T))
Function '(f + g)':
t     5   6
u v
1 2  15  11
3 4  12  10
5 6   6  10

For functions whose domains are only partially aligned, the add method performs a merge on the common variable names and then adds. We demonstrate with functions with 1-dimensional outputs.

>>> Y = Domain([(2, 7), (4, 8)], variable_names=["v", "w"], name="Y")
>>> f = Function.from_rand(domain=X, dim=1, random_state=rng)
>>> print(f)
Function 'f':
     f
u v
1 2  7
3 4  6
5 6  4
>>> g = Function.from_rand(domain=Y, dim=1, name="g", random_state=rng)
>>> print(g)
Function 'g':
     g
v w
2 7  8
4 8  5
>>> print(f.add(g))
Function '(f + g)':
       (f + g)
u v w
1 2 7       15
3 4 8       11

For functions whose domains have completely disjoint variable names, the add method forms the Cartesian product of the domains and then adds.

>>> Y = Domain([(2, 7), (4, 8)], variable_names=["a", "b"], name="Y")
>>> f = Function.from_rand(domain=X, dim=1, random_state=rng)
>>> print(f)
Function 'f':
     f
u v
1 2  4
3 4  4
5 6  2
>>> g = Function.from_rand(domain=Y, dim=1, name="g", random_state=rng)
>>> print(g)
Function 'g':
     g
a b
2 7  0
4 8  5
>>> print(f.add(g))
Function '(f + g)':
         (f + g)
u v a b
1 2 2 7        4
    4 8        9
3 4 2 7        4
    4 8        9
5 6 2 7        2
    4 8        7

Finally, it is possible to add scalars to instances of Function.

>>> print(f.add(4))
Function '(f + 4)':
     (f + 4)
u v
1 2        8
3 4        8
5 6        6

all

all()

Check if all outputs of the function are True.

Returns:

Name Type Description
all_true bool

True if all outputs are True.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...     },
... )
>>> f.all()
True
>>> g = Function(
...     domain=X,
...     mapping={
...         0: (1, 0),
...         1: (0, 1),
...     },
...     name="g",
... )
>>> g.all()
False

any

any()

Check if any output of the function is True.

Returns:

Name Type Description
any_true bool

True if any output is True.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 0),
...     },
... )
>>> f.any()
True
>>> g = Function(
...     domain=X,
...     mapping={
...         0: (0, 0),
...         1: (0, 0),
...     },
...     name="g",
... )
>>> g.any()
False

ascend

ascend(sig_alg, measure=None, name=None)

Ascend a function from a function on the atom identifiers of a sigma-algebra to the domain of the sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The given sigma-algebra.

required
measure Measure | None

An optional measure for the function to carry.

None
name Hashable | None

The name of the new function. If None, the name of the function will be used.

None

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define a domain and sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )

Define a function on the atom identifiers of the sigma-algebra.

>>> f = Function(
...     domain=F.atom_space,
...     mapping={
...         0: 2,
...         1: 3,
...     },
... )

Ascend f from a function on the atom identifiers to a function on X. Effectively, this "broadcasts" the values of f across the atoms of F.

>>> f.ascend(F)
MeasurableFunction(parameters=(x), domain=X, sig_alg=F, name=f)
>>> print(f.ascend(F))
Measurable function 'f':
   f
x
0  2
1  3
2  3

atom_data

atom_data(sig_alg=None)

Get the unique values of the function on the atoms of the underlying sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra | None

The sigma-algebra. If None and the function carries a sig_alg attribute, that sigma-algebra will be used.

None

Returns:

Name Type Description
atom_data Series | None

A pd.Series or pd.DataFrame containing the unique values of the function on the atoms of the given sigma-algebra.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Function,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a function and a sigma-algebra.

>>> X = Domain.from_sequence(size=4)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: 1,
...         1: 3,
...         2: 3,
...         3: 8,
...     }
... )
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )

Get the unique values of the function on the atoms of F.

>>> print(f.atom_data(F))
F
0    1
1    3
2    8
Name: sigma(f), dtype: int64

The atom_data method also works with parametrized functions. To see this, define a 1-dimensional parameter space and a parametrized measurable function.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (0, 3): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
...     (1, 3): -3,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3
3      2 -3

By leaving the parameter to atom_data as its default None, it computes the unique values of the parametrized measurable function on each of the atoms of the underlying sigma-algebra (accessed through the sig_alg attribute).

>>> print(f.atom_data())
theta  0    1
F
0      1    0
1      2   -3
2      2   -3

Note that the function is also measurable with respect to the following finer sigma-algebra.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
...     name="G",
... )
>>> f in G
True

We may thus pass G into the atom_data method to get the unique values of the function on each of the atoms of G.

>>> print(f.atom_data(G))
theta  0    1
G
0      1    0
1      2   -3
2      2   -3

cartesian_power classmethod

cartesian_power(
    function,
    n,
    index=None,
    index_kind="Index",
    index_name=None,
)

Form the Cartesian power of a function.

Parameters:

Name Type Description Default
function Function

The base of the Cartesian power.

required
n int

The power of the Cartesian power.

required
index IndexLike | None

The index of the Cartesian power.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None

Returns:

Name Type Description
power Function

The Cartesian power.

Examples:

>>> from sigalg.core import Domain, Function

Define a function with 2-dimensional outputs.

>>> X = Domain.from_sequence(size=4, variable_name="x")
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (5, 6),
...     },
... )

Compute the second Cartesian power of the function f.

>>> cart_pow = Function.cartesian_power(f, 2)
>>> print(cart_pow)
Function 'f ^ 2':
i        0  1  2  3
x_0 x_1
0   0    1  2  1  2
    1    1  2  3  4
    2    1  2  3  4
    3    1  2  5  6
1   0    3  4  1  2
    1    3  4  3  4
    2    3  4  3  4
    3    3  4  5  6
2   0    3  4  1  2
    1    3  4  3  4
    2    3  4  3  4
    3    3  4  5  6
3   0    5  6  1  2
    1    5  6  3  4
    2    5  6  3  4
    3    5  6  5  6

Compute the third Cartesian power using the ^ operator.

>>> print(f ^ 3)
Function 'f ^ 3':
i            0  1  2  3  4  5
x_0 x_1 x_2
0   0   0    1  2  1  2  1  2
        1    1  2  1  2  3  4
        2    1  2  1  2  3  4
        3    1  2  1  2  5  6
    1   0    1  2  3  4  1  2
...         .. .. .. .. .. ..
3   2   3    5  6  3  4  5  6
    3   0    5  6  5  6  1  2
        1    5  6  5  6  3  4
        2    5  6  5  6  3  4
        3    5  6  5  6  5  6

[64 rows x 6 columns]

cartesian_product classmethod

cartesian_product(
    factors,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Form the Cartesian product of a list of functions.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
factors list[Function]

The factors of the Cartesian product.

required
domain_name Hashable | None

The name of the domain of the Cartesian product.

None
index IndexLike | None

The index of the Cartesian product.

None
index_kind Literal['Index', 'Time']

The kind of index.

"Index"
index_name Hashable | None

The name of the index.

None
name Hashable | None

The name of the Cartesian product. If None, a default will be generated.

None

Returns:

Name Type Description
product Function

The Cartesian product of the functions.

Examples:

>>> from sigalg.core import Domain, Function

Define two functions on two 1-dimensional domains.

>>> X = Domain.from_sequence(size=2)
>>> Y = Domain.from_sequence(size=3, variable_name="y", name="Y")
>>> f = Function(domain=X, mapping=dict(zip(X, [1, 2])))
>>> g = Function(domain=Y, mapping=dict(zip(Y, [(3, 4), (5, 6), (7, 8)])), name="g")

Form the Cartesian product of the two functions using the cartesian_product method.

>>> product = Function.cartesian_product([f, g])
>>> print(product)
Function 'f x g':
i    0  1  2
x y
0 0  1  3  4
  1  1  5  6
  2  1  7  8
1 0  2  3  4
  1  2  5  6
  2  2  7  8

Form the same Cartesian product using the @ operator.

>>> print(f @ g)
Function 'f x g':
i    0  1  2
x y
0 0  1  3  4
  1  1  5  6
  2  1  7  8
1 0  2  3  4
  1  2  5  6
  2  2  7  8
Notes

Given one function \(f: X \to \mathbb{R}^d\) and a second one \(g: Y \to \mathbb{R}^e\), their Cartesian product, denoted \(f \times g\), is the function

\[ (f \times g) : X \times Y \to \mathbb{R}^{d+e}, \quad (f\times g)(x, y) = (f(x),g(y)). \]

concatenate classmethod

concatenate(
    factors,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Concatenate a list of functions into a function with multi-dimensional outputs.

Parameters:

Name Type Description Default
factors list[Function]

A list of functions to concatenate.

required
index IndexLike | None

The index of the resulting function. If None, the method will attempt to join the indices of the factors, but the indices must be pairwise disjoint.

None
index_kind Literal['Index', 'Time']

The kind of index.

"Index"
index_name Hashable | None

The name of the index.

None
name Hashable | None

The name of the resulting function. If None, a default will be generated.

None

Returns:

Name Type Description
concatenation Function

A new function created by combining the input functions.

Examples:

>>> from sigalg.core import Domain, Function

Generate two functions.

>>> X = Domain.from_sequence(size=4)
>>> f = Function.from_rand(
...     domain=X,
...     index=[0, 1, 2],
...     max_value=2,
...     random_state=42,
... )
>>> print(f)
Function 'f':
i       0  1  2
x
0       0  1  1
1       0  0  1
2       0  1  0
3       0  1  1
>>> g = Function.from_rand(
...     domain=X,
...     index=[3, 4],
...     max_value=2,
...     random_state=42,
...     name="g",
... )
>>> print(g)
Function 'g':
i       3  4
x
0       0  1
1       1  0
2       0  1
3       0  1

Concatenate the two functions.

>>> fg = Function.concatenate([f, g])
>>> print(fg)
Function 'fg':
i  0  1  2  3  4
x
0  0  1  1  0  1
1  0  0  1  1  0
2  0  1  0  0  1
3  0  1  1  0  1

The same concatenation may be constructed using the | operator.

>>> print(f | g)
Function 'fg':
i  0  1  2  3  4
x
0  0  1  1  0  1
1  0  0  1  1  0
2  0  1  0  0  1
3  0  1  1  0  1

We may also concatenate with a 1-dimensional function. Note that the name of the function is used as the index value.

>>> h = Function.from_rand(
...     domain=X,
...     dim=1,
...     max_value=2,
...     random_state=42,
...     name="h",
... )
>>> print(f | g | h)
Function 'fgh':
i  0  1  2  3  4  h
x
0  0  1  1  0  1  0
1  0  0  1  1  0  1
2  0  1  0  0  1  1
3  0  1  1  0  1  0

divide

divide(
    other,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Divide this function by another Function instance or scalar.

Parameters:

Name Type Description Default
other Function | Real

The divisor.

required
domain_name Hashable | None

The name of the domain on which the quotient is defined. If None, a default will be generated.

None
index Index | None

An optional custom index for the quotient, provided that the dimensions of the functions are > 1. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the quotient, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

"Index"
index_name Hashable | None

The name of the index of the quotient, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

None
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
quotient Function

The quotient of the function and the object.

drop_zeros

drop_zeros(name=None, tol=1e-08)

Drop zeros from the range of the function.

The function must have 1-dimensional outputs in order to use this method.

Parameters:

Name Type Description Default
name Hashable | None

A name for the new function. If None, the name of the original function will be used.

None
tol float

A float below which numbers will be deemed 0.

1e-8

Returns:

Name Type Description
func Function

A new function with all zeros dropped from its range.

Examples:

>>> from sigalg.core import Domain, Function
>>> X = Domain([-1, 0, 1])
>>> f = Function(domain=X, mapping=lambda x: x**2)
>>> print(f)
Function 'f':
    f
x
-1  1
 0  0
 1  1
>>> print(f.drop_zeros())
Function 'f':
    f
x
-1  1
 1  1

expon

expon(
    other,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Raise this function to the power of another Function instance or a scalar.

Parameters:

Name Type Description Default
other Function | Real

The power.

required
domain_name Hashable | None

The name of the domain on which the power is defined. If None, a default will be generated.

None
index Index | None

An optional custom index for the power, provided that the dimensions of the functions are > 1. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the power, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

"Index"
index_name Hashable | None

The name of the index of the power, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

None
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
power Function

The power of the function and the object.

from_constant classmethod

from_constant(
    domain,
    constant,
    domain_kind="Domain",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Create a function that maps every point in the domain to the same constant output value.

Parameters:

Name Type Description Default
domain IndexLike

The domain of the function.

required
constant Hashable | None

The constant output value of the function. If a tuple, the function will have multi-dimensional outputs.

required
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None
**kwargs

Additional keyword arguments passed to subclasses.

{}

Returns:

Name Type Description
const_func Function

The function that maps every point in its domain to the given constant value.

Examples:

>>> from sigalg.core import Domain, Function

Create a constant function with 2-dimensional outputs.

>>> X = Domain.from_sequence(size=3)
>>> f = Function.from_constant(domain=X, constant=(1, 2), index=[1, 2])
>>> print(f)
Function 'f':
i  1  2
x
0  1  2
1  1  2
2  1  2

Create a constant function.

>>> g = Function.from_constant(domain=X, constant=2, name="g")
>>> print(g)
Function 'g':
        g
x
0       2
1       2
2       2

from_numpy classmethod

from_numpy(
    arr,
    variable_names=None,
    kind="any",
    domain_kind="Domain",
    domain_name=None,
    name=None,
)

Create a function from a NumPy array.

The function is generated in such a way that f(i, j, ...) corresponds to the element at position (i, j, ...) in the NumPy array.

Parameters:

Name Type Description Default
arr ArrayLike

The array-like object representing the function values.

required
variable_names list[Hashable] | None

The names of the variables. If None, defaults will be generated.

None
kind Literal['any', 'measure', 'probability']

The kind of outputs of the function.

"any"
domain_kind Literal['Domain', 'SampleSpace']

The type of domain.

'Domain'
domain_name Hashable | None

The name of the domain.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None

Examples:

>>> import numpy as np
>>> from sigalg.core import Function

Construct a function from a NumPy array of shape (2, 2). The function is constructed so that f(i, j) = arr[i, j].

>>> arr = np.array([[1, 2], [3, 4]])
>>> f = Function.from_numpy(arr=arr, name="f")
>>> print(f)
Function 'f':
          f
x_0 x_1
0   0     1
    1     2
1   0     3
    1     4

Evaluate the function.

>>> f(x_0=0, x_1=0)
1
>>> f(x_0=0, x_1=1)
2
>>> f(x_0=1, x_1=0)
3
>>> f(x_0=1, x_1=1)
4

from_rand classmethod

from_rand(
    domain=None,
    dim=None,
    distribution="uniform",
    min_value=0,
    max_value=10,
    loc=0.0,
    scale=1.0,
    domain_kind="Domain",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    random_state=None,
    **kwargs,
)

Generate a random function.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the function.

None
dim int | None

The dimension of the outputs of the function.

None
distribution Literal['uniform', 'normal']

The distribution to use for generating random values.

"uniform"
min_value int

The minimum value for the uniform distribution.

0
max_value int

The maximum value for the uniform distribution.

10
loc float

The mean for the normal distribution.

0.0
scale float

The standard deviation for the normal distribution.

1.0
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain. If None, a default will be generated.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be used.

None
random_state int | Generator | None

The random state for reproducibility.

None

Returns:

Name Type Description
function Function

A random function.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Function
>>> rng = np.random.default_rng(42)

Generate a random function with 2-dimensional values drawn from a standard normal distribution.

>>> X = Domain.from_sequence(size=3)
>>> f = Function.from_rand(
...     domain=X,
...     dim=2,
...     distribution="normal",
...     random_state=rng,
... )
>>> print(f)
Function 'f':
i         0         1
x
0  0.304717 -1.039984
1  0.750451  0.940565
2 -1.951035 -1.302180

Generate a function with random 1-dimensional outputs drawn from a uniform distribution on the integers [-10, 10).

>>> g = Function.from_rand(
...     domain=X,
...     dim=1,
...     distribution="uniform",
...     low=-10,
...     high=10,
...     name="g",
...     random_state=rng,
... )
>>> print(g)
Function 'g':
   g
x
0  7
1  7
2  7

get_component

get_component(index)

Get a component function of the function.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
index Hashable

The index for which to get the component function.

required

Returns:

Name Type Description
component Function

The desired component function.

Examples:

>>> from sigalg.core import Domain, Function

Define a function with 3-dimensional outputs.

>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2, 3),
...         1: (4, 5, 6),
...     },
... )
>>> print(f)
Function 'f':
i   0  1  2
x
0   1  2  3
1   4  5  6

Get a component function using the get_component method.

>>> f_1 = f.get_component(1)
>>> print(f_1)
Function 'f_1':
   f_1
x
0    2
1    5

Get a component function using subscript notation.

>>> f_0 = f[0]
>>> print(f_0)
Function 'f_0':
   f_0
x
0    1
1    4
Notes

Given a function \(f: X \to \mathbb{R}^d\), for each \(x \in X\) we may write

\[ f(x) = (f_1(x), f_2(x), \ldots, f_d(x)), \]

where \(f_j: X \to \mathbb{R}\) are the component functions of \(f\).

get_inverse_image

get_inverse_image(value)

Get the inverse image of a value under the function.

Parameters:

Name Type Description Default
value Hashable | tuple[Hashable] | Series

The value to find the inverse image of.

required

Returns:

Name Type Description
inverse_image Set

The inverse image of value under the function.

Examples:

>>> import pandas as pd
>>> from sigalg.core import (
...     Domain,
...     Function,
... )

Define a function.

>>> X = Domain.from_sequence(size=4)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 0),
...         1: (1, 0),
...         2: (0, 1),
...         3: (1, 0),
...     }
... )
>>> print(f)
Function 'f':
i  0  1
x
0  1  0
1  1  0
2  0  1
3  1  0

Get an inverse image using the get_inverse_image method.

>>> inv = f.get_inverse_image((1, 0))
>>> print(inv)
Set '{f = (1, 0)}':
 x
 0
 1
 3

Get an inverse image using the overloaded operator ==.

>>> print(f == (1, 0))
Set '{f = (1, 0)}':
 x
 0
 1
 3

Get an inverse image using the overloaded operator == and a pd.Series.

>>> s = pd.Series([1, 0], index=f.index)
>>> print(f == s)
Set '{f = (1, 0)}':
 x
 0
 1
 3

get_sub_vector

get_sub_vector(indices)

Get a sub-vector of the function by selecting a collection of component functions by their indices.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
indices list[Hashable]

List of indices to select for the sub-vector.

required

Returns:

Name Type Description
sub_vector Function

A new Function containing only the specified component functions.

Examples:

>>> from sigalg.core import Domain, Function

Define a function with 3-dimensional outputs.

>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2, 3),
...         1: (4, 5, 6),
...     },
... )
>>> print(f)
Function 'f':
i  0  1  2
x
0  1  2  3
1  4  5  6

Get a sub-vector by using the get_sub_vector method.

>>> f_sub = f.get_sub_vector([1, 2])
>>> print(f_sub)
Function '(f_1, f_2)':
i  1  2
x
0  2  3
1  5  6

Get a sub-vector by using subscript notation.

>>> f_sub = f[0, 1]
>>> print(f_sub)
Function '(f_0, f_1)':
i  0  1
x
0  1  2
1  4  5
Notes

Given a function \(f: X \to \mathbb{R}^d\), for each \(x\in X\) we may write

\[ f(x) = (f_1(x), f_2(x), \ldots, f_d(x)), \]

where \(f_j: X \to \mathbb{R}\) are the component functions of \(f\). We may create a sub-vector by choosing a collection of the component functions to get a function of smaller output dimension. For example, we may select the first and last components to create the function with \(2\)-dimensional outputs

\[ x \mapsto (f_1 (x), f_d(x)). \]

is_constant_on

is_constant_on(subset)

Determine whether the function is constant on a given subtset of its domain.

Parameters:

Name Type Description Default
subset Set

The subset.

required

Returns:

Name Type Description
is_constant bool | None

Either True if the function is constant on the set, False if not, or None if the data of the function is not a pd.Series or pd.DataFrame.

Examples:

>>> from sigalg.core import Domain, Function, Set
>>> X = Domain.from_sequence(size=4, variable_name="x")
>>> f = Function(domain=X, mapping=dict(zip(X, [0, 1, 1, 2])))
>>> print(f)
Function 'f':
   f
x
0  0
1  1
2  1
3  2
>>> U = Set([1, 2], domain=X, name="U")
>>> f.is_constant_on(U)
True
>>> V = Set([1, 2, 3], domain=X, name="V")
>>> f.is_constant_on(V)
False

is_measurable

is_measurable(sig_alg=None)

Check if the function is measurable with respect to a given sigma-algebra.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra to check measurability against.

None

Returns:

Name Type Description
is_measurable bool

True if the measurable vector is measurable with respect to the given sigma-algebra, False otherwise.

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define two functions with 2-dimensional outputs and a sigma-algebra. The first is constant on the atoms of the sigma-algebra and hence measurable, while the second is not.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (3, 4),
...     },
... )
>>> g = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (5, 6),
...         3: (7, 8),
...     },
...     name="g",
... )
>>> print(f.is_measurable(F))
True
>>> print(g.is_measurable(F))
False
Notes

Let \(f: X \to \mathbb{R}^d\) be a function on a set \(X\). In the case that \(X\) is finite (as in SigAlg), the function \(f\) is measurable with respect to a \(\sigma\)-algebra \(\mathcal{F}\) on \(X\) if \(f\) is constant on the atoms of \(\mathcal{F}\). When the identity of the \(\sigma\)-algebra needs to made explict, we shall say that \(f\) is \(\mathcal{F}\)-measurable.

item

item()

Get the output value of a constant function.

Returns:

Name Type Description
output Hashable | Series

The single output value of the function.

Examples:

>>> from sigalg.core import Domain, Function

Get the output of a constant function with 2-dimensional outputs.

>>> X = Domain.from_sequence(size=2)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...     },
... )
>>> print(f.item())
(1, 2)

Get the output of a constant function with 1-dimensional outputs.

>>> g = Function(domain=X, mapping=dict(zip(X, [1, 1])), name="g")
>>> g.item()
1

multiply

multiply(
    other,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Multiply another Function instance or scalar with this function.

Parameters:

Name Type Description Default
other Function | Real

The object to multiply with the current function.

required
domain_name Hashable | None

The name of the domain on which the product is defined. If None, a default will be generated.

None
index Index | None

An optional custom index for the product, provided that the dimensions of the functions are > 1. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the product, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

"Index"
index_name Hashable | None

The name of the index of the product, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

None
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
product Function

The product of the function and the object.

negate

negate(name=None, **kwargs)

Negate this function.

Parameters:

Name Type Description Default
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
negation Function

The negation of the function.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Function
>>> rng = np.random.default_rng(42)
>>> X = Domain([(1, 2), (3, 4), (5, 6)], variable_names=["u", "v"])
>>> f = Function.from_rand(domain=X, dim=2, random_state=rng)
>>> print(f)
Function 'f':
i    0  1
u v
1 2  0  7
3 4  6  4
5 6  4  8
>>> print(-f)
Function '(-f)':
i    0  1
u v
1 2  0 -7
3 4 -6 -4
5 6 -4 -8
>>> g = Function.from_rand(domain=X, dim=1, name="g", random_state=rng)
>>> print(g)
Function 'g':
     g
u v
1 2  0
3 4  6
5 6  2
>>> print(-g)
Function '(-g)':
     (-g)
u v
1 2     0
3 4    -6
5 6    -2

restrict_to

restrict_to(subset, subset_name='A', **kwargs)

Restrict the function to a subset of its domain.

Parameters:

Name Type Description Default
subset Set | list[Hashable]

The set to restrict the measurable vector to.

required
subset_name Hashable

The name to use for the subset. Ignored if subset is an instance of Set.

"A"
kwargs dict

Keyword arguments passed to subclasses.

{}

Returns:

Name Type Description
restriction Function

The restriction of the function.

Examples:

>>> from sigalg.core import Domain, Function, Set

Define a function with 2-dimensional outputs.

>>> X = Domain.from_sequence(size=4)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (5, 6),
...     },
... )

Restrict the function to a set using the restrict_to method.

>>> A = Set([1, 2, 3], domain=X)
>>> f_A = f.restrict_to(A)
>>> print(f_A)
Function 'f|A':
i  0  1
x
1  3  4
2  3  4
3  5  6

Compute the same restriction using the overloaded | operator.

>>> print(f | A)
Function 'f|A':
i  0  1
x
1  3  4
2  3  4
3  5  6

Test with a function with 1-dimensional outputs and a list of points, instance of a Set.

>>> g = Function(domain=X, mapping=dict(zip(X, [3, 2, 4, 1])), name="g")
>>> print(g | [1, 2, 3])
Function 'g|A':
   g|A
x
1    2
2    4
3    1

subtract

subtract(
    other,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    **kwargs,
)

Subtract another Function instance or scalar from this function.

Parameters:

Name Type Description Default
other Function | Real

The object to subtract from the current function.

required
domain_name Hashable | None

The name of the domain on which the difference is defined. If None, a default will be generated.

None
index Index | None

An optional custom index for the difference, provided that the dimensions of the functions are > 1. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the difference, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

"Index"
index_name Hashable | None

The name of the index of the difference, provided that the dimensions of the functions are > 1. If index is not None, this parameter is ignored.

None
name Hashable | None

The name of the result. If None, a default will be generated.

None
kwargs dict

Keyword arguments for subclasses.

{}

Returns:

Name Type Description
difference Function

The difference of the function and the object.

tensor_power classmethod

tensor_power(function, n)

Get the tensor power of the function.

Parameters:

Name Type Description Default
function Function

The base of the tensor power.

required
n int

The power of the tensor power.

required

Returns:

Name Type Description
tensor_power Function

The tensor power.

Examples:

>>> from sigalg.core import Domain, Function

Define a function.

>>> X = Domain([1, 2, 3], variable_names=["x"])
>>> f = Function(
...     domain=X,
...     mapping=lambda *, x: x**2,
... )
>>> print(f)
Function 'f':
   f
x
1  1
2  4
3  9

Compute the second tensor power using the tensor_power class method.

>>> print(Function.tensor_power(f, 2))
Function 'f ^ 2':
          f ^ 2
x_0 x_1
1   1         1
    2         4
    3         9
2   1         4
    2        16
    3        36
3   1         9
    2        36
    3        81

tensor_product classmethod

tensor_product(
    factors,
    variable_names=None,
    domain_name=None,
    name=None,
)

Compute the tensor product of a list of functions.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
factors list[Function]

The factors of the tensor product.

required
variable_names list[Hashable] | None

The variable names of the resulting function. If None, the variable names will be inferred from the input functions.

None
domain_name Hashable | None

The name of the domain. If None, a default will be generated.

None
name Hashable | None

The name of the resulting function. If None, a default will be generated.

None

Returns:

Name Type Description
tensor_prod Function

The tensor product of the input functions.

Examples:

Define two functions.

>>> from sigalg.core import Domain, Function
>>> X = Domain.from_sequence(size=2, variable_name="x")
>>> Y = Domain.from_sequence(size=2, variable_name="y", name="Y")
>>> f = Function(
...     domain=X @ Y,
...     mapping=lambda *, x, y: x**2 + y + 2,
... )
>>> g = Function(
...     domain=Y,
...     mapping=lambda *, y: y + 5,
...     name="g",
... )
>>> print(f)
Function 'f':
     f
x y
0 0  2
  1  3
1 0  3
  1  4
>>> print(g)
Function 'g':
   g
y
0  5
1  6

Compute their tensor product using the tensor_product method.

>>> prod = Function.tensor_product([f, g])
>>> print(prod)
Function 'f x g':
            f x g
x y_0 y_1
0 0   0        10
      1        12
  1   0        15
      1        18
1 0   0        15
      1        18
  1   0        20
      1        24
Notes

Let \(f:X \to \mathbb{R}\) and \(g: Y \to \mathbb{R}\) be two functions. Their tensor product, denoted \(f\otimes g\), is the function defined by

\[ f \otimes g: X \times Y \to \mathbb{R}, \quad (f \otimes g)(x,y) = f(x)g(y). \]

to_measurable_vector

to_measurable_vector(sig_alg, measure=None, name=None)

Generate a measurable vector from the function.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra on the underlying measurable space.

required
measure Measure | None

The optional measure carried by the measurable vector.

None
name Hashable | None

The name of the measurable vector. If None, the name of the function will be used.

None

Returns:

Name Type Description
measurable_vec MeasurableVector

The current function instance converted to a measurable vector.

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define a measurable space and a function.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> f = Function(
...     domain=X,
...     mapping={
...         0: 4,
...         1: 3,
...         2: 3,
...     },
... )

Convert to a measurable function.

>>> print(f.to_measurable_vector(F))
Measurable function 'f':
   f
x
0  4
1  3
2  3

to_measure

to_measure(
    sig_alg=None,
    kind="measure",
    domain_name=None,
    parameter_names=None,
    parameter_domain_name="Theta",
    name=None,
)

Generate a (parametrized) measure from the function.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra | None

The sigma-algebra on which the measure will be defined. If None, the power set of the domain of the function will be used.

None
kind Literal['measure', 'probability']

The kind of measure to create.

"measure"
domain_name Hashable | None

The name of the domain of the measure. If None, the domain of the existing domain will be used.

None
parameter_names list[Hashable] | None

The list of (optional) parameter names to create a parametrized measure.

None
parameter_domain_name Hashable | None

The name of the parameter domain.

None
name Hashable | None

The name of the resulting measure. If None, a default will be generated.

None

Returns:

Name Type Description
measure Measure | ParametrizedMeasure

Either a measure or parametrized measure produced from the current Function instance.

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define a measurable space.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )

Define an instance of Function, and convert it to a measure.

>>> f = Function(
...     domain=F.atom_space,
...     mapping={
...         0:1,
...         1:2,
...     },
... )
>>> print(f)
Function 'f':
   f
F
0  1
1  2
>>> f.to_measure(F)
Measure(domain=X, sig_alg=F, name=f)
>>> print(f.to_measure(F))
Measure 'f':
    f
F
0   1
1   2

We also manufacture probability measures from Function instances.

>>> g = Function(
...     domain=F.atom_space,
...     mapping={
...         0:0.2,
...         1:0.8,
...     },
...     name="g",
... )
>>> print(g)
Function 'g':
     g
F
0  0.2
1  0.8
>>> g.to_measure(g, kind="probability")
ProbabilityMeasure(domain=F, sig_alg=g, name=g)
>>> print(g.to_measure(F, kind="probability"))
Probability measure 'g':
     g
F
0  0.2
1  0.8

We may also create parametrized measures from Function instances. Define a 1-dimensional parameter domain.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")

Define a function on the Cartesian product of Theta and the atom space of F.

>>> mapping = {
...     (0, 0): 2,  # (theta, F) = (0, 0), ...
...     (0, 1): 1,
...     (1, 0): 0,
...     (1, 1): 3,
... }
>>> h = Function(domain=Theta @ F.atom_space, mapping=mapping, name="h")
>>> print(h)
Function 'h':
         h
theta F
0     0  2
      1  1
1     0  0
      1  3

Convert h to a parametrized measure.

>>> print(h.to_measure(sig_alg=F, parameter_names=["theta"]))
Parametrized measure 'h':
theta  0  1
F
0      2  0
1      1  3

Define a second function on a 2-dimensional domain.

>>> mapping = {
...     (0, 0): 0.1,  # (theta, F) = (0, 0), ...
...     (0, 1): 0.9,
...     (1, 0): 0.0,
...     (1, 1): 1.0,
... }
>>> k = Function(domain=Theta @ F.atom_space, mapping=mapping, name="k")
>>> print(k)
Function 'k':
           k
theta F
0     0  0.1
      1  0.9
1     0  0.0
      1  1.0

Convert k to a parametrized probability measure.

>>> print(k.to_measure(sig_alg=F, kind="probability", parameter_names=["theta"]))
Parametrized probability measure 'k':
theta    0    1
F
0      0.1  0.0
1      0.9  1.0

to_numpy

to_numpy(multi_dim=False, dtype=None, copy=None)

Return the function's data as a NumPy array.

Parameters:

Name Type Description Default
dtype data - type | None

The desired data-type for the array. If None, the data type of the underlying data is used.

None
copy bool | None

Whether to return a copy of the data. If None, the default behavior is used.

None

Returns:

Type Description
ndarray

The function's data as a NumPy array.

with_name

with_name(name)

Return a new instance of Function with the same data but a new name.

with_variable_names

with_variable_names(variable_names)

Return a new instance of the function with updated variable names.

Index

A base class representing an ordered collection of hashable items.

Parameters:

Name Type Description Default
indices IndexLike | None

The object from which to construct the Index.

None
variable_names list[Hashable] | None

A list of variable names for the dimensions of the index. If None, defaults will be generated.

None
name Hashable | None

Name identifier for the index. If None, a default name will be generated.

None

Examples:

>>> import pandas as pd
>>> from sigalg.core import Index

Build an Index from a list of hashable items.

>>> lst = ["a", "b", "c"]
>>> I1 = Index(indices=lst, name="I1")
>>> print(I1)
Index 'I1':
 i
 a
 b
 c

Build an Index from a pd.Index object. Note that the name of the pd.Index becomes the variable name of the Index.

>>> idx = pd.Index([1, 2, 3], name="x")
>>> I2 = Index(indices=idx, name="I2")
>>> print(I2)
Index 'I2':
 x
 1
 2
 3

Build an Index from a pd.MultiIndex object. Note that custom variable names are passed to the Index constructor.

>>> multi_idx = pd.MultiIndex.from_tuples([(1, "a"), (2, "b")])
>>> I3 = Index(indices=multi_idx, name="I3", variable_names=["num", "letter"])
>>> print(I3)
Index 'I3':
 num letter
 1      a
 2      b

Build an Index from the multi-index, but with default variable names.

>>> multi_idx = pd.MultiIndex.from_tuples([(1, "a"), (2, "b")])
>>> I4 = Index(indices=multi_idx, name="I4")
>>> print(I4)
Index 'I4':
 i_0  i_1
   1    a
   2    b

dimension property

dimension

Get the dimension of the index.

Returns:

Name Type Description
dimension int | None

The dimension of the index.

variable_names property

variable_names

Get the variable names of the index.

Returns:

Name Type Description
variable_names list[Hashable] | None

The variable names of the underlying pd.Index object, if set.

Examples:

>>> import pandas as pd
>>> from sigalg.core import Index

Get the variable names of an Index built from a list with custom variable names passed to the constructor.

>>> I1 = Index(["x", "y", "z"], name="I1", variable_names=["letters"])
>>> print(I1.variable_names)
['letters']

Get the variable names of an Index built from a pd.MultiIndex object with names.

>>> indices = pd.MultiIndex.from_tuples(
...     [("a", 1), ("b", 2), ("c", 3)], names=["letter", "number"]
... )
>>> I2 = Index(indices=indices, name="I2")
>>> print(I2.variable_names)
['letter', 'number']

Print the default variable names of an Index built from a list of integers.

>>> I3 = Index(indices=[0, 1], name="I3")
>>> print(I3.variable_names)
['i']

Print the default variable names of an Index built from a list of tuples.

>>> I4 = Index(indices=[(1, 2), (3, 4)], name="I4")
>>> print(I4.variable_names)
['i_0', 'i_1']

cartesian_power classmethod

cartesian_power(index, n, variable_names=None, name=None)

Form the Cartesian power of an index.

Parameters:

Name Type Description Default
index (IndexLike | Index,)

The index used as the base of the Cartesian power.

required
n int

The power of the Cartesian power.

required
variable_names list[Hashable] | None

A list of variable names for the resulting index. If None, defaults will be generated.

None
name Hashable | None

The name of the Cartesian power. If None, a default will be generated.

None

Returns:

Name Type Description
cartesian_power Index

The Cartesian power.

Examples:

Form the Cartesian power using the cartesian_power class method.

>>> from sigalg.core import Index
>>> I = Index([(1, "a"), (2, "b"), (3, "c")], variable_names=["x", "y"])
>>> I_2 = Index.cartesian_power(I, 2)
>>> print(I_2)
Index 'I ^ 2':
x_0 y_0  x_1 y_1
  1   a    1   a
  1   a    2   b
  1   a    3   c
  2   b    1   a
  2   b    2   b
  2   b    3   c
  3   c    1   a
  3   c    2   b
  3   c    3   c

Form the Cartesian power using the ^ operator.

>>> print(I ^ 2)
Index 'I ^ 2':
x_0 y_0  x_1 y_1
  1   a    1   a
  1   a    2   b
  1   a    3   c
  2   b    1   a
  2   b    2   b
  2   b    3   c
  3   c    1   a
  3   c    2   b
  3   c    3   c

cartesian_product classmethod

cartesian_product(
    factors, variable_names=None, name=None, **kwargs
)

Create an index from the Cartesian product of a list of indices.

Parameters:

Name Type Description Default
factors list[IndexLike | Index]

The factors of the Cartesian product.

required
variable_names list[Hashable] | None

A list of variable names for the resulting index. If None, defaults will be generated.

None
name Hashable | None

The name of the Cartesian product. If None, a default will be generated.

None

Returns:

Name Type Description
cartesian_product Index

The Cartesian product.

Examples:

Build an Index from the Cartesian product of two Index instances.

>>> from sigalg.core import Index
>>> I = Index(indices=[1, 2, 3], variable_names=["x"])
>>> J = Index(indices=["a", "b"], name="J", variable_names=["y"])
>>> product_1 = Index.cartesian_product([I, J])
>>> print(product_1)
Index 'I x J':
 x y
 1 a
 1 b
 2 a
 2 b
 3 a
 3 b

Build an Index from the Cartesian product of two Index instances with custom variable names passed to the class method.

>>> product_2 = Index.cartesian_product([I, J], variable_names=["u", "v"])
>>> print(product_2)
Index 'I x J':
 u v
 1 a
 1 b
 2 a
 2 b
 3 a
 3 b

Build an Index from two Index instances with identical variable names. Note that subscripts are automatically generated.

>>> U = Index([(1, 2), (3, 4)], name="U", variable_names=["x", "y"])
>>> V = Index([(5, 6), (7, 8)], name="V", variable_names=["x", "y"])
>>> product_3 = Index.cartesian_product([U, V])
>>> print(product_3)
Index 'U x V':
x_0  y_0  x_1  y_1
  1    2    5    6
  1    2    7    8
  3    4    5    6
  3    4    7    8

Build an Index from a pair of lists with custom variable names passed to the class method.

>>> from sigalg.core import Index
>>> list1 = [1, 2, 3]
>>> list2 = ["a", "b"]
>>> I = Index.cartesian_product([list1, list2], variable_names=["x", "y"])
>>> print(I)
Index 'I':
 x y
 1 a
 1 b
 2 a
 2 b
 3 a
 3 b

Build an Index from a pair of lists with default variable names.

>>> J = Index.cartesian_product([list1, list2], name="J")
>>> print(J)
Index 'J':
 i_0 i_1
   1   a
   1   b
   2   a
   2   b
   3   a
   3   b

Build an Index from a pair of lists, where the second list consists of tuples.

>>> list3 = [("a", "red"), ("b", "blue")]
>>> K = Index.cartesian_product([list1, list3], name="K", variable_names=["x", "y", "z"])
>>> print(K)
Index 'K':
x y    z
1 a  red
1 b blue
2 a  red
2 b blue
3 a  red
3 b blue

Build an Index from a list of indices and and instance of Index.

>>> L = Index.cartesian_product([list1, U], name="L")
>>> print(L)
Index 'L':
 i  x  y
 1  1  2
 1  3  4
 2  1  2
 2  3  4
 3  1  2
 3  3  4

from_rand classmethod

from_rand(
    size,
    dim=1,
    sample_range=None,
    variable_names=None,
    name=None,
    random_state=None,
)

Generate a random Index.

Parameters:

Name Type Description Default
size int

Number of indices to generate.

required
dim int

The dimension of index.

1
sample_range tuple[int, int] | None

A tuple specifying the range of values for the indices. If None, the range will be [0, size).

None
variable_names list[Hashable] | None

A list of variable names for the dimensions of the index.

None
name Hashable | None

Name identifier for the index.

None
random_state int | Generator | None

A seed or random number generator for reproducibility.

None

Returns:

Name Type Description
index Index

A new Index with randomly generated indices.

Examples:

Generate a random index with default parameters, except for the size and random state.

>>> from sigalg.core import Index
>>> I = Index.from_rand(size=4, random_state=42)
>>> print(I)
Index 'I':
 i
 0
 2
 3
 1

Generate a 2-dimensional random index with a specified range and variable names.

>>> J = Index.from_rand(
...     size=5,
...     dim=3,
...     sample_range=(0, 10),
...     random_state=42,
...     variable_names=["x", "y", "z"],
...     name="J",
... )
>>> print(J)
Index 'J':
 x  y  z
 5  0  5
 3  9  7
 7  7  7
 0  7  5
 4  1  8

from_sequence classmethod

from_sequence(
    size,
    initial_index=0,
    prefix=None,
    variable_name=None,
    name=None,
)

Create an index with sequentially numbered items.

Parameters:

Name Type Description Default
size int

Number of indices to generate. Must be positive.

required
initial_index int

Starting index for sequential numbering.

0
prefix Hashable | None

Prefix for index values. If None, then numerical indices are used.

None
variable_name Hashable | None

An optional single element for the variable name. If None, a default name will be generated.

None
name Hashable | None

Name identifier for the index. If None, a default name will be generated.

None

Returns:

Name Type Description
index Index

A new Index with automatically generated indices.

Examples:

Build an Index consisting of the numbers 0, 1, 2, with default name and variable name.

>>> from sigalg.core import Index
>>> I = Index.from_sequence(size=3)
>>> print(I)
Index 'I':
 i
 0
 1
 2

Build an Index consisting of the strings F_0, F_1, F_2.

>>> I2 = Index.from_sequence(size=3, name="I2", prefix="F")
>>> print(I2)
Index 'I2':
   i
 F_0
 F_1
 F_2

Build an Index consisting of the numbers 5 and 6, with a custom variable name.

>>> I3 = Index.from_sequence(size=2, name="I3", initial_index=5, variable_name="x")
>>> print(I3)
Index 'I3':
 x
 5
 6

sort

sort(ascending=True)

Return a sorted copy of the index.

Parameters:

Name Type Description Default
ascending bool

Whether to sort in ascending order. If False, sort in descending order.

True

Returns:

Name Type Description
sorted_index Index

A new index with elements sorted.

to_kwargs

to_kwargs()

Return a list of dictionaries mapping variable names to their values.

Returns:

Name Type Description
kwargs list[dict]

A list of dictionaries mapping variable names to their values.

to_numpy

to_numpy(dtype=None, copy=None)

Return the index's data as a NumPy array.

Parameters:

Name Type Description Default
dtype data - type | None

The desired data-type for the array. If None, the data-type of the underlying data is used.

None
copy bool | None

Whether to return a copy of the data. If None, the default behavior is used.

None

Returns:

Type Description
ndarray

The index's data as a NumPy array.

with_name

with_name(name)

Set the name of the index and return self for chaining.

Parameters:

Name Type Description Default
name Hashable

New name for the index.

required

Returns:

Name Type Description
self Index

The current index with a new name.

L2

A class representing an L2-space of measurable functions defined on a given measure space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain on which the L2-space is defined.

None
sig_alg SigmaAlgebra | None

The sigma-algebra over which the L2-space is defined. If None, the power-set sigma-algebra on the domain is used.

None
measure Measure | None

The measure over which the L2-space is defined. If None, the counting measure on the domain is used.

None
name Hashable

The name of the L2-space.

"H"

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define a measure space and the L2-space over it.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 0,
...         3: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 6,
...         1: 3,
...     },
... )
>>> H = L2(X, F, mu)

Print the L2-space.

>>> print(H)
H = L2(X, F, mu)
================

* Domain 'X':
 x
 0
 1
 2
 3

* Sigma algebra 'F':
     F
x
0    0
1    1
2    0
3    1

* Measure 'mu':
        mu
F
0        6
1        3
Notes

Let \((X,\mathcal{F},\mu)\) be a measure space. We define \(L^2(X,\mathcal{F},\mu)\) to be the set of all \(\mathcal{F}\)-measurable functions \(f: X \to \mathbb{R}\) such that

\[ \int_X f^2 \, d\mu < \infty. \]

When the domain \(X\) and measure \(\mu\) are fixed and understood, we will write \(L^2(\mathcal{F})\) in place of \(L^2(X,\mathcal{F},\mu)\). We agree to identify two functions \(f\) and \(g\) in \(L^2(\mathcal{F})\) provided that they are equal almost everywhere, i.e., the set of points on which they are not equal has measure \(0\).

The set \(L^2(\mathcal{F})\) is a (real) vector space under the standard point-wise operations. Even more, it is also a Hilbert space when equipped with the inner product

\[ \langle f, g \rangle \stackrel{\text{def}}{=} \int_X fg \, d\mu, \]

for \(f,g\in L^2(\mathcal{F})\).

In the case that \(X\) is finite (as it always is, in SigAlg), the square-integrable condition is automatically satisfied, so \(L^2(\mathcal{F})\) is simply the vector space of all \(\mathcal{F}\)-measurable functions.

basis cached property

basis

Return an orthonormal basis of the L2-space.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
basis dict[Hashable, MeasurableFunction] | None

A dictionary mapping the atom ID to the corresponding basis vector of the L2-space.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0.0,
...         1: 7,
...         2: 2,
...     },
... )
>>> H = L2(X, F, mu)

Print the basis vectors.

>>> for atom_id, phi in H.basis.items():
...     print(f"Atom identifier: {atom_id}")
...     print(f"Basis function:\n{phi}\n")
Atom identifier: 1
Basis function:
Measurable function 'phi_1':
          phi_1
x
0      0.000000
1      0.000000
2      0.377964
3      0.377964
4      0.000000
5      0.000000

Atom identifier: 2
Basis function:
Measurable function 'phi_2':
          phi_2
x
0      0.000000
1      0.000000
2      0.000000
3      0.000000
4      0.707107
5      0.707107
Notes

Let \((X,\mathcal{F},\mu)\) be a measure space and set \(H = L^2(X, \mathcal{F}, \mu)\). In the case that \(X\) is finite, so that the \(\sigma\)-algebra \(\mathcal{F}\) is determined by its set \(\{A_i\}_{i\in I}\) of (finitely many) atoms, the vector space \(H\) has an orthonormal basis given by the normalized indicator functions of the atoms of nonzero measure.

Indeed, if we suppose \(i\neq j\), then the product \(I_{A_i}I_{A_j}\) of indicator functions is \(0\) since \(A_i\) and \(A_j\) are disjoint. Thus, we have

\[ \langle I_{A_i}, I_{A_j} \rangle = \int_X I_{A_i} I_{A_j} \, d\mu = 0, \]

which proves that \(\{I_{A_i}\}_{i\in I}\) is an orthogonal set.

If \(f\) is an \(\mathcal{F}\)-measurable function, then it must be constant on each atom \(A_i\). Thus, we have

\[ f = \sum_{i\in I} c_i I_{A_i} \]

for some real numbers \(c_i\). If an atom \(A_i\) has measure \(0\), then the corresponding summand \(c_i I_{A_i}\) may be dropped, which still yields an equality in the \(L^2\)-space \(H\) since \(f\) and the linear combination on the right-hand side are still equal almost everywhere.

Thus, the set of indicator functions of atoms with nonzero measure form an orthogonal basis \(H\). To obtain an orthonormal basis, we first compute the norms of the indicator functions:

\[ \|I_{A_i}\|^2 = \langle I_{A_i}, I_{A_i} \rangle = \int_X I_{A_i}^2 \, d\mu = \mu(A_i). \]

Thus, provided that \(\mu(A_i)\neq 0\), we obtain a normalized basis function:

\[ \phi_i \stackrel{\text{def}}{=} \frac{I_{A_i}}{\sqrt{\mu(A_i)}}. \]

basis_df cached property

basis_df

Return a pd.DataFrame whose columns are the orthonormal basis vectors of the L2-space.

Returns:

Name Type Description
basis_df DataFrame | None

A pd.DataFrame whose columns are the orthonormal basis vectors of the L2-space, or None if the underlying measure space is empty.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 5,
...         2: 4,
...     },
... )
>>> H = L2(X, F, mu)

Print the basis dataframe.

>>> print(H.basis_df)
          1    2
x
0  0.000000  0.0
1  0.447214  0.0
2  0.000000  0.5
3  0.000000  0.5

dim property

dim

The dimension of the L2-space.

Returns:

Name Type Description
dim int | None

The dimension of the L2-space, or None if the basis is not defined.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 7,
...         2: 2,
...     },
... )
>>> H = L2(X, F, mu)
>>> print(H.dim)
2

domain property

domain

The underlying domain on which the L2-space is defined.

Returns:

Name Type Description
domain Domain | None

The domain on which the L2-space is defined, or None if not set.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 5,
...         2: 4,
...     },
... )
>>> H = L2(X, F, mu)

Print its domain.

>>> print(H.domain)
Domain 'X':
 x
 0
 1
 2
 3

measure property

measure

The underlying measure on which the L2-space is defined.

Returns:

Name Type Description
measure Measure | None

The measure on which the L2-space is defined, or None if not set.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 4,
...         2: 5,
...     },
... )
>>> H = L2(X, F, mu)

Print its measure.

>>> print(H.measure)
Measure 'mu':
         mu
F
0         1
1         4
2         5

sig_alg property

sig_alg

The underlying sigma-algebra on which the L2-space is defined.

Returns:

Name Type Description
sig_alg SigmaAlgebra | None

The sigma-algebra on which the L2-space is defined, or None if not set.

Examples:

>>> from sigalg.core import Domain, L2, Measure, SigmaAlgebra

Define an L2-space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0.1,
...         1: 4,
...         2: 5,
...     },
... )
>>> H = L2(X, F, mu)

Print its sigma-algebra.

>>> print(H.sig_alg)
Sigma algebra 'F':
       F
x
0      0
1      1
2      2
3      2

fourier_coefficients

fourier_coefficients(function)

Compute the Fourier coefficients of a measurable function with respect to the orthonormal basis of the L2-space contained in the basis attribute.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
function MeasurableFunction

The measurable function whose Fourier coefficients are to be computed.

required

Returns:

Name Type Description
coefficients dict[Hashable, Real]

A dictionary mapping the name of each basis vector to the corresponding Fourier coefficient of function with respect to that basis vector.

Examples:

>>> from sigalg.core import (
...     Domain,
...     L2,
...     Measure,
...     MeasurableFunction,
...     SigmaAlgebra,
... )

Define an L2-spaces.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: "b",
...         1: "b",
...         2: "a",
...         3: "a",
...         4: "c",
...         5: "c",
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         "b": 7,
...         "a": 0,
...         "c": 2,
...     },
... )
>>> H = L2(X, F, mu)

Define a function and compute its Fourier coefficients.

>>> f = MeasurableFunction(
...     domain=X,
...     mapping={
...         0: -1,
...         1: -1,
...         2: 3,
...         3: 3,
...         4: 1,
...         5: 1,
...     },
... )
>>> c = H.fourier_coefficients(function=f)
>>> I = c.keys()

Get the basis vectors of the L2-space and compute the Fourier expansion of the function.

>>> phi = H.basis
>>> f_fourier = sum(c[i] * phi[i] for i in I).with_name("f_fourier")

Print the Fourier expansion and check that it is equal to the original function almost everywhere.

>>> print(f_fourier)
Measurable function 'f_fourier':
       f_fourier
x
0           -1.0
1           -1.0
2            0.0
3            0.0
4            1.0
5            1.0
>>> print(mu.equal_almost_everywhere(f, f_fourier))
True
Notes

Let \((X,\mathcal{F},\mu)\) be a measure space and set \(H = L^2(X, \mathcal{F},\mu)\). Provided that \(X\) is finite (as it always is, in SigAlg), the vector space \(H\) has an orthonormal basis \(\{\phi_i\}_{i\in I}\) consisting of the normalized indicator functions of the atoms of \(\mathcal{F}\) of nonzero measure. Thus, given a function \(f\in H\), we have its generalized Fourier expansion:

\[ f = \sum_{i\in I} \langle f, \phi_i \rangle \phi_i. \]

The coefficients \(c_i = \langle f,\phi_i \rangle\) are called the Fourier coefficients of \(f\).

inner

inner(first, second)

Compute the inner product of two measurable functions.

Parameters:

Name Type Description Default
first MeasurableFunction

The first measurable function.

required
second MeasurableFunction

The second measurable function.

required

Returns:

Name Type Description
inner_product Real

The inner product of the two measurable functions.

Examples:

>>> from sigalg.core import (
...     Domain,
...     L2,
...     Measure,
...     MeasurableFunction,
...     SigmaAlgebra,
... )

Define an L2-space.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: "b",
...         1: "b",
...         2: "a",
...         3: "a",
...         4: "c",
...         5: "c",
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         "b": 7,
...         "a": 0,
...         "c": 2,
...     },
... )
>>> H = L2(X, F, mu)

Define a function.

>>> f = MeasurableFunction(
...     *H.measure_space,
...     mapping={
...         0: -1,
...         1: -1,
...         2: 3,
...         3: 3,
...         4: 1,
...         5: 1,
...     },
... )

Get the basis vectors of the L2-space and compute the Fourier expansion of the function using the inner products of the Hilbert space structure.

>>> phi = H.basis
>>> I = phi.keys()
>>> f_fourier = sum(H.inner(f, phi[i]) * phi[i] for i in I).with_name("f_fourier")

Print the Fourier expansion and check that it is equal to the original function almost everywhere.

>>> print(f_fourier)
Measurable function 'f_fourier':
       f_fourier
x
0           -1.0
1           -1.0
2            0.0
3            0.0
4            1.0
5            1.0
>>> print(mu.equal_almost_everywhere(f, f_fourier))
True

metric

metric(first, second)

Compute the distance between two measurable functions.

See the Notes for the mathematical details.

Parameters:

Name Type Description Default
first MeasurableFunction

The first measurable function.

required
second MeasurableFunction

The second measurable function.

required

Returns:

Name Type Description
distance Real

The distance between the two measurable functions in the L2-space.

Examples:

>>> from sigalg.core import (
...     L2,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define an L2-space over a probability space, a random variable, and a sub-sigma-algebra.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.25,
...         1: 0.6,
...         2: 0.15,
...     },
... )
>>> H = L2(Omega, F, P)
>>> X = RandomVariable(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: -1,
...         1: -1,
...         2: 2,
...         3: 2,
...         4: 1,
...         5: 1,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 0,
...         3: 0,
...         4: 1,
...         5: 1,
...     },
...     name="G",
... )

Compute the conditional expectation of the random variable given the sub-sigma-algebra and compute the L2-distance between the random variable and its conditional expectation.

>>> E = X.expectation(given=G)
>>> squared_distance = H.metric(X, E)
>>> print(round(squared_distance, 2))
1.26

The distance between the random variable and its conditional expectation is less than (or equal to) the distance between it and any other random variable that is measurable with respect to the sub-sigma-algebra. For example, consider the random variable Y defined below, which is measurable with respect to the sub-sigma-algebra.

>>> Y = RandomVariable(
...     domain=Omega,
...     sig_alg=G,
...     measure=P | G,
...     mapping={
...         0: 2,
...         1: 2,
...         2: 2,
...         3: 2,
...         4: -4,
...         5: -4,
...     },
...     name="Y",
... )
>>> squared_distance = H.metric(X, Y)
>>> print(round(squared_distance, 2))
2.45
Notes

Any normed vector space \(H\) has a norm-induced metric \(d\) given by

\[ d(f,g) = \|f - g \|, \]

for \(f,g\in H\). In particular, if \(H\) is a vector space of the form \(L^2(X,\mathcal{F},\mu)\), then the induced metric is given by

\[ d(f,g) = \|f-g\| = \sqrt{\int_X (f-g)^2 \, d\mu}, \]

for \(f,g\in L^2(X, \mathcal{F},\mu)\).

norm

norm(function)

Compute the norm of a measurable function.

Parameters:

Name Type Description Default
function MeasurableFunction

The measurable function whose norm is to be computed.

required

Returns:

Name Type Description
norm Real

The norm of the measurable function in the L2-space.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Domain,
...     L2,
...     Measure,
...     MeasurableFunction,
...     SigmaAlgebra,
... )

Define an L2-space.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 7,
...         2: 2,
...     },
... )
>>> H = L2(X, F, mu)

Get the indicator functions of the atoms of the sigma-algebra and check that their squared norms are equal to the measures of the atoms.

>>> all(np.allclose(H.norm(A.indicator) ** 2, mu(A)) for A in F)
True

proj

proj(function, subspace, name=None)

Compute the orthogonal projection of a measurable function onto the subspace spanned by a set of measurable functions.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
function MeasurableFunction

The measurable function to be projected.

required
subspace list[MeasurableFunction]

A list of measurable functions spanning the subspace onto which function is to be projected.

required
name Hashable | None

The name of the projection. If None, a default will be generated.

None

Returns:

Name Type Description
proj MeasurableFunction

The orthogonal projection of function onto the subspace spanned by subspace.

coefficients ndarray

The coefficients of the projection of function onto the subspace spanned by the measurable functions in subspace. See the Notes section below for the mathematical details.

dim int

The dimension of the subspace spanned by subspace.

Examples:

>>> from sigalg.core import (
...     L2,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define an L2-space over a probability space and a random variable in it.

>>> Omega = SampleSpace.from_sequence(size=4)
>>> F = SigmaAlgebra.power_set(Omega, name="F")
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.4,
...         2: 0.2,
...         3: 0.2,
...     },
... )
>>> H = L2(Omega, F, P)
>>> X = RandomVariable(
...     *H.measure_space,
...     mapping={
...         0: 2.0,
...         1: 3.0,
...         2: 5.0,
...         3: 7.0,
...     },
... )

For a quadratic regression example, we will project a random variable Y onto the subspace spanned by 1, X, and X**2.

>>> one = RandomVariable.from_constant(*H.measure_space, constant=1, name="one")
>>> Y = RandomVariable(
...     *H.measure_space,
...     mapping={
...         0: 1.0,
...         1: 3.0,
...         2: 2.0,
...         3: 4.0,
...     },
...     name="Y",
... )
>>> proj, c, dim = H.proj(function=Y, subspace=[one, X, X**2])

The c coefficients returned by the proj method are the coefficients of the orthogonal projection of Y onto the subspace spanned by 1, X, and X**2. We can verify that the projection is correct by computing the expected projection using the coefficients and comparing it to the projection returned by the proj method.

>>> expected_proj = sum([c[k] * X**k for k in range(dim)])
>>> print(proj == expected_proj)
True
Notes

Let \((X, \mathcal{F}, \mu)\) be a measure space, set \(H = L^2(X, \mathcal{F},\mu)\), and suppose for simplicitly that \(X\) is finite, so that \(H\) is finite-dimensional. Suppose \(g\) is a function in \(H\) and that \(\{f_1,f_2,\ldots,f_n\} \subset H\) spans a subspace \(V\) of \(H\). The orthogonal projection of \(g\) onto \(V\) is the unique function \(\widehat{g}\in V\) such that

\[ \|g - \widehat{g}\| \leq \|g - h\|, \]

for all \(h\in V\). The existence and uniqueness of \(\widehat{g}\) is a consequence of the Projection Theorem for Hilbert spaces.

In applications, one computes \(\widehat{g}\) by identifying \(\widehat{g}\) as the global minimizer of the objective function

\[ \psi: \mathbb{R}^n \to \mathbb{R}, \quad \psi(c) = \frac{1}{2} \left \|\sum_{j=1}^n c_j f_j - g \right\|^2. \]

The first (Fréchet) derivative \(D\psi(c)\) of \(\psi\) at \(c\) is given by

\[ D\psi(c)h = \sum_{j=1}^n \left[ \sum_{k=1}^n c_k \langle f_j,f_j \rangle - \langle f_j, g \rangle \right]h_j, \]

for \(h\in \mathbb{R}^n\). At the minimizer \(\widehat{g} = \sum_{k=1}^n c_k f_k\), we must have \(D\psi(c)=0\), which yields the linear system of equations

\[ \begin{bmatrix} \langle f_1, f_1 \rangle & \cdots & \langle f_1, f_n \rangle \\ \vdots & \ddots & \vdots \\ \langle f_n, f_1 \rangle & \cdots & \langle f_n, f_n \rangle \\ \end{bmatrix} \begin{bmatrix} c_1 \\ \vdots \\ c_n \end{bmatrix} = \begin{bmatrix} \langle f_1, g \rangle \\ \vdots \\ \langle f_n,g \rangle \end{bmatrix}, \]

for the unknown \(c\). The coefficient matrix on the left is the Gram matrix \(G\) of the functions \(f_1,\ldots,f_n\) as vectors in the Hilbert space \(H\). Provided that \(G\) is invertible (which is equivalent to linear independence of the \(f_j\)'s in \(H\)), there is a unique solution \(c\). Otherwise, if \(G\) is not invertible, then there are infinitely many choices for \(c\). In this case, the method proj returns the \(c\) for which \(\sum_{k=1}^n c_k^2\) is minimum.

Finally, for a solution \(c\) for which \(\widehat{g} = \sum_{k=1}^n c_k f_k\), note that the linear system above can equivalently be expressed as the system

\[ \langle f_1, \widehat{g} - g \rangle = \cdots = \langle f_n, \widehat{g}-g \rangle = 0. \]

These are called the normal equations, which confirm that \(\widehat{g}\) really is the orthogonal projection of \(g\) onto the subspace spanned by the \(f_j\)'s.

Lattice

A class representing a lattice of sigma-algebras.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base SigmaAlgebra

The base sigma-algebra of the lattice.

required
type Literal['upward', 'downward']

The type of the lattice, indicating whether it is an upward or downward lattice.

required
Notes

Mathematically, a lattice is a partially-ordered set in which every pair of elements has a unique supremum (least upper bound, or join) and an infimum (greatest lower bound, or meet).

An example is the set of all \(\sigma\)-algebras on a fixed nonempty set \(X\), which we assume to be finite for simplicity.

  1. The partial order is given by set inclusion, i.e., for two \(\sigma\)-algebras \(\mathcal{F}\) and \(\mathcal{G}\) on \(X\), we say \(\mathcal{F} \leq \mathcal{G}\) if and only if \(\mathcal{F}\) is a subset of \(\mathcal{G}\).

  2. The join of two \(\sigma\)-algebras \(\mathcal{F}\) and \(\mathcal{G}\), denoted by \(\mathcal{F} \vee \mathcal{G}\), is the \(\sigma\)-algebra whose atoms are the nonempty intersections of the atoms of \(\mathcal{F}\) and \(\mathcal{G}\).

  3. The meet of two \(\sigma\)-algebras \(\mathcal{F}\) and \(\mathcal{G}\), denoted by \(\mathcal{F} \wedge \mathcal{G}\), is the \(\sigma\)-algebra whose atoms are ...

add

add(sig_alg)

Add a sigma-algebra to the lattice, if possible.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra to add.

required

Raises:

Type Description
ValueError

If sig_alg is not a SigmaAlgebra with the same domain as the base sigma-algebra.

NonMeasurableError

If the sigma-algebra is not contained in the lattice.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define three sigma-algebras on a domain.

>>> X = Domain.from_sequence(size=5)
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 2])))
>>> H = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 3])), name="H")
>>> G = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 1, 1])), name="G")

The sigma-algebra G is a sub-sigma-algebra of F, so it may be added to the downward lattice of the latter.

>>> F.down_lattice.add(G)
>>> G in F.down_lattice
True

However, the sigma-algebra H is not a sub-sigma-algebra of F, so attempting to add it raises an exception.

>>> F.down_lattice.add(H)
Traceback (most recent call last):
    ...
NonMeasurableError: The candidate set is not measurable.
>>> H in F.down_lattice
False

But H is a super-sigma-algebra of F, so it may be added to the upward lattice of the latter.

>>> F.up_lattice.add(H)
>>> H in F.up_lattice
True

get_atom_data

get_atom_data(sig_alg)

Get the atom data of the base sigma-algebra relative to given a sigma-algebra.

If the lattice istype='upward', then sig_alg is a super-sigma-algebra of the base sigma-algebra. In this case, every atom of sig_alg is contained in a unique atom of the base sigma-algebra. The pandas structure returned by this method encodes this mapping. If the lattice is type='downward', then everything is reversed. See the Examples below.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra relative to which the atom data is requested.

required

Returns:

Name Type Description
data DataFrame

The atom data of the base sigma-algebra relative to the given sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define three sigma-algebras on a domain.

>>> X = Domain.from_sequence(size=5)
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 2])))
>>> H = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 3])), name="H")
>>> G = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 1, 1])), name="G")

The sigma-algebra G is a sub-sigma-algebra of F, so it may be added to the downward lattice of the latter.

>>> F.down_lattice.add(G)

Every atom of F is contained in a unique atom of G according to the following mapping on identifiers: 0->0, 1->1, and 2->1. This mapping is encoded in the atom data returned by the get_atom_data method.

>>> F_to_G_atom_data = F.down_lattice.get_atom_data(G)
>>> print(F_to_G_atom_data)
F
0    0
1    1
2    1
Name: G, dtype: int64

Symmetrically, the same atom data may be obtained through the upward lattice of G. Note that we do not need to add F to this lattice — the call to add above already did this.

>>> same_atom_data = G.up_lattice.get_atom_data(F)
>>> print(same_atom_data)
F
0    0
1    1
2    1
Name: G, dtype: int64

The sigma-algebra H is a super-sigma-algebra of F, so it may be added to the upward lattice of the latter.

>>> F.up_lattice.add(H)

There is now a mapping from the atom IDs of H to those of F.

>>> H_to_F_atom_data = F.up_lattice.get_atom_data(H)
>>> print(H_to_F_atom_data)
H
0    0
1    1
2    2
3    2
Name: F, dtype: int64

is_subalgebra staticmethod

is_subalgebra(sub_algebra, super_algebra)

Test whether a sigma-algebra is a sub-sigma-algebra of another.

Parameters:

Name Type Description Default
sub_algebra SigmaAlgebra

The sigma-algebra who will be tested in the sub-sigma-algebra position.

required
super_algebra SigmaAlgebra

The sigma-algebra who will be test in the sub-sigma-algebra position.

required

Raises:

Type Description
ValueError

If the sigma-algebras are not instances of SigmaAlgebra with the same domain.

Returns:

Name Type Description
is_sub_alg bool

True if sub_algebra is a sub-sigma-algebra of super_algebra; False otherwise.

Examples:

>>> from sigalg.core import Domain, Lattice, SigmaAlgebra

Define three sigma-algebras on a domain.

>>> X = Domain.from_sequence(size=5)
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 2])))
>>> H = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 3])), name="H")
>>> G = SigmaAlgebra(
...     domain=X, mapping=dict(zip(X, [0, 1, 1, 1, 1])), name="G")

The sigma-algebras sit in a chain G <= F <= H. Test this.

>>> Lattice.is_subalgebra(G, G)
True
>>> Lattice.is_subalgebra(G, F)
True
>>> Lattice.is_subalgebra(G, H)
True
>>> Lattice.is_subalgebra(F, G)
False
>>> Lattice.is_subalgebra(F, F)
True
>>> Lattice.is_subalgebra(F, H)
True
>>> Lattice.is_subalgebra(H, G)
False
>>> Lattice.is_subalgebra(H, F)
False
>>> Lattice.is_subalgebra(H, H)
True

items

items()

Return a list of tuples (sig_alg, data) of a sigma-algebra and the atom data relative to the base sigma-algebra.

Returns:

Name Type Description
items list[SigmaAlgebra, Series | DataFrame]

The above mentioned list.

join staticmethod

join(
    sigma_algebras,
    variable_names=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
)

Compute the join (least upper bound) of a list of sigma-algebras on the same domain.

Parameters:

Name Type Description Default
sigma_algebras list[SigmaAlgebra]

A list of sigma-algebras instances to join.

required
variable_names list[Hashable] | None

The list of variable names for the join. If None, defaults will be generated.

None
name Hashable | None

Name identifier for the resulting sigma algebra. If None, a default will be generated.

None

Raises:

Type Description
ValueError

If sigma_algebras is not a nonempty list of sigma-algebras on the same domain.

Examples:

>>> from sigalg.core import Domain, Lattice, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (3, 4),
...         2: (6, 7),
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 2,
...         1: 5,
...         2: 8,
...     },
...     name="G",
... )
>>> join = Lattice.join([F, G])
>>> print(join)
Sigma algebra 'F v G':
i  0  1  2
x
0  0  1  2
1  3  4  5
2  6  7  8
>>> F <= join
True
>>> G <= join
True
>>> print(join.atom_space)
Domain 'F v G':
 F_0  F_1  G
   0    1  2
   3    4  5
   6    7  8

meet staticmethod

meet(sigma_algebras, variable_name=None, name=None)

Compute the meet (greatest lower bound) of a list of sigma-algebras on the same domain.

Parameters:

Name Type Description Default
sigma_algebras list[SigmaAlgebra]

A list of sigma-algebras instances to meet.

required
name Hashable | None

Name identifier for the resulting sigma algebra. If None, a default will be generated.

None

Raises:

Type Description
ValueError

If sigma_algebras is not a nonempty list of sigma-algebras on the same domain.

Examples:

>>> from sigalg.core import Domain, Lattice, SigmaAlgebra
>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...         4: 3,
...         5: 4,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 3,
...         5: 4,
...     },
...     name="G",
... )
>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,
...         5: 4,
...     },
...     name="H",
... )
>>> meet = Lattice.meet([F, G, H])
>>> print(meet)
Sigma algebra 'F ^ G ^ H':
   F ^ G ^ H
x
0          0
1          0
2          0
3          0
4          1
5          2
>>> meet <= F
True
>>> meet <= G
True
>>> meet <= H
True

MeasurableFunction

Bases: MeasurableVector

Marker class for a measurable function.

MeasurableSpace

Bases: SigmaAlgebraMethods

A class representing a measurable space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the measurable space.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the measurable space. If domain is given but sig_alg is None, the power-set sigma-algebra on the domain will be set as the default.

None

Examples:

>>> from sigalg.core import Domain, MeasurableSpace, SigmaAlgebra

Define a domain and sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )

Create a measurable space and print it.

>>> measurable_space = MeasurableSpace(domain=X, sig_alg=F)
>>> print(measurable_space)
Measurable space (X, F)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    0
2    1
Notes

A measurable space is a pair \((X, \mathcal{F})\) consisting of a nonempty set \(X\) and a \(\sigma\)-algebra \(\mathcal{F}\) on \(X\).

domain property

domain

Get the domain of the measurable space.

Returns:

Name Type Description
domain Domain | None

The domain of the measurable space.

Examples:

>>> from sigalg.core import Domain, MeasurableSpace, SigmaAlgebra

Define a domain and sigma-algebra.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )

Instantiate a measurable space and print its domain.

>>> measurable_space = MeasurableSpace(domain=X, sig_alg=F)
>>> print(measurable_space.domain)
Domain 'X':
 x
 0
 1
 2
 3

make_measure_space

make_measure_space(measure=None)

Convert this measurable space to a measure space by adding a measure.

Parameters:

Name Type Description Default
measure Measure | None

Measure to use. If None, the counting measure will be created.

None

Returns:

Name Type Description
measure_space MeasureSpace

A measure space with this measurable space's domain and sigma-algebra.

Examples:

>>> from sigalg.core import Domain, Measure, MeasurableSpace, SigmaAlgebra

Create an instance of MeasurableSpace.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> measurable_space = MeasurableSpace(domain=X, sig_alg=F)

Create a measure space with a counting measure.

>>> measure_space = measurable_space.make_measure_space()
>>> print(measure_space)
Measure space (X, F, C)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    0
2    1

* Measure 'C':
        C
F
0       2
1       1

Create a measure space with a custom measure.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 7,
...         1: 3,
...     },
... )
>>> measure_space = measurable_space.make_measure_space(measure=mu)
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    0
2    1

* Measure 'mu':
        mu
F
0        7
1        3

MeasurableVector

Bases: Function, OperatorsMethods

A class representing a measurable vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the function.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the underlying measurable space.

None
measure Measure | None

An optional measure carried by the function.

None
mapping MappingLike | None

The underlying rule defining the function.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None

Examples:

>>> from sigalg.core import (
...     Domain,
...     MeasurableSpace,
...     MeasurableVector,
...     SigmaAlgebra,
... )

Generate a 2-dimensional measurable vector on a pre-existing domain from a dictionary mapping. The power-set sigma-algebra is automatically generated.

>>> X = Domain.from_sequence(size=3)
>>> f = MeasurableVector(
...     domain=X,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...         2: (2, 2),
...     },
...     name="f",
... )
>>> print(f)
Measurable vector 'f':
i  0  1
x
0  1  1
1  1  1
2  2  2
>>> print(f.sig_alg)
Sigma algebra 'R':
   R
x
0  0
1  1
2  2

Generate a measurable vector on a pre-existing measurable space.

>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> measurable_space = MeasurableSpace(X, F)
>>> g = MeasurableVector(
...     *measurable_space,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...         2: (2, 2),
...     },
...     name="g",
... )
>>> print(g.sig_alg)
Sigma algebra 'F':
   F
x
0  0
1  0
2  1

Attempt to define a measurable vector that is not measurable.

>>> h = MeasurableVector(
...     *measurable_space,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (5, 6),
...     },
...     name="h",
... )
Traceback (most recent call last):
    ...
ValueError: Function h is not measurable.

Generate a 2-dimensional measurable vector from a function on a domain and a custom index.

>>> S = Domain([(0, 1), (1, 2)], variable_names=["x", "y"], name="S")
>>> def mapping(*, x, y):  # noqa: D103
...     return (x + y, x)
>>> v = MeasurableVector(
...     domain=S,
...     mapping=mapping,
...     index=[1, 2],
...     name="v",
... )
>>> print(v)
Measurable vector 'v':
i    1  2
x y
0 1  1  0
1 2  3  1
Notes

Given a measurable space \((X,\mathcal{F})\), a measurable vector is an \(\mathcal{F}\)-measurable function \(f: X \to \mathbb{R}^d\), where \(d\) is the dimension of the vector and \(\mathbb{R}^d\) is equipped with its Borel \(\sigma\)-algebra. If \(X\) is finite (as it always is, in SigAlg), then \(f\) is \(\mathcal{F}\)-measurable if and only if \(f\) is constant on the atoms of \(\mathcal{F}\).

measurable_space cached property

measurable_space

Get the measurable space on which the measurable vector is defined.

Returns:

Name Type Description
measurable_space MeasurableSpace | None

The measurable space on which the measurable vector is defined.

Examples:

>>> from sigalg.core import (
...     Domain,
...     MeasurableSpace,
...     MeasurableVector,
...     SigmaAlgebra,
... )

Define a function on a measurable space.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> measurable_space = MeasurableSpace(X, F)
>>> f = MeasurableVector(
...     *measurable_space,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...     },
... )

Extract the measurable space.

>>> print(f.measurable_space)
Measurable space (X, F)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
    F
x
0   0
1   1
2   1

measure_space cached property

measure_space

Get the measure space on which the measurable vector is defined.

Returns:

Name Type Description
measure_space MeasureSpace | None

The measure space on which the measurable vector is defined.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     MeasureSpace,
...     MeasurableVector,
...     SigmaAlgebra,
... )

Define a function on a measure space.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 2,
...         1: 8,
...     },
... )
>>> measure_space = MeasureSpace(X, F, mu)
>>> f = MeasurableVector(
...     *measure_space,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...     },
... )

Extract the measure space.

>>> print(f.measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    1
2    1

* Measure 'mu':
      mu
F
0      2
1      8

cartesian_power classmethod

cartesian_power(
    function,
    n,
    index=None,
    index_kind="Index",
    index_name=None,
)

Form the Cartesian power of a measurable vector.

Parameters:

Name Type Description Default
function MeasurableVector

The base of the Cartesian power.

required
n int

The power of the Cartesian power.

required
index IndexLike | None

The index of the Cartesian power.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None

Returns:

Name Type Description
power MeasurableVector

The Cartesian power.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     MeasurableVector,
...     SigmaAlgebra,
... )

Define a 2-dimensional measurable vector f.

>>> X = Domain.from_sequence(size=4, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.4,
...         2: 0.4,
...     },
... )
>>> f = MeasurableVector(
...     domain=X,
...     sig_alg=F,
...     measure=mu,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (5, 6),
...     },
... )

Compute the second Cartesian power of the measurable vector f and print its measure space.

>>> cart_pow = MeasurableVector.cartesian_power(f, 2)
>>> print(cart_pow)
Measurable vector 'f ^ 2':
i        0  1  2  3
x_0 x_1
0   0    1  2  1  2
    1    1  2  3  4
    2    1  2  3  4
    3    1  2  5  6
1   0    3  4  1  2
    1    3  4  3  4
    2    3  4  3  4
    3    3  4  5  6
2   0    3  4  1  2
    1    3  4  3  4
    2    3  4  3  4
    3    3  4  5  6
3   0    5  6  1  2
    1    5  6  3  4
    2    5  6  3  4
    3    5  6  5  6
>>> print(cart_pow.measure_space)
Measure space (X ^ 2, F ^ 2, mu ^ 2)
====================================

* Domain 'X ^ 2':
x_0  x_1
0    0
0    1
0    2
0    3
1    0
1    1
1    2
1    3
2    0
2    1
2    2
2    3
3    0
3    1
3    2
3    3

* Sigma algebra 'F ^ 2':
i        0  1
x_0 x_1
0   0    0  0
    1    0  1
    2    0  1
    3    0  2
1   0    1  0
    1    1  1
    2    1  1
    3    1  2
2   0    1  0
    1    1  1
    2    1  1
    3    1  2
3   0    2  0
    1    2  1
    2    2  1
    3    2  2

* Measure 'mu ^ 2':
        mu ^ 2
F_0 F_1
0   0      0.04
    1      0.08
    2      0.08
1   0      0.08
    1      0.16
    2      0.16
2   0      0.08
    1      0.16
    2      0.16

Compute the third Cartesian power using the ^ operator.

>>> print(f ^ 3)
Measurable vector 'f ^ 3':
i            0  1  2  3  4  5
x_0 x_1 x_2
0   0   0    1  2  1  2  1  2
        1    1  2  1  2  3  4
        2    1  2  1  2  3  4
        3    1  2  1  2  5  6
    1   0    1  2  3  4  1  2
...         .. .. .. .. .. ..
3   2   3    5  6  3  4  5  6
    3   0    5  6  5  6  1  2
        1    5  6  5  6  3  4
        2    5  6  5  6  3  4
        3    5  6  5  6  5  6

[64 rows x 6 columns]

cartesian_product classmethod

cartesian_product(
    factors,
    domain_name=None,
    sig_alg_name=None,
    measure_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
)

Form the Cartesian product of a list of measurable vectors.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
factors list[MeasurableVector]

The factors of the Cartesian product.

required
domain_name Hashable | None

The name of the domain of the Cartesian product.

None
sig_alg_name Hashable | None

The name of the sigma-algebra of the Cartesian product. If None, a default will be generated.

None
measure_name Hashable | None

The name of the measure of the Cartesian product. If None, a default will be generated.

None
index IndexLike | None

The index of the Cartesian product.

None
index_kind Literal['Index', 'Time']

The kind of index.

"Index"
index_name Hashable | None

The name of the index.

None
name Hashable | None

The name of the Cartesian product. If None, a default will be generated.

None

Returns:

Name Type Description
product MeasurableVector

The Cartesian product of the measurable vectors.

Examples:

>>> from sigalg.core import (
...     MeasurableVector,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define two probability measures on two sigma-algebras on the same sample space.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra(domain=Omega, mapping=dict(zip(Omega, [0, 0, 1])))
>>> G = SigmaAlgebra(domain=Omega, mapping=dict(zip(Omega, [0, 1, 1])), name="G")
>>> P = ProbabilityMeasure(domain=F, mapping=dict(zip(F.atom_ids, [0.4, 0.6])))
>>> Q = ProbabilityMeasure(domain=G, mapping=dict(zip(G.atom_ids, [0.25, 0.75])), name="Q")

Define two random variables on the two probability spaces.

>>> X = RandomVariable(Omega, F, P, mapping=dict(zip(Omega, [1, 1, 0])))
>>> Y = RandomVariable(Omega, G, Q, mapping=dict(zip(Omega, [2, 3, 3])), name="Y")

Form the Cartesian product of the two random variables.

>>> product = MeasurableVector.cartesian_product([X, Y])
>>> print(product)
Random vector 'X x Y':
i                0  1
omega_0 omega_1
0       0        1  2
        1        1  3
        2        1  3
1       0        1  2
        1        1  3
        2        1  3
2       0        0  2
        1        0  3
        2        0  3

Print the measure space of the Cartesian product.

>>> print(product.measure_space)
Probability space (Omega x Omega, F x G, P x Q)
===============================================

* Domain 'Omega x Omega':
 omega_0  omega_1
       0        0
       0        1
       0        2
       1        0
       1        1
       1        2
       2        0
       2        1
       2        2

* Sigma algebra 'F x G':
i                0  1
omega_0 omega_1
0       0        0  0
        1        0  1
        2        0  1
1       0        0  0
        1        0  1
        2        0  1
2       0        1  0
        1        1  1
        2        1  1

* Probability measure 'P x Q':
     P x Q
F G
0 0   0.10
  1   0.30
1 0   0.15
  1   0.45

Form the same Cartesian product using the @ operator.

>>> print(X @ Y)
Random vector 'X x Y':
i                0  1
omega_0 omega_1
0       0        1  2
        1        1  3
        2        1  3
1       0        1  2
        1        1  3
        2        1  3
2       0        0  2
        1        0  3
        2        0  3
Notes

Given one measurable vector \(f: X \to \mathbb{R}^d\) on a measurable space \((X,\mathcal{F})\), and a second measurable vector \(g: Y \to \mathbb{R}^e\) on a measurable space \((Y,\mathcal{G})\), their Cartesian product, denoted \(f \times g\), is the \((\mathcal{F} \otimes \mathcal{G})\)-measurable measurable vector defined

\[ (f \times g) : X \times Y \to \mathbb{R}^{d+e}, \quad (f\times g)(x, y) = (f(x),g(y)). \]

Here, \(\mathcal{F} \otimes \mathcal{G}\) is the tensor product \(\sigma\)-algebra.

concatenate classmethod

concatenate(
    factors,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
)

Concatenate a list of measurable vectors into a multi-dimensional vector.

Parameters:

Name Type Description Default
factors list[Function]

A list of functions to concatenate.

required
index IndexLike | None

The index of the resulting function. If None, the method will attempt to join the indices of the factors, but the indices must be pairwise disjoint.

None
index_kind Literal['Index', 'Time']

The kind of index.

"Index"
index_name Hashable | None

The name of the index.

None
name Hashable | None

The name of the resulting measurable vector. If None, a default will be generated.

None

Returns:

Name Type Description
concatenation MeasurableVector

A new measurable vector created by combining the input measurable vectors.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     MeasurableFunction,
...     MeasurableVector,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Generate a measure space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra.from_rand(
...     domain=X,
...     num_atoms=3,
...     random_state=42,
... )
>>> mu = Measure(domain=F, mapping={0: 1, 1: 2, 2: 3})

Generate two measurable vectors. One has a measure, the other does not.

>>> f = MeasurableVector.from_rand(
...     domain=X,
...     sig_alg=F,
...     measure=mu,
...     index=[0, 1, 2],
...     max_value=2,
...     random_state=42,
... )
>>> print(f)
Measurable vector 'f':
i       0  1  2
x
0       0  1  1
1       0  0  1
2       0  1  1
3       0  1  0
>>> g = MeasurableVector.from_rand(
...     domain=X,
...     sig_alg=F,
...     index=[3, 4],
...     max_value=2,
...     random_state=42,
...     name="g",
... )
>>> print(g)
Measurable vector 'g':
i       3  4
x
0       0  1
1       1  0
2       0  1
3       0  1

Concatenate the two vectors. The measure of the one will propagate to the concatenation.

>>> fg = MeasurableVector.concatenate([f, g])
>>> print(fg)
Measurable vector 'fg':
i       0  1  2  3  4
x
0       0  1  1  0  1
1       0  0  1  1  0
2       0  1  1  0  1
3       0  1  0  0  1
>>> print(fg.measure)
Measure 'mu':
     mu
F
1     2
0     1
2     3

Together with the pushforward method, the concatenation method is SigAlg's mechanism for generating the joint distributions of random variables. We demonstrate.

>>> Omega = SampleSpace.from_sequence(size=4)
>>> P = ProbabilityMeasure(domain=Omega, mapping=dict(zip(Omega, [0.1, 0.2, 0.3, 0.4])))
>>> X = RandomVariable(domain=Omega, measure=P, mapping=dict(zip(Omega, [1, 1, 0, 1])))
>>> Y = RandomVariable(domain=Omega, measure=P, mapping=dict(zip(Omega, [1, 1, 1, 0])), name="Y")

The joint distribution of the two random variables comes by pushing the measure P forward along the random vector X | Y.

>>> joint = P >> (X | Y)
>>> joint
ProbabilityMeasure(domain=XY_range, sig_alg=R, name=P_XY)
>>> print(joint)
Probability measure 'P_XY':
     P_XY
X Y
0 1   0.3
1 0   0.4
  1   0.3

from_constant classmethod

from_constant(
    domain,
    sig_alg=None,
    measure=None,
    domain_kind="Domain",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    *,
    constant,
)

Create a measurable vector that maps every point in the domain to the same constant output vector.

Parameters:

Name Type Description Default
domain IndexLike

The domain of the measurable vector.

required
constant Hashable | None

The constant output value of the function. If a tuple, the function will have multi-dimensional outputs.

required
sig_alg SigmaAlgebra | None

The sigma-algebra of the underlying measurable space. If None, the power set sigma-algebra is used.

None
measure Measure | None

An optional measure carried by the function.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None

Returns:

Name Type Description
const_vec MeasurableVector

A measurable vector mapping every point in the domain to the same constant output vector.

Examples:

>>> from sigalg.core import Domain, MeasurableVector

Create a constant 2-dimensional measurable vector.

>>> X = Domain.from_sequence(size=3)
>>> f = MeasurableVector.from_constant(domain=X, constant=(1, 2), index=[1, 2])
>>> print(f)
Measurable vector 'f':
i  1  2
x
0  1  2
1  1  2
2  1  2

Create a constant 1-dimensional measurable function.

>>> g = MeasurableVector.from_constant(domain=X, constant=2, name="g")
>>> print(g)
Measurable function 'g':
        g
x
0       2
1       2
2       2

from_rand classmethod

from_rand(
    domain,
    sig_alg=None,
    measure=None,
    dim=None,
    diff_values=0,
    distribution="uniform",
    min_value=0,
    max_value=10,
    loc=0.0,
    scale=1.0,
    domain_kind="Domain",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
    random_state=None,
)

Generate a random measurable vector.

Parameters:

Name Type Description Default
domain IndexLike

The domain of the function.

required
sig_alg SigmaAlgebra | None

The sigma-algebra of the underlying measurable space. If None, the power set sigma-algebra is used.

None
measure Measure | None

An optional measure carried by the function.

None
dim int | None

The dimension of the outputs of the function.

None
diff_values int

If nonzero, the function is randomly generated so that it is measurable with respect to a randomly generated sub-sigma-algebra of sig_alg. Then diff_values = sig_alg.num_atoms - sub_sig_alg.num_atoms. See the Examples section.

0
distribution Literal['uniform', 'normal']

The distribution to use for generating random values.

"uniform"
min_value int

The minimum value for the uniform distribution.

0
max_value int

The maximum value for the uniform distribution.

10
loc float

The mean for the normal distribution.

0.0
scale float

The standard deviation for the normal distribution.

1.0
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain. If None, a default will be generated.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be used.

None
random_state int | Generator | None

The random state for reproducibility.

None

Returns:

Name Type Description
vector MeasurableVector

A random measurable vector.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, MeasurableVector, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Create a 2-dimensional measurable vector with integer outputs uniformly sampled from the range [0, 5).

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...         4: 2,
...         5: 3,
...     },
... )
>>> f = MeasurableVector.from_rand(
...     domain=X,
...     sig_alg=F,
...     min_value=0,
...     max_value=5,
...     dim=2,
...     random_state=rng,
... )
>>> print(f)
Measurable vector 'f':
i  0  1
x
0  0  3
1  0  3
2  3  2
3  2  4
4  2  4
5  0  3

Create a 2-dimensional measurable vector with values drawn from a standard normal distribution.

>>> g = MeasurableVector.from_rand(
...     domain=X,
...     sig_alg=F,
...     distribution="normal",
...     dim=2,
...     name="g",
...     random_state=rng,
... )
>>> print(g)
Measurable vector 'g':
i         0         1
x
0 -1.951035 -1.302180
1 -1.951035 -1.302180
2  0.127840 -0.316243
3 -0.016801 -0.853044
4 -0.016801 -0.853044
5  0.879398  0.777792

The maximum number of unique values of a measurable vector is equal to the number of atoms of the underlying sigma-algebra. Notice that this last vector achieves this upper bound. We can decrease the number of unique values by generating the vector so that it is measurable with respect to a sub-sigma-algebra by specifying a nonzero value for the diff_values parameter. This parameter is equal to diff_values = sig_alg.num_atoms - sub_sig_alg.num_atoms.

>>> h = MeasurableVector.from_rand(
...     domain=X,
...     sig_alg=F,
...     diff_values=2,
...     distribution="normal",
...     dim=2,
...     name="h",
...     random_state=rng,
... )
>>> print(h)
Measurable vector 'h':
i         0         1
x
0 -0.859292  0.368751
1 -0.859292  0.368751
2 -0.958883  0.878450
3 -0.958883  0.878450
4 -0.958883  0.878450
5 -0.859292  0.368751

restrict_to

restrict_to(subset, subset_name='A', normalize=False)

Restrict the measurable vector to a measurable subset.

Parameters:

Name Type Description Default
subset Set | list[Hashable]

The set to restrict the measurable vector to.

required
subset_name Hashable

The name to use for the subset. Ignored if subset is an instance of Set.

"A"
normalize bool

If the measurable vector carries a measure, whether to normalize the restricted measure to create a probability space.

False

Returns:

Name Type Description
restriction MeasurableVector

The restriction of the function.

Examples:

>>> from sigalg.core import Domain, Measure, MeasurableVector, Set, SigmaAlgebra

Define a 2-dimensional measurable vector with a measure.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 2,
...         1: 5,
...         2: 3,
...     },
... )
>>> f = MeasurableVector(
...     domain=X,
...     sig_alg=F,
...     measure=mu,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (5, 6),
...     },
... )

Restrict the measurable vector to a set using the restrict_to method.

>>> A = Set([1, 2, 3], domain=X)
>>> f_A = f.restrict_to(A)
>>> print(f_A)
Measurable vector 'f|A':
i  0  1
x
1  3  4
2  3  4
3  5  6

Print its measure space consisting of the restricted sigma-algebra and measure.

>>> print(f_A.measure_space)
Measure space (A, F|A, mu|A)
============================

* Domain 'A':
 x
 1
 2
 3

* Sigma algebra 'F|A':
        F|A
x
1         1
2         1
3         2

* Measure 'mu|A':
     mu|A
F
1       5
2       3

Compute the same restriction using the overloaded | operator.

>>> print(f | A)
Measurable vector 'f|A':
i   0  1
x
1   3  4
2   3  4
3   5  6

Restrict the measurable vector using a list with a custom name. Pass normalize=True to create a probability measure.

>>> f_B = f.restrict_to([1, 2, 3], normalize=True, subset_name="B")
>>> print(f_B)
Random vector 'f|B':
i   0  1
x
1   3  4
2   3  4
3   5  6
>>> print(f_B.measure_space)
Probability space (B, F|B, mu|B)
================================

* Domain 'B':
 x
 1
 2
 3

* Sigma algebra 'F|B':
        F|B
x
1         1
2         1
3         2

* Probability measure 'mu|B':
    mu|B
F
1  0.625
2  0.375

to_function

to_function()

Promote to a Function instance.

Measure

Bases: Function

A class representing a measure on a sigma-algebra.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain MeasureDomain | None

The domain of the measure. Either a SigmaAlgebra or an IndexLike object that can be coerced into a Domain. In the latter case, the domain will be set to the power set of the domain.

None
mapping MappingLike | None

A mapping from the domain to the measure values.

None
kind Literal['measure', 'probability']

The kind of measure.

"measure"
output_name Hashable | None

The name of the output variable of the measure. If None, a default will be generated.

None
name Hashable | None

The name of the measure. If None, a default will be generated.

None

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra

Define a measure on a sigma-algebra with two atoms.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...    domain=X,
...    mapping={
...        0: 0,
...        1: 0,
...        2: 1,
...    },
... )
>>> mu = Measure(domain=F, mapping={0: 1, 1: 2})
>>> print(mu)
Measure 'mu':
         mu
F
0   1
1   2

Define a measure directly on a domain, which will use the power-set sigma-algebra by default.

>>> nu = Measure(
...     domain=X,
...     mapping={
...         0: 3,
...         1: 1,
...         2: 4,
...     },
...     name="nu",
... )
>>> print(nu)
Measure 'nu':
   nu
x
0   3
1   1
2   4
>>> print(nu.sig_alg)
Sigma algebra 'R':
   R
x
0  0
1  1
2  2

Define the same measure directly on a list of points.

>>> nu = Measure(
...     domain=[0, 1, 2],
...     mapping={
...         0: 3,
...         1: 1,
...         2: 4,
...     },
...     name="nu",
... )
>>> print(nu)
Measure 'nu':
   nu
x
0   3
1   1
2   4
>>> print(nu.sig_alg)
Sigma algebra 'R':
   R
x
0  0
1  1
2  2

Measures may also be defined from callables.

>>> xi = Measure(domain=F, mapping=lambda u: u+1, name="xi")
>>> print(xi)
Measure 'xi':
   xi
F
0   1
1   2

Define a probability measure using the Measure constructor with the parameter kind set to probability.

>>> P = Measure(
...     domain=X,
...     mapping={
...         0: 0.5,
...         1: 0.2,
...         2: 0.3,
...     },
...     kind="probability",
...     name="P",
... )
>>> print(P)
Probability measure 'P':
     P
x
0  0.5
1  0.2
2  0.3
>>> type(P).__name__
'ProbabilityMeasure'
>>> print(P.domain)
Domain 'X':
 x
 0
 1
 2
Notes

Let \((X, \mathcal{F})\) be a measurable space consisting of a \(\sigma\)-algebra \(\mathcal{F}\) on a set \(X\). A measure \(\mu\) is a countably additive function \(\mu: \mathcal{F} \to [0,\infty)\). Here, countable additivity means that

\[ \mu \left( \bigcup_{k=1}^\infty A_k \right) = \sum_{k=1}^\infty \mu(A_k) \]

for all collections \(\{A_k\}_{k=1}^\infty\) of pairwise disjoint measurable sets. If \(X\) is finite (as it always is, in SigAlg), then \(\mu\) needs only to be finitely additive in order to be countably additive.

lattice cached property

lattice

Return the downward lattice of all sigma-algebras contained in the domain sigma-algebra of the measure.

Returns:

Name Type Description
down_lattice Lattice | None

The downward lattice of all sigma-algebras contained in the domain sigma-algebra of the measure.

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra

Define a measure on a measurable space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 9,
...         1: 8,
...         2: 7,
...     },
... )

The lattice of the measure is initialized with the domain sigma-algebra F.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=1)

Define a sub-sigma-algebra of F and check that it is in the lattice.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...     },
...     name="G",
... )
>>> G in mu.lattice
True

Check that the lattice now includes G.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=2)

Since G is a sub-sigma-algebra of F, we may restrict the measure mu to G.

>>> print(mu | G)
Measure 'mu|G':
   mu|G
G
0     9
1    15

Define another sigma-algebra, and check if it is a sub-sigma-algebra of F.

>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...     },
...     name="H",
... )
>>> H in mu.lattice
False

Check that H was not added to the lattice.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=2)

non_null_atoms cached property

non_null_atoms

Get the non-null atoms of the sigma-algebra of the measure.

Returns:

Name Type Description
non_null_atoms list[Set] | None

A list of the atoms of the sigma-algebra that have non-zero measure.

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 0,
...     },
... )
>>> for A in mu.non_null_atoms:
...     print(f"Atom id of non-null atom: {A.name}")
Atom id of non-null atom: 0
Atom id of non-null atom: 1

counting classmethod

counting(domain, output_name=None, name='C')

Create a counting measure on a sigma-algebra.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain MeasureDomain

The domain of the measure. Either a SigmaAlgebra or an IndexLike object that can be coerced into a Domain. In the latter case, the domain of the measure will be set to the power set of the domain.

required
output_name Hashable | None

The output name of the measure. If None, a default will be generated.

None
name Hashable

A name for the measure.

"C"

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 0,
...         3: 0,
...     }
... )
>>> print(F)
Sigma algebra 'F':
   F
x
0  0
1  1
2  0
3  0
>>> C = Measure.counting(domain=F)
>>> print(C)
Measure 'C':
   C
F
0  3
1  1
>>> D = Measure.counting(domain=X, name="D")
>>> print(D)
Measure 'D':
   D
x
0  1
1  1
2  1
3  1
Notes

Let \((X,\mathcal{F})\) be a finite measurable space. The counting measure on \(\mathcal{F}\) is the unique measure \(C\) for which

\[ C(A) = |A| \]

for all atoms \(A\) of \(\mathcal{F}\). Here, \(|A|\) is the cardinality of \(A\).

equal

equal(other, are_close=False, rtol=1e-05, atol=1e-08)

Check equality with another measure (as measures).

Two measures are considered equal if they have the same sigma-algebras and identical values for each atom.

Parameters:

Name Type Description Default
other Measure

The other measure to compare with.

required
are_close bool

Check whether the measures are approximately equal.

False
rtol float

Relative tolerance for checking approximate equality. Ignored if are_close is False.

1e-5
atol float

Absolute tolerance for checking approximate equality. Ignored if are_close is False.

1e-8

Returns:

Name Type Description
is_equal bool

True if the two measures are equal, False otherwise.

equal_almost_everywhere

equal_almost_everywhere(
    first, second, rtol=1e-05, atol=1e-08
)

Determine whether two functions are equal almost everywhere.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
first Functin

The first function.

required
second Function

The second function.

required
rtol float

The relative tolerance for np.isclose when comparing the functions.

1e-5
atol float

The absolute tolerance for np.isclose when comparing the functions.

1e-8

Returns:

Name Type Description
equal_ae bool

True if the measurable vectors are equal almost everywhere; False otherwise.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     MeasurableFunction,
...     SigmaAlgebra,
... )

Define a measure space and three measurable functions.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,  # null atom
...         1: 1,
...         2: 1,
...     }
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 2,
...     },
... )
>>> f = MeasurableFunction(
...     domain=X,
...     sig_alg=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 2,
...     },
... )
>>> g = MeasurableFunction(
...     domain=X,
...     sig_alg=F,
...     mapping={
...         0: 0,
...         1: 2,
...         2: 2,
...     },
...     name="g",
... )
>>> h = MeasurableFunction(
...     domain=X,
...     sig_alg=F,
...     mapping={
...         0: 1,
...         1: 3,
...         2: 3,
...     },
...     name="h",
... )

Notice that f and g are equal, except on the atom with identifier 0. However, this is a null atom.

>>> mu.equal_almost_everywhere(f, g)
True

The functions f and h differ on a non-null atom.

>>> mu.equal_almost_everywhere(f, h)
False
Notes

Two functions \(f,g:X \to \mathbb{R}^d\) defined on a measure space \((X, \mathcal{F}, \mu)\) are equal almost everywhere if

\[ \mu \left( \{x \in X : f(x) \neq g(x)\} \right) = 0. \]

from_rand classmethod

from_rand(
    domain,
    num_null_atoms=0,
    kind="measure",
    distribution="uniform",
    max_value=10,
    rate=5.0,
    output_name=None,
    name=None,
    random_state=None,
)

Generate a random measure.

Parameters:

Name Type Description Default
domain MeasureDomain

The domain of the measure. Either a SigmaAlgebra or an IndexLike object that can be coerced into a Domain; in the latter case, the domain will be set to the power set of the domain.

required
num_null_atoms int

The number of atoms in the sigma-algebra that should be assigned a measure of 0.

0
kind Literal['measure', 'probability']

The kind of measure to generate. If 'probability', generates a probability measure using a Dirichlet distribution.

"measure"
distribution Literal['uniform', 'poisson', 'dirichlet']

The type of distribution from which to sample the values of the measure.

"uniform"
max_value int

The maximum value for uniform integer sampling when distribution='uniform'. Integers are sampled from the interval [1, max_value).

10
rate float

The rate parameter for Poisson sampling when distribution='poisson'.

5.0
output_name Hashable | None = None

Output name of measure. If None, a default will be generated.

None
name Hashable | None

The name of the measure. If None, a default will be generated.

None
random_state int | Generator | None

An optional random seed.

None

Returns:

Name Type Description
random_measure Measure

A randomly generated measure.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, Measure, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Define a 1-dimensional domain and a sigma-algebra with four atoms.

>>> X = Domain.from_sequence(size=5, variable_name="x")
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 3])))

Generate a random measure with values drawn from a uniform distribution on the integers in [1, 10) and with one null atom.

>>> mu = Measure.from_rand(
...     domain=F,
...     num_null_atoms=1,
...     random_state=rng,
... )
>>> print(mu)
Measure 'mu':
   mu
F
0   0
1   6
2   1
3   7

Generate a random measure with values drawn from a Poisson distribution with rate=5.0.

>>> nu = Measure.from_rand(
...     domain=F,
...     distribution="poisson",
...     random_state=rng,
...     name="nu",
... )
>>> print(nu)
Measure 'nu':
   nu
F
0   2
1   7
2   7
3   5

Generate a random probability measure with values drawn from a Dirichlet distribution.

>>> P = Measure.from_rand(
...     domain=F,
...     num_null_atoms=1,
...     distribution="dirichlet",
...     random_state=rng,
... )
>>> print(P)
Probability measure 'P':
          P
F
0  0.164599
1  0.535368
2  0.300033
3  0.000000

get_random_set

get_random_set(
    num_atoms, is_null=False, name="A", random_state=None
)

Get a random (possibly null) measurable set from the sigma-algebra of the measure.

Parameters:

Name Type Description Default
num_atoms int

The number of atoms to include in the random set.

required
is_null bool

If True, the random set will be a null set.

False
name Hashable

The name of the random set.

"A"
random_state int | Generator | None

An optional random seed.

None

Returns:

Name Type Description
random_set MeasurableSet

A random measurable set from the sigma-algebra of the measure.

is_absolutely_continuous

is_absolutely_continuous(other, tol=1e-08)

Determine whether the current measure is absolutely continuous with respect to another.

See the Notes section below for the mathematical details,

Parameters:

Name Type Description Default
other Measure

The other measure.

required
tol float

A tolerance below which a quantity will be deemed 0.

1e-8

Returns:

Name Type Description
is_absolutely_continuous bool

A Boolean flagging whether the current measure is absolutely continuous with respect to the other measure.

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra

Define three measures on a measurable space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 2,
...     },
... )
>>> nu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...     },
...     name="nu",
... )
>>> xi = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 0,
...     },
...     name="xi",
... )

Notice that the only nu-null atom is also mu-null, and so mu is absolutely continuous with respect to nu.

>>> mu.is_absolutely_continuous(nu)
True

Note that there is a xi-null atom that is not mu-null, and so mu is not absolutely continuous with respect to xi.

>>> mu.is_absolutely_continuous(xi)
False

We can check these conditions using the << operators, as well.

>>> mu << nu
True
>>> mu << xi
False
Notes

Let \(\mu\) and \(\nu\) be two measures on a measurable space \((X,\mathcal{F})\). We shall say that \(\mu\) is absolutely continuous with respect to \(\nu\), and write \(\mu \ll \nu\), provided that \(\mu(U)=0\) whenever \(\nu(U)=0\), for all \(U\in \mathcal{F}\).

is_restriction_of

is_restriction_of(other, rtol=1e-05, atol=1e-08)

Check whether this measure is the restriction of the other measure to a sub-sigma-algebra.

Parameters:

Name Type Description Default
other Measure

The other measure to compare to.

required
rtol float

Relative tolerance for comparing values.

1e-5
atol float

Absolute tolerance for comparing values.

1e-8

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra

Define a measurable space and a sub-sigma-algebra.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...     },
...     name="G",
... )

Define two measures.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...     },
... )
>>> nu = Measure(
...     domain=G,
...     mapping={
...         0: 1,
...         1: 5,
...     },
...     name="nu",
... )

Note that nu is indeed the restriction of mu to the sub-sigma-algebra G.

>>> nu.is_restriction_of(mu)
True

Returns:

Name Type Description
is_le bool

True if this measure is the restriction of the other measure or is equal to it, False otherwise.

restrict_to

restrict_to(
    obj, normalize=False, subset_name="A", name=None
)

Restrict the measure to either a sub-sigma-algebra or measurable set.

Parameters:

Name Type Description Default
obj SigmaAlgebra | Set | list[Hashable]

The sub-sigma-algebra or Set instance to which to restrict the measure.

required
normalize bool

If the measure is restricted to a Set instance, whether to normalize the measure values to create a probability measure.

False
subset_name Hashable | None

The name of the subset. Only used if obj is a list[Hashable].

"A"
name Hashable | None

The name of the restriction. If None, a default will be generated.

None

Returns:

Name Type Description
measure Measure

The restricted measure.

Examples:

>>> from sigalg.core import Domain, Measure, Set, SigmaAlgebra

Define a sigma-algebra, a sub-sigma-algebra, and a measure on the larger sigma-algebra.

>>> X = Domain.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...         4: 2,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 0,
...         3: 1,
...         4: 1,
...     },
...     name="G",
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 3,
...         2: 4,
...     },
... )

Restrict the measure using the restrict_to method.

>>> mu_G = mu.restrict_to(G)
>>> print(mu_G)
Measure 'mu|G':
     mu|G
G
0       4
1       4

Restrict the measure using the | operator.

>>> mu_G = mu | G
>>> print(mu_G)
Measure 'mu|G':
     mu|G
G
0       4
1       4

We may also restrict the measure to a subset in its sigma-algebra.

>>> U = Set([2, 3, 4], domain=X, name="U")
>>> print(mu | U)
Measure 'mu|U':
   mu|U
F
1     3
2     4

We may use the restrict_to method, passing normalize=True, to create a probability measure.

>>> P = mu.restrict_to(U, normalize=True, name="P")
>>> print(P)
Probability measure 'P':
          P
F
1  0.428571
2  0.571429

to_function

to_function()

Promote to a Function instance.

with_variable_names

with_variable_names(variable_names, name=None)

Set the variable names of the underlying sigma-algebra of the measure and return a new instance.

Parameters:

Name Type Description Default
variable_names list[Hashable]

The new variable names.

required
name Hashable | None

The name of the new measure. If None, the name of the current instance will be used.

None

Returns:

Name Type Description
renamed_measure Measure

A new instance with the given variable names.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra, Measure

Define a measure. Notice the custom variable name of the sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
...     variable_names=["u"],
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 3,
...         1: 4,
...     },
... )
>>> print(mu)
Measure 'mu':
   mu
u
0   3
1   4

Set the variable names of the measure.

>>> nu = mu.with_variable_names(["v"])
>>> print(nu)
Measure 'mu':
   mu
v
0   3
1   4

The variable names of the underlying sigma-algebra change accordingly.

>>> print(nu.sig_alg.variable_names)
['v']

MeasureSpace

Bases: SigmaAlgebraMethods

A class representing a measure space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain Domain | IndexLike | None

The domain of the measure space.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the measure space.

None
measure Measure | None

The measure of the measure space.

None

Examples:

>>> from sigalg.core import Domain, Measure, MeasureSpace, SigmaAlgebra

Build a MeasureSpace object with only a domain. The power-set sigma-algebra and counting measure will be generated by default.

>>> X = Domain.from_sequence(size=3)
>>> measure_space = MeasureSpace(domain=X)
>>> print(measure_space)
Measure space (X, R, C)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'R':
     R
x
0    0
1    1
2    2

* Measure 'C':
      C
x
0     1
1     1
2     1

Create a measure space with a custom sigma-algebra and measure.

>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...     },
... )
>>> measure_space = MeasureSpace(domain=X, sig_alg=F, measure=mu)
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    1
2    1

* Measure 'mu':
        mu
F
0        1
1        2

The same measure space as the latter one may be created with only the measure as an explicit parameter. The sigma-algebra of the measure space is automatically extracted from the measure, and the domain is automatically extracted from the sigma-algebra. Thus, the minimal amount of information needed to construct a MeasureSpace object is just the measure.

>>> measure_space = MeasureSpace(measure=mu)
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
    x
    0
    1
    2

* Sigma algebra 'F':
        F
x
0    0
1    1
2    1

* Measure 'mu':
        mu
F
0        1
1        2
Notes

A measure space is a triple \((X, \mathcal{F}, \mu)\) consisting of a nonempty set \(X\), a \(\sigma\)-algebra \(\mathcal{F}\) on \(X\), and a measure \(\mu\) defined on \(\mathcal{F}\).

domain cached property

domain

Get the domain of the measure space.

Returns:

Name Type Description
domain Domain | None

The domain of the measure space.

Examples:

Define a measure space and print it.

>>> from sigalg.core import Domain, Measure, MeasureSpace, SigmaAlgebra
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 2,
...         1: 3,
...         2: 5,
...     },
... )
>>> measure_space = MeasureSpace(X, F, mu)
>>> print(measure_space.domain)
Domain 'X':
 x
 0
 1
 2
 3

measurable_space cached property

measurable_space

Get the measurable space of the measure space.

Returns:

Name Type Description
measurable_space MeasurableSpace

The measurable space of the measure space.

Examples:

>>> from sigalg.core import Domain, Measure, MeasureSpace, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...     },
... )
>>> measure_space = MeasureSpace(domain=X, sig_alg=F, measure=mu)
>>> print(measure_space.measurable_space)
Measurable space (X, F)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
   F
x
0  0
1  1
2  1

sig_alg cached property

sig_alg

Get the sigma-algebra of the measure space.

Returns:

Name Type Description
sig_alg SigmaAlgebra

The sigma-algebra of the measure space.

Examples:

Define a measure space and print it.

>>> from sigalg.core import Domain, Measure, MeasureSpace, SigmaAlgebra
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 2,
...         1: 3,
...         2: 5,
...     },
... )
>>> measure_space = MeasureSpace(X, F, mu)
>>> print(measure_space.sig_alg)
Sigma algebra 'F':
   F
x
0  0
1  1
2  2
3  2

from_rand classmethod

from_rand(
    domain_size,
    domain_dim=1,
    domain_range=None,
    domain_variable_names=None,
    domain_name=None,
    num_atoms=1,
    sig_alg_dim=1,
    atom_ID_range=None,
    sig_alg_variable_names=None,
    num_null_atoms=0,
    sig_alg_name="F",
    measure_distribution="uniform",
    measure_max_value=10,
    measure_rate=5.0,
    measure_name=None,
    random_state=None,
)

Generate a random measure space.

Parameters:

Name Type Description Default
domain_size int

The number of points in the domain of the measure space.

required
domain_dim int

The dimension of the sample space.

1
domain_range tuple[int, int] | None

The range of values for the sample space. If None, the sample space values will be drawn from the range [0, domain_size).

None
domain_variable_names list[Hashable] | None

The variable names for the sample space.

None
domain_name Hashable | None

The name of the sample space.

None
num_atoms int

The number of atoms in the sigma-algebra of the measure space.

1
sig_alg_dim int

The dimension of the sigma-algebra.

1
atom_ID_range tuple[int, int] | None

The range of atom IDs. If None, the atom IDs will be drawn from the range [0, num_atoms).

None
sig_alg_variable_names list[Hashable] | None

The variable names for the sigma-algebra.

None
num_null_atoms int

The number of null atoms in the sigma-algebra.

0
sig_alg_name Hashable

The name of the sigma-algebra.

"F"
measure_distribution Literal["uniform", "poisson", "dirichlet]

The distribution from which to draw the values of the measure. If uniform, the values are drawn from the set of integers in [measure_min_value, measure_max_value); if poisson, the values are drawn from a Poisson distribution with rate equal to measure_rate; if dirichlet, the values are drawn from a Dirichlet distribution with concentration parameters equal to 1, producing a probability measure.

"uniform"
measure_max_value int

The maximum value for the uniform measure distribution.

10
measure_rate float

The rate parameter for the poisson measure distribution.

5.0
measure_name Hashable | None

The name of the measure.

None
random_state int | Generator | None

The random state for reproducibility.

None

Returns:

Name Type Description
measure_space MeasureSpace

A new randomly generated measure space with the specified parameters.

Examples:

>>> from sigalg.core import MeasureSpace

Generate a random measure space with a 1-dimensional domain containing five points, sigma-algebra with 1-dimensional atom IDs and three atoms, and a measure with values drawn randomly from the integers in the (default) interval [1,10).

>>> measure_space = MeasureSpace.from_rand(
...     domain_size=5,
...     num_atoms=3,
...     random_state=42,
... )
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
x
4
1
0
3
2

* Sigma algebra 'F':
F
x
4  1
1  1
0  0
3  1
2  2

* Measure 'mu':
mu
F
1   5
0   5
2   4

Now, suppose we draw the measure values from a Dirichlet distribution. This will produce a random probability space.

>>> prob_space = MeasureSpace.from_rand(
...     domain_size=5,
...     num_atoms=3,
...     measure_distribution="dirichlet",
...     random_state=42,
... )
>>> print(prob_space)
Probability space (Omega, F, P)
===============================

* Sample space 'Omega':
omega
    4
    1
    0
    3
    2

* Sigma algebra 'F':
    F
omega
4      1
1      1
0      0
3      1
2      2

* Probability measure 'P':
        P
F
1  0.598912
0  0.024366
2  0.376722

Now, we generate a random measure space with a 4-dimensional sample space of cardinality 10 with custom variable names, a sigma-algebra with 5 atoms and 2-dimensional identifiers with custom names, and a measure with values drawn from a Poisson distribution with (default) rate parameter 5.0.

>>> measure_space = MeasureSpace.from_rand(
...     domain_size=10,
...     domain_dim=4,
...     domain_variable_names=["x", "y", "z", "w"],
...     num_atoms=5,
...     sig_alg_dim=2,
...     sig_alg_variable_names=["A", "B"],
...     measure_distribution="poisson",
...     random_state=42,
... )
>>> print(measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x  y  z  w
 2  8  4  5
 9  3  1  9
 1  7  6  4
 6  8  5  4
 3  4  2  0
 8  5  8  0
 5  8  8  2
 7  6  1  7
 4  7  3  0
 0  9  4  8

* Sigma algebra 'F':
i        0  1
x y z w
2 8 4 5  3  3
9 3 1 9  4  3
1 7 6 4  4  3
6 8 5 4  1  4
3 4 2 0  2  2
8 5 8 0  2  2
5 8 8 2  0  0
7 6 1 7  4  3
4 7 3 0  4  3
0 9 4 8  2  2

* Measure 'mu':
     mu
A B
3 3   8
4 3   5
1 4   1
2 2   8
0 0   7

from_set classmethod

from_set(subset, measure, normalize=False)

Create a measure space from a measurable set.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
subset Set

The subset on which to create the measure space

required
measure Measure

The measure to use for the measure space.

required
normalize bool

Whether to normalize the measure to create a probability space.

False

Returns:

Name Type Description
set_measure_space MeasureSpace

A new measure space built from the measurable set.

Examples:

>>> from sigalg.core import Domain, Measure, MeasureSpace, SigmaAlgebra

Define a measure space.

>>> X = Domain.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 2,
...         5: 0,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...     },
... )
>>> measure_space = MeasureSpace(X, F, mu)

Extract a subset from the sigma-algebra of the measure space, and build a measure space from it. The sigma-algebra and measure of the new measure space are just the restrictions of the original ones.

>>> A = measure_space.get_set([1, 2, 3, 4])
>>> set_space = MeasureSpace.from_set(A, mu)
>>> print(set_space)
Measure space (A, F|A, mu|A)
============================

* Domain 'A':
 x
 1
 2
 3
 4

* Sigma algebra 'F|A':
   F|A
x
1    1
2    1
3    2
4    2

* Measure 'mu|A':
        mu|A
F
1          2
2          3

Now, create a new measure space from the same set, but normalize the measure to create a probability space.

>>> conditional_space = MeasureSpace.from_set(A, mu, normalize=True)
>>> print(conditional_space)
Probability space (A, F|A, mu|A)
================================

* Domain 'A':
 x
 1
 2
 3
 4

* Sigma algebra 'F|A':
   F|A
x
1    1
2    1
3    2
4    2

* Probability measure 'mu|A':
         mu|A
F
1         0.4
2         0.6
Notes

Let \(A\) be a nonempty measurable set in a measure space \((X,\mathcal{F},\mu)\). We may create a measure space \((A,\mathcal{F}_A,\mu_A)\) where \(\mathcal{F}_A\) is the \(\sigma\)-algebra consisting of all \(B\in \mathcal{F}\) with \(B\subset A\), and \(\mu_A\) is the measure on \(\mathcal{F}_A\) defined so that \(\mu_A(B) = \mu(B)\).

Provided that \(\mu(A) \neq 0\), we may replace \(\mu_A\) with \(\mu_A/\mu(A)\) and then \((A,\mathcal{F}_A,\mu_A/\mu(A))\) is a probability space.

get_random_set

get_random_set(
    num_atoms, is_null=False, name="A", random_state=None
)

Get a random measurable (possibly null) set consisting of a specified number of atoms.

Parameters:

Name Type Description Default
num_atoms int

The number of atoms to include in the random measurable set. If is_null is True, num_atoms will be reset to the minimum of num_atoms and the number of null atoms in the measure space.

required
is_null bool

If True, the random set will be a null set.

False
name Hashable

Name identifier for the set.

"A"
random_state int | Generator | None

The random state for reproducibility.

None

Returns:

Name Type Description
random_set MeasurableSet

A random measurable set consisting of the specified number of atoms. If is_null is True, the set will be a null set.

is_subspace

is_subspace(other)

Check if this measure space is a subspace of another measure space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
other MeasureSpace

The other measure space to compare with.

required

Returns:

Name Type Description
is_subspace bool

True if this measure space is a subspace of the other measure space, False otherwise.

Examples:

Define a measure space that will serve as a subspace of another.

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     MeasureSpace,
...     SigmaAlgebra,
... )
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 4,
...         1: 6,
...     },
... )
>>> sub_space = MeasureSpace(X, F, mu)

Define a measure space that will serve as the superspace.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
...     name="G",
... )
>>> nu = Measure(
...     domain=G,
...     mapping={
...         0: 4,
...         1: 4,
...         2: 2,
...     },
... )

Check the sub- and super-space relations.

>>> super_space = MeasureSpace(X, G, nu)
>>> print(sub_space.is_subspace(super_space))
True
>>> print(super_space.is_subspace(sub_space))
False
Notes

A measure space \((X, \mathcal{F}, \mu)\) is a subspace of another measure space \((X, \mathcal{G}, \nu)\) if \(\mathcal{F} \subseteq \mathcal{G}\) and \(\mu\) is the restriction of \(\nu\) to \(\mathcal{F}\). In other words, every set in \(\mathcal{F}\) is also a set in \(\mathcal{G}\), and the measure assigned to each set in \(\mathcal{F}\) by \(\mu\) is the same as the measure assigned to that set by \(\nu\) when restricted to \(\mathcal{F}\).

to_prob_space

to_prob_space(measure=None, in_place=False)

Convert the measure space to a probability space.

Parameters:

Name Type Description Default
measure ProbabilityMeasure | None

The probability measure to use for the conversion. If not provided, the current measure will be used if it is a probability measure; otherwise, a uniform probability measure will be created on the current sigma-algebra.

None
in_place bool

If True, convert the current measure space to a probability space in place. If False, return a new ProbabilitySpace instance without modifying the current measure space.

False

Returns:

Name Type Description
prob_space ProbabilitySpace | None

If in_place is False, returns a new ProbabilitySpace instance. If in_place is True, returns None and modifies the current measure space to become a ProbabilitySpace.

Examples:

Create a measure space with a non-probability measure.

>>> from sigalg.core import Domain, Measure, MeasureSpace, ProbabilityMeasure, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...     },
... )
>>> measure_space = MeasureSpace(X, F, mu)

Promote to a probability space. The measure mu will be replaced with the uniform probability measure on the sigma-algebra.

>>> prob_space = measure_space.to_prob_space()
>>> print(prob_space)
Probability space (X, F, U)
===========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    0
2    1

* Probability measure 'U':
        U
F
0     0.5
1     0.5

Define a probability measure and promote the measure space to a probability space using that measure.

>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.8,
...     },
... )
>>> prob_space = measure_space.to_prob_space(measure=P)
>>> print(prob_space)
Probability space (X, F, P)
===========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
     F
x
0    0
1    0
2    1

* Probability measure 'P':
        P
F
0     0.2
1     0.8

Operators

Class for operators on functions.

corr classmethod

corr(rv1, rv2, given=None, measure=None)

Compute the correlation of two random variables, optionally conditioned on a sigma-algebra.

See the Notes section below for the mathematical details.

User note: This is a naive reference implementation. It is slow.

Parameters:

Name Type Description Default
rv1 RandomVariable

The first random variable for which to compute the correlation.

required
rv2 RandomVariable

The second random variable for which to compute the correlation

required
given SigmaAlgebra | None

The sigma-algebra to condition on. If None, the trivial sigma-algebra is used.

None
measure ProbabilityMeasure | None

The probability used to compute the correlation. If None, the common probability measure carried by the random variables is used (accessed through their prob_measure attribute).

None

Returns:

Name Type Description
corr RandomVariable

The correlation of the two random variables.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space along with two random variables.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.0,
...         2: 0.2,
...         3: 0.2,
...         4: 0.15,
...         5: 0.45,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 1,
...         4: 5,
...         5: 0,
...     },
... )
>>> Y = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 3,
...         1: 4,
...         2: 1,
...         3: 0,
...         4: 0,
...         5: 8,
...     },
...     name="Y",
... )

Give aliases to the std, cov, and corr methods.

>>> std = Operators.std
>>> cov = Operators.cov
>>> corr = Operators.corr

Check that the unconditional correlation is equal to its definition.

>>> corr(X, Y) == cov(X, Y) / (std(X) * std(Y))
True

Define a sigma-algebra for conditional correlation.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
...     name="G",
... )

Check that the conditional correlation of the random variables is equal to a linear combination of indicators weighted by unconditional correlations.

>>> np.allclose(corr(X, Y, G), sum(corr(X | B, Y | B).item() * B.indicator for B in G if P(B) > 0))
True
Notes

Let \(X,Y:\Omega \to \mathbb{R}\) be two random variables on a finite probability space \((\Omega, \mathcal{F},P)\), and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional correlation of \(X\) and \(Y\) with respect to \(\mathcal{G}\) is any \(\mathcal{G}\)-measurable random variable that is equal almost surely to the random variable

\[ \rho(X,Y\mid \mathcal{G}) = \frac{\sigma(X, Y \mid \mathcal{G})}{\sigma(X \mid \mathcal{G}) \sigma(Y \mid \mathcal{G})}. \]

The unconditional correlation of \(X\) and \(Y\), denoted \(\rho(X, Y)\), is the case when \(\mathcal{G}\) is the trivial \(\sigma\)-algebra with \(\Omega\) as its only atom. The unconditional correlation is a constant random variable.

The \(\sigma\)-algebra \(\mathcal{G}\) is determined by its (finitely many) atoms, and we have the following formula for a conditional correlation:

\[ \rho(X,Y\mid \mathcal{G}) = \sum_B \rho(X|_B, Y|_B) I_B, \]

where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability, and where \(\rho(X|_B, Y|_B)\) is the unconditional correlation of the restricted random variables \(X|_B, Y|_B:B\to \mathbb{R}\).

cov classmethod

cov(rv1, rv2, given=None, measure=None)

Compute the covariance of two random variables, optionally conditioned on a sigma-algebra.

See the Notes section below for the mathematical details.

User note: This is a naive reference implementation. It is slow.

Parameters:

Name Type Description Default
rv1 RandomVariable

The first random variable for which to compute the covariance.

required
rv2 RandomVariable

The second random variable for which to compute the covariance

required
given SigmaAlgebra | None

The sigma-algebra to condition on. If None, the trivial sigma-algebra is used.

None
measure ProbabilityMeasure | None

The probability used to compute the covariance. If None, the common probability measure carried by the random variables is used (accessed through their prob_measure attribute).

None

Returns:

Name Type Description
cov RandomVariable

The covariance of the random variables.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space along with two random variables.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.0,
...         2: 0.2,
...         3: 0.2,
...         4: 0.15,
...         5: 0.45,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 1,
...         4: 5,
...         5: 0,
...     },
... )
>>> Y = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 3,
...         1: 4,
...         2: 1,
...         3: 0,
...         4: 0,
...         5: 8,
...     },
...     name="Y",
... )

Give aliases to the expectation and cov methods.

>>> E = Operators.expectation
>>> cov = Operators.cov

Check that the unconditional covariance is equal to its definition.

>>> cov(X, Y) == E(X * Y) - E(X) * E(Y)
True

Define a sigma-algebra for conditional covariance.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
...     name="G",
... )

Check that the conditional covariance of the random variables is equal to a linear combination of indicators weighted by unconditional covariances.

>>> np.allclose(cov(X, Y, G), sum(cov(X | B, Y | B).item() * B.indicator for B in G if P(B) > 0))
True
Notes

Let \(X,Y:\Omega \to \mathbb{R}\) be two random variables on a finite probability space \((\Omega, \mathcal{F},P)\), and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional covariance of \(X\) and \(Y\) with respect to \(\mathcal{G}\) is any \(\mathcal{G}\)-measurable random variable that is equal almost surely to the random variable

\[ \sigma(X,Y\mid \mathcal{G}) = E(XY \mid \mathcal{G}) - E(X\mid \mathcal{G})E(Y\mid \mathcal{G}). \]

The unconditional covariance of \(X\) and \(Y\), denoted \(\sigma(X, Y)\), is the case when \(\mathcal{G}\) is the trivial \(\sigma\)-algebra with \(\Omega\) as its only atom. The unconditional covariance is a constant random variable.

The \(\sigma\)-algebra \(\mathcal{G}\) is determined by its (finitely many) atoms, and we have the following formula for a conditional covariance:

\[ \sigma(X,Y\mid \mathcal{G}) = \sum_B \sigma(X|_B, Y|_B) I_B, \]

where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability, and where \(\sigma(X|_B, Y|_B)\) is the unconditional covariance of the restricted random variables \(X|_B, Y|_B:B\to \mathbb{R}\).

cross_entropy classmethod

cross_entropy(rv1, rv2, given=None, base='2', tol=1e-08)

Compute the cross entropy from an initial random variable to a second one, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
rv1 RandomVariable

The initial random variable of the cross entropy. See the Notes section for an explanation of the "initial" terminology.

required
rv2 RandomVariable

The terminal random variable of the cross entropy. See the Notes section for an explanation of the "terminal" terminology.

required
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the cross entropy.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the cross entropy.

"2"
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
cross_entropy Real

The cross entropy from the intitial random variable to the terminal one.

Examples:

>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a pair of random variables on a probability space.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 3,
...         4: 3,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.5,
...         1: 0.4,
...         2: 0.1,
...         3: 0.0,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: 1,
...         1: -1,
...         2: 4,
...         3: 5,
...         4: 5,
...     },
... )
>>> Y = RandomVariable(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: 4,
...         1: 1,
...         2: -1,
...         3: 5,
...         4: 5,
...     },
...     name="Y",
... )

Compute the cross entropy from X to Y.

>>> H = Operators.cross_entropy
>>> H(X, Y)
2.089735285398626

Check that this computation agrees with the mathematical definition of the cross entropy as an integral.

>>> H(X, Y) == (P >> Y).surprisal().integrate(measure=(P >> X).with_variable_names(["Y"]))
True

Define a sub-sigma-algebra for conditional cross entropy.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 1,
...     },
...     name="G",
... )

Compute the conditional cross entropy.

>>> H(X, Y, given=G)
0.5849625007211562

Check that this computation agrees with the mathematical definition of the conditional cross entropy as a double integral.

>>> H(X, Y, given=G) == (P.conditional(G) >> Y).surprisal().integrate(
...     measure=(P.conditional(G) >> X).with_variable_names(["Y"])
... ).ascend(G).integrate(measure=P)
True

cumavg classmethod

cumavg(vec, name=None)

Compute the cumulative average of a measurable vector along its index.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector for which to compute the cumulative average.

required
name Hashable | None

The name of the transformed vector. If None, a default name will be generated.

None

Returns:

Name Type Description
cumavg_vector MeasurableVector

A new measurable vector representing the cumulative average of the input vector.

Examples:

>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import RandomWalk
>>> T = Time.discrete(length=3)
>>> X = RandomWalk.generate(mode="enum", p=0.5, initial_state=3, index=T)
>>> print(X)
Random walk 'X':
t           0  1  2  3
omega
0           3  2  1  0
1           3  2  1  2
2           3  2  3  2
3           3  2  3  4
4           3  4  3  2
5           3  4  3  4
6           3  4  5  4
7           3  4  5  6
>>> X_cumavg = Operators.cumavg(X)
>>> print(X_cumavg)
Stochastic process 'X_cumavg':
t        0    1         2    3
omega
0      3.0  2.5  2.000000  1.5
1      3.0  2.5  2.000000  2.0
2      3.0  2.5  2.666667  2.5
3      3.0  2.5  2.666667  3.0
4      3.0  3.5  3.333333  3.0
5      3.0  3.5  3.333333  3.5
6      3.0  3.5  4.000000  4.0
7      3.0  3.5  4.000000  4.5

cumprod classmethod

cumprod(vec, name=None)

Compute the cumulative product of a measurable vector along its index.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector for which to compute the cumulative product.

required
name Hashable | None

The name of the transformed vector. If None, a default name will be generated.

None

Returns:

Name Type Description
cumprod_vector MeasurableVector

A new measurable vector representing the cumulative product of the input vector.

Examples:

>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import RandomWalk
>>> T = Time.discrete(length=3)
>>> X = RandomWalk.generate(mode="enum", p=0.5, initial_state=3, index=T)
>>> print(X)
Random walk 'X':
t           0  1  2  3
omega
0           3  2  1  0
1           3  2  1  2
2           3  2  3  2
3           3  2  3  4
4           3  4  3  2
5           3  4  3  4
6           3  4  5  4
7           3  4  5  6
>>> X_cumprod = Operators.cumprod(X)
>>> print(X_cumprod)
Stochastic process 'X_cumprod':
t           0   1   2    3
omega
0           3   6   6    0
1           3   6   6   12
2           3   6  18   36
3           3   6  18   72
4           3  12  36   72
5           3  12  36  144
6           3  12  60  240
7           3  12  60  360

cumsum classmethod

cumsum(vec, name=None)

Compute the cumulative sum of a measurable vector along its index.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector for which to compute the cumulative sum.

required
name Hashable | None

The name of the transformed measurable vector. If None, a default name will be generated.

None

Returns:

Name Type Description
cumsum_vector MeasurableVector

A new measurable vector representing the cumulative sum of the input vector.

Examples:

>>> from scipy.stats import bernoulli
>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import IIDProcess
>>> T = Time.discrete(start=1, length=2)
>>> X = IIDProcess.generate(mode="enum", distribution=bernoulli(p=0.6), support=[0, 1], index=T)
>>> print(X)
IID process 'X':
t           1  2  3
omega
0           0  0  0
1           0  0  1
2           0  1  0
3           0  1  1
4           1  0  0
5           1  0  1
6           1  1  0
7           1  1  1
>>> X_cumsum = Operators.cumsum(X)
>>> print(X_cumsum)
Stochastic process 'X_cumsum':
t           1  2  3
omega
0           0  0  0
1           0  0  1
2           0  1  1
3           0  1  2
4           1  1  1
5           1  1  2
6           1  2  2
7           1  2  3

density classmethod

density(rv, given=None, name=None, tol=1e-08)

Pass.

divergence classmethod

divergence(rv1, rv2, given=None, base='2', tol=1e-08)

Compute the Kullback Leibler divergence from an initial random variable to a second one, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
rv1 RandomVariable

The initial random variable of the divergence. See the Notes section for an explanation of the "initial" terminology.

required
rv2 RandomVariable

The terminal random variable of the divergence. See the Notes section for an explanation of the "terminal" terminology.

required
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the divergence.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the divergence.

"2"
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
divergence Real

The KL divergence from the intitial random variable to the terminal one.

Examples:

>>> from sigalg.core import Operators
>>> from sigalg.processes import MarkovChain

We will demonstrate attempting to fit an order-1 Markov chain model to data generated by an order-2 Markov chain, and assessing goodness of fit using conditional KL divergence.

First, define the data generating process, an order-2 Markov chain of length 4.

>>> X = MarkovChain.from_rand(
...     order=2,
...     mode="enum",
...     length=4,
...     random_state=42,
... )
>>> P = X.measure

Extract the marginal distribution of X[0] and the conditional distribution P(X[1]|X[0]) from the order-2 chain and use these to define an order-1 Markov chain.

>>> Y = MarkovChain.generate(
...     kernel=P.conditional(X[0]) >> X[1],
...     initial_distribution=P >> X[0],
...     mode="enum",
...     length=4,
...     name="Y",
... )
>>> Q = Y.measure

For demonstration purposes, we will condition on the random variable X[1]. If the Markov chain Y provides a good fit for the data generated by X, then we should expect that the conditional distributions of Y[2] and X[2] should be near each other, as measured by KL divergence. We check this.

>>> D = Operators.divergence
>>> D(X[2], Y[2], given=X[1])
1.4017398209976233

entropy classmethod

entropy(rv, given=None, base='2', tol=1e-08)

Compute the entropy of a random variable with respect to a base measure, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
rv RandomVariable

The random variable whose entropy is to be computed.

required
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the entropy.

None
base Real | Literal['2', 'e', '10']

The base of the logarithm used to compute the entropy.

"2"
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
entropy Real

The entropy of the random variable.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a random variable on a probability space.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 3,
...         4: 3,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.5,
...         1: 0.4,
...         2: 0.1,
...         3: 0.0,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 5,
...         2: 4,
...         3: -1,
...         4: -1,
...     },
... )

Compute the entropy of the random variable.

>>> H = Operators.entropy
>>> H(X)
1.360964047443681

We may check that the entropy is the integral of the surprisal of the pushforward measure.

>>> P_X = P >> X
>>> H(X) == (P_X).surprisal().integrate(measure=P_X)
True

Define a sub-sigma-algebra for conditional entropy.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 1,
...     },
...     name="G",
... )

Compute the conditional entropy.

>>> H(X, G)
0.8919684538543998

Check that the conditional entropy agrees with its mathematical definition as a double integral.

>>> P_X_G = P.conditional(G) >> X
>>> np.allclose(H(X, G), P_X_G.surprisal().integrate(measure=P_X_G).ascend(G).integrate(measure=P))
True

expectation classmethod

expectation(rv, given=None, measure=None, name=None)

Compute the expectation of a random vector, optionally conditioned on a sigma-algebra.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
rv RandomVector

The random vector for which to compute the expectation.

required
given SigmaAlgebra | RandomVector | None

The sigma-algebra or random vector to condition on. If None, the trivial sigma-algebra is used.

None
measure ProbabilityMeasure | None

The probability measure with respect to which to integrate. If None, the probability measure of the underlying probability space of the random vector is used.

None
name Hashable | None

The name to assign to the resulting expected value random vector. If None, a default name is generated.

None

Returns:

Name Type Description
exp RandomVector

The expected value of the random vector.

Examples:

>>> from sigalg.core import (
...     Function,
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space along with a 1-dimensinonal random variable and a 2-dimensional random vector.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.0,
...         2: 0.2,
...         3: 0.2,
...         4: 0.15,
...         5: 0.45,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 1,
...         4: 5,
...         5: 0,
...     },
... )
>>> Y = RandomVector(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: (1, 3),
...         1: (2, 4),
...         2: (3, 1),
...         3: (1, 0),
...         4: (5, 0),
...         5: (0, 8),
...     },
...     name="Y",
... )

Give aliases to the integrate and expectation methods, and get the constant random variable whose unique value is 1.

>>> E = Operators.expectation
>>> integrate = Operators.integrate
>>> one = Function.from_constant(domain=Omega, constant=1)

Check that the unconditional expectation of the random variable X is equal to the constant random variable whose unique value is the Lebesgue integral of the random variable.

>>> E(X) == integrate(X) * one
True

Compute the unconditional expectation of the random vector Y, and check that its components are the unconditional expectations of the components of Y.

>>> all(E_Y_i == integrate(Y_i) * one for E_Y_i, Y_i in zip(E(Y), Y))
True

Define a sigma-algebra for conditional expectations.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
...     name="G",
... )

Check that the conditional expectation of the random variable X is equal to its Fourier expansion.

>>> E(X, G) == sum(integrate(X, B) / P(B) * B.indicator for B in G if P(B) != 0)
True

Check the same for the components of the conditional expectation of the random vector Y.

>>> all(
...     E_Y_i_G == sum(integrate(Y_i, B) / P(B) * B.indicator for B in G if P(B) != 0)
...     for E_Y_i_G, Y_i in zip(E(Y, G), Y)
... )
True
Notes

Let \(X:\Omega \to \mathbb{R}\) be a random variable on a finite probability space \((\Omega, \mathcal{F},P)\), and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional expectation of \(X\) with respect to \(\mathcal{G}\) is any \(\mathcal{G}\)-measurable random variable \(E(X\mid \mathcal{G})\) for which

\[ \int_V E(X\mid \mathcal{G}) \, dP = \int_V X \, dP, \]

for all \(V\in \mathcal{G}\). All such random variables are equal almost surely.

The \(\sigma\)-algebra \(\mathcal{G}\) is determined by its (finitely many) atoms, and we have the following formula for a conditional expectation called a Fourier expansion:

\[ E(X\mid \mathcal{G}) = \sum_B \frac{\int_B X \, dP}{P(B)} I_B, \]

where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability and \(I_B\) is the indicator function of \(B\).

The unconditional expectation of \(X\), denoted \(E(X)\), is the case when \(\mathcal{G}\) is the trivial \(\sigma\)-algebra with \(\Omega\) as its only atom. In this case \(E(X)\) is the constant random variable with

\[ E(X)(\omega) = \int_\Omega X \, dP, \]

for all \(\omega\in \Omega\).

If \(X : \Omega \to \mathbb{R}^d\) is a random vector of dimension \(d>1\), with components

\[ X = (X_1,X_2,\ldots,X_d), \]

then we define the conditional expectation to be the \(d\)-dimensional vector whose entries are the separate conditional expectations \(E(X_j \mid \mathcal{G})\), for \(j=1,2,\ldots,d\).

integrate classmethod

integrate(
    function, subset=None, measure=None, subset_name=None
)

Compute the Lebesgue integral of a measurable vector with respect to a measure over an (optional) set.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
function MeasurableVector | ParametrizedMeasurableFunction

The measurable vector or parametrized measurable function to integrate.

required
subset Set | list[Hashable] | None

The optional set over which to integrate. If None, the integral will be taken over the entire domain of the measurable vector.

None
measure Measure | ParametrizedMeasure | None

The measure or parametrized measure with respect to which to integrate. If None, the measure of the underlying measure space is used (if it exists) carried by the measurable vector or parametrized measurable function.

None
subset_name Hashable | None

If the subset is passed as a list, the name that will be assigned to the subset. Ignored otherwise.

None

Returns:

Name Type Description
integral Real | Series | Function

Returns the following:

  • If function is a MeasurableFunction and measure is a Measure, returns a Real representing the integral of the function with respect to the measure over the specified set.

  • If function is a MeasurableVector of dimension > 1 and measure is a Measure, returns a pd.Series representing the integral of each component of the vector with respect to the measure over the specified set.

  • If function is a ParametrizedMeasurableFunction and measure is a Measure, returns a Function representing the integral of the function with respect to the measure over the specified set for each parameter value.

  • If function is a MeasurableFunction and measure is a ParametrizedMeasure, returns a Function representing the integral of the function with respect to the measure over the specified set for each parameter value.

  • If function is a ParametrizedMeasurableFunction and measure is a ParametrizedMeasure, returns a Function representing the integral of the function with respect to the measure over the specified set for each parameter value.

Examples:

>>> from sigalg.core import (
...     Domain,
...     MeasurableFunction,
...     Measure,
...     MeasureSpace,
...     Operators,
...     ParametrizedMeasurableFunction,
...     ParametrizedMeasure,
...     Set,
...     SigmaAlgebra,
... )

Define a measure space and a measurable function.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 1),
...         2: (1, 1),
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         (0, 1): 2,
...         (1, 1): 3,
...     },
... )
>>> f = MeasurableFunction(
...     domain=X,
...     sig_alg=F,
...     measure=mu,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 2,
...     },
... )

Get a measurable set from the sigma-algebra, compute the integral over this set, and check that it agrees with the defining formula for the Lebesgue integral.

>>> U = F.get_set([1, 2], name="U")
>>> I_U = U.indicator
>>> integrate = Operators.integrate
>>> integrate(f, U) == sum(I_U(A) * f(A) * mu(A) for A in F)
True

Define a parametrized measure and parametrized measurable function over the same parameter domain.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> nu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping={
...         (0, 0, 1): 3,  # (theta, F_0, F_1) = (0, 0, 0), etc ...
...         (0, 1, 1): 4,
...         (1, 0, 1): 1,
...         (1, 1, 1): 2,
...     },
...     name="nu",
... )
>>> g = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping={
...         (0, 0): 2,  # (theta, X) = (0, 0), etc ...
...         (0, 1): 4,
...         (0, 2): 4,
...         (1, 0): 1,
...         (1, 1): -1,
...         (1, 2): -1,
...     },
...     name="g",
... )

It is convenient to conceptualize a parametrized measure as a family of measures. Then integration of a measurable function against a parametrized measure returns a function of the parameters whose values are the integrals of the function against the measures. Iteration over the parametrized measure yields the measures, allowing us to check that these integrals all match.

>>> all(integrate(f, U, nu)(**param) == integrate(f, U, measure) for param, measure in nu)
True

Likewise, it is convenient to conceptualize a parametrized measurable function as a family of measurable functions. Then integration of a parametrized measurable function against a measure returns a function of the parameters whose values are the integrals of the functions against the measure. Iteration over the parametrized measurable function yields the functions, allowing us to check that these integrals match.

>>> all(integrate(g, U, mu)(**param) == integrate(function, U, mu) for param, function in g)
True

It is also possible to integrate a parametrized measurable function against a parametried measure as long as their parameter domains agree. We leave the reader to guess the meaning of the following verification.

>>> all(
...     integrate(g, U, nu)(**param) == integrate(function, U, measure)
...     for (param, function), (_, measure) in zip(g, nu)
... )
True
Notes

Let \(f: X \to \mathbb{R}\) be a measurable function on a measure space \((X, \mathcal{F}, \mu)\). Assuming \(X\) is finite (as it always is, in SigAlg), the \(\sigma\)-algebra \(\mathcal{F}\) is determined by its set \(\alpha(\mathcal{F})\) of atoms. Let \(U\) be a measurable set in \(\mathcal{F}\), and write \(I_U\) for its indicator function. Since both \(f\) and \(I_U\) are \(\mathcal{F}\)-measurable, they take constant values on each atom \(A\in \alpha(\mathcal{F})\) that we write as \(f(A)\) and \(I_U(A)\), respectively. Then the Lebesgue integral of \(f\) over \(U\) is the number

\[ \int_U f \, d\mu = \sum_{A\in \alpha(\mathcal{F})} I_U(A)f(A) \mu(A). \]

If \(f:X \to \mathbb{R}^d\) is instead a measurable vector of dimension \(d>1\), with components

\[ f = (f_1, f_2, \ldots, f_d), \]

then we define the Lebesgue integral of \(f\) over \(U\) to be the \(d\)-dimensional vector whose entries are the separate Lebesgue integrals \(\int_U f_j \, d\mu\), for \(j=1,2,\ldots,d\).

max classmethod

max(func1, func2, name=None)

Compute the pointwise maximum of a pair of functions with multi-dimensional outputs.

Parameters:

Name Type Description Default
func1 Function | Real

The first function (or scalar). One of func1 or func2 must be an instance of Function.

required
func1 Function | Real

The second function (or scalar). One of func1 or func2 must be an instance of Function.

required
name Hashable | None

The name of the resulting function. If None, a default name will be generated.

None

Returns:

Name Type Description
maximum Function

A function representing the pointwise maximum of the pair of functions.

Examples:

>>> import pandas as pd
>>> from sigalg.core import Domain, Function, Operators

Define two functions with 2-dimensional outputs.

>>> X = Domain.from_sequence(size=3)
>>> mapping_f = pd.DataFrame([(1, 2), (3, 4), (5, 6)])
>>> f = Function(domain=X, mapping=mapping_f)
>>> print(f)
Function 'f':
i  0  1
x
0  1  2
1  3  4
2  5  6
>>> mapping_g = pd.DataFrame([(2, 1), (0, 5), (1, 2)])
>>> g = Function(domain=X, mapping=mapping_g, name="g")
>>> print(g)
Function 'g':
i  0  1
x
0  2  1
1  0  5
2  1  2

Compute the maximum of the two functions.

>>> max = Operators.max(f, g)
>>> print(max)
Function 'max(f, g)':
i  0  1
x
0  2  2
1  3  5
2  5  6

Compute the maximum of the function f with a scalar value.

>>> max = Operators.max(f, 4)
>>> print(max)
Function 'max(f, 4)':
i  0  1
x
0  4  4
1  4  4
2  5  6

max_value classmethod

max_value(vec)

Get the maximum value across all outputs and indices of a measurable vector.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector for which to find the maximum value.

required

Returns:

Name Type Description
max_value Real

The maximum value found in the measurable vector.

Examples:

>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import RandomWalk
>>> T = Time.discrete(length=2)
>>> X = RandomWalk.generate(mode="enum", p=0.5, index=T, initial_state=3)
>>> print(X)
Random walk 'X':
t           0  1  2
omega
0           3  2  1
1           3  2  3
2           3  4  3
3           3  4  5
>>> max_value = Operators.max_value(X)
>>> print(max_value)
5

min_value classmethod

min_value(vec)

Get the minimum value across all outputs and indices of a measurable vector.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector for which to find the minimum value.

required

Returns:

Name Type Description
min_value Real

The minimum value found in the measurable vector.

Examples:

>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import RandomWalk
>>> T = Time.discrete(length=2)
>>> X = RandomWalk.generate(mode="enum", p=0.5, index=T, initial_state=3)
>>> print(X)
Random walk 'X':
t           0  1  2
omega
0           3  2  1
1           3  2  3
2           3  4  3
3           3  4  5
>>> min_value = Operators.min_value(X)
>>> print(min_value)
1

pointwise_map classmethod

pointwise_map(vec, function, name=None)

Apply a function pointwise to the values of a measurable vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector to which the function will be applied.

required
function Callable[[Hashable], Hashable]

A function that takes a single value and returns a transformed value. This function will be applied to each value in the measurable vector.

required
name Hashable | None

The name of the transformed measurable vector. If None, a default name will be generated.

None

Returns:

Name Type Description
mapped_vector MeasurableVector

A new measurable vector with the function applied pointwise to its values.

Examples:

>>> from sigalg.core import Operators, Time
>>> from sigalg.processes import RandomWalk
>>> T = Time.discrete(length=2)
>>> X = RandomWalk.generate(mode="enum", p=0.5, index=T, initial_state=3)
>>> print(X)
Random walk 'X':
t       0  1  2
omega
0       3  2  1
1       3  2  3
2       3  4  3
3       3  4  5
>>> def f(x):
...     return x + 1
>>> X_mapped = Operators.pointwise_map(vec=X, function=f)
>>> print(X_mapped)
Stochastic process 'X_mapped':
t       0  1  2
omega
0       4  3  2
1       4  3  4
2       4  5  4
3       4  5  6

pushforward classmethod

pushforward(vec, measure=None, name=None)

Push forward a (parametrized) measure on the domain of a measurable vector to a measure on its range.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector along which to push forward the measure.

required
measure Measure | ParametrizedMeasure | None

Measure to push forward. If None, the measure carried by the measurable vector is used.

None
name Hashable | None

The name of the pushforward measure. If None, a default name is generated.

None

Returns:

Name Type Description
pushforward Measure | ParametrizedMeasure

The measure pushed forward along the measurable vector.

Examples:

>>> from sigalg.core import (
...     Domain,
...     MeasurableVector,
...     Measure,
...     Operators,
...     ParametrizedProbabilityMeasure,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a measure space.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
...     variable_names=["u"],
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...     },
... )

Define a 2-dimensional measurable vector and pushforward the measure mu.

>>> f = MeasurableVector(
...     domain=X,
...     sig_alg=F,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (3, 4),
...         3: (3, 4),
...     },
... )
>>> mu_f = Operators.pushforward(f, mu)
>>> print(mu_f)
Measure 'mu_f':
            mu_f
f_0 f_1
1   2          1
3   4          5

Now define a measurable space with a sample space.

>>> Omega = SampleSpace.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
... )

Define a parametrized probability measure on the sigma-algebra.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> mapping = {
...     (0, 0): 0.1,  # (theta, F) = (0, 0), etc ...
...     (0, 1): 0.2,
...     (0, 2): 0.7,
...     (1, 0): 0.4,
...     (1, 1): 0.5,
...     (1, 2): 0.1,
... }
>>> P = ParametrizedProbabilityMeasure.from_domains(
...     measure_domain=F, parameter_domain=Theta, mapping=mapping
... )

Define a 2-dimensional random vector and pushforward the parametrized probability measure P.

>>> X = RandomVector.with_uniform(
...     domain=Omega,
...     sig_alg=F,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...         2: (3, 1),
...         3: (3, 1),
...     },
... )
>>> P_X = Operators.pushforward(X, P)
>>> print(P_X)
Parametrized probability measure 'P_X':
theta      0    1
X_0 X_1
1   1    0.1  0.4
3   1    0.9  0.6
Notes

Let \(f: X \to \mathbb{R}^d\) be a measurable vector on a measure space \((X, \mathcal{F}, \mu)\). Then we define a measure \(\mu_X\) on \(\mathbb{R}^d\), called the pushforward (or image) measure of \(\mu\) along \(f\), by setting

\[ \mu_X(A) = \mu\left( \{x \in X : f(x) \in A\}\right), \]

for all Borel subsets \(A\subset \mathbb{R}^d\).

If \(\mu\) is a parametrized measure on \(X\) with parameter domain \(\Theta\), then we define a parametrized measure \(\mu_X\) on \(\mathbb{R}^d\), called the pushforward (or image) measure of \(\mu\) along \(f\), by setting

\[ \mu_X(\theta, A) = \mu\left(\theta, \{x \in X : f(x) \in A\}\right), \]

for all \(\theta \in \Theta\) and all Borel subsets \(A\subset \mathbb{R}^d\).

std classmethod

std(rv, given=None, measure=None)

Compute the standard deviation of a random vector, optionally conditioned on a sigma-algebra.

See the Notes section below for the mathematical details.

User note: This is a naive reference implementation. It is slow.

Parameters:

Name Type Description Default
rv RandomVector

The random vector for which to compute the standard deviation.

required
given SigmaAlgebra | None

The sigma-algebra to condition on. If None, the trivial sigma-algebra is used.

None
measure ProbabilityMeasure | None

The probability measure with respect to which to integrate. If None, the probability measure of the underlying probability space of the random vector is used.

None

Returns:

Name Type Description
std RandomVector

The standard deviation of the random vector.

Examples:

>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space along with a 1-dimensinonal random variable and a 2-dimensional random vector.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.0,
...         2: 0.2,
...         3: 0.2,
...         4: 0.15,
...         5: 0.45,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 1,
...         4: 5,
...         5: 0,
...     },
... )
>>> Y = RandomVector(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: (1, 3),
...         1: (2, 4),
...         2: (3, 1),
...         3: (1, 0),
...         4: (5, 0),
...         5: (0, 8),
...     },
...     name="Y",
... )

Give aliases to the variance and std methods.

>>> V = Operators.variance
>>> std = Operators.std

Check that the unconditional standard deviation is equal to its definition.

>>> std(X) == V(X) ** 0.5
True

Check the same for Y.

>>> std(Y) == V(Y) ** 0.5
True

Define a sigma-algebra for conditional standard deviations.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
...     name="G",
... )

Check that the conditional standard deviation of the random variable X is equal to a linear combination of indicators weighted by unconditional standard deviations.

>>> std(X, G) == sum(std(X | B).item() * B.indicator for B in G if P(B) > 0)
True

Check the same for the random vector Y.

>>> all(
...     std_Y_i_G == sum(std(Y_i | B).item() * B.indicator for B in G if P(B) != 0)
...     for std_Y_i_G, Y_i in zip(std(Y, G), Y)
... )
True
Notes

Let \(X:\Omega \to \mathbb{R}\) be a random variable on a finite probability space \((\Omega, \mathcal{F},P)\), and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional standard deviation of \(X\) with respect to \(\mathcal{G}\) is any \(\mathcal{G}\)-measurable random variable \(\sigma(X \mid \mathcal{G})\) that is equal almost surely to the random variable

\[ \sigma(X\mid \mathcal{G}) = \sqrt{V(X\mid \mathcal{G})}. \]

The unconditional standard deviation of \(X\), denoted \(\sigma(X)\), is the case when \(\mathcal{G}\) is the trivial \(\sigma\)-algebra with \(\Omega\) as its only atom. The unconditional standard deviation is a constant random variable.

The \(\sigma\)-algebra \(\mathcal{G}\) is determined by its (finitely many) atoms, and we have the following formula for a conditional standard deviation:

\[ \sigma(X\mid \mathcal{G}) = \sum_B \sigma(X|_B) I_B, \]

where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability, and where \(\sigma(X|_B)\) is the unconditional standard deviation of the restricted random variable \(X|_B:B\to \mathbb{R}\).

If \(X : \Omega \to \mathbb{R}^d\) is a random vector of dimension \(d>1\), with components

\[ X = (X_1,X_2,\ldots,X_d), \]

then we define the conditional standard deviation of \(X\) to be the \(d\)-dimensional vector whose entries are the separate conditional standard deviations \(\sigma(X_j \mid \mathcal{G})\), for \(j=1,2,\ldots,d\).

sum classmethod

sum(func, name=None)

Compute the sum of the components of a function with multi-dimensional outputs.

Parameters:

Name Type Description Default
func Function

The function whose components are to be summed.

required
name Hashable | None

The name of the resulting function. If None, a default name will be generated.

None

Returns:

Name Type Description
summed_func Function

A function representing the sum of the components of the input function.

Examples:

>>> from sigalg.core import Domain, Operators, MeasurableVector
>>> D = Domain.from_sequence(size=2, variable_name="flip", name="D")
>>> X = (D ^ 3).with_name("X")
>>> f = X.to_function()
>>> print(f)
Function 'f':
i      flip_0  flip_1  flip_2
omega
0           0       0       0
1           0       0       1
2           0       1       0
3           0       1       1
4           1       0       0
5           1       0       1
6           1       1       0
7           1       1       1
>>> g = Operators.sum(f, name="g")
>>> print(g)
Function 'g':
       g
omega
0      0
1      1
2      1
3      2
4      1
5      2
6      2
7      3

surprisal classmethod

surprisal(rv, given=None, base='2', name=None, tol=1e-08)

Pass.

transform classmethod

transform(vec, functions, index=None, name=None)

Apply a transformation to a measurable vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
vec MeasurableVector

The measurable vector to transform.

required
functions list[Callable[[MeasurableVector], MeasurableFunction]]

A list of functions to apply to the measurable vector.

required
index IndexLike | None

The new index for the transformed vector. If None, the original index of vec will be used.

None
name Hashable | None

The name of the transformed vector. If None, a default name will be generated.

None

Returns:

Name Type Description
transformed_vector MeasurableVector

The transformed measurable vector.

Examples:

>>> from scipy.stats import bernoulli
>>> from sigalg.core import Operators, RandomVariable, Time
>>> from sigalg.processes import IIDProcess, StochasticProcess
>>> T = Time.discrete(start=0, length=2)
>>> X = IIDProcess.generate(
...     mode="enum",
...     distribution=bernoulli(p=0.5),
...     support=[0, 1],
...     index=T,
... )
>>> print(X)
IID process 'X':
t       0  1  2
omega
0       0  0  0
1       0  0  1
2       0  1  0
3       0  1  1
4       1  0  0
5       1  0  1
6       1  1  0
7       1  1  1
>>> S = Time.discrete(start=4, stop=5)
>>> def f4(process: StochasticProcess) -> RandomVariable:
...     X0, X1, _ = X
...     return X0 + X1
>>> def f5(process: StochasticProcess) -> RandomVariable:
...     _, X1, X2 = X
...     return X1 + X2
>>> X_transform = Operators.transform(vec=X, functions=[f4, f5], index=S)
>>> print(X_transform)
Stochastic process 'X_transform':
t       4  5
omega
0       0  0
1       0  1
2       1  1
3       1  2
4       1  0
5       1  1
6       2  1
7       2  2

variance classmethod

variance(rv, given=None, measure=None)

Compute the variance of a random vector, optionally conditioned on a sigma-algebra.

See the Notes section below for the mathematical details.

User note: This is a naive reference implementation. It is slow.

Parameters:

Name Type Description Default
rv RandomVector

The random vector for which to compute the variance.

required
given SigmaAlgebra | None

The sigma-algebra to condition on. If None, the trivial sigma-algebra is used.

None
measure ProbabilityMeasure | None

The probability measure with respect to which to integrate. If None, the probability measure of the underlying probability space of the random vector is used.

None

Returns:

Name Type Description
var RandomVector

The variance of the random vector.

Examples:

>>> from sigalg.core import (
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space along with a 1-dimensinonal random variable and a 2-dimensional random vector.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.0,
...         2: 0.2,
...         3: 0.2,
...         4: 0.15,
...         5: 0.45,
...     },
... )
>>> X = RandomVariable(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 1,
...         4: 5,
...         5: 0,
...     },
... )
>>> Y = RandomVector(
...     domain=Omega,
...     measure=P,
...     mapping={
...         0: (1, 3),
...         1: (2, 4),
...         2: (3, 1),
...         3: (1, 0),
...         4: (5, 0),
...         5: (0, 8),
...     },
...     name="Y",
... )

Give aliases to the variance and expectation methods.

>>> V = Operators.variance
>>> E = Operators.expectation

Check that the variance may be computed via the "short-cut" formula.

>>> V(X) == E(X**2) - E(X) ** 2
True

Check the same for Y.

>>> V(Y) == E(Y**2) - E(Y) ** 2
True

Define a sigma-algebra for conditional variances.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
...     name="G",
... )

Check that the conditional variance of the random variable X is equal to a linear combination of indicators weighted by unconditional variances.

>>> V(X, G) == sum(V(X | B).item() * B.indicator for B in G if P(B) > 0)
True

Check the same for the random vector Y.

>>> all(
...     V_Y_i_G == sum(V(Y_i | B).item() * B.indicator for B in G if P(B) > 0)
...     for V_Y_i_G, Y_i in zip(V(Y, G), Y)
... )
True
Notes

Let \(X:\Omega \to \mathbb{R}\) be a random variable on a finite probability space \((\Omega, \mathcal{F}, P)\), and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional variance of \(X\) with respect to \(\mathcal{G}\) is any \(\mathcal{G}\)-measurable random variable that is equal almost surely to the random variable

\[ V(X\mid \mathcal{G}) = E\left[ (X-E(X\mid \mathcal{G}))^2 \mid \mathcal{G}\right]. \]

The unconditional variance of \(X\), denoted \(V(X)\), is the case when \(\mathcal{G}\) is the trivial \(\sigma\)-algebra with \(\Omega\) as its only atom. The unconditional variance is a constant random variable.

The \(\sigma\)-algebra \(\mathcal{G}\) is determined by its (finitely many) atoms, and we have the following formula for a conditional variance:

\[ V(X\mid \mathcal{G}) = \sum_B V(X|_B) I_B, \]

where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability, and where \(V(X|_B)\) is the unconditional variance of the restricted random variable \(X|_B:B\to \mathbb{R}\).

If \(X : \Omega \to \mathbb{R}^d\) is a random vector of dimension \(d>1\), with components

\[ X = (X_1,X_2,\ldots,X_d), \]

then we define the conditional variance of \(X\) to be the \(d\)-dimensional vector whose entries are the separate conditional variances \(V(X_j \mid \mathcal{G})\), for \(j=1,2,\ldots,d\).

ParametrizedMeasurableFunction

Bases: Function, OperatorsMethods

A class representing a parametrized measurable function.

The __init__ constructor is not meant to be used directly. Instead, the user should use the from_domains class method.

See the Notes section below for the mathematical details.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a 1-dimensional parameter domain and measurable domain.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")

Define a sigma-algebra.

>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )

Define the mapping of a parametrized measurable function.

>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }

Instantiate a parametrized measurable function and print it.

>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

The printout displays the function as a 2-dimensional array, where the parameters run alog the horizontal axis and the "measurable" variables along the vertical.

Evaluate the function at a parameter to obtain a measurable function.

>>> print(f(theta=0))
Measurable function 'f(theta=0)':
   f(theta=0)
x
0           1
1           2
2           2

Evaluate the function at a "measurable" variable to get an instance of Function.

>>> print(f(x=0))
Function 'f(x=0)':
       f(x=0)
theta
0           1
1           0
Notes

Let \((X, \mathcal{F})\) be a measurable space and \(\Theta\) a nonempty set. A parametrized measurable function is a function

\[ f : \Theta \times X \to \mathbb{R} \]

such that, for each fixed \(\theta \in \Theta\), the partial function

\[ f(\theta, -): X \to \mathbb{R}, \quad x \mapsto f(\theta,x), \]

is a measurable function with respect to the \(\sigma\)-algebra \(\mathcal{F}\). The set \(\Theta\) is called the parameter domain, elements \(\theta\in \Theta\) are called parameters, and \(X\) is called the measurable domain.

generated_sig_alg cached property

generated_sig_alg

Get the sigma-algebra generated by the function.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
sig_alg SigmaAlgebra | None

The sigma-algebra generated by the parametrized measurable function.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a 1-dimensional domain, a 1-dimensional parameter space, and a sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )

Define a parametrized measurable function.

>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): -3,
...     (1, 1): 4,
...     (1, 2): 4,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1 -3
1      2  4
2      2  4

Print the sigma-algebra generated by f and its atom space.

>>> print(f.generated_sig_alg)
Sigma algebra 'sigma(f)':
theta  0  1
x
0      1 -3
1      2  4
2      2  4
>>> print(f.generated_sig_alg.atom_space)
Domain 'sigma(f)':
 f_0  f_1
   1   -3
   2    4

For a second example, consider a function parametrized by a 2-dimensional space.

>>> Phi = Domain.cartesian_power([0, 1], n=2, variable_names=["phi_0", "phi_1"], name="Phi")
>>> mapping = {
...     (0, 0, 0): 1,  # (phi_0, phi_1, x) = (0, 0, 0), etc ...
...     (0, 0, 1): 2,
...     (0, 0, 2): 2,
...     (0, 1, 0): -3,
...     (0, 1, 1): 4,
...     (0, 1, 2): 4,
...     (1, 0, 0): 0,
...     (1, 0, 1): 1,
...     (1, 0, 2): 1,
...     (1, 1, 0): 8,
...     (1, 1, 1): 0,
...     (1, 1, 2): 0,
... }
>>> g = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Phi,
...     sig_alg=F,
...     mapping=mapping,
...     name="g",
... )
>>> print(g)
Parametrized measurable function 'g':
phi_0  0     1
phi_1  0  1  0  1
x
0      1 -3  0  8
1      2  4  1  0
2      2  4  1  0

Print the sigma-algebra generated by g and its atom space.

>>> print(g.generated_sig_alg)
Sigma algebra 'sigma(g)':
phi_0  0     1
phi_1  0  1  0  1
x
0      1 -3  0  8
1      2  4  1  0
2      2  4  1  0
>>> print(g.generated_sig_alg.atom_space)
Domain 'sigma(g)':
 g_0_0  g_0_1  g_1_0  g_1_1
     1     -3      0      8
     2      4      1      0
Notes

Let \(f: \Theta \times X \to \mathbb{R}\) be a parametrized measurable function relative to a \(\sigma\)-algebra on \(X\). Then for each parameter \(\theta \in \Theta\) we have the partial function

\[ f_\theta : X \to \mathbb{R}, \quad x \mapsto f(\theta,x). \]

We define the \(\sigma\)-algebra generated by \(f\), denoted \(\sigma(f)\), to be the \(\sigma\)-algebra given by the join

\[ \sigma(f) = \vee_{\theta\in \Theta} \sigma(f_\theta). \]

measurable_domain cached property

measurable_domain

Get the measurable domain of the function.

Returns:

Name Type Description
measurable_domain Domain | None

The measurable domain of the function, or None if not set.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a parametrized function.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

Print the measurable domain.

>>> print(f.measurable_domain)
Domain 'X':
 x
 0
 1
 2

measurable_names property

measurable_names

Get the measurable names of the function.

Returns:

Name Type Description
measurable_names list[Hashable] | None

The names of the measurable variables of the function, or None if not set.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a parametrized function.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

Print the names of the measurable variables.

>>> f.measurable_names
['x']

measurable_space cached property

measurable_space

Get the measurable space of the function.

Returns:

Name Type Description
measurable_space MeasurableSpace | None

The measurable space of the function, or None if not set.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a parametrized function.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

Print the underlying measurable space.

>>> print(f.measurable_space)
Measurable space (X, F)
=======================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
   F
x
0  0
1  1
2  1

measure_space cached property

measure_space

Get the measure space of the function.

Returns:

Name Type Description
measure_space MeasureSpace | None

The measure space of the function, or None if not set.

Examples:

>>> from sigalg.core import (
...     Domain,
...     Measure,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a parametrized function.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 3,
...     },
... )
>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     measure=mu,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

Get the underlying measure space.

>>> print(f.measure_space)
Measure space (X, F, mu)
========================

* Domain 'X':
 x
 0
 1
 2

* Sigma algebra 'F':
F
x
0  0
1  1
2  1

* Measure 'mu':
   mu
F
0   1
1   3

parameter_domain cached property

parameter_domain

Get the parameter domain of the function.

Returns:

Name Type Description
parameter_domain Domain | None

The parameter domain of the function, or None if not set.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasurableFunction, SigmaAlgebra

Define a parametrized function.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )
>>> mapping = {
...     (0, 0, 0): 0,  # (theta_0, theta_1, x) = (0, 0, 0), etc...
...     (0, 0, 1): 1,
...     (0, 0, 2): 1,
...     (0, 1, 0): 2,
...     (0, 1, 1): 8,
...     (0, 1, 2): 8,
...     (1, 0, 0): 4,
...     (1, 0, 1): 1,
...     (1, 0, 2): 1,
...     (1, 1, 0): 5,
...     (1, 1, 1): 0,
...     (1, 1, 2): 0,
... }
>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta_0  0     1
theta_1  0  1  0  1
x
0        0  2  4  5
1        1  8  1  0
2        1  8  1  0

Print the parameter domain of the function.

>>> print(f.parameter_domain)
Domain 'Theta':
 theta_0  theta_1
       0        0
       0        1
       1        0
       1        1

from_domains classmethod

from_domains(
    measurable_domain,
    parameter_domain=None,
    complete_domain=None,
    sig_alg=None,
    measure=None,
    mapping=None,
    parameter_names=None,
    complete_domain_name=None,
    parameter_domain_name=None,
    output_name=None,
    name=None,
)

Construct a parametrized measurable function from a measurable domain and parameter domain.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
measurable_domain IndexLike

The measurable domain of the function.

required
parameter_domain IndexLike

The parameter domain of the function.

None
sig_alg SigmaAlgebra

The sigma-algebra of the underlying measurable space.

None
measure Measure | None

An optional measure on the underlying measurable space.

None
mapping MappingLike

The mapping of the parametrized function.

None
parameter_names list[Hashable] | None

The names of the parameters of the function.

None
complete_domain_name Hashable | None

The name of the domain of the function.

None
parameter_domain_name Hashable | None

The name of the parameter domain.

None
output_name Hashable | None

The name of the output variable for the parametrized function. If None, a default will be generated.

None
name Hashable | None

The name of the parametrized function. If None, a default will be generated.

None

Returns:

Name Type Description
param_func ParametrizedMeasurableFunction

The constructed parametrized measurable function.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedMeasurableFunction,
...     SigmaAlgebra,
... )

Define a 1-dimensional parameter domain and measurable domain.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=3, variable_name="x")

Define a sigma-algebra.

>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )

Define the mapping of a parametrized measurable function.

>>> mapping = {
...     (0, 0): 1,  # (theta, x) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }

Instantiate a parametrized measurable function and print it.

>>> f = ParametrizedMeasurableFunction.from_domains(
...     measurable_domain=X,
...     parameter_domain=Theta,
...     sig_alg=F,
...     mapping=mapping,
... )
>>> print(f)
Parametrized measurable function 'f':
theta  0  1
x
0      1  0
1      2 -3
2      2 -3

The printout displays the function as a 2-dimensional array, where the parameters run alog the horizontal axis and the "measurable" variables along the vertical.

Evaluate the function at a parameter to obtain a measurable function.

>>> print(f(theta=0))
Measurable function 'f(theta=0)':
    f(theta=0)
x
0           1
1           2
2           2

Evaluate the function at a "measurable" variable to get an instance of Function.

>>> print(f(x=0))
Function 'f(x=0)':
        f(x=0)
theta
0           1
1           0
Notes

Let \((X, \mathcal{F})\) be a measurable space and \(\Theta\) a nonempty set. A parametrized measurable function is a function

\[ f : \Theta \times X \to \mathbb{R} \]

such that, for each fixed \(\theta \in \Theta\), the partial function

\[ f(\theta, -): X \to \mathbb{R}, \quad x \mapsto f(\theta,x), \]

is a measurable function with respect to the \(\sigma\)-algebra \(\mathcal{F}\). The set \(\Theta\) is called the parameter domain, elements \(\theta\in \Theta\) are called parameters, and \(X\) is called the measurable domain.

ParametrizedMeasure

Bases: Function

A class representing a parametrized measure.

The __init__ constructor is not meant to be used directly. Instead, the user should use the from_domains class method.

See the Notes section below for the mathematical details.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a 2-dimensional parameter domain, a 1-dimensional domain, and a sigma-algebra with 2-dimensional atom identifiers.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )

Define a parametrized measure and print it.

>>> mapping = {
...     (0, 0, 0, 1): 0,  # (theta_0, theta_1, F_0, F_1) = (0, 0, 0, 1), etc...
...     (0, 0, 1, 2): 1,
...     (0, 1, 0, 1): 2,
...     (0, 1, 1, 2): 8,
...     (1, 0, 0, 1): 4,
...     (1, 0, 1, 2): 1,
...     (1, 1, 0, 1): 5,
...     (1, 1, 1, 2): 0,
... }
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0    0     1
theta_1    0  1  0  1
F_0 F_1
0   1      0  2  4  5
1   2      1  8  1  0
Notes

Let \((X, \mathcal{F})\) be a measurable space and \(\Theta\) a nonempty set. A parametrized measure is a function

\[ \mu : \Theta \times \mathcal{F} \to \mathbb{R} \]

such that, for each fixed \(\theta \in \Theta\), the partial function

\[ \mu(\theta, -): \mathcal{F} \to \mathbb{R}, \quad U \mapsto \mu(\theta,U), \]

is a measure on the \(\sigma\)-algebra \(\mathcal{F}\). The set \(\Theta\) is called the parameter domain and elements \(\theta\in \Theta\) are called parameters.

lattice cached property

lattice

Return the downward lattice of all sigma-algebras contained in the domain sigma-algebra of the parametrized measure.

Returns:

Name Type Description
down_lattice Lattice | None

The downward lattice of all sigma-algebras contained in the domain sigma-algebra of the measure.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a parametrized measure.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )
>>> mapping = {
...     (0, 0, 0, 1): 0,  # (theta_0, theta_1, F_0, F_1) = (0, 0, 0, 1), etc...
...     (0, 0, 1, 2): 1,
...     (0, 1, 0, 1): 2,
...     (0, 1, 1, 2): 8,
...     (1, 0, 0, 1): 4,
...     (1, 0, 1, 2): 1,
...     (1, 1, 0, 1): 5,
...     (1, 1, 1, 2): 0,
... }
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0  0     1
theta_1  0  1  0  1
F_0 F_1
0   1    0  2  4  5
1   2    1  8  1  0

The lattice of the measure is initialized with the domain sigma-algebra F.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=1)

Define a sub-sigma-algebra of F and check that it is in the lattice.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 0,
...     },
...     name="G",
... )
>>> G in mu.lattice
True

Check that the lattice now includes G.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=2)

Since G is a sub-sigma-algebra of F, we may restrict the measure mu to G.

>>> print(mu | G)
Parametrized measure 'mu|G':
theta_0  0      1
theta_1  0   1  0  1
G
0        1  10  5  5

Define another sigma-algebra, and check if it is a sub-sigma-algebra of F.

>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
...     name="H",
... )
>>> H in mu.lattice
False

Check that H was not added to the lattice.

>>> mu.lattice
Lattice(base=F, type=downward, num_sig_algs=2)

measure_domain_names property

measure_domain_names

Get the domain names of the parametrized measure.

Returns:

Name Type Description
domain_names list[Hashable] | None

The domain names associated with the parametrized measure, or None if not set.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a parametrized measure.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )
>>> mapping = {
...     (0, 0, 0, 1): 0,  # (theta_0, theta_1, F_0, F_1) = (0, 0, 0, 1), etc...
...     (0, 0, 1, 2): 1,
...     (0, 1, 0, 1): 2,
...     (0, 1, 1, 2): 8,
...     (1, 0, 0, 1): 4,
...     (1, 0, 1, 2): 1,
...     (1, 1, 0, 1): 5,
...     (1, 1, 1, 2): 0,
... }
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0  0     1
theta_1  0  1  0  1
F_0 F_1
0   1    0  2  4  5
1   2    1  8  1  0

Print the variable names of the measure domain.

>>> mu.measure_domain_names
['F_0', 'F_1']

parameter_domain cached property

parameter_domain

Get the parameter domain of the parametrized measure.

Returns:

Name Type Description
parameter_domain Domain | None

The parameter domain associated with the parametrized measure, or None if not set.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a parametrized measure.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )
>>> mapping = {
...     (0, 0, 0, 1): 0,  # (theta_0, theta_1, F_0, F_1) = (0, 0, 0, 1), etc...
...     (0, 0, 1, 2): 1,
...     (0, 1, 0, 1): 2,
...     (0, 1, 1, 2): 8,
...     (1, 0, 0, 1): 4,
...     (1, 0, 1, 2): 1,
...     (1, 1, 0, 1): 5,
...     (1, 1, 1, 2): 0,
... }
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0    0     1
theta_1    0  1  0  1
F_0 F_1
0   1      0  2  4  5
1   2      1  8  1  0

Print the parameter domain of the measure.

>>> print(mu.parameter_domain)
Domain 'Theta':
 theta_0  theta_1
       0        0
       0        1
       1        0
       1        1

from_domains classmethod

from_domains(
    measure_domain,
    parameter_domain=None,
    complete_domain=None,
    mapping=None,
    kind="param_measure",
    parameter_names=None,
    complete_domain_name=None,
    parameter_domain_name=None,
    output_name=None,
    name=None,
)

Construct a parametrized measure from a measure domain and parameter domain.

Parameters:

Name Type Description Default
measure_domain MeasureDomain

The domain of the measure, if a SigmaAlgebra is provided. If an IndexLike object that can be coerced to a Domain is provided, the sigma-algebra of the measure will be the power-set sigma-algebra of the domain.

required
parameter_domain Domain | None

The parameter domain for the measure.

None
complete_domain Domain | None

If measure_domain and parameter_domain are specified, the domain of the parametrized measure will be the Cartesian product of the two. Alternativaly, the user may specify the domain of the measure directly through this parameter.

None
mapping MappingLike | None

The mapping of the parametrized measure.

None
kind Literal['param_measure', 'param_probability']

The kind of the parametrized measure.

"param_measure"
parameter_names list[Hashable] | None

The names of the parameters of the measure.

None
complete_domain_name Hashable | None

The name of the domain of the measure.

None
parameter_domain_name Hashable | None

The name of the parameter domain.

None
output_name Hashable | None

The name of the output variable for the parametrized measure. If None, a default will be generated.

None
name Hashable | None

The name of the parametrized measure. If None, a default name will be assigned.

"mu"

Returns:

Name Type Description
param_measure ParametrizedMeasure

The constructed parametrized measure.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a 2-dimensional parameter domain, a 1-dimensional domain, and a sigma-algebra with 2-dimensional atom identifiers.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (1, 2),
...         2: (1, 2),
...     },
... )

Define a parametrized measure and print it.

>>> mapping = {
...     (0, 0, 0, 1): 0,  # (theta_0, theta_1, F_0, F_1) = (0, 0, 0, 1), etc...
...     (0, 0, 1, 2): 1,
...     (0, 1, 0, 1): 2,
...     (0, 1, 1, 2): 8,
...     (1, 0, 0, 1): 4,
...     (1, 0, 1, 2): 1,
...     (1, 1, 0, 1): 5,
...     (1, 1, 1, 2): 0,
... }
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0    0     1
theta_1    0  1  0  1
F_0 F_1
0   1      0  2  4  5
1   2      1  8  1  0

from_rand classmethod

from_rand(
    measure_domain,
    parameter_domain=None,
    complete_domain=None,
    num_null_atoms=0,
    kind="param_measure",
    distribution="uniform",
    max_value=10,
    rate=5.0,
    parameter_names=None,
    complete_domain_name=None,
    parameter_domain_name=None,
    output_name=None,
    name=None,
    random_state=None,
)

Generate a random parametrized measure.

Parameters:

Name Type Description Default
measure_domain MeasureDomain

The domain of the measure, if a SigmaAlgebra is provided. If an IndexLike object that can be coerced to a Domain is provided, the sigma-algebra of the measure will be the power-set sigma-algebra of the domain.

required
parameter_domain Domain | None

The parameter domain for the measure.

None
complete_domain Domain | None

If measure_domain and parameter_domain are specified, the domain of the parametrized measure will be the Cartesian product of the two. Alternativaly, the user may specify the domain of the measure directly through this parameter.

None
num_null_atoms int

The number of atoms in the sigma-algebra that should be assigned a measure of 0.

0
kind Literal['param_measure', 'param_probability']

The kind of measure to generate. If 'param_probability', generates a probability measure using a Dirichlet distribution.

"param_measure"
distribution Literal['uniform', 'poisson', 'dirichlet']

The type of distribution from which to sample the values of the measure.

"uniform"
max_value int

The maximum value for uniform integer sampling when distribution='uniform'. Integers are sampled from the interval [1, max_value).

10
rate float

The rate parameter for Poisson sampling when distribution='poisson'.

5.0
parameter_names list[Hashable] | None

The names of the parameters of the measure.

None
complete_domain_name Hashable | None

The name of the domain of the measure.

None
parameter_domain_name Hashable | None

The name of the parameter domain.

None
output_name Hashable | None

The name of the output variable for the parametrized measure. If None, a default will be generated.

None
name Hashable | None

The name of the function. If None, a default name will be used.

None
random_state int | Generator | None

The random state for reproducibility.

None

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Define a 2-dimensional parameter domain, a 1-dimensional domain, and a sigma-algebra with four atoms.

>>> Theta = Domain.cartesian_power(
...     [0, 1], n=2, variable_names=["theta_0", "theta_1"], name="Theta"
... )
>>> X = Domain.from_sequence(size=5, variable_name="x")
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2, 3])))

Generate a random parametrized measure with values drawn from a uniform distribution on the integers in [0, 10) and with one null atom.

>>> mu = ParametrizedMeasure.from_rand(
...     measure_domain=F,
...     parameter_domain=Theta,
...     random_state=rng,
...     num_null_atoms=1,
... )
>>> print(mu)
Parametrized measure 'mu':
theta_0  0     1
theta_1  0  1  0  1
F
0        4  1  6  9
1        2  0  5  0
2        1  7  1  4
3        0  8  0  7

Generate a random parametrized measure with values drawn from a Poisson distribution with rate=5.0.

>>> nu = ParametrizedMeasure.from_rand(
...     measure_domain=F,
...     parameter_domain=Theta,
...     distribution="poisson",
...     random_state=rng,
...     name="nu",
... )
>>> print(nu)
Parametrized measure 'nu':
theta_0  0     1
theta_1  0  1  0  1
F
0        3  6  3  5
1        3  7  5  8
2        5  2  3  5
3        6  9  8  7

Generate a parametrized probability measure with values drawn from a Dirichlet distribution.

>>> P = ParametrizedMeasure.from_rand(
...     measure_domain=F,
...     parameter_domain=Theta,
...     distribution="dirichlet",
...     random_state=rng,
... )
>>> print(P)
Parametrized probability measure 'P':
theta_0         0                       1
theta_1         0             1         0         1
F
0        0.029892  6.987893e-02  0.137316  0.007920
1        0.628674  8.958606e-02  0.000001  0.029446
2        0.000276  8.405348e-01  0.735544  0.776833
3        0.341159  2.074313e-07  0.127139  0.185800

restrict_to

restrict_to(sig_alg, name=None)

Restrict the measure to a sub-sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sub-sigma-algebra that the measure will be restricted to.

required
name Hashable | None

The name of the restriction. If None, a default will be generated.

None

Returns:

Name Type Description
restriction ParametrizedMeasure

The restricted measure.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a parametrized measure.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping={
...         (0, 0): 0,  # (theta, F) = (0, 0), etc...
...         (0, 1): 1,
...         (0, 2): 8,
...         (1, 0): 3,
...         (1, 1): 4,
...         (1, 2): 1,
...     },
... )
>>> print(mu)
Parametrized measure 'mu':
theta  0  1
F
0      0  3
1      1  4
2      8  1

Define a sub-sigma-algebra of F and restrict the measure.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...     },
...     name="G",
... )
>>> print(mu.restrict_to(G))
Parametrized measure 'mu|G':
theta  0  1
G
0      0  3
1      9  5

Alternatively, we may restrict the measure using the | operator.

>>> print(mu | G)
Parametrized measure 'mu|G':
theta  0  1
G
0      0  3
1      9  5

to_function

to_function()

Promote to a Function instance.

with_variable_names

with_variable_names(variable_names, name=None)

Set the variable names of the underlying sigma-algebra of the parametrizec measure and return a new instance.

Parameters:

Name Type Description Default
variable_names list[Hashable]

The new variable names.

required
name Hashable | None

The name of the new measure. If None, the name of the current instance will be used.

None

Returns:

Name Type Description
renamed_measure ParametrizedMeasure

A new instance with the given variable names.

Examples:

>>> from sigalg.core import Domain, ParametrizedMeasure, SigmaAlgebra

Define a parametrized measure. Notice the custom variable name of the sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
...     variable_names=["u"],
... )
>>> mu = ParametrizedMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping={
...         (0, 0): 3,
...         (0, 1): 4,
...         (1, 0): 1,
...         (1, 1): 2,
...     },
... )
>>> print(mu)
Parametrized measure 'mu':
theta  0  1
u
0      3  1
1      4  2

Set the variable names of the measure.

>>> nu = mu.with_variable_names(["v"])
>>> print(nu)
Parametrized measure 'mu':
theta  0  1
v
0      3  1
1      4  2

The variable names of the underlying sigma-algebra change accordingly.

>>> print(nu.sig_alg.variable_names)
['v']

ParametrizedProbabilityMeasure

Bases: ParametrizedMeasure

A class representing a parametrized probability measure.

The __init__ constructor is not meant to be used directly. Instead, the user should use the from_domains class method. See the Examples section below.

See the Notes section below for the mathematical details.

Examples:

>>> from math import comb
>>> from sigalg.core import (
...     Domain,
...     ParametrizedProbabilityMeasure,
...     SampleSpace,
... )

Define a 1-dimensional parameter domain and sample space.

>>> Theta = Domain([0.0, 0.25, 0.75, 1.0], name="Theta", variable_names=["theta"])
>>> Omega = SampleSpace.from_sequence(size=3, variable_name="omega")

Define a binomial probability distribution Bin(n=2, theta), parametrized by theta.

>>> def mapping(*, theta, omega):
...     omega = int(omega)
...     return comb(2, omega) * theta**omega * (1 - theta) ** (2 - omega)
>>> P = ParametrizedProbabilityMeasure.from_domains(
...     measure_domain=Omega, parameter_domain=Theta, mapping=mapping
... )
>>> print(P)
Parametrized probability measure 'P':
theta  0.00    0.25    0.75  1.00
omega
0       1.0  0.5625  0.0625   0.0
1       0.0  0.3750  0.3750   0.0
2       0.0  0.0625  0.5625   1.0

Evaluate at a parameter to obtain a probability measure.

>>> print(P(theta=0.25))
Probability measure 'P(theta=0.25)':
       P(theta=0.25)
omega
0             0.5625
1             0.3750
2             0.0625
Notes

Let \((\Omega, \mathcal{F})\) be a measurable space and \(\Theta\) a nonempty set. A parametrized probability measure is a function

\[ P : \Theta \times \mathcal{F} \to \mathbb{R} \]

such that, for each fixed \(\theta \in \Theta\), the partial function

\[ P(\theta, -): \mathcal{F} \to \mathbb{R}, \quad U \mapsto P(\theta,U), \]

is a probability measure on the \(\sigma\)-algebra \(\mathcal{F}\). The set \(\Theta\) is called the parameter domain and elements \(\theta\in \Theta\) are called parameters.

derivative

derivative(base_measure=None, name=None, tol=1e-08)

Compute the Radon-Nikodym derivative with respect to a base measure.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure | None

The base measure with respect to which the Radon-Nikodym derivative is computed. If None, the counting measure on the domain of the sigma-algebra is used.

None
name Hashable | None

The name of the resulting Radon-Nikodym derivative. If None, a default name is generated based on the names of the current measure and the base measure.

None

Returns:

Name Type Description
derivative ParametrizedMeasurableFunction

The Radon-Nikodym derivative of the current measure with respect to the base measure.

Examples:

>>> from sigalg import (
...     Domain,
...     Measure,
...     Operators,
...     ParametrizedProbabilityMeasure,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a parametrized probability measure on a measurable space.

>>> Omega = SampleSpace.from_sequence(size=6, variable_name="omega")
>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mapping = dict(zip(Theta @ F.atom_space, [0.3, 0.5, 0.2, 0.0, 0.1, 0.9]))
>>> P = ParametrizedProbabilityMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(P)
Parametrized probability measure 'P':
theta    0    1
F
0      0.3  0.0
1      0.5  0.1
2      0.2  0.9

Define a base measure.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 4,
...     },
... )

In SigAlg, the derivative P.derivative(mu) is an instance of ParametrizedMeasurableFunction. Conceptually, it is convenient to think of a parametrized probability measure as a family of probability measures, one for each parameter value. Likewise, a parametrized measurable function should be thought of as a family, too. Then the derivative P.derivative(mu) is just the family of Radon-Nikodym derivatives of the component measures of the parametrized measure, one for each parameter.

>>> P.derivative(mu)
ParametrizedMeasurableFunction(parameters=(theta), measurable_vars=(omega), domain=Omega, sig_alg=F, measure=mu, name=dP_dmu)

Print the derivative for inspection.

>>> print(P.derivative(mu))
Parametrized measurable function 'dP_dmu':
theta     0      1
omega
0      0.30  0.000
1      0.30  0.000
2      0.25  0.050
3      0.25  0.050
4      0.05  0.225
5      0.05  0.225

Check that the Radon-Nikodym derivative P.derivative(mu) has the defining property of a Radon-Nikodym derivative.

>>> integrate = Operators.integrate
>>> all(P(A) == integrate(P.derivative(mu), A) for A in F)
True

The derivative method of ParametrizedProbabilityMeasure allows us to recover the familiar conditional probability mass functions from elementary probability theory. To demonstrate, let's define a new measure on the power-set sigma-algebra of the sample space above.

>>> Q = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.0,
...         1: 0.05,
...         2: 0.1,
...         3: 0.2,
...         4: 0.2,
...         5: 0.45,
...     },
...     name="Q",
... )

Define a pair of random variables.

>>> X = RandomVariable(
...     domain=Omega,
...     measure=Q,
...     mapping={
...         0: 1,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,
...         5: 3,
...     },
... )
>>> Y = RandomVariable(
...     domain=Omega,
...     measure=Q,
...     mapping={
...         0: 2,
...         1: 3,
...         2: 4,
...         3: 7,
...         4: 2,
...         5: 5,
...     },
...     name="Y"
... )

Condition the measure on X, creating an instance of ParametrizedProbabilityMeasure.

>>> print(Q.conditional(X))
Parametrized probability measure 'Q(-|X)':
X        1         2         3
omega
0      0.0  0.000000  0.000000
1      1.0  0.000000  0.000000
2      0.0  0.333333  0.000000
3      0.0  0.666667  0.000000
4      0.0  0.000000  0.307692
5      0.0  0.000000  0.692308

Notice that the parameters of this measure are the values of X. Notice also that this is a parametrized probability measure on the original sample space. We push it forward along the random variable Y.

>>> print(Q.conditional(X) >> Y)
Parametrized probability measure 'Q(-|X)_Y':
X    1         2         3
Y
2  0.0  0.000000  0.307692
3  1.0  0.000000  0.000000
4  0.0  0.333333  0.000000
5  0.0  0.000000  0.692308
7  0.0  0.666667  0.000000

Notice now that it is a parametrized probability measure on the range of Y. If we apply the derivative method without an explict measure parameter, it defaults to the counting measure and we obtain the conditional probability mass function of Y given X.

>>> print((Q.conditional(X) >> Y).derivative())
Parametrized measurable function 'dQ(-|X)_Y_dC':
X    1         2         3
Y
2  0.0  0.000000  0.307692
3  1.0  0.000000  0.000000
4  0.0  0.333333  0.000000
5  0.0  0.000000  0.692308
7  0.0  0.666667  0.000000

Notice the subtle (but meaningful) difference between this last printout and the one before: One is an instance of ParametrizedProbabilityMeasure, while the other is an instance of ParametrizedMeasurableFunction. These are different things!

Finally, we verify that integrating the conditional probability mass function yields conditional probability.

>>> all(
...     (Q.conditional(X) >> Y)([y]) == integrate((Q.conditional(X) >> Y).derivative(), [y])
...     for y in Y.range
... )
True
Notes

By definition, a parametrized probability measure on a measurable space \((\Omega, \mathcal{F})\), with parameter domain \(\Theta\), is a function

\[ P: \Theta \times \mathcal{F} \to [0,1] \]

for which each partial function

\[ P(\theta, -) : \mathcal{F} \to [0,1], \quad U \mapsto P(\theta, U), \]

is a probability measure, for each \(\theta \in \Theta\). If \(\mu\) is a measure on \(\mathcal{F}\) with the property that \(P(\theta, -) \ll \mu\) for each \(\theta\), then the Radon-Nikodym derivative of \(P\) with respect to \(\mu\) is the function

\[ \frac{dP}{d\mu} : \Theta \times \Omega \to \mathbb{R} \]

for which each partial function

\[ \frac{dP}{d\mu}(\theta, -) : \Omega \to \mathbb{R}, \quad \omega \mapsto \frac{dP}{d\mu}(\theta, \omega) \]

is a Radon-Nikodym derivative of \(P(\theta, -)\) with respect to \(\mu\), for each \(\theta\in \Theta\). The measure \(\mu\) is called the base measure.

entropy

entropy(base_measure=None, base='2', name=None, tol=1e-08)

Compute the entropy with respect to a base measure.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure | None

The base measure with respect to which the entropy is computed. If None, the counting measure on the domain of the sigma-algebra is used.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the entropy.

"2"
name Hashable | None

The name of the resulting entropy. If None, a default name will be generated.

None
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
entropy Function

The entropy of the current measure with respect to the base measure.

Examples:

>>> import numpy as np
>>> from sigalg import (
...     Domain,
...     Measure,
...     ParametrizedProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a parametrized probability measure.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mapping = dict(zip(Theta @ F.atom_space, [0.3, 0.5, 0.2, 0.0, 0.1, 0.9]))
>>> P = ParametrizedProbabilityMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(P)
Parametrized probability measure 'P':
theta    0    1
F
0      0.3  0.0
1      0.5  0.1
2      0.2  0.9

Define a base measure for the entropy.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 4,
...     },
... )

Compute the entropy of P with respect to mu.

>>> P.entropy(mu)
Function(parameters=(theta), domain=Theta, name=H(P; mu))
>>> print(P.entropy(mu))
Function 'H(P; mu)':
       H(P; mu)
theta
0      2.385475
1      2.368996

We may check that the entropy is given by the integral of the surprisal function.

>>> P.entropy(mu) == P.surprisal(mu).integrate(measure=P)
True

from_scipy classmethod

from_scipy(dist, support, parameter_domain, name='P')

Initialize the parametrized probability measure from a discrete SciPy probability distribution.

Parameters:

Name Type Description Default
dist rv_discrete

A discrete SciPy probability distribution.

required
support tuple[Hashable, list]

A tuple containing the name of the support variable and a list of its possible values.

required
parameter_domain Domain

The domain of the parameters for the parametrized probability measure.

required
name Hashable

The name of the parametrized probability measure.

"P"

Returns:

Name Type Description
param_prob_measure ParametrizedProbabilityMeasure

The constructed parametrized probability measure.

Examples:

>>> from scipy.stats import binom, hypergeom
>>> from sigalg.core import (
...     Domain,
...     ParametrizedProbabilityMeasure,
... )

We will build a parametrized binomial probability measure using the binom class from SciPy. First, we inspect the signature of the pmf method on the SciPy website and notice that the measure is parametrized by n and p. We build a parameter domain for these.

>>> Theta = Domain(
...     [(2, 0.25), (3, 0.75)],
...     variable_names=["n", "p"],
...     name="Theta",
... )

The support variable of the pmf is k. We choose it to have support [0, 1, 2, 3].

>>> P = ParametrizedProbabilityMeasure.from_scipy(
...     dist=binom,
...     support=("k", [0, 1, 2, 3]),
...     parameter_domain=Theta,
... )
>>> print(P)
Parametrized probability measure 'P':
n       2         3
p    0.25      0.75
k
0  0.5625  0.015625
1  0.3750  0.140625
2  0.0625  0.421875
3  0.0000  0.421875

For a second example, we build a parametrized hypergeometric distribution. Again, we inspect the signature of the pmf method on the SciPy website and note that it is parametrized by M, n, and N.

>>> Theta = Domain(
...     [(5, 3, 3), (10, 5, 5)],
...     variable_names=["M", "n", "N"],
...     name="Theta",
... )

The support variable of the pmf is again k.

>>> Q = ParametrizedProbabilityMeasure.from_scipy(
...     dist=hypergeom,
...     support=("k", [0, 1, 2, 3, 4, 5]),
...     parameter_domain=Theta,
...     name="Q",
... )
>>> print(Q)
Parametrized probability measure 'Q':
M    5        10
n    3         5
N    3         5
k
0  0.0  0.003968
1  0.3  0.099206
2  0.6  0.396825
3  0.1  0.396825
4  0.0  0.099206
5  0.0  0.003968

surprisal

surprisal(
    base_measure=None, base="2", name=None, tol=1e-08
)

Compute the surprisal with respect to a base measure.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure | None

The base measure with respect to which the surprisal is computed. If None, the counting measure on the domain of the sigma-algebra is used.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the surprisal.

"2"
name Hashable | None

The name of the resulting surprisal. If None, a default name will be generated.

None
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
surprisal ParametrizedMeasurableFunction

The surprisal of the current measure with respect to the base measure.

Examples:

>>> import numpy as np
>>> from sigalg import (
...     Domain,
...     Measure,
...     ParametrizedProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a parametrized probability measure.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,
...         5: 2,
...     },
... )
>>> mapping = dict(zip(Theta @ F.atom_space, [0.3, 0.5, 0.2, 0.0, 0.1, 0.9]))
>>> P = ParametrizedProbabilityMeasure.from_domains(
...     measure_domain=F,
...     parameter_domain=Theta,
...     mapping=mapping,
... )
>>> print(P)
Parametrized probability measure 'P':
theta    0    1
F
0      0.3  0.0
1      0.5  0.1
2      0.2  0.9

Define a base measure for the surprisal.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 4,
...     },
... )

Compute the surprisal of P with respect to mu.

>>> P.surprisal(mu)
ParametrizedMeasurableFunction(parameters=(theta), measurable_vars=(omega), domain=Omega, sig_alg=F, measure=None, name=s(P; mu))
>>> print(P.surprisal(mu))
Parametrized measurable function 's(P; mu)':
theta         0         1
omega
0      1.736966  0.000000
1      1.736966  0.000000
2      2.000000  4.321928
3      2.000000  4.321928
4      4.321928  2.152003
5      4.321928  2.152003

We may check that the surprisal is the negative logarithm of the Radon-Nikodym derivative of P with respect to mu.

>>> with np.errstate(divide='ignore'):
...     print(-np.log(P.derivative(mu)))
Parametrized measurable function '(-log(dP_dmu))':
theta         0         1
omega
0      1.203973       inf
1      1.203973       inf
2      1.386294  2.995732
3      1.386294  2.995732
4      2.995732  1.491655
5      2.995732  1.491655

ParametrizedRandomVariable

Bases: ParametrizedMeasurableFunction

A class representing a parametrized random variable.

The __init__ constructor is not meant to be used directly. Instead, the user should use the from_domains class method.

See the documentation for ParametrizedMeasurableFunction for more details.

from_domains classmethod

from_domains(
    measurable_domain,
    parameter_domain=None,
    complete_domain=None,
    sig_alg=None,
    measure=None,
    mapping=None,
    parameter_names=None,
    complete_domain_name=None,
    parameter_domain_name=None,
    output_name=None,
    name=None,
)

Construct a parametrized random variable from a measurable domain and parameter domain.

See the documentation for ParametrizedMeasurableFunction for usage examples.

Parameters:

Name Type Description Default
measurable_domain IndexLike

The measurable domain of the function.

required
parameter_domain IndexLike

The parameter domain of the function.

None
sig_alg SigmaAlgebra

The sigma-algebra of the underlying measurable space.

None
measure Probability | None

The probability measure of the underlying probability space.

None
mapping MappingLike

The mapping of the parametrized function.

None
parameter_names list[Hashable] | None

The names of the parameters of the function.

None
complete_domain_name Hashable | None

The name of the domain of the function.

None
parameter_domain_name Hashable | None

The name of the parameter domain.

None
output_name Hashable | None

The name of the output variable for the parametrized function. If None, a default will be generated.

None
name Hashable | None

The name of the parametrized function. If None, a default will be generated.

None

Returns:

Name Type Description
param_rv ParametrizedRandomVariable

The constructed parametrized random variable.

Examples:

>>> from sigalg.core import (
...     Domain,
...     ParametrizedRandomVariable,
...     ProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a 1-dimensional parameter domain and measurable domain.

>>> Theta = Domain.from_sequence(size=2, variable_name="theta", name="Theta")
>>> Omega = SampleSpace.from_sequence(size=3)

Define a sigma-algebra and probability measure.

>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.8,
...     },
... )

Define the mapping of a parametrized random variable.

>>> mapping = {
...     (0, 0): 1,  # (theta, omega) = (0, 0), etc ...
...     (0, 1): 2,
...     (0, 2): 2,
...     (1, 0): 0,
...     (1, 1): -3,
...     (1, 2): -3,
... }

Instantiate a parametrized random variable and print it.

>>> X = ParametrizedRandomVariable.from_domains(
...     measurable_domain=Omega,
...     parameter_domain=Theta,
...     sig_alg=F,
...     measure=P,
...     mapping=mapping,
... )
>>> print(X)
Parametrized random variable 'X':
theta  0  1
omega
0      1  0
1      2 -3
2      2 -3

The printout displays the random variable as a 2-dimensional array, where the parameters run alog the horizontal axis and the "measurable" variables along the vertical.

Evaluate the function at a parameter to obtain a random variable.

>>> print(X(theta=0))
Random variable 'X(theta=0)':
    X(theta=0)
omega
0           1
1           2
2           2

Evaluate the function at a "measurable" variable to get an instance of Function.

>>> print(X(omega=0))
Function 'X(omega=0)':
        X(omega=0)
theta
0                1
1                0

ProbabilityMeasure

Bases: Measure

A class representing a probability measure on a sigma-algebra.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain MeasureDomain | None

The domain of the measure. Either a SigmaAlgebra or an IndexLike object that can be coerced into a Domain. In the latter case, the domain will be set to the power set of the domain.

None
mapping MappingLike | None

A mapping from the domain to the probability values.

None
output_name Hashable | None

The name of the output variable of the measure. If None, a default will be generated.

None
name Hashable | None

The name of the measure. If None, a default will be generated.

None

Examples:

>>> from sigalg.core import ProbabilityMeasure, SampleSpace, SigmaAlgebra

Define a probability measure on a sigma-algebra with two atoms.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra(
...    domain=Omega,
...    mapping={
...        0: 0,
...        1: 0,
...        2: 1,
...    },
... )
>>> P = ProbabilityMeasure(domain=F, mapping={0: 0.2, 1: 0.8})
>>> print(P)
Probability measure 'P':
         P
F
0      0.2
1      0.8

Define a probability measure directly on a sample space, which will use the power-set sigma-algebra by default.

>>> Q = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.1,
...         1: 0.3,
...         2: 0.6,
...     },
...     name="Q",
... )
>>> print(Q)
Probability measure 'Q':
        Q
omega
0     0.1
1     0.3
2     0.6
>>> print(Q.sig_alg)
Sigma algebra 'R':
        R
omega
0       0
1       1
2       2

Define the same probability measure directly on a list of points.

>>> Q = ProbabilityMeasure(
...     domain=[0, 1, 2],
...     mapping={
...         0: 0.1,
...         1: 0.3,
...         2: 0.6,
...     },
...     name="Q",
... )
>>> print(Q)
Probability measure 'Q':
        Q
x
0     0.1
1     0.3
2     0.6
>>> print(Q.sig_alg)
Sigma algebra 'R':
        R
x
0       0
1       1
2       2
Notes

Let \((\Omega, \mathcal{F})\) be a measurable space. A probability measure \(P\) is a countably additive function \(P: \mathcal{F} \to [0,\infty)\) such that \(P(\Omega) = 1\). Here, countable additivity means that

\[ P \left( \bigcup_{k=1}^\infty A_k \right) = \sum_{k=1}^\infty P(A_k) \]

for all collections \(\{A_k\}_{k=1}^\infty\) of pairwise disjoint measurable sets. If \(\Omega\) is finite (as it always is, in SigAlg), then \(P\) needs only to be finitely additive in order to be countably additive.

are_independent

are_independent(given1, given2, tol=1e-08)

Check if two events, two random vectors, or two sigma-algebras are independent.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
given1 Set | MeasurableVector | SigmaAlgebra

The first given to test for independence.

required
given2 Set | MeasurableVector | SigmaAlgebra

The second given to test for independence.

required
tol Real

The numerical tolerance for checking independence.

1e-8

Returns:

Name Type Description
is_independent bool

True if the events, random vectors, or sigma-algebras are independent, False otherwise.

Examples:

>>> from sigalg.core import (
...     ProbabilityMeasure,
...     ProbabilitySpace,
...     RandomVector,
...     SampleSpace,
... )
>>> Omega = SampleSpace.cartesian_power([0, 1], n=2, variable_names=["s_0", "s_1"], name="Omega")
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping=lambda *, s_0, s_1: 0.75 ** (s_0 + s_1) * 0.25 ** (2 - s_0 - s_1),
... )
>>> prob_space = ProbabilitySpace(domain=Omega, measure=P)
>>> print(prob_space)
Probability space (Omega, R, P)
===============================

* Sample space 'Omega':
 s_0  s_1
   0    0
   0    1
   1    0
   1    1

* Sigma algebra 'R':
i        0  1
s_0 s_1
0   0    0  0
    1    0  1
1   0    1  0
    1    1  1

* Probability measure 'P':
                P
s_0 s_1
0   0    0.0625
    1    0.1875
1   0    0.1875
    1    0.5625
>>> A = prob_space.get_set(
...     [(0, 0), (0, 1)],
...     name="A",
... )
>>> B = prob_space.get_set(
...     [(0, 1), (1, 1)],
...     name="B",
... )
>>> P.are_independent(A, B)
True
>>> X = prob_space.to_rv()
>>> X_1 = X["s_0"].with_name("X_1")
>>> X_2 = X["s_1"].with_name("X_2")
>>> P = X.measure
>>> print(X_1)
Random variable 'X_1':
       X_1
omega
0        0
1        0
2        1
3        1
>>> print(X_2)
Random variable 'X_2':
       X_2
omega
0        0
1        1
2        0
3        1
>>> P.are_independent(X_1, X_2)
True
>>> Y = X_1 + X_2
>>> print(Y)
Random variable '(X_1 + X_2)':
       (X_1 + X_2)
omega
0                0
1                1
2                1
3                2
>>> P.are_independent(X_1, Y)
False
Notes

Let \((\Omega, \mathcal{F}, P)\) be a probability space, and let \(\mathcal{G}\) and \(\mathcal{H}\) be two sub-\(\sigma\)-algebras of \(\mathcal{F}\). We say that \(\mathcal{G}\) and \(\mathcal{H}\) are independent if for every \(G \in \mathcal{G}\) and \(H \in \mathcal{H}\), we have

\[ P(G \cap H) = P(G) P(H). \]

In the special case where \(\mathcal{G} = \sigma(A)\) and \(\mathcal{H} = \sigma(B)\) are the \(\sigma\)-algebras generated by two events \(A\) and \(B\) in \(\mathcal{F}\), this reduces to the condition that

\[ P(A \cap B) = P(A) P(B), \]

and we say that the events \(A\) and \(B\) are independent.

conditional

conditional(
    given, subset=None, base_measure=None, name=None
)

Compute a conditional probability.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
given SigmaAlgebra | Set | MeasurableVector

The given condition, which can be a sigma-algebra, an event, or a random vector.

required
subset Set | None

This method will either return a RandomVariable or a ParametrizedProbabilityMeasure depending on whether this parameter is None or not. See the Notes and Examples section below.

None
base_measure Measure | None

If the given sigma-algebra contains null atoms, a default probability measure will be generated for those atoms. This parameter is an optional measure with respect to which these default distributions will be absolutely continuous. See the Notes and Examples section below.

None
name Hashable | None

The name of the resulting parametrized probability measure. If None, a default will be generated.

None

Returns:

Name Type Description
cond_prob_measure RandomVariable | ParametrizedProbabilityMeasure

A random variable or a parametrized probability measure representing the conditional probability.

Examples:

>>> from sigalg.core import (
...     Measure,
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     Set,
...     SigmaAlgebra,
... )

Define a probability space. Notice that the sigma-algebra has two null atoms with identifiers 3 and 4.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # null atom
...         5: 4,  # null atom
...         6: 4,  # null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )

Define a sub-sigma-algebra of F. Notice that this sigma-algebra also has a null atom, with identifier 2.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,  # null atom
...         5: 2,  # null atom
...         6: 2,  # null atom
...     },
...     name="G",
... )

We first compute the conditional probability with an explit subset parameter, returning an instance of RandomVariable.

>>> U = Set([1, 2, 3], domain=Omega, name="U")
>>> P.conditional(G, subset=U)
RandomVariable(parameters=(omega), domain=Omega, sig_alg=G, measure=P|G, name=P(U|G))
>>> print(P.conditional(G, subset=U))
Random variable 'P(U|G)':
       P(U|G)
omega
0        0.75
1        0.75
2        1.00
3        1.00
4        0.00
5        0.00
6        0.00

Notice the random variable takes the constant value 0.00 on the null atom of G. (This is the default SigAlg behavior.)

If the subset parameter of the conditional method is left as its default value None, then the method will return an instance of ParametrizedProbabilityMeasure.

>>> P.conditional(G)
ParametrizedProbabilityMeasure(parameters=(G), domain_vars=(F), sig_alg=F, name=P(-|G))
>>> print(P.conditional(G))
Parametrized probability measure 'P(-|G)':
G     0    1    2
F
0  0.25  0.0  0.2
1  0.75  0.0  0.2
2  0.00  1.0  0.2
3  0.00  0.0  0.2
4  0.00  0.0  0.2

Notice that the parameter space for the measure is the set of atom identifiers of the sub-sigma-algebra G. The null atom of G has identifier 2 (the third column). The mathematical theory does not place any restrictions on the probability distribution given a null atom, just as long as it is some valid distribution. The default in SigAlg is to create the uniform one.

However, sometimes it is useful for this distribution to be chosen absolutely continuous with respect to a base measure. We create a base measure.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 3,
...         4: 4,
...     }
... )

Notice that mu vanishes on the atom with identifier 0. The default uniform measure P.conditional(G) when G=2 is not absolutely continuous with respect to mu, but we can generate one that is by passing mu as the base_measure parameter into the conditional method.

>>> print(P.conditional(G, base_measure=mu))
Parametrized probability measure 'P(-|G)':
G     0    1     2
F
0  0.25  0.0  0.00
1  0.75  0.0  0.25
2  0.00  1.0  0.25
3  0.00  0.0  0.25
4  0.00  0.0  0.25

In any case, we may call either of these parametrized measures on the subset U from above.

>>> print(P.conditional(G)(U))
Function 'P(-|G)(U)':
   P(-|G)(U)
G
0       0.75
1       1.00
2       0.40

Notice the difference between P.conditional(G, subset=U) from above and this last printout of P.conditional(G)(U). The former is a random variable defined on the sample space Omega, while the latter is a function defined on the set of atom identifiers of G. We may "ascend" the latter to a function on the sample space by calling the ascend method. (Essentially, we are "broadcasting" the values of the function from the atom identifiers to the entire sample space).

>>> print(P.conditional(G)(U).ascend(G))
Measurable function 'P(-|G)(U)':
       P(-|G)(U)
omega
0           0.75
1           0.75
2           1.00
3           1.00
4           0.40
5           0.40
6           0.40

We check that these two ways of creating a random variable are equal P-a.s.

>>> P.equal_almost_surely(P.conditional(G)(U).ascend(G), P.conditional(G, subset=U))
True

Finally, we check the law of total probability. (See the Notes section below.)

>>> integrate = Operators.integrate
>>> all(P(U & B) == integrate(P.conditional(G, U), B, measure=P) for B in G)
True
Notes

Let \((\Omega, \mathcal{F}, P)\) be a probability space, and let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\). The conditional probability of an event \(U\in \mathcal{G}\), given \(\mathcal{G}\), is any \(\mathcal{G}\)-measurable random variable \(P(U\mid \mathcal{G})\) such that

\[ P(U \cap V) = \int_V P(U \mid \mathcal{G}) \, dP, \]

for all \(V\in \mathcal{G}\). This equation is called the law of total probability. In particular, we may take

\[ P(U\mid \mathcal{G}) = E(I_U \mid \mathcal{G}), \]

where \(I_U\) is the indicator function of \(U\).

Suppose now that \(\Omega\) is finite. From the Fourier expansion of the conditional expectation, we see that

\[ P(U \mid \mathcal{G}) = \sum_B \frac{P(U\cap B)}{P(B)} I_B, \]

\(P\)-almost surely, where the sum extends over all atoms \(B\) of \(\mathcal{G}\) with nonzero probability. Using this formula, one may show that for each \(\omega\in \Omega\) not contained in a \(P\)-null atom of \(\mathcal{G}\), the function

\[ P(- \mid \mathcal{G})(\omega): \mathcal{F} \to [0,1], \quad U \mapsto P(U\mid \mathcal{G})(\omega), \]

is a probability measure on \(\mathcal{F}\). If \(\omega\) is in a \(P\)-null atom, then we suppose that some fixed probability measure is assigned to \(P(- \mid \mathcal{G})(\omega)\). It may be arbitrarily assigned — it just needs to be a valid measure. In applications, it is sometimes convenient to select these measures to be absolutely continuous with respect to a given base measure.

cross_entropy

cross_entropy(
    other,
    base_measure=None,
    given=None,
    base="2",
    tol=1e-08,
)

Compute the cross entropy from this to another probability measure with respect to a base measure, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
other ProbabilityMeasure

The other probability measure.

required
base_measure Measure

The base measure with respect to which the cross entropy is computed. If None, the counting measure is used.

None
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the cross entropy.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the cross entropy.

"2"
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
cross_entropy Real

The cross entropy from this measure to another.

Examples:

>>> from sigalg.core import (
...     Operators,
...     Measure,
...     ProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define two probability measures and a base measure for cross entropy. Notice that the sigma-algebra has null atoms.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # P-null atom
...         5: 4,  # P-, Q-, and mu-null atom
...         6: 4,  # P-, Q-, and mu-null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )
>>> Q = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.3,
...         1: 0.5,
...         2: 0.1,
...         3: 0.1,
...         4: 0.0,
...     },
...     name="Q",
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 4,
...         4: 0,
...     },
... )

Compute the cross entropy from P to Q with base measure mu.

>>> P.cross_entropy(Q, mu)
2.528771237954945

We may check that the cross entropy agrees with its mathematical definition as an integral.

>>> P.cross_entropy(Q, mu) == Q.surprisal(mu).integrate(measure=P)
True

Define a sub-sigma-algebra for conditional cross entropy.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,  # P-null atom
...         5: 2,  # P-null atom
...         6: 2,  # P-null atom
...     },
...     name="G",
... )

Compute the cross entropy from P to Q, conditioned on G.

>>> P.cross_entropy(Q, mu, given=G)
1.6068431430675827

We may check that the cross entropy agrees with its mathematical definition as a double integral.

>>> integrate = Operators.integrate
>>> P.cross_entropy(Q, mu, given=G) == Q.surprisal(mu, G).integrate(
...     measure=P.conditional(G)
... ).ascend(G).integrate(measure=P)
True
Notes

Let \((\Omega, \mathcal{F})\) be a finite measurable space and \(P\) and \(Q\) two probability measures with \(P \ll Q\), and \(\mu\) a base measure with \(Q\ll \mu\). The cross entropy from \(P\) to \(Q\) (with respect to \(\mu\)) is the number

\[ H_P(Q;\mu) = \int_\Omega s(Q; \mu) \, dP, \]

where \(s(Q;\mu)\) is a surprisal function of \(Q\) with respect to \(\mu\).

derivative

derivative(
    base_measure=None, given=None, name=None, tol=1e-08
)

Compute the Radon-Nikodym derivative with respect to a base measure, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure | None

The base measure with respect to which the derivative is computed. If None, the counting measure is used.

None
given SigmaAlgebra | MeasurableVector | None

The optional sigma-algebra or random vector on which to condition the derivative.

None
name Hashable | None

The name of the derivative. If None, a default name is generated.

None
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
derivative MeasurableFunction | ParametrizedMeasurableFunction

Either an instance of MeasurableFunction if the derivative is unconditional, or an instance of ParametrizedMeasurableFunction.

Examples:

>>> from sigalg.core import (
...     Measure,
...     Operators,
...     ProbabilityMeasure,
...     RandomVariable,
...     SampleSpace,
...     Set,
...     SigmaAlgebra,
... )

Define a probability space and a base measure for Radon-Nikodym derivatives. Notice that the sigma-algebra has null atoms.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # P-null atom
...         5: 4,  # P- and mu-null atom
...         6: 4,  # P- and mu-null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 4,
...         4: 0,
...     },
... )

Compute the Radon-Nikodym derivative of P with respect to mu.

>>> P.derivative(mu)
MeasurableFunction(parameters=(omega), domain=Omega, sig_alg=F, measure=mu, name=dP_dmu)
>>> print(P.derivative(mu))
Measurable function 'dP_dmu':
       dP_dmu
omega
0    0.200000
1    0.300000
2    0.066667
3    0.066667
4    0.000000
5    0.000000
6    0.000000

Notice that the derivative is 0.0 on the mu-null atom. This is the default SigAlg behavior.

Extract a set from the sigma-algebra and check that the derivative has its defining property.

>>> integrate = Operators.integrate
>>> U = Set([1, 2, 3], domain=Omega, name="U")
>>> P(U) == integrate(P.derivative(mu), U)
True

Define a sub-sigma-algebra for conditional derivatives. Notice the null atom.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,  # P-null atom
...         5: 2,  # P-null atom
...         6: 2,  # P-null atom
...     },
...     name="G",
... )

In SigAlg, conditional derivatives are instances of ParametrizedMeasurableFunction.

>>> P.derivative(mu, G)
ParametrizedMeasurableFunction(parameters=(G), measurable_vars=(omega), domain=Omega, sig_alg=F, measure=mu, name=dP(-|G)_dmu)
>>> print(P.derivative(mu, G))
Parametrized measurable function 'dP(-|G)_dmu':
G          0         1         2
omega
0      0.250  0.000000  0.250000
1      0.375  0.000000  0.125000
2      0.000  0.333333  0.083333
3      0.000  0.333333  0.083333
4      0.000  0.000000  0.062500
5      0.000  0.000000  0.000000
6      0.000  0.000000  0.000000

Note that the parameter space for the function is the set of atom identifiers of the conditioning sigma-algebra G.

The integral of the conditional Radon-Nikodym derivative is supposed to coincide (P-almost surely) with the conditional probability distribution. We check this, using the set U from above.

>>> integrate(P.derivative(mu, G), U, measure=mu)
Function(parameters=(G), domain=G, name=int_U dP(-|G)_dmu dmu)
>>> print(integrate(P.derivative(mu, G), U, measure=mu))
Function 'int_U dP(-|G)_dmu dmu':
   int_U dP(-|G)_dmu dmu
G
0                   0.75
1                   1.00
2                   0.50

This is a Function instance defined on the atom identifiers of G. We may "ascend" to a function on the entire sample space by calling the ascend method. (Essentially, broadcasting the values of the function from the atom identifers to the entire sample space.) We check that this function coincides with the conditional distribution.

>>> P.equal_almost_surely(integrate(P.derivative(mu, G), U, measure=mu).ascend(G), P.conditional(G, subset=U))
True

Now define a random variable to demonstrate the connnection with expectations.

>>> X = RandomVariable(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: 1,
...         1: -2,
...         2: 4,
...         3: 4,
...         4: 5,
...         5: 3,
...         6: 3,
...     },
... )

As we saw above, in SigAlg, the conditional derivative is an instance of ParametrizedMeasurableFunction, where the parameters are the atom identifiers of G. Conceptually, one should think of the conditional derivative as a family of derivatives, one for each atom identifier.

Multiplication of a RandomVariable instance against an instance of ParametrizedMeasurableFunction is defined in SigAlg as long as both are defined on the same sample space. The multiplication is the usual function multiplication, parameter by parameter, and yields an instance of ParametrizedMeasurableFunction.

>>> X * P.derivative(mu, G)
ParametrizedMeasurableFunction(parameters=(G), measurable_vars=(omega), domain=Omega, sig_alg=F, measure=None, name=(X * dP(-|G)_dmu))

Think of a ParametrizedMeasurableFunction as a family of measurable functions, one for each parameter. It is possible to pass such an instance into the integrate operator. The result is a Function whose values are the integrals of the random variables, parameter by parameter.

>>> integrate(X * P.derivative(mu, G), measure=mu)
Function(parameters=(G), domain=G, name=int (X * dP(-|G)_dmu) dmu)

This is a function on the atom identifiers of the sigma-algebra G. As above, it naturally "ascends" to a function on the sample space, which we obtain using the ascend method. The mathematical theory says that this function is equal to the conditional expectation (at least P-almost surely).

>>> E = Operators.expectation
>>> P.equal_almost_surely(E(X, G), integrate(X * P.derivative(mu, G), measure=mu).ascend(G))
True
Notes

Let \(P\) be a probability measure on a finite measurable space \((\Omega, \mathcal{F})\) that is absolutely continuous with respect to a second measure \(\mu\). A Radon-Nikodym derivative of \(P\) with respect to \(\mu\) is an \(\mathcal{F}\)-measurable function \(h:\Omega \to \mathbb{R}\) for which

\[ P(U) = \int_\Omega h \, d\mu \]

for all \(U\in \mathcal{F}\). We often write \(dP/d\mu\) for \(h\).

Now suppose that \(\mathcal{G}\) is a sub-\(\sigma\)-algebra of \(\mathcal{F}\). A conditional Radon-Nikodym derivative of \(P\) given \(\mathcal{G}\) (with respect to \(\mu\)) is any \((\mathcal{F} \otimes \mathcal{G})\)-measurable function \(h: \Omega \times \Omega \to \mathbb{R}\) for which

\[ P(U\cap V) = \int_{U\times V} h(\omega, \omega') \, d(\mu \otimes P)(\omega, \omega') \]

for all \(U\in \mathcal{F}\) and \(V\in \mathcal{G}\). By Fubini's theorem, we may write this latter equation as

\[ P(U\cap V) = \int_V \left[ \int_U h(\omega, \omega') \, d\mu(\omega) \right] \, dP(\omega'), \]

from which it follows that

\[ P(U\mid \mathcal{G})(\omega') = \int_U h(\omega, \omega') \, d\mu(\omega), \]

where the equality holds for all \(\omega'\) \(P\)-almost surely. It thus follows that for all such \(\omega'\), the partial function

\[ h(-, \omega') : \Omega \to \mathbb{R}, \quad \omega \mapsto h(\omega, \omega'), \]

is a Radon-Nikodym derivative of the probability measure \(P(- \mid \mathcal{G})(\omega')\) with respect to \(\mu\).

divergence

divergence(other, given=None, base='2', tol=1e-08)

Compute the Kullback Leibler divergence from this to another probability measure, optinally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
other ProbabilityMeasure

The other probability measure.

required
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the entropy.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the divergence.

"2"
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
kl Real

The KL divergence from this measure to another.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Measure,
...     ProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define two probability measures for KL divergence. Notice that the sigma-algebra has null atoms.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # P-null atom
...         5: 4,  # P- and Q-null atom
...         6: 4,  # P- and Q-null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )
>>> Q = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.3,
...         1: 0.5,
...         2: 0.1,
...         3: 0.1,
...         4: 0.0,
...     },
...     name="Q",
... )

Compute the KL divergence from P to Q.

>>> P.divergence(Q)
0.24082814335604508

The KL divergence is the negative entropy of P with base measure Q.

>>> P.divergence(Q) == -P.entropy(Q)
True

The KL divergence is also the difference between a cross entropy and a plain entropy. Before checking this, we define a base measure for the entropies.

>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 4,
...         4: 0,
...     },
... )
>>> np.allclose(P.divergence(Q), P.cross_entropy(Q, mu) - P.entropy(mu))
True
Notes

Let \((\Omega, \mathcal{F})\) be a finite measurable space and \(P\) and \(Q\) two probability measures with \(P \ll Q\). The Kullback Leibler (KL) divergence from \(P\) to \(Q\) is the number

\[ D(P \parallel Q) = -H(P;Q), \]

where \(H(P;Q)\) is the entropy of \(P\) with base measure \(Q\).

entropy

entropy(
    base_measure=None,
    given=None,
    base="2",
    name=None,
    tol=1e-08,
)

Compute the entropy of the probability measure with respect to a base measure, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure

The base measure with respect to which the entropy is computed. If None, the counting measure is used.

None
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the entropy.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the entropy.

"2"
name Hashable | None

The name of the entropy. If None, a default name is generated.

None
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
entropy Real

The entropy of the probability measure.

Examples:

>>> from sigalg.core import (
...     Measure,
...     ProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space and a base measure for entropies. Notice that the sigma-algebra has null atoms.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # P-null atom
...         5: 4,  # P- and mu-null atom
...         6: 4,  # P- and mu-null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 4,
...         4: 0,
...     },
... )

Compute the entropy of P with respect to mu.

>>> P.entropy(mu)
2.2879430945988997

Check that the entropy is the integral of the surprisal function.

>>> P.surprisal(mu).integrate(measure=P)
2.2879430945988997

Define a sub-sigma-algebra for conditional entropies. Notice the null atom.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,  # P-null atom
...         5: 2,  # P-null atom
...         6: 2,  # P-null atom
...     },
...     name="G",
... )

Compute the conditional entropy.

>>> P.entropy(mu, G)
1.5660149997115376

We check that this value coincides with the mathematical definition of the conditional entropy. First, we integrate the conditional entropy against the conditional distribution.

>>> inner_integral = P.surprisal(mu, G).integrate(measure=P.conditional(G))
>>> print(inner_integral)
Function 'int s(P|G; mu) dP(-|G)':
   int s(P|G; mu) dP(-|G)
G
0                1.561278
1                1.584963
2                2.516993

Notice that this is a function on the atom identifiers of G. We "ascend" to a function on the entire sample space using the ascend method, and then we integrate against P.

>>> inner_integral.ascend(G).integrate(measure=P)
1.5660149997115376

Note this value coincides with the conditional entropy computed above.

Notes

Let \((\Omega,\mathcal{F},P)\) be a finite probability space and \(\mu\) a measure on \(\mathcal{F}\) for which \(P \ll \mu\). We define the \textit{entropy} of \(P\) (with respect to \(\mu\)) to be the number

\[ H(P;\mu) = \int_\Omega s(P;\mu) \, dP. \]

Now let \(\mathcal{G}\) be a sub-\(\sigma\)-algebra of \(\mathcal{F}\) and suppose \(P(-\mid \mathcal{G})(\omega) \ll \mu\) for all \(\omega\in \Omega\). We define the conditional entropy of \(P\) given \(\mathcal{G}\) (with respect to \(\mu\)) to be the number

\[ H(P\mid \mathcal{G} ;\mu) = \int_\Omega \left[ \int_\Omega s(P\mid \mathcal{G};\mu)(\omega,\omega') \, d\left(P(-\mid \mathcal{G})(\omega')\right)(\omega) \right] \, dP(\omega'). \]

equal_almost_surely

equal_almost_surely(first, second, rtol=1e-05, atol=1e-08)

Determine whether two random vectors are equal almost surely.

See the Notes section below for the mathematical details.

This method calls the Measure.equal_almost_everywhere method.

Parameters:

Name Type Description Default
first MeasurableVector

The first random vector.

required
second MeasurableVector

The second random vector.

required
rtol float

The relative tolerance for np.isclose when comparing the random vectors.

1e-5
atol float

The absolute tolerance for np.isclose when comparing the random vectors.

1e-8

Returns:

Name Type Description
equal_as bool

True if the random vectors are equal almost surely; False otherwise.

Examples:

>>> from sigalg.core import (
...     ProbabilityMeasure,
...     RandomVariable,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.4,
...         1: 0.6,
...         2: 0.0,
...     },
... )
>>> X = RandomVariable.with_uniform(
...     domain=Omega,
...     mapping={
...         0: 1.0,
...         1: 2.0,
...         2: 3.0,
...     },
... )
>>> Y = RandomVariable.with_uniform(
...     domain=Omega,
...     mapping={
...         0: 1.0,
...         1: 2.0,
...         2: 4.0,
...     },
...     name="Y",
... )
>>> Z = RandomVariable.with_uniform(
...     domain=Omega,
...     mapping={
...         0: 1.0,
...         1: 3.0,
...         2: 3.0,
...     },
...     name="Z",
... )
>>> print(P.equal_almost_surely(X, Y))
True
>>> print(P.equal_almost_surely(X, Z))
False
>>> U = RandomVector.with_uniform(
...     domain=Omega,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (3, 2),
...     },
...     name="U",
... )
>>> V = RandomVector.with_uniform(
...     domain=Omega,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (-1, 4),
...     },
...     name="V",
... )
>>> W = RandomVector.with_uniform(
...     domain=Omega,
...     mapping={
...         0: (1, 2),
...         1: (-1, 1),
...         2: (3, 2),
...     },
...     name="W",
... )
>>> print(P.equal_almost_surely(U, V))
True
>>> print(P.equal_almost_surely(U, W))
False
Notes

Two random vectors \(X,Y:\Omega \to \mathbb{R}^d\) defined on a probability space \((\Omega, \mathcal{F}, P)\) are equal almost surely if

\[ P \left( \{\omega \in \Omega : X(\omega) \neq Y(\omega)\} \right) = 0. \]

mutual_info

mutual_info(
    first_variables=None, second_variables=None, base="2"
)

Compute the mutual information of the current probability measure.

Parameters:

Name Type Description Default
first_variables list[Hashable] | None

The variables for the first factor of the mutual information. See the Examples and Notes section below.

None
second_variables list[Hashable] | None

The variables for the second factor of the mutual information. See the Examples and Notes section below.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the mutual information.

"2"

Returns:

Name Type Description
mutual_info Real

The mutual information of this probability measure.

Examples:

>>> from sigalg.core import (
...     ProbabilityMeasure,
...     SampleSpace,
... )

Define a probability measure on a Cartesian product.

>>> Omega = SampleSpace.cartesian_power([0, 1], n=2)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         (0, 0): 0.15,
...         (0, 1): 0.15,
...         (1, 0): 0.45,
...         (1, 1): 0.25,
...     },
... )

Since P is defined on a 2-fold Cartesian product, we may leave first_variables and second_variables as their default values in the call to mutual_info. If this were not the case (see below), we would need to specify using these parameters which variables of the underlying domain correspond to which marginal distribution.

>>> P.mutual_info()
0.012750423385226917

Notice that the mutual information is not 0.0, and hence the measure is not the tensor product of its two marginals.

Define a second measure on the same space and compute its mutual information.

>>> Q = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         (0, 0): 0.25**2,
...         (0, 1): 0.75 * 0.25,
...         (1, 0): 0.75 * 0.25,
...         (1, 1): 0.75**2,
...     },
...     name="Q",
... )
>>> Q.mutual_info()
0.0

Since the mutual information is 0.0, this measure is the tensor product of its two marginals.

Now, define a measure on a 3-fold Cartesian product.

>>> S = SampleSpace.cartesian_power(
...     [0, 1], n=3, variable_names=["s_0", "s_1", "s_2"], name="S"
... )
>>> R = ProbabilityMeasure(
...     domain=S,
...     mapping={
...         (0, 0, 0): 0.2,
...         (0, 0, 1): 0.0,
...         (0, 1, 0): 0.1,
...         (0, 1, 1): 0.1,
...         (1, 0, 0): 0.3,
...         (1, 0, 1): 0.15,
...         (1, 1, 0): 0.05,
...         (1, 1, 1): 0.1,
...     },
...     name="R",
... )

We pass lists of variables into the mutual_info method to specify which factors of the domain S correspond to which marginals in the computation of the mutual information. For this example, the first marginal corresponds to the first variable of the domain, while the second marginal corresponds to the second and third variables.

>>> R.mutual_info(["s_0"], ["s_1", "s_2"])
0.14773092211916083

To be more precise, what this means is that the method will use the following pandas data for the marginals.

>>> R.data.groupby("s_0").sum()
s_0
0    0.4
1    0.6
Name: R, dtype: float64

The second marginal has the following data.

>>> R.data.groupby(["s_1", "s_2"]).sum()
s_1  s_2
0    0      0.50
     1      0.15
1    0      0.15
     1      0.20
Name: R, dtype: float64

sample

sample(size=1, name=None, random_state=None)

Generate random samples from this probability measure.

Parameters:

Name Type Description Default
size int

Number of samples to generate. Must be positive.

1
name Hashable | None

A name for the random sample. If None, a default will be generatd.

None
random_state int | Generator | None

Random seed or generator for reproducibility.

None

Returns:

Name Type Description
sample MeasureSpace

An instance of MeasureSpace whose domain consists of the random samples and whose measure is a counting measure giving the number of each sample produced.

Examples:

>>> import numpy as np
>>> from sigalg.core import ProbabilityMeasure, SampleSpace
>>> rng = np.random.default_rng(42)

Define a probability measure on the power set of a sample space containing three sample points.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> P = ProbabilityMeasure(
...     domain=Omega,
...     mapping={
...         0: 0.25,
...         1: 0.45,
...         2: 0.3,
...     },
... )

Draw a random sample from the probability measure of size 150.

>>> P_sample = P.sample(size=150, random_state=rng)

Print the measure of the sample.

>>> print(P_sample.measure)
Measure 'C':
       C
omega
1     74
2     39
0     37

Divide the counting measure by the sample size to obtain a probability measure. Note that it closely matches the original probability measure.

>>> C = P_sample.measure
>>> print(C / 150)
Function '(C / 150)':
        (C / 150)
omega
1        0.493333
2        0.260000
0        0.246667

surprisal

surprisal(
    base_measure=None,
    given=None,
    base="2",
    name=None,
    tol=1e-08,
)

Compute the surprisal of the probability measure with respect to a base measure, optionally conditioned on a sigma-algebra or random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
base_measure Measure

The base measure with respect to which the surprisal is computed. If None, the counting measure is used.

None
given SigmaAlgebra | RandomVector | None

The optional sigma-algebra or random vector on which to condition the surprisal.

None
base Literal['2', 'e', '10']

The base of the logarithm used to compute the surprisal.

"2"
name Hashable | None

The name of the surprisal. If None, a default name is generated.

None
tol float

Tolerance for testing for absolute continuity.

1e-8

Returns:

Name Type Description
surprisal MeasurableFunction | ParametrizedMeasurableFunction

Either an instance of MeasurableFunction if the surprisal is unconditional, or an instance of ParametrizedMeasurableFunction.

Examples:

>>> import numpy as np
>>> from sigalg.core import (
...     Measure,
...     ProbabilityMeasure,
...     SampleSpace,
...     SigmaAlgebra,
... )

Define a probability space and a base measure for surprisals. Notice that the sigma-algebra has null atoms.

>>> Omega = SampleSpace.from_sequence(size=7)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 2,
...         3: 2,
...         4: 3,  # P-null atom
...         5: 4,  # P- and mu-null atom
...         6: 4,  # P- and mu-null atom
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.6,
...         2: 0.2,
...         3: 0.0,
...         4: 0.0,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 3,
...         3: 4,
...         4: 0,
...     },
... )

Compute the surprisal of P with respect to mu.

>>> P.surprisal(mu)
RandomVariable(parameters=(omega), domain=Omega, sig_alg=F, measure=P, name=s(P; mu))
>>> print(P.surprisal(mu))
Random variable 's(P; mu)':
       s(P; mu)
omega
0      2.321928
1      1.736966
2      3.906891
3      3.906891
4      0.000000
5      0.000000
6      0.000000

We may compare these values to the negative logarithm of the Radon-Nikodym derivative.

>>> with np.errstate(divide='ignore'):
...     derivative = -np.log(P.derivative(mu))
>>> print(derivative)
Measurable function '(-log(dP_dmu))':
       (-log(dP_dmu))
omega
0            1.609438
1            1.203973
2            2.708050
3            2.708050
4                 inf
5                 inf
6                 inf

Note that the surprisal method in SigAlg sets the surprisal equal to 0 where the Radon-Nikodym derivative is equal to 0, instead of inf. Since the main use of the surprisal is to yield the entropy by integrating it, and since the Radon-Nikodym derivative only vanishes on P-null atoms, whether the surprisal is 0 or inf on null atoms will not matter.

Define a sub-sigma-algebra for conditional surprisals. Notice the null atom.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...         4: 2,  # P-null atom
...         5: 2,  # P-null atom
...         6: 2,  # P-null atom
...     },
...     name="G",
... )

Compute the conditional surprisal, which is an instance of ParametrizedRandomVariable.

>>> P.surprisal(mu, G)
ParametrizedRandomVariable(parameters=(G), measurable_vars=(omega), domain=Omega, sig_alg=F, measure=P, name=s(P|G; mu))
>>> print(P.surprisal(mu, G))
Parametrized random variable 's(P|G; mu)':
G             0         1         2
omega
0      2.000000  0.000000  2.000000
1      1.415037  0.000000  3.000000
2      0.000000  1.584963  3.584963
3      0.000000  1.584963  3.584963
4      0.000000  0.000000  4.000000
5      0.000000  0.000000  0.000000
6      0.000000  0.000000  0.000000

Notice that the parameter space of the conditional surprisal is the set of atom identifiers of G. If we compare to the conditional Radon-Nikodym derivative of P, we note that the conditional surprisal is nothing but its negative logarithm, atom by atom.

>>> with np.errstate(divide='ignore'):
...     derivative = -np.log(P.derivative(mu, G))
>>> print(derivative)
Parametrized measurable function '(-log(dP(-|G)_dmu))':
G             0         1         2
omega
0      1.386294       inf  1.386294
1      0.980829       inf  2.079442
2           inf  1.098612  2.484907
3           inf  1.098612  2.484907
4           inf       inf  2.772589
5           inf       inf       inf
6           inf       inf       inf
Notes

Let \(P\) be a probability measure on a finite measurable space \((\Omega, \mathcal{F})\) that is absolutely continuous with respect to a second measure \(\mu\). A surprisal of \(P\) with respect to \(\mu\) is an \(\mathcal{F}\)-measurable function \(s(P;\mu):\Omega \to \mathbb{R} \cup\{\infty\}\) with values on the extended real line for which \(e^{-s(P;\mu)}\) is a Radon-Nikodym derivative of \(P\) with respect to \(\mu\) and we take \(e^{-\infty} = 0\) by convention.

If we select a version \(dP/d\mu\) of the Radon-Nikodym derivative, we have

\[ s(P;\mu)(\omega) = -\log{ \left( \frac{dP}{d\mu}(\omega) \right)}, \]

for all \(\omega \in \Omega\). Since a Radon-Nikodym derivative is only uniquely determined \(\mu\)-a.e., so too is a surprisal function.

Now suppose that \(\mathcal{G}\) is a sub-\(\sigma\)-algebra of \(\mathcal{F}\) for which \(P(-\mid \mathcal{G})(\omega) \ll \mu\) for all \(\omega \in \Omega\). We define a conditional surprisal of \(P\) given \(\mathcal{G}\) (with respect to \(\mu\)) to be any \((\mathcal{F} \otimes \mathcal{G})\)-measurable function \(s(P\mid \mathcal{G};\mu): \Omega \times \Omega \to \mathbb{R} \cup \{\infty\}\) with values on the extended real line for which \(e^{-s(P\mid \mathcal{G};\mu)}\) is a conditional Radon-Nikodym derivative of \(P\) given \(\mathcal{G}\) (with respect to \(\mu\)) and we take \(e^{-\infty}=0\) by convention.

If for each \(\omega'\in \Omega\) we select a version \(dP(-\mid \mathcal{G})(\omega')/d\mu\) of the Radon-Nikodym derivative, we have

\[ s(P\mid \mathcal{G};\mu)(\omega,\omega') = -\log{ \left( \frac{dP(-\mid \mathcal{G})(\omega')}{d\mu}(\omega) \right)} \]

for all \(\omega\in \Omega\).

uniform classmethod

uniform(domain, output_name=None, name='U')

Create a uniform probability measure on a sigma-algebra or sample space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain MeasureDomain

The domain of the measure. Either a SigmaAlgebra or an IndexLike object that can be coerced into a Domain; in the latter case, the domain will be set to the power set of the Domain instance.

required
output_name Hashable | None

The name of the output variable of the measure. If None, a default will be generated.

None
name Hashable

A name for the measure.

"U"

Returns:

Name Type Description
prob_measure ProbabilityMeasure

A uniform ProbabilityMeasure instance on the provided sigma-algebra.

Examples:

>>> from sigalg.core import ProbabilityMeasure, SampleSpace, SigmaAlgebra

Define a measurable space with a sigma-algebra having three atoms.

>>> Omega = SampleSpace.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
... )

Define a uniform probability measure on the sigma-algebra. Each atom is given equal probability 1/3.

>>> U = ProbabilityMeasure.uniform(domain=F)
>>> print(U)
Probability measure 'U':
          U
F
0   0.333333
1   0.333333
2   0.333333

Define a uniform probability measure directly on the sample space. This will create the power-set sigma-algebra by default, and each outcome is given equal probability 1/4.

>>> V = ProbabilityMeasure.uniform(domain=Omega, name="V")
>>> print(V)
Probability measure 'V':
          V
omega
0       0.25
1       0.25
2       0.25
3       0.25
Notes

Let \((\Omega,\mathcal{F})\) be a measurable space where \(\Omega\) is finite, and suppose that \(\mathcal{F}\) has \(n\) atoms. The uniform probability measure on \(\mathcal{F}\) is the unique probability measure \(P\) such that

\[ P(A) = \frac{1}{n} \]

for all atoms \(A\in \mathcal{F}\).

ProbabilitySpace

Bases: MeasureSpace

A class representing a probability space.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain Domain | IndexLike | None

The sample space of the probability space.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the probability space.

None
measure ProbabilityMeasure | None

The probability measure of the probability space.

None

Examples:

>>> from sigalg.core import ProbabilityMeasure, ProbabilitySpace, SampleSpace, SigmaAlgebra

Build a ProbabilitySpace object with only a sample space. The power-set sigma-algebra and uniform probability measure will be generated by default.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> prob_space = ProbabilitySpace(domain=Omega)
>>> print(prob_space)
Probability space (Omega, R, U)
===============================

* Sample space 'Omega':
 omega
     0
     1
     2

* Sigma algebra 'R':
       R
omega
0      0
1      1
2      2

* Probability measure 'U':
              U
omega
0      0.333333
1      0.333333
2      0.333333

Create a probability space with a custom sigma-algebra and probability measure.

>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.1,
...         1: 0.9,
...     },
... )
>>> prob_space = ProbabilitySpace(domain=Omega, sig_alg=F, measure=P)
>>> print(prob_space)
Probability space (Omega, F, P)
===============================

* Sample space 'Omega':
 omega
     0
     1
     2

* Sigma algebra 'F':
       F
omega
0      0
1      1
2      1

* Probability measure 'P':
        P
F
0     0.1
1     0.9

The same probability space as the latter one may be created with only the measure as an explicit parameter. The sigma-algebra of the probability space is automatically extracted from the measure, and the domain is automatically extracted from the sigma-algebra. Thus, the minimal amount of information needed to construct a ProbabilitySpace object is just the measure.

>>> prob_space = ProbabilitySpace(measure=P)
>>> print(prob_space)
Probability space (Omega, F, P)
===============================

* Sample space 'Omega':
omega
    0
    1
    2

* Sigma algebra 'F':
    F
omega
0      0
1      1
2      1

* Probability measure 'P':
        P
F
0     0.1
1     0.9
Notes

A probability space is a triple \((\Omega, \mathcal{F}, P)\) consisting of a sample space \(\Omega\), a \(\sigma\)-algebra \(\mathcal{F}\) on \(\Omega\), and a probability measure \(P\) defined on \(\mathcal{F}\).

from_event classmethod

from_event(event, measure=None)

Create a conditional probability space from an event.

This method is an alias for MeasureSpace.from_set with the normalize parameter set to True. See the documentation of that method for more details.

Parameters:

Name Type Description Default
event MeasurableSet

The event on which to create the conditional probability space.

required
measure ProbabilityMeasure | None

The probability measure to use for the conditional probability space. If None, the probability measure carried by event will be used.

None

Returns:

Name Type Description
prob_space ProbabilitySpace

A new probability space representing the conditional probability space on the event.

Examples:

>>> from sigalg.core import ProbabilityMeasure, ProbabilitySpace, SampleSpace, SigmaAlgebra

Construct a probability space.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 2,
...         5: 0,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.13,
...         1: 0.55,
...         2: 0.32,
...     },
... )
>>> prob_space = ProbabilitySpace(Omega, F, P)

Extract an event from the probability space and form the conditional probability space.

>>> A = prob_space.get_set([1, 2, 3, 4])
>>> conditional_space = ProbabilitySpace.from_event(A, P)
>>> print(conditional_space)
Probability space (A, F|A, P|A)
===============================

* Sample space 'A':
 omega
     1
     2
     3
     4

* Sigma algebra 'F|A':
       F|A
omega
1        1
2        1
3        2
4        2

* Probability measure 'P|A':
           P|A
F
1     0.632184
2     0.367816

to_rv

to_rv(
    domain_name="Omega",
    domain_variable_name="omega",
    index_kind="Index",
    index_name=None,
    name="X",
)

Convert the current probability space to a RandomVector instance.

Parameters:

Name Type Description Default
domain_name Hashable

Name of the domain of the random vector.

"Omega"
domain_variable_name Hashable

Name of the domain variable.

"omega"
index_kind Literal['Index', 'Time']

Kind of the index.

"Index"
index_name Hashable | None

Name of the index.

None
name Hashable

Name of the random vector.

"X"

Returns:

Name Type Description
rv RandomVector

A new RandomVector object representing the current probability space.

Examples:

>>> from sigalg.core import ProbabilityMeasure, ProbabilitySpace, SampleSpace, SigmaAlgebra

Define a probability space with a 2-dimensional sample space.

>>> Omega = SampleSpace.cartesian_product([[1, 2], [1, 3]], variable_names=["A", "B"])
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         (1, 1): 0,
...         (1, 3): 1,
...         (2, 1): 1,
...         (2, 3): 2,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.7,
...         2: 0.1,
...     },
... )
>>> prob_space = ProbabilitySpace(Omega, F, P)

Convert to a random vector with default domain parameter values.

>>> X = prob_space.to_rv()
>>> print(X)
Random vector 'X':
i      A  B
omega
0      1  1
1      1  3
2      2  1
3      2  3

Convert to a random vector with custom domain parameters values.

>>> Y = prob_space.to_rv(domain_name="S", domain_variable_name="s", name="Y")
>>> print(Y)
Random vector 'Y':
i  A  B
s
0  1  1
1  1  3
2  2  1
3  2  3

RandomVariable

Bases: MeasurableFunction, RandomVector

Marker class for a 1-dimensional random vector.

RandomVector

Bases: MeasurableVector

A class representing a random vector.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the function.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the underlying probability space.

None
measure ProbabilityMeasure | None

The probability measure of the underlying probability space. This is a required argument. The default None is only provided to maintain consistency with the parent class MeasurableVector, which does not require a probability measure.

None
mapping MappingLike | None

The mapping defining the random vector.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None

Examples:

>>> from sigalg.core import (
...     MeasurableSpace,
...     ProbabilityMeasure,
...     ProbabilitySpace,
...     RandomVector,
...     SampleSpace,
...     SigmaAlgebra,
... )

Generate a 2-dimensional random vector on a pre-existing sample space from a dictionary mapping using the with_uniform class method. The power-set sigma-algebra and uniform probability measure are automatically generated.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> X = RandomVector.with_uniform(
...     domain=Omega,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...         2: (2, 2),
...     },
... )
>>> print(X)
Random vector 'X':
i      0  1
omega
0      1  1
1      1  1
2      2  2
>>> print(X.sig_alg)
Sigma algebra 'R':
      R
omega
0     0
1     1
2     2
>>> print(X.measure)
Probability measure 'U':
              U
omega
0      0.333333
1      0.333333
2      0.333333

Generate a random vector on a pre-existing probability space.

>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     }
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.5,
...         1: 0.5,
...     },
... )
>>> prob_space = ProbabilitySpace(Omega, F, P)
>>> Z = RandomVector(
...     *prob_space,
...     mapping={
...         0: (1, 1),
...         1: (1, 1),
...         2: (2, 2),
...     },
...     name="Z",
... )
>>> print(Z.sig_alg)
Sigma algebra 'F':
      F
omega
0     0
1     0
2     1
>>> print(Z.measure)
Probability measure 'P':
         P
F
0      0.5
1      0.5

Attempt to define a random vector that is not F-measurable

>>> try:
...     W = RandomVector(
...         *prob_space,
...         mapping={
...             0: (1, 2),
...             1: (3, 4),
...             2: (5, 6),
...         },
...         name="W",
...     )
... except ValueError as e:
...     print(e)
Function W is not measurable.
Notes

Given a probability space \((\Omega,\mathcal{F},P)\), a random vector is an \(\mathcal{F}\)-measurable function \(X: \Omega \to \mathbb{R}^d\), where \(d\) is the dimension of the vector and \(\mathbb{R}^d\) is equipped with its Borel \(\sigma\)-algebra. If \(\Omega\) is finite (as it always is, in SigAlg), so that \(\mathcal{F}\) is determined by its atoms, then \(X\) is \(\mathcal{F}\)-measurable if and only if \(X\) is constant on the atoms of \(\mathcal{F}\).

prob_measure property

prob_measure

Get the probability measure of the underlying probability space of the random vector.

This is an alias for the measure property of the parent class MeasurableVector.

prob_space property

prob_space

Get the underlying probability space of the random vector.

This is an alias for the measure_space property of the parent class MeasurableVector.

sample_space property

sample_space

Get the domain of the underlying probability space of the random vector.

This is an alias for the domain property of the parent class Function.

restrict_to

restrict_to(subset, subset_name='A')

Restrict the measurable vector to a measurable subset.

Parameters:

Name Type Description Default
subset Set | list[Hashable]

The set to restrict the measurable vector to.

required
subset_name Hashable

The name to use for the subset. Ignored if subset is an instance of Set.

"A"

Returns:

Name Type Description
restriction RandomVector

The restriction of the function.

Examples:

>>> from sigalg.core import ProbabilityMeasure, RandomVector, SampleSpace, Set, SigmaAlgebra

Define a probability space and 2-dimensional random vector.

>>> Omega = SampleSpace.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.2,
...         1: 0.5,
...         2: 0.3,
...     },
... )
>>> X = RandomVector(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     mapping={
...         0: (1, 2),
...         1: (3, 4),
...         2: (3, 4),
...         3: (5, 6),
...     },
... )

Restrict the random vector to a set using the restrict_to method.

>>> A = Set([1, 2, 3], domain=Omega)
>>> X_A = X.restrict_to(A)
>>> print(X_A)
Random vector 'X|A':
i      0  1
omega
1      3  4
2      3  4
3      5  6

Print its measure space consisting of the restricted sigma-algebra and (normalized) restricted measure.

>>> print(X_A.prob_space)
Probability space (A, F|A, P|A)
===============================

* Sample space 'A':
 omega
     1
     2
     3

* Sigma algebra 'F|A':
       F|A
omega
1        1
2        1
3        2

* Probability measure 'P|A':
     P|A
F
1  0.625
2  0.375

Compute the same restriction using the overloaded | operator.

>>> print(X | A)
Random vector 'X|A':
i      0  1
omega
1      3  4
2      3  4
3      5  6

sample

sample(
    size=1,
    replace=True,
    return_domain_mapping=False,
    name=None,
    random_state=None,
)

Generate random samples from the range space of this random vector.

Parameters:

Name Type Description Default
size int

Number of samples to generate. Must be positive.

1
name Hashable | None

A name for the random sample. If None, a default will be generatd.

None
random_state int | Generator | None

Random seed or generator for reproducibility.

None

Returns:

Name Type Description
sample MeasureSpace

An instance of MeasureSpace whose domain consists of the random samples and whose measure is a counting measure giving the number of each sample produced.

Examples:

>>> import numpy as np
>>> from sigalg.core import ProbabilityMeasure, RandomVariable, RandomVector, SampleSpace, SigmaAlgebra
>>> rng = np.random.default_rng(42)

Generate a random probability space and 2-dimensional random vector.

>>> Omega = SampleSpace.from_sequence(size=10)
>>> F = SigmaAlgebra.from_rand(
...     domain=Omega,
...     num_atoms=4,
...     random_state=rng,
... )
>>> P = ProbabilityMeasure.from_rand(domain=F, random_state=rng)
>>> X = RandomVector.from_rand(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     max_value=10,
...     dim=2,
...     random_state=rng,
... )
>>> print(X)
Random vector 'X':
i      0  1
omega
0      2  6
1      2  6
2      1  7
3      2  6
4      7  3
5      2  6
6      0  9
7      2  6
8      0  9
9      2  6

Print the underlying measure space of the random vector.

>>> print(X.measure_space)
Probability space (Omega, F, P)
===============================

* Sample space 'Omega':
    omega
        0
        1
        2
        3
        4
        5
        6
        7
        8
        9

* Sigma algebra 'F':
        F
omega
0     1
1     1
2     3
3     1
4     2
5     1
6     0
7     1
8     0
9     1

* Probability measure 'P':
            P
F
1  0.049134
3  0.207580
2  0.082504
0  0.660782

Sample from the range of the random vector.

>>> X_sample = X.sample(size=1_000, random_state=rng)
>>> print(X_sample)
Random vector 'X_sample':
i       0  1
sample
0       0  9
1       0  9
2       1  7
3       0  9
4       2  6
...    .. ..
995     2  6
996     0  9
997     0  9
998     0  9
999     0  9

[1000 rows x 2 columns]

Sample from a 1-dimensional random variable.

>>> Y = RandomVariable.from_rand(
...     domain=Omega,
...     sig_alg=F,
...     measure=P,
...     max_value=10,
...     random_state=rng,
...     name="Y",
... )
>>> print(Y)
Random variable 'Y':
        Y
omega
0      4
1      4
2      5
3      4
4      7
5      4
6      6
7      4
8      6
9      4
>>> Y_sample = Y.sample(size=1_000, random_state=rng)
>>> print(Y_sample)
Random variable 'Y_sample':
        Y_sample
sample
0              7
1              6
2              6
3              6
4              6
...          ...
995            6
996            5
997            4
998            5
999            6

[1000 rows x 1 columns]

to_measurable_vec

to_measurable_vec()

Promote to a MeasurableVector instance.

with_uniform classmethod

with_uniform(
    domain=None,
    sig_alg=None,
    mapping=None,
    domain_kind="SampleSpace",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
)

Construct a random vector on a given measurable space with a uniform probability measure.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain of the function.

None
sig_alg SigmaAlgebra | None

The sigma-algebra of the underlying probability space.

None
mapping MappingLike | None

The mapping defining the random vector.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of the domain.

"Domain"
domain_name Hashable | None

The name of the domain.

None
output_name Hashable | None

The name of the outputs of the function. If None, a default will be generated.

None
index IndexLike | None

The index for the outputs of the function. Only used if the outputs are multi-dimensional.

None
index_kind Literal['Index', 'Time']

The kind of index. Only used if the outputs are multi-dimensional.

"Index"
index_name Hashable | None

The name of the index. Only used if the outputs are multi-dimensional.

None
name Hashable | None

The name of the function. If None, a default name will be generated.

None

Returns:

Name Type Description
random_vector RandomVector

A random vector on the given measurable space with a uniform probability measure.

Examples:

>>> from sigalg.core import RandomVector, SampleSpace, SigmaAlgebra

Define a 2-dimensional random vector on a measure space with the uniform probability measure.

>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
... )
>>> X = RandomVector.with_uniform(
...     domain=Omega,
...     sig_alg=F,
...     mapping={
...         0: (0, 1),
...         1: (2, 1),
...         2: (2, 1),
...     },
... )
>>> print(X.measure_space)
Probability space (Omega, F, U)
===============================

* Sample space 'Omega':
 omega
     0
     1
     2

* Sigma algebra 'F':
       F
omega
0      0
1      1
2      1

* Probability measure 'U':
     U
F
0  0.5
1  0.5

SampleSpace

Bases: Domain

A class representing a sample space.

Parameters:

Name Type Description Default
indices IndexLike | None

An IndexLike object containing the points in the sample space.

None
variable_names list[Hashable] | None

A list of names of the variables for the sample space. If None, defaults will be generated.

None
name Hashable | None

Name identifier for the sample space. If None, a default will be generated.

None

Examples:

>>> from sigalg.core import SampleSpace
>>> import pandas as pd

Construct a 1-dimensional SampleSpace from a list of sample points.

>>> indices = ["red", "green", "blue"]
>>> Omega1 = SampleSpace(indices=indices, name="Omega1")
>>> print(Omega1)
Sample space 'Omega1':
 omega
   red
 green
  blue

Construct a 1-dimensional SampleSpace from a pd.Index object.

>>> indices = pd.Index(["a", "b", "c"], name="letter")
>>> Omega2 = SampleSpace(indices=indices, name="Omega2")
>>> print(Omega2)
Sample space 'Omega2':
letter
     a
     b
     c

Construct a 2-dimensional SampleSpace from a list of ordered pairs.

>>> indices = [("red", 1), ("green", 2), ("blue", 3)]
>>> Omega3 = SampleSpace(indices=indices, name="Omega3", variable_names=["color", "number"])
>>> print(Omega3)
Sample space 'Omega3':
 color  number
   red       1
 green       2
  blue       3

Construct a 2-dimensional SampleSpace from a pd.MultiIndex object.

>>> indices = pd.MultiIndex.from_tuples(
...     [("a", 1), ("b", 2), ("c", 3)], names=["letter", "number"]
... )
>>> Omega4 = SampleSpace(indices=indices, name="Omega4")
>>> print(Omega4)
Sample space 'Omega4':
 letter  number
      a       1
      b       2
      c       3

Set

Bases: Index

A class representing a set.

Parameters:

Name Type Description Default
indices IndexLike

The items that belong to the set.

required
domain Domain

The domain containing the set.

required
name Hashable | None

The name of the set. If None, a default will be generated.

None

Examples:

>>> from sigalg.core import Domain, Set, SigmaAlgebra

Extract a set from a domain and print it.

>>> X = Domain.from_sequence(size=4)
>>> A = Set([1, 2], domain=X)
>>> print(A)
Set 'A':
 x
 1
 2

generated_sig_alg cached property

generated_sig_alg

Get the sigma-algebra generated by the set.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
gen_sig_alg SigmaAlgebra | None

The sigma-algebra generated by the set.

Examples:

>>> from sigalg.core import Domain, Set

Define a domain and extract a subset.

>>> X = Domain.from_sequence(size=3)
>>> A = Set([0, 1], domain=X)

Print the sigma-algebra generated by the set. Notice that there are only two atoms with identifiers 0 and 1. Points that are in the set get an identifier of 1, while points in the complement have identifier 0. In other words, the atom identifier mapping is just the indiciator function of the set.

>>> print(A.generated_sig_alg)
Sigma algebra 'sigma(A)':
   A
x
0  1
1  1
2  0
Notes

Let \(A\) be a nonempty, proper subset of a nonempty finite set \(X\). The \(\sigma\)-algebra generated by \(A\), denoted \(\sigma(A)\), is the \(\sigma\)-algebra whose atoms are \(A\) and its complement \(A^c\). If \(A\) is either empty or coincides with the whole set \(X\), then \(\sigma(A)\) is the trivial \(\sigma\)-algebra whose single atom is \(X\) itself.

indicator cached property

indicator

Get the indicator function of the set.

Returns:

Name Type Description
indicator Function | None

The indicator function of the set.

Examples:

>>> from sigalg.core import Domain, Set
>>> X = Domain.from_sequence(size=4)
>>> A = Set([0, 1], domain=X)
>>> print(A.indicator)
Function 'I_A':
        I_A
x
0        1
1        1
2        0
3        0

indicator_data cached property

indicator_data

Get the underlying data of the indicator function of the set as a pd.Series.

Returns:

Name Type Description
indicator_data Series | None

The data of the indicator function of the set.

Examples:

>>> from sigalg.core import Domain, Set
>>> X = Domain.from_sequence(size=4)
>>> A = Set([0, 1], domain=X)
>>> print(A.indicator_data)
     x
0    1
1    1
2    0
3    0
Name: I_A, dtype: int64

lattice cached property

lattice

Get the (upward) lattice of sigma-algebras containing this set.

See the Notes section below for the mathematical details.

Returns:

Name Type Description
lattice Lattice

The (upward) lattice of sigma-algebras containing this set.

Examples:

>>> from sigalg.core import Domain, Set, SigmaAlgebra

Define three sigma-algebras on a domain.

>>> X = Domain.from_sequence(size=4, variable_name="x")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 0,
...     },
...     name="G",
... )
>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 0,
...         3: 0,
...     },
...     name="H",
... )

Extract a subset of the domain.

>>> A = Set([1, 2], domain=X)

We may test whether the set is measurable with respect to a sigma-algebra by using the in operator with the lattice attribute.

>>> F in A.lattice
True

We may get the indicator values of the set on the atoms of the sigma-algebra by calling the get_atom_data method.

>>> print(A.lattice.get_atom_data(F))
F
0    0
1    1
2    0
Name: sigma(A), dtype: int64

Whenever a measurability check is executed, and the result is True, the sigma-algebra is added to the internal lattice.

>>> A.lattice
Lattice(base=sigma(A), type=upward, num_sig_algs=2)

Perform another measurability check, inspect the lattice attribute to see the updated number of sigma-algebras, and print the atom data.

>>> G in A.lattice
True
>>> A.lattice
Lattice(base=sigma(A), type=upward, num_sig_algs=3)
>>> print(A.lattice.get_atom_data(G))
G
0    0
1    1
Name: sigma(A), dtype: int64

Notice that the set is not measurable with respect to the third sigma-algebra. The measurability check accordingly returns False, and the contents of lattice are not changed.

>>> H in A.lattice
False
>>> A.lattice
Lattice(base=sigma(A), type=upward, num_sig_algs=3)
Notes

Let \(A\) be a subset of a nonempty finite set \(X\). The lattice of \(\sigma\)-algebras associated with \(A\) is the collection of all \(\sigma\)-algebras on \(X\) that contain \(A\). A \(\sigma\)-algebra \(\mathcal{F}\) is in this lattice if and only if the set is \(\mathcal{F}\)-measurable.

atom_id

atom_id(sig_alg)

Return the atom identifiers of this set relative to a given sigma-algebra provided that it is an atom, or None otherwise.

Returns:

Name Type Description
atom_id Hashable | None

The atom ID of the current set if it is an atom of the given sigma-algebra, and None otherwise.

Examples:

>>> from sigalg.core import SampleSpace, Set, SigmaAlgebra

Define a sigma-algebra on a sample space.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 1,
...         1: 1,
...         2: 0,
...         3: 0,
...         4: 2,
...     },
... )

Extract a set and get its atom identifier.

>>> A = Set([0, 1], domain=Omega)
>>> A.atom_id(F)
{'F': 1}

Attempt to access the atom identifier of a non-atom measurable set.

>>> B = Set([0, 1, 2, 3], domain=Omega, name="B")
>>> print(B.atom_id(F))
None

Attempt to access the atom identifier of a non-measurable set.

>>> C = Set([0, 1, 2], domain=Omega, name="C")
>>> print(C.atom_id(F))
None

cartesian_product classmethod

cartesian_product(factors, name=None)

Form the Cartesian product of a list of sets.

Parameters:

Name Type Description Default
factors list[Set]

The factors of the Cartesian product.

required
name Hashable | None

The name of the Cartesian product. If None, a default will be generated.

None

Examples:

>>> from sigalg.core import Domain, Set

Define a domain and extract two subsets.

>>> X = Domain.from_sequence(size=3)
>>> U = Set([0, 1], domain=X, name="U")
>>> V = Set([1, 2], domain=X, name="V")
>>> product = Set.cartesian_product([U, V])
>>> print(product)
Set 'U x V':
 x_0  x_1
   0    1
   0    2
   1    1
   1    2

complement

complement()

Return the complement of this set.

Returns:

Name Type Description
set Set

A set containing all points not in this set.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0])
>>> print(A.complement())
Set 'A complement':
 omega
     1
     2

difference

difference(other)

Return the set difference of this set and another set.

Parameters:

Name Type Description Default
other Set

Another set from the same domain.

required

Returns:

Name Type Description
set Set

A set containing all points in this set but not in other.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0, 1])
>>> B = F.get_set([1, 2], name="B")
>>> print(A.difference(B))
Set 'A difference B':
 omega
     0

indicator_atom_data

indicator_atom_data(sig_alg)

Given a sigma-algebra containing the set, get a pd.Series indicating the atoms whose union is the set.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra with respect to which the set is measurable.

required

Returns:

Name Type Description
atom_data Series | None

A pd.Series indicating the atoms of the sigma-algebra whose union is the set.

Examples:

>>> from sigalg.core import Domain, Set, SigmaAlgebra

Define a domain and a sigma-algebra.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1, 2])))

Extract a set.

>>> U = Set([1, 2], domain=X, name="U")

Notice that U is the atom of F with identifier 1. This will be revealed through the indicator_atom_data.

>>> print(U.indicator_atom_data(F))
   I_U
F
0    0
1    1
2    0

Extract another set.

>>> V = Set([1, 2, 3], domain=X, name="V")

Notice that V is the union of the two atoms of F with identifiers 1 and 2.

>>> print(V.indicator_atom_data(F))
   I_V
F
0    0
1    1
2    1

intersection

intersection(other)

Return the intersection of this set with another set.

Parameters:

Name Type Description Default
other Set

Another set from the same domain.

required

Returns:

Name Type Description
set Set

A set containing all points in both sets.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0, 1])
>>> B = F.get_set([1, 2], name="B")
>>> print(A.intersection(B))
Set 'A intersect B':
 omega
     1

is_atom

is_atom(sig_alg)

Return whether this set is an atom in a given sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The sigma-algebra with respect to which to check if this set is an atom.

required

Returns:

Name Type Description
is_atom bool | None

Whether the current set is an atom or not relative to the given sigma-algebra.

Examples:

>>> from sigalg.core import SampleSpace, Set, SigmaAlgebra

Define a sigma-algebra on a sample space.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 1,
...         1: 1,
...         2: 0,
...         3: 0,
...         4: 2,
...     },
... )

Extract a set and check whether it is an atom.

>>> A = Set([0, 1], domain=Omega)
>>> A.is_atom(F)
True

Extract a second set and perform the same check.

>>> B = Set([0, 1, 2, 3], domain=Omega, name="B")
>>> B.is_atom(F)
False

Check whether a non-measurable set is an atom.

>>> C = Set([0, 1, 2], domain=Omega, name="C")
>>> C.is_atom(F)
False

to_domain

to_domain()

Convert this set to a domain.

Creates a new Domain containing only the points in this set.

Returns:

Name Type Description
domain Domain

A domain containing this set's points.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0, 1])
>>> print(A.to_domain())
Domain 'A':
 x
 0
 1

to_sample_space

to_sample_space()

Convert this set to a sample space.

Creates a new SampleSpace containing only the points in this set.

Returns:

Name Type Description
sample_space SampleSpace

A sample space containing this set's points.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0, 1])
>>> print(A.to_sample_space())
Sample space 'A':
 omega
     0
     1

union

union(other)

Return the union of this set with another set.

Parameters:

Name Type Description Default
other Set

Another set from the same domain.

required

Returns:

Name Type Description
set Set

A set containing all points in either set.

Examples:

>>> from sigalg.core import Set, SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra.power_set(Omega)
>>> A = F.get_set([0])
>>> B = F.get_set([1], name="B")
>>> print(A.union(B))
Set 'A union B':
 omega
     0
     1

SigmaAlgebra

A class representing a sigma-algebra on a domain.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain over which the sigma-algebra is defined.

None
mapping MappingLike | None

The mapping from points to atom identifiers.

None
variable_names list[Hashable] | None

The variables names of the atom identifiers of the sigma-algebra. If None, defaults will be generated.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of domain of the sigma-algebra.

"Domain"
domain_name Hashable | None

The name of the domain of the sigma-algebra. If None, a default will be generated.

None
output_name Hashable | None

The name of the output mapping of the sigma-algebra. If None, a default will be generated.

None
index IndexLike | None

The index of the atom identifiers of the sigma-algebra. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the sigma-algebra.

"Index"
index_name Hashable | None

The name of the index of the sigma-algebra. If None, a default will be generated.

None
name Hashable

The name of the sigma-algebra.

"F"

Examples:

>>> from sigalg.core import Domain, Index, SigmaAlgebra

Construct a sigma-algebra with two atoms and 1-dimensional atom identifiers.

>>> X = Domain.from_sequence(size=3)
>>> mapping = {
...     0: 1,
...     1: 0,
...     2: 0,
... }
>>> F = SigmaAlgebra(domain=X, mapping=mapping)
>>> print(F)
Sigma algebra 'F':
   F
x
0  1
1  0
2  0

Construct a sigma-algebra on the same domain with 2-dimensional atom identifiers and a custom index.

>>> mapping = {
...     0: (1, 2),
...     1: (0, 1),
...     2: (0, 1),
... }
>>> I = Index([1, 2])
>>> G = SigmaAlgebra(domain=X, mapping=mapping, index=I, name="G")
>>> print(G)
Sigma algebra 'G':
i  1  2
x
0  1  2
1  0  1
2  0  1
Notes

A \(\sigma\)-algebra \(\mathcal{F}\) on a nonempty set \(X\) is a collection of subsets of \(X\) that contains \(X\), and is closed under complementation and countable unions. In the case that \(X\) is finite (as it always is, in SigAlg), then \(\mathcal{F}\) needs only to be closed under finite unions.

A \(\sigma\)-algebra \(\mathcal{F}\) determines its atoms, which are the nonempty sets \(A\in \mathcal{F}\) that are minimal with respect to subset inclusion, in the following sense: If \(B\in \mathcal{F}\) is nonempty and \(B\subset A\), then necessarily \(A=B\). Conversely, provided that \(X\) is finite, the \(\sigma\)-algebra \(\mathcal{F}\) is completely recoverable from its atoms, in the sense that every set \(A\in \mathcal{F}\) is a disjoint union of atoms.

If \(\{A_i\}_{i\in I}\) is the set of atoms, indexed by a finite set \(I\), then there is a mapping \(X \to I\) given by \(x \mapsto i\), where \(A_i\) is the unique atom that contains \(x\). This mapping is what SigAlg uses to represent \(\sigma\)-algebras. The indices in \(I\) are called atom identifiers. The atom identifiers may consist of tuples, in which case the \(\sigma\)-algebra is said to have multi-dimensional atom identifiers, and the dimension of the \(\sigma\)-algebra is the common length of the tuples.

atom_id_to_atom cached property

atom_id_to_atom

Get a mapping from atom IDs to Set objects in this sigma-algebra.

Returns:

Name Type Description
atom_id_to_atom dict[Hashable, Set] | None

A dictionary mapping each atom ID to its corresponding Set object.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> for atom_id, atom in F.atom_id_to_atom.items():
...     print(f"Atom ID: {atom_id}\n{atom}\n")
Atom ID: 0
Set '0':
 x
 0
 1

Atom ID: 1
Set '1':
 x
 2

atom_id_to_cardinality property

atom_id_to_cardinality

Get a mapping from atom IDs to their cardinalities in this sigma-algebra.

Returns:

Name Type Description
atom_id_to_cardinality dict[Hashable, int] | None

A dictionary mapping each atom ID to the number of points it contains.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.atom_id_to_cardinality)
{0: 2, 1: 1}

atom_id_to_points cached property

atom_id_to_points

Get a mapping from atom IDs to lists of points.

Returns:

Name Type Description
atom_id_to_points dict[Hashable, list[Hashable]] | None

A dictionary mapping each atom ID to a list of points contained in that atom.

Examples:

>>> from sigalg.core import SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.atom_id_to_points)
{0: [0, 1], 1: [2]}

atom_ids property

atom_ids

Get a list of atom IDs in this sigma-algebra.

Returns:

Name Type Description
atom_ids list[Hashable] | None

A list of atom IDs in this sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.atom_ids)
[0, 1]

atom_indicator_df cached property

atom_indicator_df

Get a pd.DataFrame whose columns are indicators for membership of each point in the atoms of the sigma-algebra.

Returns:

Name Type Description
atom_indicator_df DataFrame | None

A pd.DataFrame where each column corresponds to an atom of the sigma-algebra and each row corresponds to a point in the domain. The entries are 1 if the point belongs to the atom and 0 otherwise.

Examples:

>>> from sigalg.core import SampleSpace, SigmaAlgebra

Define a sigma-algebra with three atoms.

>>> Omega = SampleSpace.from_sequence(size=6)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: "b",
...         1: "b",
...         2: "a",
...         3: "a",
...         4: "c",
...         5: "c",
...     },
... )
>>> print(F.atom_indicator_df)
        a  b  c
omega
0       0  1  0
1       0  1  0
2       1  0  0
3       1  0  0
4       0  0  1
5       0  0  1

Define a sigma-algebra with 2-dimensional atom IDs.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: ("a", "b"),
...         1: ("a", "b"),
...         2: ("a", "b"),
...         3: ("c", "d"),
...         4: ("c", "d"),
...         5: ("a", "b"),
...     },
...     name="G",
... )
>>> print(G.atom_indicator_df)
        (a, b)  (c, d)
omega
0            1       0
1            1       0
2            1       0
3            0       1
4            0       1
5            1       0

atom_space cached property

atom_space

Get the domain consisting of atom identifiers.

Returns:

Name Type Description
atom_space Domain | None

The domain whose points are the atom identifiers of the sigma-algebra.

Examples:

>>> from sigalg.core import Domain, Index, SigmaAlgebra

Define a sigma-algebra with two atoms.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 1,
...         1: 0,
...         2: 0,
...     },
... )

The atom space is an instance of Domain consisting of the atom identifiers 0 and 1.

>>> print(F.atom_space)
Domain 'F':
    F
    1
    0

Create a second sigma-algebra with 2-dimensional atom IDs.

>>> J = Index([1, 2], variable_names=["j"], name="J")
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (0, 1),
...         2: (0, 1),
...     },
...     index=J,
...     name="G",
... )
>>> print(G.atom_space)
Domain 'G':
    G_1  G_2
    1    2
    0    1

If the atom identifiers of a sigma-algebra are the points of the underlying domain itself (i.e., if the sigma-algebra is the power set), then the atom space is the domain.

>>> H = SigmaAlgebra(domain=X, mapping=dict(zip(X, X)), name="H")
>>> print(H)
Sigma algebra 'H':
    H
x
0  0
1  1
2  2
>>> print(H.atom_space)
Domain 'X':
    x
    0
    1
    2

We check the atom space of a power-set sigma-algebra on a 2-dimensional domain.

>>> Y = Domain.cartesian_product(
...     [[0, 1], [2, 3]], variable_names=["y_0", "y_1"], name="Y"
... )
>>> K = SigmaAlgebra(domain=Y, mapping=dict(zip(Y, Y)), name="K")
>>> print(K)
Sigma algebra 'K':
i        0  1
y_0 y_1
0   2    0  2
    3    0  3
1   2    1  2
    3    1  3
>>> print(K.atom_space)
Domain 'Y':
    y_0  y_1
    0    2
    0    3
    1    2
    1    3

atoms cached property

atoms

Get a list of atoms as Set objects in this sigma-algebra.

Returns:

Name Type Description
atoms list[Set] | None

A list of Set objects representing the atoms in this sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra with two atoms.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> for atom in F.atoms:
...     print(atom, "\n")
Set '0':
 x
 0
 1

Set '1':
 x
 2

dimension property

dimension

Get the dimension of the atom identifiers of the sigma-algebra.

Returns:

Name Type Description
dim int | None

The dimension of the atom identifiers of the sigma-algebra.

domain property

domain

Get the domain over which this sigma-algebra is defined.

Returns:

Name Type Description
domain Domain | None

The domain of this sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra and print its domain.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 1,
...         1: 0,
...         2: 1,
...     },
...     name="F",
... )
>>> print(F.domain)
Domain 'X':
 x
 0
 1
 2

down_lattice cached property

down_lattice

Get the downward lattice of all sigma-algebras contained in this one.

Returns:

Name Type Description
down_lattice Lattice | None

The downward lattice of all sigma-algebras contained in this one.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra on a domain with two atoms.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
... )

The downward lattice of a sigma-algebra is initialized containing the sigma-algebra itself.

>>> F.down_lattice
Lattice(base=F, type=downward, num_sig_algs=1)

Define a second sigma-algebra.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...     },
...     name="G",
... )

We may verify if G is a sub-sigma-algebra of F by checking if the latter is in the downward lattice of the former.

>>> G in F.down_lattice
True

Check the downward lattice to see that it now includes G.

>>> F.down_lattice
Lattice(base=F, type=downward, num_sig_algs=2)

Define a third sigma-algebra.

>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...     },
...     name="H",
... )

Notice that H is not a sub-sigma-algebra of F.

>>> H in F.down_lattice
False
>>> F.down_lattice
Lattice(base=F, type=downward, num_sig_algs=2)

index property

index

Get the index of the sigma-algebra.

Returns:

Name Type Description
index Index | None

The index of the sigma-algebra.

Examples:

>>> from sigalg.core import Domain, Index, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> I = Index([1, 2])
>>> F = SigmaAlgebra(domain=X, index=I, mapping=dict(zip(X, [(0, 0), (1, 1), (1, 1)])))
>>> print(F)
Sigma algebra 'F':
i  1  2
x
0  0  0
1  1  1
2  1  1
>>> print(F.index)
Index 'I':
 i
 1
 2

is_canonical_power_set property

is_canonical_power_set

Boolean flag signaling a canonical power-set sigma-algebra.

In SigAlg, the canonical power-set sigma-algebra on a domain is the one created by the power_set constructor. Its atom identifiers are the points of the domain itself.

Returns:

Name Type Description
is_canonical bool | None

A flag whether the current sigma-algebra is the canonical power-set sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a 1-dimensional domain and obtain the sigma-algebra created by the power_set constructor.

>>> X = Domain.from_sequence(size=3)
>>> R = SigmaAlgebra.power_set(X)

This sigma-algebra is both a power-set sigma-algebra and it is the canonical one.

>>> R.is_power_set
True
>>> R.is_canonical_power_set
True

Create a power-set sigma-algebra which is not the canonical one.

>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, ["a", "b", "c"])))
>>> F.is_power_set
True
>>> F.is_canonical_power_set
False

Now, run through the same routine with a 2-dimensional domain.

>>> Y = Domain.cartesian_product(
...     [[0, 1], [2, 3]], variable_names=["y_0", "y_1"], name="Y"
... )
>>> S = SigmaAlgebra.power_set(Y, name="S")
>>> S.is_power_set
True
>>> S.is_canonical_power_set
True
>>> G = SigmaAlgebra(domain=Y, mapping=dict(zip(Y, [0, 1, 2, 3])), name="G")
>>> G.is_power_set
True
>>> G.is_canonical_power_set
False

is_power_set property

is_power_set

Boolean flag signaling a power-set sigma-algebra.

Returns:

Name Type Description
is_power_set bool | None

A boolean signaling whether the sigma-algebra is the power-set sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.is_power_set)
False
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 1,
...         1: 6,
...         2: 7,
...     },
...     name="G",
... )
>>> print(G.is_power_set)
True

is_trivial property

is_trivial

Boolean flag signaling a trivial sigma-algebra.

Returns:

Name Type Description
is_trivial bool | None

A boolean signaling whether the sigma-algebra is the trivial sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.is_trivial)
False
>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 1,
...         1: 1,
...         2: 1,
...     },
...     name="G",
... )
>>> print(G.is_trivial)
True

num_atoms property

num_atoms

Get the number of atoms in this sigma-algebra.

Returns:

Name Type Description
num_atoms int | None

The number of atoms in this sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.num_atoms)
2

point_to_atom_id cached property

point_to_atom_id

Get the mapping from points to atom IDs.

Returns:

Name Type Description
point_to_atom_id Mapping[Hashable, Hashable] | None

A mapping from points to atom IDs.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
...     name="F",
... )
>>> print(F.point_to_atom_id)
{0: 0, 1: 0, 2: 1}

up_lattice cached property

up_lattice

Get the upward lattice of all sigma-algebras containing this one.

Returns:

Name Type Description
up_lattice Lattice | None

The upward lattice of all sigma-algebras containing this one.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra on a domain with two atoms.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...     },
... )

The upward lattice of a sigma-algebra is initialized containing the sigma-algebra itself.

>>> F.up_lattice
Lattice(base=F, type=upward, num_sig_algs=1)

Define a second sigma-algebra.

>>> G = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...     },
...     name="G",
... )

We may verify if F is a sub-sigma-algebra of G by checking if the latter is in the upward lattice of the former.

>>> G in F.up_lattice
True

Check the upward lattice to see that it now includes G.

>>> F.up_lattice
Lattice(base=F, type=upward, num_sig_algs=2)

Define a third sigma-algebra.

>>> H = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 1,
...     },
...     name="H",
... )

Notice that F is not a sub-sigma-algebra of H.

>>> H in F.up_lattice
False
>>> F.up_lattice
Lattice(base=F, type=upward, num_sig_algs=2)

variable_names property

variable_names

Get the variable names of the sigma-algebra.

Returns:

Name Type Description
names list[Hashable] | None

The variables names of the sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (0, 1),
...     },
...     variable_names=["x", "y"],
... )
>>> F.variable_names
['x', 'y']
>>> print(F.atom_space)
Domain 'F':
 x  y
 1  2
 0  1

entropy

entropy(measure, given=None, base='2')

Compute the entropy of the current sigma-algebra with respect to a given probability measure, optionally conditioned on a given sigma-algebra.

Parameters:

Name Type Description Default
measure ProbabilityMeasure

The probability measure.

required
given SigmaAlgebra | None

Optional sigma-algebra for conditional entropy.

None
base Real | Literal['2', 'e', '10']

The base of the logarithm used to compute the entropy.

"2"

Returns:

Name Type Description
entropy Real

The entropy of the sigma-algebra.

Examples:

>>> import numpy as np
>>> from sigalg.core import ProbabilityMeasure, SampleSpace, SigmaAlgebra

Define a probability space. Notice that the sigma-algebra has a null atom.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 3,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.0,
...         1: 0.2,
...         2: 0.7,
...         3: 0.1,
...     },
... )

Compute the entropy of the F with respect to P.

>>> F.entropy(P)
1.1567796494470395

Check that this value agrees with the mathematical definition.

>>> bool(F.entropy(P) == sum(-np.log2(P(A)) * P(A) for A in F if P(A) > 0))
True

We may also find the entropy of a sub-sigma-algebra of F.

>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 2,
...     },
...     name="G",
... )
>>> G.entropy(P)
0.7219280948873623

Define a second sub-sigma-algebra of F for conditional entropy.

>>> H = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...         4: 2,
...     },
...     name="H",
... )

Compute the conditional entropy of G given H.

>>> G.entropy(P, given=H)
0.6877840558577581

Check that the conditional entropy is equal to its mathematical definition, as the joint entropy (the entropy of the meet) minus the marginal entropy of the given sigma-algebra.

>>> G.entropy(P, given=H) == (G | H).entropy(P) - H.entropy(P)
True

from_function classmethod

from_function(function)

Create a sigma-algebra induced by a function.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
function Function

The function from which to generate the sigma-algebra.

required

Returns:

Name Type Description
sig_alg SigmaAlgebra

A new SigmaAlgebra instance induced by the given function.

Examples:

>>> from sigalg.core import Domain, Function, SigmaAlgebra

Define a function with 2-dimensional outputs on a set.

>>> X = Domain.from_sequence(size=3)
>>> f = Function(
...     domain=X,
...     mapping={
...         0: (1, 2),
...         1: (1, 2),
...         2: (2, 4),
...     },
... )

Obtain the sigma-algebra generated by the function. Notice that the atom identifiers are the outputs of the function.

>>> sigma_f = SigmaAlgebra.from_function(f)
>>> print(sigma_f)
Sigma algebra 'sigma(f)':
i  0  1
x
0  1  2
1  1  2
2  2  4

Print the atom space of the sigma-algebra. The variable names are the names of the components of the function.

>>> print(sigma_f.atom_space)
Domain 'sigma(f)':
 f_0  f_1
   1    2
   2    4
Notes

Let \(f: X \to \mathbb{R}^d\) be a function defined on a set \(X\). The \(\sigma\)-algebra induced by \(f\), denoted \(\sigma(f)\), is the \(\sigma\)-algebra generated by the preimages of Borel sets in \(\mathbb{R}^d\) under \(f\). In SigAlg, in which \(X\) is finite and \(\sigma\)-algebras are determined by their atoms, we may take the atom identifiers to be the unique values of \(f\) on \(X\).

from_rand classmethod

from_rand(
    domain=None,
    super=None,
    num_atoms=1,
    dim=1,
    atom_ID_range=None,
    variable_names=None,
    domain_kind="Domain",
    domain_name=None,
    output_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name="F",
    random_state=None,
)

Generate a sigma-algebra with random atom identifiers.

Parameters:

Name Type Description Default
domain IndexLike | None

The domain over which to create the sigma-algebra. If None, then super must be provided and the domain will be obtained from it.

None
super SigmaAlgebra | None

If provided, the randomly generated sigma-algebra will be a sub-sigma-algebra of super.

None
num_atoms int

The number of atoms in the sigma-algebra.

1
dim int

The dimension of the atom identifiers.

1
atom_ID_range tuple[int, int] | None

A tuple of the form (min, max), or None. If not None, the atom identifiers will be drawn from the range [min, max). If None, the atom identifiers will be drawn from the range [0, num_atoms).

None
variable_names list[Hashable] | None

A list of variable names for the atom identifiers. If None, defaults will be generated.

None
domain_kind Literal['Domain', 'SampleSpace']

The type of domain of the sigma-algebra.

"Domain"
domain_name Hashable | None

The name of the domain of the sigma-algebra. If None, a default will be generated.

None
output_name Hashable | None

The name of the output mapping of the sigma-algebra. If None, a default will be generated.

None
index IndexLike | None

The index of the atom identifiers of the sigma-algebra. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the sigma-algebra.

"Index"
index_name Hashable | None

The name of the index of the sigma-algebra. If None, a default will be generated.

None
name Hashable

Name identifier for the sigma-algebra.

"F"
random_state int | Generator | None

An optional seed for the random number generator.

None

Returns:

Name Type Description
random_sig_alg SigmaAlgebra

A new SigmaAlgebra instance with random atom identifiers.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Generate a sigma-algebra with three random atoms and 1-dimensional atom identifiers.

>>> X = Domain.from_sequence(size=5)
>>> F = SigmaAlgebra.from_rand(
...     domain=X,
...     num_atoms=3,
...     random_state=42,
... )
>>> print(F)
Sigma algebra 'F':
   F
x
0  0
1  0
2  0
3  2
4  1

Generate a sigma-algebra with three random atoms and 3-dimensional atom identifiers.

>>> G = SigmaAlgebra.from_rand(
...     domain=X,
...     num_atoms=3,
...     dim=3,
...     random_state=42,
...     name="G",
... )
>>> print(G)
Sigma algebra 'G':
i  0  1  2
x
0  2  1  2
1  0  0  2
2  2  1  2
3  2  1  2
4  1  0  0

Generate a sigma-algebra with three random atoms and 2-dimensional atom identifiers with values in the range [10, 15).

>>> H = SigmaAlgebra.from_rand(
...     domain=X,
...     num_atoms=3,
...     atom_ID_range=(10, 15),
...     dim=2,
...     random_state=42,
...     name="H",
... )
>>> print(H)
Sigma algebra 'H':
i   0   1
x
0  14  14
1  10  10
2  14  14
3  13  13
4  14  14

Create a random sub-sigma-algebra of H with two atoms:

>>> K = SigmaAlgebra.from_rand(
...     super=H,
...     num_atoms=2,
...     random_state=42,
...     name="K",
... )
>>> print(K)
Sigma algebra 'K':
   K
x
0  0
1  1
2  0
3  1
4  0
>>> print(K <= H)
True

get_atom_containing

get_atom_containing(point)

Get the atom containing a given point.

Parameters:

Name Type Description Default
point Hashable

The point for which to retrieve the containing atom.

required

Returns:

Name Type Description
atom Set

The Set object representing the atom that contains the given point.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.get_atom_containing(0))
Set '0':
 x
 0
 1

get_random_set

get_random_set(num_atoms, name='A', random_state=None)

Get a random measurable set consisting of a specified number of atoms.

Parameters:

Name Type Description Default
num_atoms int

The number of atoms to include in the random measurable set.

required
name Hashable

Name identifier for the set.

"A"
random_state int | Generator | None

The random state for reproducibility.

None

Returns:

Name Type Description
random_set Set

A Set object representing the random measurable set.

get_set

get_set(indices, name='A')

Extract a measurable set from a list of points.

Parameters:

Name Type Description Default
indices list[Hashable]

List of points to include in the measurable set.

required
name Hashable

Name identifier for the set.

"A"

Returns:

Name Type Description
measurable_set Set

A Set object containing the specified points.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Create a sigma-algebra with two atoms.

>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (0, 1),
...         1: (0, 1),
...         2: (1, 2),
...         3: (1, 2),
...     },
... )

Extract a measurable set.

>>> A = F.get_set([0, 1], name="A")
>>> print(A)
Set 'A':
 x
 0
 1

Try to extract a non-measurable set.

>>> B = F.get_set([0, 2], name="B")
Traceback (most recent call last):
    ...
NonMeasurableError: The candidate set is not measurable.

is_measurable

is_measurable(candidate)

Check if a candidate set is measurable with respect to this sigma-algebra.

Parameters:

Name Type Description Default
candidate Set | list[Hashable]

The set to check for measurability.

required

Returns:

Name Type Description
is_measurable bool

True if the set is measurable with respect to this sigma-algebra, False otherwise.

Examples:

>>> import numpy as np
>>> from sigalg.core import Domain, SigmaAlgebra
>>> rng = np.random.default_rng(101)

Define a sigma-algebra with four atoms.

>>> X = Domain.from_sequence(size=10)
>>> F = SigmaAlgebra.from_rand(
...     num_atoms=4,
...     domain=X,
...     random_state=rng,
... )
>>> print(F)
Sigma algebra 'F':
   F
x
0  0
1  3
2  2
3  2
4  1
5  1
6  1
7  3
8  3
9  2

Define a sub-sigma-algebra of the first with two atoms.

>>> G = SigmaAlgebra.from_rand(
...     num_atoms=2,
...     super=F,
...     name="G",
...     random_state=rng,
... )
>>> print(G)
Sigma algebra 'G':
   G
x
0  1
1  1
2  0
3  0
4  1
5  1
6  1
7  1
8  1
9  0

Extract a measurable set from the sub-sigma-algebra.

>>> A = G.get_set([0, 1, 4, 5, 6, 7, 8], name="A")

Check that the set is measurable with respect to the super-sigma-algebra.

>>> F.is_measurable(A)
True

Check measurability of a non-measurable set using the in operators.

>>> [0, 1, 4, 5] in F
False

mutual_info

mutual_info(other, measure, base='2')

Compute the mutual information between this and another sigma-algebra relative to a given probability measure.

Parameters:

Name Type Description Default
other SigmaAlgebra

The other sigma-algebra.

required
measure ProbabilityMeasure

The probability measure.

required
base Real | Literal['2', 'e', '10']

The base of the logarithm used to compute the mutual information.

"2"

Returns:

Name Type Description
mutual_info Real

The mutual information between this and another sigma-algebra.

Examples:

>>> from sigalg.core import ProbabilityMeasure, SampleSpace, SigmaAlgebra

Define a probability space and two sigma-algebras that we will compute the mutual information between.

>>> Omega = SampleSpace.from_sequence(size=5)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 3,
...     },
... )
>>> P = ProbabilityMeasure(
...     domain=F,
...     mapping={
...         0: 0.0,
...         1: 0.2,
...         2: 0.7,
...         3: 0.1,
...     },
... )
>>> G = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 2,
...         4: 2,
...     },
...     name="G",
... )
>>> H = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...         3: 1,
...         4: 2,
...     },
...     name="H",
... )

Compute the mutual information between G and H.

>>> G.mutual_info(H, measure=P)
0.03414403902960417

Check that this value is equal to its mathematical definition as a difference between a marginal entropy and a conditional one.

>>> G.mutual_info(H, measure=P) == G.entropy(P) - G.entropy(P, given=H)
True

Check the symmetry propety of mutual information.

>>> G.mutual_info(H, measure=P) == H.mutual_info(G, measure=P)
True

non_null_atoms

non_null_atoms(measure)

Get the non-null atoms of this sigma-algebra with respect to a given measure.

Parameters:

Name Type Description Default
measure Measure

A measure defined on a super-sigma-algebra of this sigma-algebra.

required

Examples:

>>> from sigalg.core import Domain, Measure, SigmaAlgebra
>>> X = Domain.from_sequence(size=4)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...         3: 2,
...     },
... )
>>> mu = Measure(
...     domain=F,
...     mapping={
...         0: 1,
...         1: 2,
...         2: 0,
...     },
... )
>>> for A in F.non_null_atoms(measure=mu):
...     print(f"Atom id of non-null atom: {A.name}")
Atom id of non-null atom: 0
Atom id of non-null atom: 1

power_set classmethod

power_set(
    domain,
    domain_kind="Domain",
    domain_name=None,
    index=None,
    index_name=None,
    name="R",
)

Create the power-set sigma-algebra over a given domain.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain IndexLike

The domain over which to create the power-set sigma-algebra.

required
domain_kind Literal['Domain', 'SampleSpace']

The type of domain of the sigma-algebra.

"Domain"
domain_name Hashable | None

The name of the domain of the sigma-algebra. If None, a default will be generated.

None
index IndexLike | None

The index of the atom identifiers of the sigma-algebra. If None, a default will be generated.

None
index_name Hashable | None

The name of the index of the sigma-algebra. If None, a default will be generated.

None
name Hashable

Name identifier for the sigma algebra.

"R"

Returns:

Name Type Description
sig_alg SigmaAlgebra

A new SigmaAlgebra instance representing the power-set sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Create a power-set sigma-algebra.

>>> X = Domain.from_sequence(size=3)
>>> G = SigmaAlgebra.power_set(X, name="G")
>>> print(G)
Sigma algebra 'G':
   G
x
0  0
1  1
2  2

The atom space of a power set contructed via this method is the original domain itself.

>>> print(G.atom_space)
Domain 'X':
 x
 0
 1
 2

Create another power-set sigma-algebra, this time on a 2-dimensional domain.

>>> Y = Domain.cartesian_product(
...     [[1, 2], ["a", "b"]], name="Y", variable_names=["number", "letter"]
... )
>>> F = SigmaAlgebra.power_set(Y, name="F")
>>> print(F)
Sigma algebra 'F':
i              0  1
number letter
1      a       1  a
       b       1  b
2      a       2  a
       b       2  b
>>> print(F.atom_space)
Domain 'Y':
 number letter
     1      a
     1      b
     2      a
     2      b
Notes

The power-set \(\sigma\)-algebra on a nonempty set \(X\) consists of all subsets of \(X\). Its atoms are all singleton subsets. It is the finest \(\sigma\)-algebra on \(X\).

restrict_to

restrict_to(subset, subset_name='A', name=None)

Restrict the sigma-algebra to a measurable subset.

Parameters:

Name Type Description Default
subset Set | list[Hashable]

The measurable subset to restrict the sigma-algebra to.

required
subset_name Hashable

The name of the measurable subset. Ignored if subset is an instance of Set.

"A"
name Hashable | None

The name of the restriction. If None, a default will be generated.

None

Returns:

Name Type Description
restriction SigmaAlgebra

The restriction of the sigma-algebra.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra with two atoms.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(domain=X, mapping=dict(zip(X, [0, 1, 1])))

Restrict the sigma-algebra to a subset. Notice that the atom identifiers of the restriction are the same as the identifiers of the original sigma-algebra.

>>> F_B = F.restrict_to([1, 2], subset_name="B")
>>> print(F_B)
Sigma algebra 'F|B':
  F|B
x
1   1
2   1

tensor_power classmethod

tensor_power(sig_alg, n)

Form the tensor power of the sigma-algebra.

Parameters:

Name Type Description Default
sig_alg SigmaAlgebra

The base of the tensor power.

required
n int

The power of the tensor power.

required

Returns:

Name Type Description
power SigmaAlgebra

The tensor power.

Examples:

>>> from sigalg.core import SampleSpace, SigmaAlgebra
>>> Omega = SampleSpace.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=Omega,
...     mapping={
...         0: (1, "a"),
...         1: (1, "a"),
...         2: (2, "b"),
...     },
... )
>>> F_3 = SigmaAlgebra.tensor_power(F, 3)
>>> print(F_3)
Sigma algebra 'F ^ 3':
i                        0  1  2  3  4  5
omega_0 omega_1 omega_2
0       0       0        1  a  1  a  1  a
                1        1  a  1  a  1  a
                2        1  a  1  a  2  b
        1       0        1  a  1  a  1  a
                1        1  a  1  a  1  a
                2        1  a  1  a  2  b
        2       0        1  a  2  b  1  a
                1        1  a  2  b  1  a
                2        1  a  2  b  2  b
1       0       0        1  a  1  a  1  a
                1        1  a  1  a  1  a
                2        1  a  1  a  2  b
        1       0        1  a  1  a  1  a
                1        1  a  1  a  1  a
                2        1  a  1  a  2  b
        2       0        1  a  2  b  1  a
                1        1  a  2  b  1  a
                2        1  a  2  b  2  b
2       0       0        2  b  1  a  1  a
                1        2  b  1  a  1  a
                2        2  b  1  a  2  b
        1       0        2  b  1  a  1  a
                1        2  b  1  a  1  a
                2        2  b  1  a  2  b
        2       0        2  b  2  b  1  a
                1        2  b  2  b  1  a
                2        2  b  2  b  2  b
>>> print(F_3.atom_space)
Domain 'F ^ 3':
 F_0 F_1  F_2 F_3  F_4 F_5
   1   a    1   a    1   a
   1   a    1   a    2   b
   1   a    2   b    1   a
   1   a    2   b    2   b
   2   b    1   a    1   a
   2   b    1   a    2   b
   2   b    2   b    1   a
   2   b    2   b    2   b

tensor_product classmethod

tensor_product(
    factors,
    variable_names=None,
    domain_name=None,
    index=None,
    index_kind="Index",
    index_name=None,
    name=None,
)

Compute the tensor product of a list of sigma-algebras.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
factors list[SigmaAlgebra]

The factors of the tensor product.

required
variable_names list[Hashable] | None

The variable names of the atom identifiers of the resulting sigma-algebra. If None, the variable names will be generated automatically.

None
domain_name Hashable | None

The name of the domain of the sigma-algebra. If None, a default will be generated.

None
index IndexLike | None

The index of the atom identifiers of the sigma-algebra. If None, a default will be generated.

None
index_kind Literal['Index', 'Time']

The type of index of the sigma-algebra.

"Index"
index_name Hashable | None

The name of the index of the sigma-algebra. If None, a default will be generated.

None
name Hashable | None

The name of the resulting sigma-algebra. If None, the name will be generated automatically.

None

Returns:

Name Type Description
tensor_product SigmaAlgebra

The tensor product of the sigma-algebras in factors.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define two sigma-algebras on two domains, one with 1-dimensional atom identifiers the other with 2-dimensional ones.

>>> X = Domain.from_sequence(size=3, variable_name="x")
>>> Y = Domain.from_sequence(size=3, variable_name="y", name="Y")
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 1,
...         2: 1,
...     },
...     variable_names=["u"],
... )
>>> G = SigmaAlgebra(
...     domain=Y,
...     mapping={
...         0: ("a", "b"),
...         1: ("a", "b"),
...         2: ("c", "d"),
...     },
...     variable_names=["v", "w"],
...     name="G",
... )
>>> print(F)
Sigma algebra 'F':
   F
x
0  0
1  1
2  1
>>> print(G)
Sigma algebra 'G':
i  0  1
y
0  a  b
1  a  b
2  c  d

Compute the tensor product of the two sigma-algebras usings the tensor_product method.

>>> prod_sig_alg = SigmaAlgebra.tensor_product([F, G])
>>> print(prod_sig_alg)
Sigma algebra 'F x G':
i    0  1  2
x y
0 0  0  a  b
  1  0  a  b
  2  0  c  d
1 0  1  a  b
  1  1  a  b
  2  1  c  d
2 0  1  a  b
  1  1  a  b
  2  1  c  d

Print the atom space.

>>> print(prod_sig_alg.atom_space)
Domain 'F x G':
 u v w
 0 a b
 0 c d
 1 a b
 1 c d

Compute the same tensor product using the @ operator.

>>> print(F @ G)
Sigma algebra 'F x G':
i    0  1  2
x y
0 0  0  a  b
  1  0  a  b
  2  0  c  d
1 0  1  a  b
  1  1  a  b
  2  1  c  d
2 0  1  a  b
  1  1  a  b
  2  1  c  d
Notes

Let \(\mathcal{F}\) be a \(\sigma\)-algebra on a finite nonempty set \(X\), and let \(\mathcal{G}\) be a \(\sigma\)-algebra on a finite nonempty set \(Y\). The tensor product of \(\mathcal{F}\) and \(\mathcal{G}\), denoted \(\mathcal{F} \otimes \mathcal{G}\), is the \(\sigma\)-algebra on \(X\times Y\) whose atoms are all sets of the form \(A \times B\), where \(A\) is an atom of \(\mathcal{F}\) and \(B\) is an atom of \(\mathcal{G}\). If \(I\) is the set of atom identifiers of \(\mathcal{F}\) and \(J\) is the set of atom identifiers of \(\mathcal{G}\), then the atom identifiers of \(\mathcal{F} \times \mathcal{G}\) are all tuples \((i, j)\) with \(i\in I\) and \(j\in J\).

trivial classmethod

trivial(
    domain,
    domain_kind="Domain",
    domain_name=None,
    variable_name=None,
    name="T",
)

Create the trivial sigma-algebra over a given domain.

See the Notes section below for the mathematical details.

Parameters:

Name Type Description Default
domain Domain | IndexLike

The domain over which to create the trivial sigma-algebra.

required
domain_kind Literal['Domain', 'SampleSpace']

The type of domain of the sigma-algebra.

"Domain"
domain_name Hashable | None

The name of the domain of the sigma-algebra. If None, a default will be generated.

None
variable_name Hashable | None

The variable name of the sigma-algebra. If None, a default will be generated.

None
name Hashable

Name identifier for the sigma-algebra.

"T"

Returns:

Name Type Description
sig_alg SigmaAlgebra

A new SigmaAlgebra instance representing the trivial sigma-algebra.

Examples:

>>> from sigalg.core import SampleSpace, SigmaAlgebra
>>> Omega1 = SampleSpace.from_sequence(size=3, name="Omega1")
>>> G = SigmaAlgebra.trivial(Omega1, name="G")
>>> print(G)
Sigma algebra 'G':
       G
omega
0      0
1      0
2      0
>>> print(G.atom_space)
Domain 'G':
 G
 0
>>> Omega2 = SampleSpace.cartesian_product(
...     [[1, 2], ["a", "b"]], name="Omega2", variable_names=["number", "letter"]
... )
>>> F = SigmaAlgebra.trivial(Omega2, name="F")
>>> print(F)
Sigma algebra 'F':
               F
number letter
1      a       0
       b       0
2      a       0
       b       0
>>> print(F.atom_space)
Domain 'F':
 F
 0
Notes

The trivial \(\sigma\)-algebra on a nonempty set \(X\) consists of only the sets \(X\) and \(\emptyset\). Its single atom is \(X\) itself. It is the coarsest \(\sigma\)-algebra on \(X\).

with_name

with_name(name, reset_variable_names=False)

Reset the name of the sigma-algebra and return a new instance.

Parameters:

Name Type Description Default
name Hashable

The new name for the sigma-algebra.

required
reset_variable_names bool

Boolean flag whether to reset the variable names of the sigma-algebra.

False

Returns:

Name Type Description
renamed_sig_alg SigmaAlgebra

A new instance of SigmaAlgebra with updated name.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra
>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: 0,
...         1: 0,
...         2: 1,
...     },
... )
>>> print(F.with_name("G"))
Sigma algebra 'G':
   G
x
0  0
1  0
2  1

with_variable_names

with_variable_names(variable_names, name=None)

Reset the variable names of the sigma-algebra and return a new instance.

Parameters:

Name Type Description Default
variable_names list[Hashable]

The new variable names for the sigma-algebra.

required
name Hashable | None

A new name for the sigma-algebra. If None, the name of the original sigma-algebra will be used.

None

Returns:

Name Type Description
new_sig_alg SigmaAlgebra

A new sigma-algebra with the given variable names.

Examples:

>>> from sigalg.core import Domain, SigmaAlgebra

Define a sigma-algebra with 2-dimensional atom identifiers.

>>> X = Domain.from_sequence(size=3)
>>> F = SigmaAlgebra(
...     domain=X,
...     mapping={
...         0: (1, 1),
...         1: (2, 3),
...         2: (2, 3),
...     },
... )
>>> F
SigmaAlgebra(domain=X, num_atoms=2, variable_names=['F_0', 'F_1'], name=F)

Note the default variable names of F. We change them using the with_variable_names method.

>>> G = F.with_variable_names(["u", "v"], name="G")
>>> G
SigmaAlgebra(domain=X, num_atoms=2, variable_names=['u', 'v'], name=G)

Test the method with a power-set sigma-algebra.

>>> R = SigmaAlgebra.power_set(X)
>>> R
SigmaAlgebra(domain=X, num_atoms=3, variable_names=['x'], name=R)

For a power-set sigma-algebra, note the default variable names are the variable names of the domain. We them using the with_variable_names method.

>>> S = R.with_variable_names(["y"], name="S")
>>> S
SigmaAlgebra(domain=X, num_atoms=3, variable_names=['y'], name=S)

Time

Bases: Index

A class representing a time index.

Parameters:

Name Type Description Default
indices IndexLike | None

An IndexLike object of real numbers for the time index.

None
variable_names list[Hashable] | None

A list consisting of a single hashable item for the variable name of the index. If None, a default variable name will be generated.

None
name Hashable | None

Name identifier for the index. If None, a default name will be generated.

None

Examples:

Create a discrete Time instance.

>>> from sigalg.core import Time
>>> T_discrete = Time.discrete(start=0, length=5, name="T_discrete")
>>> print(T_discrete)
Time 'T_discrete':
 t
 0
 1
 2
 3
 4
 5

Create a continuous Time instance.

>>> T_continuous = Time.continuous(start=0.0, stop=1.0, num_points=9, name="T_continuous")
>>> print(T_continuous)
Time 'T_continuous':
     t
 0.000
 0.125
 0.250
 0.375
 0.500
 0.625
 0.750
 0.875
 1.000

is_discrete property

is_discrete

Get whether the time index represents discrete or continuous time.

Returns:

Name Type Description
is_discrete bool | None

True if the time index represents discrete time, False if it represents continuous time, or None if not set.

continuous classmethod

continuous(
    start,
    stop,
    dt=None,
    num_points=None,
    variable_name="t",
    name="T",
)

Create a continuous time index with real-valued time points.

Generates a time index with real-valued time points either by specifying the time step (dt) or the number of points (num_points). Exactly one of these parameters must be provided.

Parameters:

Name Type Description Default
start Real

Starting time point.

required
stop Real

Ending time point.

required
dt Real | None

Time step between consecutive points. Mutually exclusive with num_points.

None
num_points int | None

Number of evenly-spaced points to generate. Mutually exclusive with dt.

None
variable_name Hashable

Variable name for the time index.

"t"
name Hashable

Name of the time index.

"T"

Returns:

Name Type Description
time Time

A continuous time index with real-valued time points.

Examples:

Create a continuous Time instance by specifying num_points.

>>> from sigalg.core import Time
>>> T1 = Time.continuous(start=0.0, stop=1.0, num_points=3, name="T1")
>>> print(T1)
Time 'T1':
   t
 0.0
 0.5
 1.0
>>> print(T1.is_discrete)
False

Create a continuous Time instance by specifying dt.

>>> T2 = Time.continuous(start=0.0, stop=1.0, dt=0.25, name="T2")
>>> print(T2)
Time 'T2':
    t
 0.00
 0.25
 0.50
 0.75
 1.00
>>> print(T2.is_discrete)
False

discrete classmethod

discrete(
    length=None,
    start=0,
    stop=None,
    variable_name="t",
    name="T",
)

Create a discrete time index with integer time steps.

Generates a time index with consecutive integer time points starting from the specified start. The user may pass either the length of the time interval, or the stop value, but not both. The relation between the three parameters is length = stop - start.

Parameters:

Name Type Description Default
length int | None

Number of time points to generate. Must be positive.

None
start int

Starting time point.

0
stop int | None

Ending time point. Mutually exclusive with length.

None
variable_name Hashable

Variable name for the time index.

"time"
name Hashable

Name of the time index.

"T"

Returns:

Name Type Description
time Time

A discrete time index with integer time points.

Examples:

>>> from sigalg.core import Time
>>> T = Time.discrete(start=0, length=5)
>>> print(T)
Time 'T':
 t
 0
 1
 2
 3
 4
 5
>>> print(T.is_discrete)
True

find_nearest_time

find_nearest_time(time_point)

Find the nearest time point to the given value.

Parameters:

Name Type Description Default
time_point Real

The time point to find the nearest time for.

required

Returns:

Name Type Description
time Real

The nearest time point in the Time index.

Examples:

>>> from sigalg.core import Time
>>> T = Time.discrete(start=0, length=5, name="T")
>>> print(T)
Time 'T':
 t
 0
 1
 2
 3
 4
 5
>>> print(T.find_nearest_time(2.3))
2
>>> print(T.find_nearest_time(4.7))
5

insert_time

insert_time(time)

Insert a new time point into the time index.

Parameters:

Name Type Description Default
time Real

The time point to insert.

required

Returns:

Name Type Description
new_time Time

A new Time object with the inserted time point.

Examples:

>>> from sigalg.core import Time
>>> T = Time.discrete(start=0, length=5, name="T")
>>> print(T)
Time 'T':
 t
 0
 1
 2
 3
 4
 5
>>> new_time = T.insert_time(6)
>>> print(new_time)
Time 'insert(T)':
 t
 0
 1
 2
 3
 4
 5
 6

remove_time

remove_time(time=None, pos=None)

Remove a time point from the time index.

Parameters:

Name Type Description Default
time Real | None

The time point to remove. Must be specified if pos is not provided.

None
pos int | None

The position of the time point to remove. Must be specified if time is not provided.

None

Returns:

Name Type Description
new_time Time

A new Time object with the specified time point removed.

Examples:

>>> from sigalg.core import Time
>>> T = Time.discrete(start=0, length=5, name="T")
>>> print(T)
Time 'T':
 t
 0
 1
 2
 3
 4
 5
>>> new_time = T.remove_time(time=2)
>>> print(new_time)
Time 'remove(T)':
 t
 0
 1
 3
 4
 5