Skip to content

torch_vn_algebra.inclusions

Source: torch_vn_algebra/inclusions.py. Signatures and docstrings as in the source.

Module documentation

Unital inclusions of finite-dimensional von Neumann algebras, conditional expectations and index.

Inclusion matrices
------------------
Let N = (+)_{c=1}^{C} M_{k_c} and M = (+)_{d=1}^{D} M_{m_d}. Every unital injective *-homomorphism
iota: N -> M is, up to an inner automorphism of M, of the standard form

    iota_0(x)_d = (+)_c  x_c (x) 1_{lambda_dc}        (summands in the order c = 1, ..., C),

given by the inclusion matrix Lambda = (lambda_dc) in Z_{>=0}^{D x C}: lambda_dc is the multiplicity of
the irreducible representation of the summand M_{k_c} in that of M_{m_d}. Unitality is the condition
m_d = sum_c lambda_dc k_c, injectivity says that every column of Lambda is non-zero. The general
inclusion is iota = Ad(u) o iota_0 with a unitary u of M ([GHJ, Ch. 2]); `Inclusion` stores Lambda
and the optional u (one unitary per sample: a batch). In block d the basis vector
off_dc + a lambda_dc + i (a < k_c, i < lambda_dc, off_dc = sum_{c' < c} lambda_dc' k_c') is e_a (x) e_i of
the i-th copy of C^{k_c}, the convention of composite.kron; the isometries

    V_{dc,i}: C^{k_c} -> C^{m_d},  e_a -> e_{off_dc + a lambda_dc + i},   iota_0(x)_d = sum_{c,i} V_{dc,i} x_c V_{dc,i}^*,

are the Kraus operators of iota as an InterSectorChannel N -> M (`Inclusion.as_channel`). Inclusion
matrices compose by multiplication: N c M c P has matrix Lambda_{PM} Lambda_{MN} (`Inclusion.compose`);
a tower of inclusions is drawn as its Bratteli diagram (`bratteli_diagram`).

Traces and conditional expectations
-----------------------------------
A faithful trace on M is tau = sum_d w_d Tr_d with w_d > 0 (w_d is the trace of a minimal projection of
the summand d); the library's traces 'blunt', 'norm', 'vn' (tau_vN) have w_d = 1, 1/m_d, 1/(D m_d). Its
restriction to N is the trace with weights nu = Lambda^T w (`Inclusion.induced_trace`):
tau(iota(x)) = sum_d w_d sum_c lambda_dc Tr x_c. The map E: M -> N,

    E(y)_c = nu_c^{-1} sum_{d,i} w_d V_{dc,i}^* u_d^* y_d u_d V_{dc,i},                       (E)

is the unique tau-preserving conditional expectation onto N. Proof (u = 1; conjugate by u otherwise).
iota(n)_d V_{dc,i} = V_{dc,i} n_c gives the bimodule property E(iota(n1) y iota(n2)) = n1 E(y) n2; with
V^* V = 1 it gives E(iota(n)) = nu_c^{-1} sum_d w_d lambda_dc n_c = n, so E o iota = id and E(1) = 1.
Summing Tr(n_c V^* y_d V) over (d, c, i) with weights w_d gives tau|_N(n E(y)) = tau(iota(n) y): E is
the adjoint of iota with respect to tau on M and the restricted trace tau|_N (weights nu, not the trace
of the same name on N) on N; n = 1 gives tau o E = tau. If F is another tau-preserving conditional
expectation, tau|_N(n F(y)) = tau(F(iota(n) y)) = tau(iota(n) y) = tau|_N(n E(y)) for all n, so F = E by
faithfulness of tau|_N. E is completely positive and unital: (E) is a Kraus form with non-negative
weights (`ConditionalExpectation.as_channel`).

Index
-----
* Markov trace and index ([Jones 1983], [GHJ, Ch. 2-3]). For a connected inclusion (the
  bipartite graph of Lambda is connected) let t > 0 be the Perron-Frobenius vector of Lambda Lambda^T,
  beta = ||Lambda||^2 its eigenvalue (largest singular value of Lambda squared), s = Lambda^T t, normalised
  by sum_d m_d t_d = 1. Then t is the weight vector of the unique Markov trace of M (s is its
  restriction to N) and Lambda s = beta t. The index [M : N] = beta: for factors N = M_k c M = M_m it is
  lambda^2 = (m/k)^2, for C c M_n it is n^2, for the diagonal masa C^n c M_n (Lambda = (1, ..., 1)) it is n.
  A disconnected inclusion is the direct sum of its connected components; `Inclusion.index` then
  returns max_j ||Lambda_j||^2 = ||Lambda||^2 with a warning, and `markov_trace` raises.
* Watatani index ([Watatani 1990]). A quasi-basis of E is a family {u_j} in M with
  y = sum_j u_j iota(E(u_j^* y)) for all y in M; Ind E = sum_j u_j u_j^* does not depend on it and is
  central. With matrix units of M a quasi-basis is u_{d,r,c,i} = (nu_c / w_d)^{1/2} u e^d_{r, off_dc + i} u^*
  (r < m_d, lambda_dc > 0, i < lambda_dc), which gives

      (Ind E)_d = sum_c lambda_dc nu_c / w_d,     i.e.  Ind E = (Lambda Lambda^T w)_d / w_d,

  equal to [M : N] 1 exactly for the Markov trace.
* Pimsner-Popa constant ([Pimsner-Popa 1986]). lambda(E) = max{l : E(y) >= l y for all y >= 0}. The
  minimum over the extreme rays y = xi xi^* (xi in C^{m_d}) gives, by the Schur-complement criterion
  A >= l xi xi^* <=> l xi^* A^+ xi <= 1 (xi in ran A), with A = iota(E(xi xi^*))_d,

      lambda(E)^{-1} = max_d sum_c (nu_c / w_d) min(k_c, lambda_dc),

  since xi^* A^+ xi = sum_c (nu_c / w_d) rank(sum_i xi_ci xi_ci^*) and the rank is at most min(k_c, lambda_dc),
  attained for generic xi. For C c M_n: lambda(E) = 1/n (not 1/n^2: for type I factors lambda(E)^{-1}
  differs from the index, unlike for II_1 subfactors, where lambda(E) = [M : N]^{-1}). The constant of
  the complete order, max{l : E - l id is completely positive}, is 1 / ||Ind E|| ([Frank-Kirchberg 1998]):
  1/n^2 for C c M_n and 1/m^2 for M_n (x) 1 c M_n (x) M_m. Both are in
  `ConditionalExpectation.pimsner_popa_constant`.

Basic construction and relative commutant
-----------------------------------------
The basic construction M_1 = <M, e_N> = (J N J)' on L^2(M, tau) has the sectors of N, sizes
(Lambda^T m)_c, and M c M_1 has inclusion matrix Lambda^T ([Jones 1983], [GHJ, Ch. 2]); iterating
gives the Jones tower N c M c M_1 c M_2 c ... with matrices Lambda, Lambda^T, Lambda, ... and constant
index. In the standard form of M c M_1 used here the Jones projection is
(e_N)_c = nu_c^{-1} sum_a eta_{c,a} eta_{c,a}^* with
eta_{c,a} = sum_{d,i} w_d^{1/2} e_{(d, off_dc + a lambda_dc + i, i)} (basis of summand d: (row of M_d, copy i))
(conjugated by the image of u), satisfying e_N y e_N = E(y) e_N; for the Markov trace the trace of M_1
with weights s / beta extends tau and tau_1(y e_N) = beta^{-1} tau(y) (Markov property).
The relative commutant is N' cap M = (+)_{(d,c): lambda_dc > 0} M_{lambda_dc}, block d acting as
1_{k_c} (x) y_dc; dim = sum lambda_dc^2.

Standard inclusions
-------------------
from_tensor_product (N (x) 1 c N (x) P, Lambda = diag blocks p_e), diagonal (C^n c M_n), scalars (C c M),
centre (Z(M) c M), and chain_tower: the U(1) spin-chain algebras A_L = (+)_{N=0}^{L} M_{binom(L, N)} of
operators on (C^2)^{(x) L} commuting with total S^z, with A_L c A_{L+1}, x -> x (x) 1_2 (the new site L is
the highest bit). Sector N of A_{L+1} receives N - 1 (new spin up) and N (new spin down) of A_L: Lambda
is the (L + 2) x (L + 1) bidiagonal 0/1 matrix, the incidence matrix of the path graph with 2L + 3
vertices, so [A_{L+1} : A_L] = ||Lambda||^2 = 4 cos^2(pi / (2L + 4)) (the norm of the adjacency matrix of
the path A_n is 2 cos(pi / (n + 1)), [GHJ, Ch. 1]). The basis order coincides with that of chains.SpinChain.

All maps act on batches (batch, C, k_max, k_max) by gathers with index maps built once per inclusion
(no loop over sectors, no host synchronisation), and are differentiable.

References
----------
[Jones 1983] V. F. R. Jones, Index for subfactors, Invent. Math. 72 (1983) 1-25.
[GHJ] F. M. Goodman, P. de la Harpe, V. F. R. Jones, Coxeter graphs and towers of algebras, MSRI Publ. 14,
    Springer (1989).
[Pimsner-Popa 1986] M. Pimsner, S. Popa, Entropy and index for subfactors, Ann. Sci. ENS 19 (1986) 57-106.
[Watatani 1990] Y. Watatani, Index for C*-subalgebras, Mem. Amer. Math. Soc. 83 (1990), no. 424.
[Frank-Kirchberg 1998] M. Frank, E. Kirchberg, On conditional expectations of finite index,
    J. Operator Theory 40 (1998) 87-111.

Inclusion

class Inclusion(N: TypeIAlgebra, M: TypeIAlgebra, matrix, unitary: Optional[Operator]=None, validate: bool=True)
Unital inclusion iota = Ad(u) o iota_0: N -> M of finite-dimensional von Neumann algebras with inclusion
matrix Lambda (see the module docstring).

N, M: TypeIAlgebra with the same field, precision and device; the block sizes used are the active
    sizes k_factors (empty sectors, k_c = 0, are not part of the algebra and need zero columns / rows).
matrix: Lambda, non-negative integers of shape (M.C, N.C) with m_d = sum_c lambda_dc k_c (unital) and a
    non-zero column for every non-empty sector of N (injective).
unitary: optional Operator of M, batch 1 or B (one inclusion per sample): iota(x) = u iota_0(x) u^*.
validate: check that `unitary` is unitary (one host synchronisation, at construction).

Inclusion.matrix

@property
def matrix(self) -> np.ndarray
The inclusion matrix Lambda, an int64 array of shape (D, C) (a copy).

Inclusion.batch_size

@property
def batch_size(self) -> int
Number of inclusions (batch of the unitary; 1 without unitary).

Inclusion.embed

def embed(self, x: Operator) -> Operator
iota(x) for an Operator x of N with batch B: an Operator of M with batch max(B, batch of u) (lazy,
differentiable; iota_0(x)_d = (+)_c x_c (x) 1_{lambda_dc}, then u . u^*). iota is a unital injective
*-homomorphism, so self-adjointness, normality, positivity, invertibility and projections carry over.

Inclusion.isometries

def isometries(self) -> torch.Tensor
Kraus tensor of iota, shape (batch, r, D, C, m_max, k_max): u_d V_{dc,i} (zero beyond lambda_dc).

Inclusion.as_channel

def as_channel(self) -> InterSectorChannel
iota as an InterSectorChannel N -> M (Kraus operators u_d V_{dc,i}), for the channel machinery:
`adjoint('blunt')` of it is the restriction of states (densities with respect to the blunt traces),
`compose` gives composite maps. iota is unital (as_channel().is_unital()), not trace preserving.

Inclusion.trace_weights

def trace_weights(self, trace: TraceSpec='blunt') -> torch.Tensor
Weights w (batch, D) of a faithful trace tau = sum_d w_d Tr_d on M: 'blunt' (1), 'norm' (1/m_d),
'vn' or 'tau_vN' (1/(D m_d)), 'markov' (the Markov trace, connected inclusions only), or explicit
weights of shape (D,) or (batch, D), positive on the non-empty sectors (checked: one synchronisation).

Inclusion.induced_trace

def induced_trace(self, trace: TraceSpec='blunt') -> torch.Tensor
Weights nu = Lambda^T w of the restriction tau|_N of tau = sum_d w_d Tr_d: tau(iota(x)) = sum_c nu_c Tr x_c.
Shape (C,) for a named trace or weights (D,), (batch, C) for weights (batch, D).

Inclusion.conditional_expectation

def conditional_expectation(self, trace: TraceSpec='blunt') -> 'ConditionalExpectation'
The unique tau-preserving conditional expectation E: M -> N for the trace `trace` of M (see
trace_weights; a batch of weights gives a batch of expectations). See ConditionalExpectation.

Inclusion.connected_components

def connected_components(self) -> List[Tuple[List[int], List[int]]]
Connected components of the bipartite graph of Lambda (non-empty sectors only), as pairs
(sectors of M, sectors of N). The inclusion is the direct sum of the inclusions of its components.

Inclusion.is_connected

@property
def is_connected(self) -> bool
True if the Bratteli diagram of N c M is connected (equivalently Z(M) cap Z(N) = C 1).

Inclusion.component_indices

def component_indices(self) -> List[float]
||Lambda_j||^2 of every connected component (float64).

Inclusion.index

def index(self) -> float
[M : N] = ||Lambda||^2 (largest singular value of Lambda squared, float64): the Jones index for factors
((m/k)^2) and the index of the Markov trace for connected inclusions (Lambda s = beta t). For a
disconnected inclusion the Markov trace does not exist or is not unique; the value returned is
max_j ||Lambda_j||^2 over the components, with a warning (component_indices gives all of them).

Inclusion.markov_trace

def markov_trace(self) -> MarkovTrace
The Markov trace of a connected inclusion (MarkovTrace(t, s, beta)): t (D,) is the Perron-Frobenius
vector of Lambda Lambda^T (computed in float64 by a symmetric eigendecomposition), normalised to
sum_d m_d t_d = 1, s = Lambda^T t (C,), beta = ||Lambda||^2; empty sectors get weight 0. Raises
ValueError for a disconnected inclusion.

Inclusion.watatani_index

def watatani_index(self, E: Union['ConditionalExpectation', TraceSpec]='markov', method: str='formula') -> Operator
Ind E for a ConditionalExpectation or a trace of M; see ConditionalExpectation.index.

Inclusion.pimsner_popa_constant

def pimsner_popa_constant(self, E: Union['ConditionalExpectation', TraceSpec]='markov', complete: bool=False) -> torch.Tensor
lambda(E) for a ConditionalExpectation or a trace of M; see ConditionalExpectation.pimsner_popa_constant.

Inclusion.relative_commutant

def relative_commutant(self) -> 'Inclusion'
N' cap M as the inclusion R c M with R = (+)_{(d,c): lambda_dc > 0} M_{lambda_dc} (sectors ordered by d,
then c; charges (d, c)): sector (d, c) acts in block d as 1_{k_c} (x) y on the copies of C^{k_c},
conjugated by u. Its inclusion matrix has entry k_c in row d and column (d, c); dim R = sum lambda_dc^2.
`commutator_error` checks [iota(N), R] = 0 numerically.

Inclusion.commutator_error

def commutator_error(self, other: 'Inclusion', batch_size: int=4) -> torch.Tensor
||[iota(x), iota'(y)]||_F / (||iota(x)||_F ||iota'(y)||_F) for random (Gaussian) x in N, y in other.N, for two
inclusions into the same M (e.g. other = self.relative_commutant()). Shape (batch,): batch_size samples,
or one per inclusion when the unitaries are batched.

Inclusion.compose

def compose(self, other: 'Inclusion') -> 'Inclusion'
The inclusion N c P of a tower N c M c P (self: N c M, other: M c P), iota_P o iota_M, with matrix
Lambda_PM Lambda_MN and unitary iota_P(u_M) u_P Pi, where the permutation Pi orders the copies of
C^{k_c} in a block e of P by (d, i, j) (sector d of M, copy i in M_d, copy j of M_d in P_e).

Inclusion.basic_construction

def basic_construction(self) -> 'Inclusion'
The inclusion M c M_1 of the basic construction M_1 = <M, e_N>: M_1 has one sector per sector c of N
(charges of N) of size (Lambda^T m)_c, and the inclusion matrix is Lambda^T (in standard form; the
unitary u of N c M enters only the Jones projection). Cached; iterate for the Jones tower.

Inclusion.jones_tower

def jones_tower(self, n: int) -> List['Inclusion']
[N c M, M c M_1, ..., M_{n-2} c M_{n-1}] (n inclusions), matrices Lambda, Lambda^T, Lambda, ...

Inclusion.jones_projection

def jones_projection(self, trace: TraceSpec='markov') -> Operator
The Jones projection e_N in M_1 = basic_construction().M for the trace `trace` of M: the projection
of L^2(M, tau) onto L^2(N, tau). (e_N)_c = nu_c^{-1} sum_a eta_{c,a} eta_{c,a}^* in the standard form (see
the module docstring), conjugated by j(u). It satisfies e_N j(y) e_N = j(iota(E(y))) e_N for y in M
(j: M -> M_1), commutes with j(iota(N)), and M_1 = span j(M) e_N j(M). Shape (batch, C, n1_max, n1_max).

ConditionalExpectation

class ConditionalExpectation(inclusion: Inclusion, weights: torch.Tensor)
The tau-preserving conditional expectation E: M -> N of an inclusion iota: N c M,

    E(y)_c = nu_c^{-1} sum_{d,i} w_d V_{dc,i}^* u_d^* y_d u_d V_{dc,i},     nu = Lambda^T w,

for the faithful trace tau = sum_d w_d Tr_d of M (weights (batch, D)). E is unital, completely positive,
N-bimodular, E o iota = id, tau o E = tau, and tau(iota(x) y) = tau|_N(x E(y)) with tau|_N = sum_c nu_c Tr_c
(the restricted trace, not a trace of the same name on N). Create with Inclusion.conditional_expectation.

ConditionalExpectation.as_channel

def as_channel(self) -> InterSectorChannel
E as an InterSectorChannel M -> N with Kraus operators (w_d / nu_c)^{1/2} V_{dc,i}^* u_d^*
(shape (batch, r, C, D, k_max, m_max)); unital, not trace preserving in general.

ConditionalExpectation.index

def index(self, method: str='formula') -> Operator
Watatani index Ind E = sum_j u_j u_j^* (central, positive, invertible), an Operator of M with batch
max(batch of w, batch of u). method='formula': (Ind E)_d = sum_c lambda_dc nu_c / w_d (exact);
method='quasi_basis': the sum over the quasi-basis of `quasi_basis` (O(J D m_max^2) memory).
For the Markov trace Ind E = [M : N] 1.

ConditionalExpectation.quasi_basis

def quasi_basis(self) -> torch.Tensor
A quasi-basis (Pimsner-Popa basis) {u_j} of E, y = sum_j u_j iota(E(u_j^* y)) for all y in M:
u_{d,r,c,i} = (nu_c / w_d)^{1/2} u e^d_{r, off_dc + i} u^* (r < m_d, lambda_dc > 0, i < lambda_dc), as a tensor
(batch, J, D, m_max, m_max) with J = sum_d m_d sum_c lambda_dc.

ConditionalExpectation.pimsner_popa_constant

def pimsner_popa_constant(self, complete: bool=False) -> torch.Tensor
Pimsner-Popa constant lambda(E) = max{l : E(y) >= l y for all y >= 0} = 1 / max_d sum_c (nu_c / w_d)
min(k_c, lambda_dc) (see the module docstring); with complete=True the best constant of the complete
order, max{l : E - l id completely positive} = 1 / ||Ind E|| = 1 / max_d sum_c lambda_dc nu_c / w_d.
Shape (batch,) (batch of the weights). Neither depends on the unitary u.

BratteliDiagram

class BratteliDiagram(levels: List[List[int]], matrices: List[np.ndarray], charges: Optional[List[list]]=None)
Bratteli diagram of a tower A_0 c A_1 c ... c A_n: `levels[l]` are the block sizes of A_l, `charges[l]`
their labels, `matrices[l]` the inclusion matrix of A_l c A_{l+1} (shape (len(levels[l+1]), len(levels[l]))).

BratteliDiagram.edges

def edges(self, level: int) -> List[Tuple[int, int, int]]
Edges between level and level + 1 as (c, d, multiplicity), c in A_level, d in A_{level+1}.

BratteliDiagram.draw

def draw(self, ax=None, show_sizes: bool=True)
Draw the diagram with matplotlib (imported on use): level l on the line y = -l, multiplicities
larger than 1 as edge labels. Returns the axes.

bratteli_diagram

def bratteli_diagram(tower: Union[Inclusion, Sequence[Inclusion]]) -> BratteliDiagram
Bratteli diagram of a tower of inclusions [A_0 c A_1, A_1 c A_2, ...] (consecutive algebras must agree).

from_tensor_product

def from_tensor_product(N: TypeIAlgebra, second: Union[int, TypeIAlgebra]) -> Inclusion
N (x) 1 c N (x) P with P = M_m (second = m) or any TypeIAlgebra P: M = composite.tensor_product(N, P)
(sector (c, e) at c * P.C + e), inclusion matrix lambda_{(c,e), c'} = delta_cc' p_e; iota(x) = kron(x, 1),
and composite.partial_trace / composite.partial_trace_channel apply to M.

diagonal

def diagonal(n: int, complex_valued: bool=True, precision: str='double', device=None) -> Inclusion
The diagonal masa C^n c M_n (Lambda = (1, ..., 1), shape (1, n)); index n.

scalars

def scalars(M: TypeIAlgebra) -> Inclusion
C 1 c M (Lambda = (m_d)_d, one column); index sum_d m_d^2 for the Markov trace (connected).

centre

def centre(M: TypeIAlgebra) -> Inclusion
The centre Z(M) = C^D c M (one sector of size 1 per non-empty sector of M, Lambda = diag(m_d)).
Disconnected for more than one sector: the components are C c M_{m_d}.

chain_algebra

def chain_algebra(L: int, complex_valued: bool=True, precision: str='double', device=None) -> TypeIAlgebra
A_L = (+)_{N=0}^{L} M_{binom(L, N)}: operators on L spins 1/2 commuting with total S^z (charges N); the
structure of chains.SpinChain(L).algebra (A_0 = C).

chain_tower

def chain_tower(L_max: int, L_min: int=0, complex_valued: bool=True, precision: str='double', device=None) -> List[Inclusion]
The U(1) spin-chain tower [A_{L_min} c A_{L_min+1}, ..., A_{L_max-1} c A_{L_max}], A_L c A_{L+1} by
x -> x (x) 1_2 (a new site L): Lambda[N, N] = 1 (new spin down, N <= L), Lambda[N, N-1] = 1 (new spin up),
with the basis order of chains.SpinChain (states with the new spin up first). Consecutive inclusions share
their algebras; [A_{L+1} : A_L] = 4 cos^2(pi / (2L + 4)).