# Project: Qhronology (https://github.com/lgbishop/qhronology)
# Author: lgbishop <lgbishop@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.org.
"""
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, expr
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 | expr:
"""Calculate the (complete) trace :math:`\\trace[\\op{\\rho}]` of :python:`matrix` (:math:`\\op{\\rho}`).
Arguments
---------
matrix : mat | QuantumObject
The input matrix.
Returns
-------
num | expr
The trace of the input :python:`matrix`.
"""
symbols = extract_symbols(matrix)
conditions = extract_conditions(matrix)
conditions = symbolize_tuples(conditions, symbols)
matrix = densify(extract_matrix(matrix))
matrix = symbolize_expression(matrix, symbols)
trace = sp.trace(matrix)
trace = recursively_simplify(trace, conditions)
return trace
[docs]
def purity(state: mat | QuantumObject) -> num | expr:
"""Calculate the purity (:math:`\\Purity`) of :python:`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 | expr
The purity of the input :python:`state`.
"""
symbols = extract_symbols(state)
conditions = extract_conditions(state)
conditions = symbolize_tuples(conditions, symbols)
matrix = densify(extract_matrix(state))
matrix = symbolize_expression(matrix, 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 | expr:
"""Calculate the trace distance (:math:`\\TraceDistance`) between two states :python:`state_A` (:math:`\\op{\\rho}`) and :python:`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 | expr
The trace distance between the inputs :python:`state_A` and :python:`state_B`.
"""
symbols = extract_symbols(state_A, state_B)
conditions = extract_conditions(state_A, state_B)
conditions = symbolize_tuples(conditions, symbols)
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)
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 | expr:
"""Calculate the fidelity (:math:`\\Fidelity`) between two states :python:`state_A` (:math:`\\op{\\rho}`) and :python:`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 | expr
The fidelity between the inputs :python:`state_A` and :python:`state_B`.
"""
symbols = extract_symbols(state_A, state_B)
conditions = extract_conditions(state_A, state_B)
conditions = symbolize_tuples(conditions, symbols)
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)
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 | expr | str | None = None,
) -> num | expr:
"""Calculate the relative von Neumann entropy (:math:`\\Entropy`) between two states :python:`state_A` (:math:`\\op{\\rho}`) and :python:`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 :python:`state_B` is not specified (i.e., :python:`None`), calculate the ordinary von Neumann entropy of :python:`state_A` (:math:`\\op{\\rho}`) instead:
.. math:: \\Entropy(\\op{\\rho}) = \\trace[\\op{\\rho}\\log_\\Base\\op{\\rho}].
Here, :math:`\\Base` represents :python:`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 | expr | str
The dimensionality of the unit of information with which the entropy is measured.
Defaults to :python:`2`.
Returns
-------
num | expr
The von Neumann entropy of the input :python:`state_A` (if :python:`state_B` is :python:`None`) or the relative entropy between :python:`state_A` and :python:`state_B` (if :python:`state_B` is not :python:`None`).
"""
symbols = extract_symbols(state_A)
conditions = extract_conditions(state_A)
conditions = symbolize_tuples(conditions, symbols)
base = 2 if base is None else base
base = symbolize_expression(base, symbols)
matrix_A = densify(extract_matrix(state_A))
matrix_A = symbolize_expression(matrix_A, 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,
base: num | expr | str | None = None,
) -> num | expr:
"""Calculate the mutual information (:math:`\\MutualInformation`) between two subsystems :python:`systems_A` (:math:`A`) and :python:`systems_B` (:math:`B`) of a composite quantum system represented by :python:`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 :python:`[0]`.
systems_B : int | list[int]
The indices of the second subsystem.
Defaults to the complement of :python:`systems_A` with respect to the entire composition of subsystems of :python:`state`.
dim : int
The dimensionality of the composite quantum system (and its subsystems).
Must be a non-negative integer.
Defaults to :python:`2`.
base : num | expr | str
The dimensionality of the unit of information with which the mutual information is measured.
Defaults to the value of :python:`dim`.
Returns
-------
num | expr
The mutual information between the subsystems :python:`systems_A` and :python:`systems_B` of the composite input :python:`state`.
"""
systems_A = [0] if systems_A is None else systems_A
dim = 2 if dim is None else dim
symbols = extract_symbols(state)
conditions = extract_conditions(state)
conditions = symbolize_tuples(conditions, symbols)
base = dim if base is None else base
base = symbolize_expression(base, symbols)
matrix_AB = densify(extract_matrix(state))
matrix_AB = symbolize_expression(matrix_AB, 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, base=base)
+ entropy(matrix_B, base=base)
- entropy(matrix_AB, base=base)
)
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 :python:`output()` method or a :python:`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 | expr:
"""Calculate the (complete) trace :math:`\\trace[\\op{\\rho}]` of the internal state (:math:`\\op{\\rho}`).
Returns
-------
num | expr
The trace of the internal state.
"""
return trace(matrix=self)
def purity(self) -> num | expr:
"""Calculate the purity (:math:`\\Purity`) of the internal state (:math:`\\op{\\rho}`):
.. math:: \\Purity(\\op{\\rho}) = \\trace[\\op{\\rho}^2].
Returns
-------
num | expr
The purity of the internal state.
"""
return purity(state=self)
def distance(self, state: mat | QuantumObject) -> num | expr:
"""Calculate the trace distance (:math:`\\TraceDistance`) between the internal state (:math:`\\op{\\rho}`) and the given :python:`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 | expr
The trace distance between the internal state and :python:`state`.
"""
return distance(state_A=self, state_B=state)
def fidelity(self, state: mat | QuantumObject) -> num | expr:
"""Calculate the fidelity (:math:`\\Fidelity`) between the internal state (:math:`\\op{\\rho}`) and the given :python:`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 | expr
The fidelity between the internal state and :python:`state`.
"""
return fidelity(state_A=self, state_B=state)
def entropy(
self, state: mat | QuantumObject = None, base: num | expr | str | None = None
) -> num | expr:
"""Calculate the relative von Neumann entropy (:math:`\\Entropy`) between the internal state (:math:`\\op{\\rho}`) and the given :python:`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 :python:`state` is not specified (i.e., :python:`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 :python:`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 | expr | str
The dimensionality of the unit of information with which the entropy is measured.
Defaults to :python:`2`.
Returns
-------
num | expr
The (relative) von Neumann entropy.
"""
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,
base: num | expr | str | None = None,
) -> num | expr:
"""Calculate the mutual information (:math:`\\MutualInformation`) between two subsystems :python:`systems_A` (:math:`A`) and :python:`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 :python:`[0]`.
systems_B : int | list[int]
The indices of the second subsystem.
Defaults to the complement of :python:`systems_A` with respect to the entire composition of the subsystems of :python:`state`.
base : num | expr | str
The dimensionality of the unit of information with which the mutual information is measured.
Defaults to the value of :python:`self.dim`.
Returns
-------
num | expr
The mutual information between the subsystems :python:`systems_A` and :python:`systems_B` of the internal state.
"""
return mutual(
state=self,
systems_A=systems_A,
systems_B=systems_B,
dim=self.dim,
base=base,
)