Source code for qhronology.mechanics.quantities

# Project: Qhronology (https://github.com/lgbishop/qhronology)
# Author: lgbishop <lachlanbishop@protonmail.com>
# Copyright: Lachlan G. Bishop 2025
# License: AGPLv3 (non-commercial use), proprietary (commercial use)
# For more details, see the README in the project repository:
# https://github.com/lgbishop/qhronology,
# or visit the website:
# https://qhronology.com.

"""
Functions and a mixin for calculating quantum quantities.
"""

# https://peps.python.org/pep-0649/
# https://peps.python.org/pep-0749/
from __future__ import annotations

import sympy as sp
from sympy.physics.quantum.dagger import Dagger

from qhronology.utilities.classification import mat, num, sym
from qhronology.utilities.helpers import (
    count_systems,
    extract_matrix,
    extract_symbols,
    symbolize_expression,
    symbolize_tuples,
    extract_conditions,
    recursively_simplify,
    apply_function,
)

from qhronology.mechanics.operations import densify, partial_trace


[docs] def trace(matrix: mat | QuantumObject) -> num | sym: """Calculate the (complete) trace :math:`\\trace[\\op{\\rho}]` of ``matrix`` (:math:`\\op{\\rho}`). Arguments --------- matrix : mat | QuantumObject The input matrix. Returns ------- num | sym The trace of the input ``matrix``. Examples -------- >>> matrix = sp.Matrix([["a", "b"], ["c", "d"]]) >>> trace(matrix) a + d >>> matrix = sp.MatrixSymbol("U", 3, 3).as_mutable() >>> trace(matrix) U[0, 0] + U[1, 1] + U[2, 2] """ conditions = extract_conditions(matrix) symbols = extract_symbols(matrix) matrix = densify(extract_matrix(matrix)) matrix = symbolize_expression(matrix, symbols) conditions = symbolize_tuples(conditions, symbols) trace = sp.trace(matrix) trace = recursively_simplify(trace, conditions) return trace
[docs] def purity(state: mat | QuantumObject) -> num | sym: """Calculate the purity (:math:`\\Purity`) of ``state`` (:math:`\\op{\\rho}`): .. math:: \\Purity(\\op{\\rho}) = \\trace[\\op{\\rho}^2]. Arguments --------- state : mat | QuantumObject The matrix representation of the input state. Returns ------- num | sym The purity of the input ``state``. Examples -------- >>> matrix = sp.Matrix([["a", "b"], ["c", "d"]]) >>> purity(matrix) a**2 + 2*b*c + d**2 >>> matrix = sp.Matrix( ... [["a*conjugate(a)", "a*conjugate(b)"], ["b*conjugate(a)", "b*conjugate(b)"]] ... ) >>> purity(matrix) (a*conjugate(a) + b*conjugate(b))**2 """ conditions = extract_conditions(state) symbols = extract_symbols(state) matrix = densify(extract_matrix(state)) matrix = symbolize_expression(matrix, symbols) conditions = symbolize_tuples(conditions, symbols) purity = sp.trace(matrix**2) purity = recursively_simplify(purity, conditions) return purity
[docs] def distance(state_A: mat | QuantumObject, state_B: mat | QuantumObject) -> num | sym: """Calculate the trace distance (:math:`\\TraceDistance`) between two states ``state_A`` (:math:`\\op{\\rho}`) and ``state_B`` (:math:`\\op{\\tau}`): .. math:: \\TraceDistance(\\op{\\rho}, \\op{\\tau}) = \\frac{1}{2}\\trace{\\abs{\\op{\\rho} - \\op{\\tau}}}. Arguments --------- state_A : mat | QuantumObject The matrix representation of the first input state. state_B : mat | QuantumObject The matrix representation of the second input state. Returns ------- num | sym The trace distance between the inputs ``state_A`` and ``state_B``. Examples -------- >>> matrix_A = sp.Matrix([["p", 0], [0, "1 - p"]]) >>> matrix_B = sp.Matrix([["q", 0], [0, "1 - q"]]) >>> distance(matrix_A, matrix_B) sqrt((p - q)*(conjugate(p) - conjugate(q))) >>> matrix_A = sp.Matrix([["1/sqrt(2)"], ["1/sqrt(2)"]]) >>> matrix_B = sp.Matrix([["1/sqrt(2)"], ["-1/sqrt(2)"]]) >>> distance(matrix_A, matrix_B) 1 >>> matrix = sp.Matrix([["a", "b"], ["c", "d"]]) >>> distance(matrix, matrix) 0 """ conditions = extract_conditions(state_A, state_B) symbols = extract_symbols(state_A, state_B) matrix_A = densify(extract_matrix(state_A)) matrix_B = densify(extract_matrix(state_B)) matrix_A = symbolize_expression(matrix_A, symbols) matrix_B = symbolize_expression(matrix_B, symbols) conditions = symbolize_tuples(conditions, symbols) matrix_A = recursively_simplify(matrix_A, conditions) matrix_B = recursively_simplify(matrix_B, conditions) product = recursively_simplify( Dagger(matrix_A - matrix_B) * (matrix_A - matrix_B), conditions ) root = recursively_simplify(sp.sqrt(product), conditions) trace = recursively_simplify(sp.trace(root) / 2, conditions) distance = trace distance = recursively_simplify(trace, conditions) return distance
[docs] def fidelity(state_A: mat | QuantumObject, state_B: mat | QuantumObject) -> num | sym: """Calculate the fidelity (:math:`\\Fidelity`) between two states ``state_A`` (:math:`\\op{\\rho}`) and ``state_B`` (:math:`\\op{\\tau}`): .. math:: \\Fidelity(\\op{\\rho}, \\op{\\tau}) = \\left(\\trace{\\sqrt{\\sqrt{\\op{\\rho}}\\,\\op{\\tau}\\sqrt{\\op{\\rho}}}}\\right)^2. Arguments --------- state_A : mat | QuantumObject The matrix representation of the first input state. state_B : mat | QuantumObject The matrix representation of the second input state. Returns ------- num | sym The fidelity between the inputs ``state_A`` and ``state_B``. Examples -------- >>> matrix_A = sp.Matrix([["a"], ["b"]]) >>> matrix_B = sp.Matrix([["c"], ["d"]]) >>> fidelity(matrix_A, matrix_A) (a*conjugate(a) + b*conjugate(b))**2 >>> fidelity(matrix_B, matrix_B) (c*conjugate(c) + d*conjugate(d))**2 >>> fidelity(matrix_A, matrix_B) (a*conjugate(c) + b*conjugate(d))*(c*conjugate(a) + d*conjugate(b)) >>> matrix_A = sp.Matrix([["p", 0], [0, "1 - p"]]) >>> matrix_B = sp.Matrix([["q", 0], [0, "1 - q"]]) >>> fidelity(matrix_A, matrix_B) (sqrt(p*q) + sqrt((1 - p)*(1 - q)))**2 >>> matrix_A = sp.Matrix([["1/sqrt(2)"], ["1/sqrt(2)"]]) >>> matrix_B = sp.Matrix([["1/sqrt(2)"], ["-1/sqrt(2)"]]) >>> fidelity(matrix_A, matrix_B) 0 """ conditions = extract_conditions(state_A, state_B) symbols = extract_symbols(state_A, state_B) matrix_A = densify(extract_matrix(state_A)) matrix_B = densify(extract_matrix(state_B)) matrix_A = symbolize_expression(matrix_A, symbols) matrix_B = symbolize_expression(matrix_B, symbols) conditions = symbolize_tuples(conditions, symbols) matrix_A = recursively_simplify(matrix_A, conditions) matrix_B = recursively_simplify(matrix_B, conditions) product = recursively_simplify(matrix_A * matrix_B, conditions) root = recursively_simplify(sp.sqrt(product), conditions) trace = recursively_simplify(sp.trace(root), conditions) square = recursively_simplify(trace**2, conditions) fidelity = square fidelity = recursively_simplify(fidelity, conditions) return fidelity
[docs] def entropy( state_A: mat | QuantumObject, state_B: mat | QuantumObject | None = None, base: num | None = None, ) -> num | sym: """Calculate the relative von Neumann entropy (:math:`\\Entropy`) between two states ``state_A`` (:math:`\\op{\\rho}`) and ``state_B`` (:math:`\\op{\\tau}`): .. math:: \\Entropy(\\op{\\rho} \\Vert \\op{\\tau}) = \\trace\\bigl[\\op{\\rho} (\\log_\\Base\\op{\\rho} - \\log_\\Base\\op{\\tau})\\bigr]. If ``state_B`` is not specified (i.e., ``None``), calculate the ordinary von Neumann entropy of ``state_A`` (:math:`\\op{\\rho}`) instead: .. math:: \\Entropy(\\op{\\rho}) = \\trace[\\op{\\rho}\\log_\\Base\\op{\\rho}]. Here, :math:`\\Base` represents ``base``, which is the dimensionality of the unit of information with which the entropy is measured. Arguments --------- state_A : mat | QuantumObject The matrix representation of the first input state. state_B : mat | QuantumObject The matrix representation of the second input state. base : num The dimensionality of the unit of information with which the entropy is measured. Defaults to ``2``. Returns ------- num | sym The von Neumann entropy of the input ``state_A`` (if ``state_B`` is ``None``) or the relative entropy between ``state_A`` and ``state_B`` (if ``state_B`` is not ``None``). Examples -------- >>> matrix = sp.Matrix([["a"], ["b"]]) >>> entropy(matrix, base='d') -(a*conjugate(a) + b*conjugate(b))**2*log(a*conjugate(a) + b*conjugate(b))/(b*log(d)*conjugate(b)) >>> matrix_A = sp.Matrix([["p", 0], [0, "1 - p"]]) >>> matrix_B = sp.Matrix([["q", 0], [0, "1 - q"]]) >>> entropy(matrix_A, base="d") (-p*log(p) + (p - 1)*log(1 - p))/log(d) >>> entropy(matrix_B, base="d") (-q*log(q) + (q - 1)*log(1 - q))/log(d) >>> entropy(matrix_A, matrix_B, base="d") (p*(log(p) - log(q)) - (p - 1)*(log(1 - p) - log(1 - q)))/log(d) >>> matrix_A = sp.Matrix([["1/sqrt(2)"], ["1/sqrt(2)"]]) >>> matrix_B = sp.eye(2) / 2 >>> entropy(matrix_A) 0 >>> entropy(matrix_B) 1 >>> entropy(matrix_A, matrix_B) 1 >>> entropy(matrix_B, matrix_A) -1 """ conditions = extract_conditions(state_A) symbols = extract_symbols(state_A) matrix_A = densify(extract_matrix(state_A)) base = 2 if base is None else base base = symbolize_expression(base, symbols) matrix_A = symbolize_expression(matrix_A, symbols) conditions = symbolize_tuples(conditions, symbols) matrix_A = recursively_simplify(matrix_A, conditions) if state_B is not None: symbols |= extract_symbols(state_B) conditions += symbolize_tuples(extract_conditions(state_B), symbols) matrix_B = densify(extract_matrix(state_B)) matrix_B = symbolize_expression(matrix_B, symbols) matrix_A = recursively_simplify(matrix_A, conditions) matrix_B = recursively_simplify(matrix_B, conditions) # Relative entropy entropy = sp.trace( matrix_A * ( apply_function(matrix_A, sp.log, arguments=[base]) - apply_function(matrix_B, sp.log, arguments=[base]) ) ) else: # von Neumann entropy. entropy = -sp.trace( matrix_A * apply_function(matrix_A, sp.log, arguments=[base]) ) entropy = recursively_simplify(entropy, conditions) return entropy
[docs] def mutual( state: mat | QuantumObject, systems_A: int | list[int] | None = None, systems_B: int | list[int] | None = None, dim: int | None = None, ) -> num | sym: """Calculate the mutual information (:math:`\\MutualInformation`) between two subsystems ``systems_A`` (:math:`A`) and ``systems_B`` (:math:`B`) of a composite quantum system represented by ``state`` (:math:`\\rho^{A,B}`): .. math:: \\MutualInformation(A : B) = \\Entropy(\\op{\\rho}^A) + \\Entropy(\\op{\\rho}^B) - \\Entropy(\\op{\\rho}^{A,B}) where :math:`\\Entropy(\\op{\\rho})` is the von Neumann entropy of a state :math:`\\op{\\rho}`. Arguments --------- state : mat | QuantumObject The matrix representation of the composite input state. systems_A : int | list[int] The indices of the first subsystem. Defaults to ``[0]``. systems_B : int | list[int] The indices of the second subsystem. Defaults to the complement of ``systems_A`` with respect to the entire composition of subsystems of ``state``. dim : int The dimensionality of the composite quantum system (and its subsystems). Must be a non-negative integer. Defaults to ``2``. Returns ------- num | sym The mutual information between the subsystems ``systems_A`` and ``systems_B`` of the composite input ``state``. Examples -------- >>> matrix = sp.Matrix([1, 0, 0, 1]) / sp.sqrt(2) >>> mutual(matrix, [0], [1]) 2 >>> matrix = sp.eye(4) / 4 >>> mutual(matrix, [0], [1]) 0 """ systems_A = [0] if systems_A is None else systems_A dim = 2 if dim is None else dim conditions = extract_conditions(state) symbols = extract_symbols(state) matrix_AB = densify(extract_matrix(state)) matrix_AB = symbolize_expression(matrix_AB, symbols) conditions = symbolize_tuples(conditions, symbols) matrix_AB = recursively_simplify(matrix_AB, conditions) num_systems = count_systems(matrix_AB, dim) systems_AB = [k for k in range(0, num_systems)] matrix_A = partial_trace( matrix=matrix_AB, targets=systems_A, discard=True, dim=dim, optimize=True ) systems_B = ( list(set(systems_AB) ^ set(systems_A)) if systems_B is None else systems_B ) matrix_B = partial_trace( matrix=matrix_AB, targets=systems_B, discard=True, dim=dim, optimize=True ) mutual = entropy(matrix_A) + entropy(matrix_B) - entropy(matrix_AB) mutual = recursively_simplify(mutual, conditions) return mutual
[docs] class QuantitiesMixin: """A mixin for endowing classes with the ability to calculate various quantum quantities. Any inheriting class must possess a matrix representation that can be accessed by either an ``output()`` method or a ``matrix`` property. Note ---- The :py:class:`~qhronology.mechanics.quantities.QuantitiesMixin` mixin is used exclusively by the :py:class:`~qhronology.quantum.states.QuantumState` class---please see the corresponding section (:ref:`sec:docs_states_quantities`) for documentation on its methods. """ def trace(self) -> num | sym: """Calculate the (complete) trace :math:`\\trace[\\op{\\rho}]` of the internal state (:math:`\\op{\\rho}`). Returns ------- num | sym The trace of the internal state. Examples -------- >>> state = QuantumState( ... spec=[("a", [0]), ("b", [1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state.trace() 1 >>> state = QuantumState( ... spec=[(1, [0]), (1, [1])], ... kind="mixed", ... symbols={"d": {"real": True}}, ... norm="1/d", ... ) >>> state.trace() 1/d """ return trace(matrix=self) def purity(self) -> num | sym: """Calculate the purity (:math:`\\Purity`) of the internal state (:math:`\\op{\\rho}`): .. math:: \\Purity(\\op{\\rho}) = \\trace[\\op{\\rho}^2]. Returns ------- num | sym The purity of the internal state. Examples -------- >>> state = QuantumState( ... spec=[("a", [0]), ("b", [1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state.purity() 1 >>> state = QuantumState(spec=[("p", [0]), ("1 - p", [1])], kind="mixed", norm=1) >>> state.purity() p**2 + (1 - p)**2 """ return purity(state=self) def distance(self, state: mat | QuantumObject) -> num | sym: """Calculate the trace distance (:math:`\\TraceDistance`) between the internal state (:math:`\\op{\\rho}`) and the given ``state`` (:math:`\\op{\\tau}`): .. math:: \\TraceDistance(\\op{\\rho}, \\op{\\tau}) = \\frac{1}{2}\\trace{\\abs{\\op{\\rho} - \\op{\\tau}}}. Arguments --------- state : mat | QuantumObject The given state. Returns ------- num | sym The trace distance between the internal state and ``state``. Examples -------- >>> state_A = QuantumState( ... spec=[("a", [0]), ("b", [1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("c", [0]), ("d", [1])], ... form="vector", ... symbols={"c": {"complex": True}, "d": {"complex": True}}, ... conditions=[("c*conjugate(c) + d*conjugate(d)", 1)], ... norm=1, ... ) >>> state_A.distance(state_A) 0 >>> state_B.distance(state_B) 0 >>> state_A.distance(state_B) sqrt((a*conjugate(b) - c*conjugate(d))*(b*conjugate(a) - d*conjugate(c)) + (b*conjugate(b) - d*conjugate(d))**2)/2 + sqrt((a*conjugate(a) - c*conjugate(c))**2 + (a*conjugate(b) - c*conjugate(d))*(b*conjugate(a) - d*conjugate(c)))/2 >>> state_A = QuantumState( ... spec=[("p", [0]), ("1 - p", [1])], ... kind="mixed", ... symbols={"p": {"positive": True}}, ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("q", [0]), ("1 - q", [1])], ... kind="mixed", ... symbols={"q": {"positive": True}}, ... norm=1, ... ) >>> state_A.distance(state_B) Abs(p - q) >>> plus_state = QuantumState(spec=[(1, [0]), (1, [1])], form="vector", norm=1) >>> minus_state = QuantumState(spec=[(1, [0]), (-1, [1])], form="vector", norm=1) >>> plus_state.distance(minus_state) 1 """ return distance(state_A=self, state_B=state) def fidelity(self, state: mat | QuantumObject) -> num | sym: """Calculate the fidelity (:math:`\\Fidelity`) between the internal state (:math:`\\op{\\rho}`) and the given ``state`` (:math:`\\op{\\tau}`): .. math:: \\Fidelity(\\op{\\rho}, \\op{\\tau}) = \\left(\\trace{\\sqrt{\\sqrt{\\op{\\rho}}\\,\\op{\\tau}\\sqrt{\\op{\\rho}}}}\\right)^2. Arguments --------- state : mat | QuantumObject The given state. Returns ------- num | sym The fidelity between the internal state and ``state``. Examples -------- >>> state_A = QuantumState( ... spec=[("a", [0]), ("b", [1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("c", [0]), ("d", [1])], ... form="vector", ... symbols={"c": {"complex": True}, "d": {"complex": True}}, ... conditions=[("c*conjugate(c) + d*conjugate(d)", 1)], ... norm=1, ... ) >>> state_A.fidelity(state_A) 1 >>> state_B.fidelity(state_B) 1 >>> state_A.fidelity(state_B) (a*conjugate(c) + b*conjugate(d))*(c*conjugate(a) + d*conjugate(b)) >>> state_A = QuantumState( ... spec=[("p", [0]), ("1 - p", [1])], ... kind="mixed", ... symbols={"p": {"positive": True}}, ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("q", [0]), ("1 - q", [1])], ... kind="mixed", ... symbols={"q": {"positive": True}}, ... norm=1, ... ) >>> state_A.fidelity(state_B) (sqrt(p)*sqrt(q) + sqrt((1 - p)*(1 - q)))**2 >>> plus_state = QuantumState(spec=[(1, [0]), (1, [1])], form="vector", norm=1) >>> minus_state = QuantumState(spec=[(1, [0]), (-1, [1])], form="vector", norm=1) >>> plus_state.fidelity(minus_state) 0 """ return fidelity(state_A=self, state_B=state) def entropy( self, state: mat | QuantumObject = None, base: num | None = None ) -> num | sym: """Calculate the relative von Neumann entropy (:math:`\\Entropy`) between the internal state (:math:`\\op{\\rho}`) and the given ``state`` (:math:`\\op{\\tau}`): .. math:: \\Entropy(\\op{\\rho} \\Vert \\op{\\tau}) = \\trace\\bigl[\\op{\\rho} (\\log_\\Base\\op{\\rho} - \\log_\\Base\\op{\\tau})\\bigr]. If ``state`` is not specified (i.e., ``None``), calculate the ordinary von Neumann entropy of the internal state (:math:`\\op{\\rho}`) instead: .. math:: \\Entropy(\\op{\\rho}) = \\trace[\\op{\\rho}\\log_\\Base\\op{\\rho}]. Here, :math:`\\Base` represents ``base``, which is the dimensionality of the unit of information with which the entropy is measured. Arguments --------- state : mat | QuantumObject The given state. base : num The dimensionality of the unit of information with which the entropy is measured. Defaults to ``2``. Returns ------- num | sym The (relative) von Neumann entropy. Examples -------- >>> state_A = QuantumState( ... spec=[("a", [0]), ("b", [1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("c", [0]), ("d", [1])], ... form="vector", ... symbols={"c": {"complex": True}, "d": {"complex": True}}, ... conditions=[("c*conjugate(c) + d*conjugate(d)", 1)], ... norm=1, ... ) >>> state_A.entropy() 0 >>> state_B.entropy() 0 >>> state_A.entropy(state_B) 0 >>> state_A = QuantumState( ... spec=[("p", [0]), ("1 - p", [1])], ... kind="mixed", ... symbols={"p": {"positive": True}}, ... norm=1, ... ) >>> state_B = QuantumState( ... spec=[("q", [0]), ("1 - q", [1])], ... kind="mixed", ... symbols={"q": {"positive": True}}, ... norm=1, ... ) >>> state_A.entropy() (-p*log(p) + (p - 1)*log(1 - p))/log(2) >>> state_B.entropy() (-q*log(q) + (q - 1)*log(1 - q))/log(2) >>> state_A.entropy(state_B) (-(p - 1)*(log(1 - p) - log(1 - q)) + log((p/q)**p))/log(2) """ return entropy(state_A=self, state_B=state, base=base) def mutual( self, systems_A: int | list[int], systems_B: int | list[int] | None = None ) -> num | sym: """Calculate the mutual information (:math:`\\MutualInformation`) between two subsystems ``systems_A`` (:math:`A`) and ``systems_B`` (:math:`B`) of the internal state (:math:`\\rho^{A,B}`): .. math:: \\MutualInformation(A : B) = \\Entropy(\\op{\\rho}^A) + \\Entropy(\\op{\\rho}^B) - \\Entropy(\\op{\\rho}^{A,B}) where :math:`\\Entropy(\\op{\\rho})` is the von Neumann entropy of a state :math:`\\op{\\rho}`. Arguments --------- systems_A : int | list[int] The indices of the first subsystem. Defaults to ``[0]``. systems_B : int | list[int] The indices of the second subsystem. Defaults to the complement of ``systems_A`` with respect to the entire composition of subsystems of ``state``. dim : int The dimensionality of the composite quantum system (and its subsystems). Must be a non-negative integer. Defaults to ``2``. Returns ------- num | sym The mutual information between the subsystems ``systems_A`` and ``systems_B`` of the internal state. Examples -------- >>> state_AB = QuantumState( ... spec=[("a", [0, 0]), ("b", [1, 1])], ... form="vector", ... symbols={"a": {"complex": True}, "b": {"complex": True}}, ... conditions=[("a*conjugate(a) + b*conjugate(b)", 1)], ... norm=1, ... ) >>> state_AB.mutual([0], [1]) 2*(-a*log(a*conjugate(a))*conjugate(a) - b*log(b*conjugate(b))*conjugate(b))/log(2) >>> state_AB = QuantumState( ... spec=[("a", [0, 0]), ("b", [1, 1])], ... kind="mixed", ... symbols={"a": {"positive": True}, "b": {"positive": True}}, ... conditions=[("a + b", 1)], ... norm=1, ... ) >>> state_AB.mutual([0], [1]) (-a*log(a) - b*log(b))/log(2) """ return mutual( state=self, systems_A=systems_A, systems_B=systems_B, dim=self.dim )