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 |
None
|
variable_names
|
list[Hashable] | None
|
A list of variable names for the dimensions of the domain. If |
None
|
name
|
Hashable | None
|
Name identifier for the domain. If |
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
|
Returns:
| Name | Type | Description |
|---|---|---|
measurable_space |
MeasurableSpace
|
A |
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
|
measure
|
Measure | None
|
Measure to use. If |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
measure_space |
MeasureSpace
|
A |
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 |
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 |
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 |
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
|
index
|
IndexLike | None
|
The index of the filtration. If |
None
|
index_name
|
Hashable | None
|
The name of the index of the filtration. If |
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 |
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
Functionis 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 |
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
|
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
|
**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 |
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
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 |
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 |
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
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 |
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 |
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 |
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 |
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
|
index
|
Index | None
|
An optional custom index for the sum, provided that the dimensions of the functions are > 1. If |
None
|
index_kind
|
Literal['Index', 'Time']
|
The type of index of the sum, provided that the dimensions of the functions are > 1. If |
"Index"
|
index_name
|
Hashable | None
|
The name of the index of the sum, provided that the dimensions of the functions are > 1. If |
None
|
name
|
Hashable | None
|
The name of the result. If |
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
|
|
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
|
|
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
|
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
|
Returns:
| Name | Type | Description |
|---|---|---|
atom_data |
Series | None
|
A |
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
|
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
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
|
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
|
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
|
index
|
Index | None
|
An optional custom index for the quotient, provided that the dimensions of the functions are > 1. If |
None
|
index_kind
|
Literal['Index', 'Time']
|
The type of index of the quotient, provided that the dimensions of the functions are > 1. If |
"Index"
|
index_name
|
Hashable | None
|
The name of the index of the quotient, provided that the dimensions of the functions are > 1. If |
None
|
name
|
Hashable | None
|
The name of the result. If |
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
|
tol
|
float
|
A float below which numbers will be deemed |
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
|
index
|
Index | None
|
An optional custom index for the power, provided that the dimensions of the functions are > 1. If |
None
|
index_kind
|
Literal['Index', 'Time']
|
The type of index of the power, provided that the dimensions of the functions are > 1. If |
"Index"
|
index_name
|
Hashable | None
|
The name of the index of the power, provided that the dimensions of the functions are > 1. If |
None
|
name
|
Hashable | None
|
The name of the result. If |
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 |
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
|
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
|
**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
|
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
|
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
|
output_name
|
Hashable | None
|
The name of the outputs of the function. If |
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
|
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
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 |
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 |
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
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
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 |
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
|
|
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
|
index
|
Index | None
|
An optional custom index for the product, provided that the dimensions of the functions are > 1. If |
None
|
index_kind
|
Literal['Index', 'Time']
|
The type of index of the product, provided that the dimensions of the functions are > 1. If |
"Index"
|
index_name
|
Hashable | None
|
The name of the index of the product, provided that the dimensions of the functions are > 1. If |
None
|
name
|
Hashable | None
|
The name of the result. If |
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
|
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 |
"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
|
index
|
Index | None
|
An optional custom index for the difference, provided that the dimensions of the functions are > 1. If |
None
|
index_kind
|
Literal['Index', 'Time']
|
The type of index of the difference, provided that the dimensions of the functions are > 1. If |
"Index"
|
index_name
|
Hashable | None
|
The name of the index of the difference, provided that the dimensions of the functions are > 1. If |
None
|
name
|
Hashable | None
|
The name of the result. If |
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
|
domain_name
|
Hashable | None
|
The name of the domain. If |
None
|
name
|
Hashable | None
|
The name of the resulting function. If |
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
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
|
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
|
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
|
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
|
Returns:
| Name | Type | Description |
|---|---|---|
measure |
Measure | ParametrizedMeasure
|
Either a measure or parametrized measure produced from the current |
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
|
copy
|
bool | None
|
Whether to return a copy of the data. If |
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 |
None
|
variable_names
|
list[Hashable] | None
|
A list of variable names for the dimensions of the index. If |
None
|
name
|
Hashable | None
|
Name identifier for the index. If |
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 |
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
|
name
|
Hashable | None
|
The name of the Cartesian power. If |
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
|
name
|
Hashable | None
|
The name of the Cartesian product. If |
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
|
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 |
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
|
variable_name
|
Hashable | None
|
An optional single element for the variable name. If |
None
|
name
|
Hashable | None
|
Name identifier for the index. If |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
index |
Index
|
A new |
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 |
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
|
copy
|
bool | None
|
Whether to return a copy of the data. If |
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
|
measure
|
Measure | None
|
The measure over which the L2-space is defined. If |
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
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
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
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
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:
Thus, provided that \(\mu(A_i)\neq 0\), we obtain a normalized basis function:
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 |
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 |
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 |
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 |
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 |
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 |
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:
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
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
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 |
required |
name
|
Hashable | None
|
The name of the projection. If |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
proj |
MeasurableFunction
|
The orthogonal projection of |
coefficients |
ndarray
|
The coefficients of the projection of |
dim |
int
|
The dimension of the subspace spanned by |
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
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
The first (Fréchet) derivative \(D\psi(c)\) of \(\psi\) at \(c\) is given by
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
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
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.
-
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}\).
-
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}\).
-
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 |
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 |
Returns:
| Name | Type | Description |
|---|---|---|
is_sub_alg |
bool
|
|
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
|
name
|
Hashable | None
|
Name identifier for the resulting sigma algebra. If |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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
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 |
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
|
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
|
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
|
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
|
measure_name
|
Hashable | None
|
The name of the measure of the Cartesian product. If |
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
|
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
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
|
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
|
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 |
required |
sig_alg
|
SigmaAlgebra | None
|
The sigma-algebra of the underlying measurable space. If |
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
|
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
|
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
|
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 |
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
|
output_name
|
Hashable | None
|
The name of the outputs of the function. If |
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
|
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 |
"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 |
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
|
name
|
Hashable | None
|
The name of the measure. If |
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
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 |
required |
output_name
|
Hashable | None
|
The output name of the measure. If |
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
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 |
1e-5
|
atol
|
float
|
Absolute tolerance for checking approximate equality. Ignored if |
1e-8
|
Returns:
| Name | Type | Description |
|---|---|---|
is_equal |
bool
|
|
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 |
1e-5
|
atol
|
float
|
The absolute tolerance for |
1e-8
|
Returns:
| Name | Type | Description |
|---|---|---|
equal_ae |
bool
|
|
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
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 |
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 |
"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 |
10
|
rate
|
float
|
The rate parameter for Poisson sampling when |
5.0
|
output_name
|
Hashable | None = None
|
Output name of measure. If |
None
|
name
|
Hashable | None
|
The name of the measure. If |
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 |
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 |
1e-8
|
Returns:
| Name | Type | Description |
|---|---|---|
is_absolutely_continuous |
bool
|
A Boolean flagging whether the current measure is absolutely continuous with respect to the |
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
|
|
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 |
required |
normalize
|
bool
|
If the measure is restricted to a |
False
|
subset_name
|
Hashable | None
|
The name of the subset. Only used if |
"A"
|
name
|
Hashable | None
|
The name of the restriction. If |
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
|
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
|
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
|
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"
|
measure_max_value
|
int
|
The maximum value for the |
10
|
measure_rate
|
float
|
The rate parameter for the |
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 |
required |
is_null
|
bool
|
If |
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_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
|
|
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 |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
prob_space |
ProbabilitySpace | None
|
If |
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
|
measure
|
ProbabilityMeasure | None
|
The probability used to compute the correlation. If |
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
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:
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
|
measure
|
ProbabilityMeasure | None
|
The probability used to compute the covariance. If |
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
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:
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 |
required |
rv2
|
RandomVariable
|
The terminal random variable of the cross entropy. See the |
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
|
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
|
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
|
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 |
required |
rv2
|
RandomVariable
|
The terminal random variable of the divergence. See the |
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
|
measure
|
ProbabilityMeasure | None
|
The probability measure with respect to which to integrate. If |
None
|
name
|
Hashable | None
|
The name to assign to the resulting expected value random vector. If |
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
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:
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
for all \(\omega\in \Omega\).
If \(X : \Omega \to \mathbb{R}^d\) is a random vector of dimension \(d>1\), with components
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
|
measure
|
Measure | ParametrizedMeasure | None
|
The measure or parametrized measure with respect to which to integrate. If |
None
|
subset_name
|
Hashable | None
|
If the |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
integral |
Real | Series | Function
|
Returns the following:
|
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
If \(f:X \to \mathbb{R}^d\) is instead a measurable vector of dimension \(d>1\), with components
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 |
required |
func1
|
Function | Real
|
The second function (or scalar). One of |
required |
name
|
Hashable | None
|
The name of the resulting function. If |
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
|
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
|
name
|
Hashable | None
|
The name of the pushforward measure. If |
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
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
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
|
measure
|
ProbabilityMeasure | None
|
The probability measure with respect to which to integrate. If |
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
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:
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
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
|
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
|
name
|
Hashable | None
|
The name of the transformed vector. If |
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
|
measure
|
ProbabilityMeasure | None
|
The probability measure with respect to which to integrate. If |
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
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:
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
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
such that, for each fixed \(\theta \in \Theta\), the partial function
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
We define the \(\sigma\)-algebra generated by \(f\), denoted \(\sigma(f)\), to be the \(\sigma\)-algebra given by the join
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 |
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 |
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 |
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 |
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 |
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
|
name
|
Hashable | None
|
The name of the parametrized function. If |
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
such that, for each fixed \(\theta \in \Theta\), the partial function
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
such that, for each fixed \(\theta \in \Theta\), the partial function
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 |
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 |
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 |
required |
parameter_domain
|
Domain | None
|
The parameter domain for the measure. |
None
|
complete_domain
|
Domain | None
|
If |
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
|
name
|
Hashable | None
|
The name of the parametrized measure. If |
"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 |
required |
parameter_domain
|
Domain | None
|
The parameter domain for the measure. |
None
|
complete_domain
|
Domain | None
|
If |
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_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 |
10
|
rate
|
float
|
The rate parameter for Poisson sampling when |
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
|
name
|
Hashable | None
|
The name of the function. If |
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
|
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
|
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
such that, for each fixed \(\theta \in \Theta\), the partial function
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
|
name
|
Hashable | None
|
The name of the resulting Radon-Nikodym derivative. If |
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
for which each partial function
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
for which each partial function
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
|
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
|
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
|
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
|
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
|
name
|
Hashable | None
|
The name of the parametrized function. If |
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 |
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
|
name
|
Hashable | None
|
The name of the measure. If |
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
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
|
|
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
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
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 |
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
|
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
for all \(V\in \mathcal{G}\). This equation is called the law of total probability. In particular, we may take
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\)-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
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
|
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
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
|
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
|
tol
|
float
|
Tolerance for testing for absolute continuity. |
1e-8
|
Returns:
| Name | Type | Description |
|---|---|---|
derivative |
MeasurableFunction | ParametrizedMeasurableFunction
|
Either an instance of |
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
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
for all \(U\in \mathcal{F}\) and \(V\in \mathcal{G}\). By Fubini's theorem, we may write this latter equation as
from which it follows that
where the equality holds for all \(\omega'\) \(P\)-almost surely. It thus follows that for all such \(\omega'\), the partial function
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
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
|
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
|
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
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
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 |
1e-5
|
atol
|
float
|
The absolute tolerance for |
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
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
|
random_state
|
int | Generator | None
|
Random seed or generator for reproducibility. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
sample |
MeasureSpace
|
An instance of |
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
|
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
|
tol
|
float
|
Tolerance for testing for absolute continuity. |
1e-8
|
Returns:
| Name | Type | Description |
|---|---|---|
surprisal |
MeasurableFunction | ParametrizedMeasurableFunction
|
Either an instance of |
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
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
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 |
required |
output_name
|
Hashable | None
|
The name of the output variable of the measure. If |
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
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
|
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 |
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
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
|
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
|
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
|
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 |
"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
|
random_state
|
int | Generator | None
|
Random seed or generator for reproducibility. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
sample |
MeasureSpace
|
An instance of |
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
|
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
|
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 |
None
|
variable_names
|
list[Hashable] | None
|
A list of names of the variables for the sample space. If |
None
|
name
|
Hashable | None
|
Name identifier for the sample space. If |
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
|
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 |
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
|
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 |
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 |
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
|
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
|
output_name
|
Hashable | None
|
The name of the output mapping of the sigma-algebra. If |
None
|
index
|
IndexLike | None
|
The index of the atom identifiers of the sigma-algebra. If |
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
|
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 |
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 |
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 |
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 |
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
|
super
|
SigmaAlgebra | None
|
If provided, the randomly generated sigma-algebra will be a sub-sigma-algebra of |
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
|
variable_names
|
list[Hashable] | None
|
A list of variable names for the atom identifiers. If |
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
|
output_name
|
Hashable | None
|
The name of the output mapping of the sigma-algebra. If |
None
|
index
|
IndexLike | None
|
The index of the atom identifiers of the sigma-algebra. If |
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
|
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 |
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 |
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 |
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 |
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
|
|
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
|
index
|
IndexLike | None
|
The index of the atom identifiers of the sigma-algebra. If |
None
|
index_name
|
Hashable | None
|
The name of the index of the sigma-algebra. If |
None
|
name
|
Hashable
|
Name identifier for the sigma algebra. |
"R"
|
Returns:
| Name | Type | Description |
|---|---|---|
sig_alg |
SigmaAlgebra
|
A new |
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 |
"A"
|
name
|
Hashable | None
|
The name of the restriction. If |
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
|
domain_name
|
Hashable | None
|
The name of the domain of the sigma-algebra. If |
None
|
index
|
IndexLike | None
|
The index of the atom identifiers of the sigma-algebra. If |
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
|
name
|
Hashable | None
|
The name of the resulting sigma-algebra. If |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tensor_product |
SigmaAlgebra
|
The tensor product of the sigma-algebras in |
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
|
variable_name
|
Hashable | None
|
The variable name of the sigma-algebra. If |
None
|
name
|
Hashable
|
Name identifier for the sigma-algebra. |
"T"
|
Returns:
| Name | Type | Description |
|---|---|---|
sig_alg |
SigmaAlgebra
|
A new |
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 |
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
|
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 |
None
|
variable_names
|
list[Hashable] | None
|
A list consisting of a single hashable item for the variable name of the index. If |
None
|
name
|
Hashable | None
|
Name identifier for the index. If |
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
|
|
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 |
None
|
num_points
|
int | None
|
Number of evenly-spaced points to generate. Mutually exclusive with |
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 |
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 |
None
|
pos
|
int | None
|
The position of the time point to remove. Must be specified if |
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