Structural morphisms

These constructors build the deterministic wiring of a Markov category. copy_tensor, identity_tensor, and delta_tensor accept either integer lengths or lists of string labels. swap, exclamation, and unit_tensor take integer lengths only.

Identity, delta, and copy

identity_tensor() is the identity kernel. delta_tensor() is an alias of it. copy_tensor() is the diagonal X → X⊗n. n=1 is the identity, n=0 is discard (all ones on the domain), and a negative n raises ValueError. An empty factor list builds the scalar 1 on the monoidal unit.

import numpy as np
from fxtensor_salmon import FXTensor

labels = [["a", "b"]]
identity = FXTensor.identity_tensor(labels)
assert identity.labels == ([["a", "b"]], [["a", "b"]])
assert identity.is_deterministic()

numeric_identity = FXTensor.identity_tensor([2, 3])
assert numeric_identity.profile == [[2, 3], [2, 3]]
assert FXTensor.delta_tensor([2]) == FXTensor.identity_tensor([2])
assert FXTensor.delta_tensor([2]).profile == [[2], [2]]
assert FXTensor.identity_tensor([]).profile == [[], []]

copy = FXTensor.copy_tensor([2])
assert copy.profile == [[2], [2, 2]]
assert np.array_equal(
    copy.data,
    [
        [[1, 0], [0, 0]],
        [[0, 0], [0, 1]],
    ],
)
labeled_copy = FXTensor.copy_tensor([["a", "b"]])
assert labeled_copy.labels == ([["a", "b"]], [["a", "b"], ["a", "b"]])
assert FXTensor.copy_tensor([2], n=1) == FXTensor.identity_tensor([2])
discarding_copy = FXTensor.copy_tensor([["a", "b"]], n=0)
assert discarding_copy.profile == [[2], []]
assert discarding_copy.labels == ([["a", "b"]], None)
assert np.array_equal(discarding_copy.data, [1.0, 1.0])
try:
    FXTensor.copy_tensor([2], n=-1)
except ValueError:
    pass
else:
    raise AssertionError("copy_tensor rejects a negative count")

Swap, discard, and the all-ones state

swap() exchanges two blocks of factors. exclamation() is discard X → I, an all-ones array on the domain and an empty codomain. unit_tensor() is a state of all ones. It is not normalized, and it is not the monoidal unit (that is the empty profile).

import numpy as np
from fxtensor_salmon import FXTensor

swap = FXTensor.swap([2], [3])
assert swap.profile == [[2, 3], [3, 2]]
assert swap.is_deterministic()
assert swap.composition(FXTensor.swap([3], [2])) == FXTensor.identity_tensor([2, 3])

discard = FXTensor.exclamation([2, 3])
assert discard.profile == [[2, 3], []]
assert np.all(discard.data == 1)
assert discard.is_deterministic()

unit = FXTensor.unit_tensor([2, 3])
assert unit.profile == [[], [2, 3]]
assert unit.labels == (None, None)
assert np.all(unit.data == 1)

Copy laws

On these tensors, copy is cocommutative, coassociative, and counital, and discard is natural: f ; ! = ! when f is a kernel. A deterministic kernel commutes with copy.

Coassociativity of copy

Copy is coassociative.

Counitality of copy

Copy is counital.

Cocommutativity of copy

Copy is cocommutative.

Discard is natural, so f composed with discard equals discard

Discard is natural: f ; ! = !.

import numpy as np
from fxtensor_salmon import FXTensor

copy = FXTensor.copy_tensor([2])
identity = FXTensor.identity_tensor([2])
discard = FXTensor.exclamation([2])
assert copy.composition(FXTensor.swap([2], [2])) == copy
assert copy.composition(identity.tensor_product(discard)) == identity
assert copy.composition(discard.tensor_product(identity)) == identity
assert copy.composition(copy.tensor_product(identity)) == FXTensor.copy_tensor([2], n=3)

kernel = FXTensor([[2], [2]], data=np.array([[0.8, 0.2], [0.4, 0.6]]))
assert kernel.composition(FXTensor.exclamation([2])) == FXTensor.exclamation([2])
assert identity.composition(copy) == copy.composition(identity.tensor_product(identity))