# src/graphcalc/quantum/invariants.py
from __future__ import annotations
from math import log2
from typing import Iterable
import numpy as np
from graphcalc.metadata import invariant_metadata
from graphcalc.quantum.states import QuantumState
__all__ = [
"purity",
"rank",
"linear_entropy",
"von_neumann_entropy",
"negativity",
"logarithmic_negativity",
"fidelity",
"entanglement_entropy",
"mutual_information",
]
[docs]
@invariant_metadata(
display_name="Purity",
notation=r"\operatorname{Tr}(\rho^2)",
category="quantum state invariants",
aliases=("state purity",),
definition=(
"The purity of a quantum state ρ is Tr(ρ^2)."
),
)
def purity(state: QuantumState) -> float:
"""
Return the purity ``Tr(rho^2)`` of a quantum state.
"""
return state.purity()
[docs]
@invariant_metadata(
display_name="Rank",
notation=r"\operatorname{rank}(\rho)",
category="quantum state invariants",
aliases=("state rank",),
definition=(
"The rank of a quantum state ρ is the rank of its density operator."
),
)
def rank(state: QuantumState) -> int:
"""
Return the rank of the density operator.
"""
return state.rank
[docs]
@invariant_metadata(
display_name="Linear entropy",
notation=r"1 - \operatorname{Tr}(\rho^2)",
category="quantum state invariants",
aliases=("state linear entropy",),
definition=(
"The linear entropy of a quantum state ρ is 1 - Tr(ρ^2)."
),
)
def linear_entropy(state: QuantumState) -> float:
"""
Return the linear entropy ``1 - Tr(rho^2)``.
"""
return float(1.0 - purity(state))
[docs]
@invariant_metadata(
display_name="von Neumann entropy",
notation=r"S(\rho)",
category="quantum state invariants",
aliases=("state entropy",),
definition=(
"The von Neumann entropy of a quantum state ρ is S(ρ) = -Tr(ρ log ρ)."
),
)
def von_neumann_entropy(state: QuantumState, *, base: float = 2.0) -> float:
"""
Return the von Neumann entropy of a quantum state.
Parameters
----------
state : QuantumState
Input quantum state.
base : float, default=2.0
Logarithm base. Must be positive and not equal to 1.
Notes
-----
The entropy is computed from the eigenvalues of the density operator:
``S(rho) = -Tr(rho log rho)``.
"""
if base <= 0.0 or base == 1.0:
raise ValueError("base must be positive and not equal to 1.")
evals = state.eigenvalues()
positive = evals[evals > state.tol]
if positive.size == 0:
return 0.0
if base == 2.0:
logs = np.log2(positive)
else:
logs = np.log(positive) / np.log(base)
return float(-np.sum(positive * logs))
[docs]
@invariant_metadata(
display_name="Negativity",
notation=r"N(\rho)",
category="quantum state invariants",
aliases=("entanglement negativity",),
definition=(
"The negativity of a quantum state ρ with respect to a chosen partial transpose is N(ρ) = (||ρ^Γ||_1 - 1)/2, equivalently the sum of absolute values of the negative eigenvalues of ρ^Γ."
),
)
def negativity(state: QuantumState, *, subsystems: Iterable[int]) -> float:
"""
Return the entanglement negativity with respect to a partial transpose.
Parameters
----------
state : QuantumState
Input quantum state.
subsystems : iterable of int
Indices of subsystems on which to take the partial transpose.
Notes
-----
If ``rho^Gamma`` denotes the partial transpose, then the negativity is
``N(rho) = (||rho^Gamma||_1 - 1) / 2``,
equivalently the sum of absolute values of the negative eigenvalues of
``rho^Gamma``.
"""
pt = state.partial_transpose(subsystems)
evals = np.linalg.eigvalsh(0.5 * (pt.rho + pt.rho.conj().T))
neg = -evals[evals < -state.tol].sum()
return float(neg)
[docs]
@invariant_metadata(
display_name="Logarithmic negativity",
notation=r"E_N(\rho)",
category="quantum state invariants",
aliases=("state logarithmic negativity",),
definition=(
"The logarithmic negativity of a quantum state ρ is E_N(ρ) = log_2 ||ρ^Γ||_1 = log_2(2N(ρ)+1)."
),
)
def logarithmic_negativity(state: QuantumState, *, subsystems: Iterable[int]) -> float:
"""
Return the logarithmic negativity of a quantum state.
Parameters
----------
state : QuantumState
Input quantum state.
subsystems : iterable of int
Indices of subsystems on which to take the partial transpose.
Notes
-----
The logarithmic negativity is
``E_N(rho) = log_2 ||rho^Gamma||_1 = log_2(2 N(rho) + 1)``.
"""
n = negativity(state, subsystems=subsystems)
return float(log2(2.0 * n + 1.0))
[docs]
@invariant_metadata(
display_name="Fidelity",
notation=r"F(\rho,\sigma)",
category="quantum state invariants",
aliases=("Uhlmann fidelity",),
definition=(
"The fidelity between quantum states ρ and σ is F(ρ,σ) = (Tr(sqrt(sqrt(ρ) σ sqrt(ρ))))^2."
),
)
def fidelity(state1: QuantumState, state2: QuantumState) -> float:
r"""
Return the Uhlmann fidelity between two quantum states.
Parameters
----------
state1 : QuantumState
First input state.
state2 : QuantumState
Second input state.
Notes
-----
The fidelity is defined by
``F(rho, sigma) = (Tr(sqrt(sqrt(rho) sigma sqrt(rho))))^2``.
This function returns a value in ``[0, 1]`` up to numerical tolerance.
The input states must have the same total dimension.
"""
if state1.dimension != state2.dimension:
raise ValueError("States must have the same total dimension.")
rho = 0.5 * (state1.rho + state1.rho.conj().T)
sigma = 0.5 * (state2.rho + state2.rho.conj().T)
evals, evecs = np.linalg.eigh(rho)
evals[evals < 0.0] = 0.0
sqrt_rho = evecs @ np.diag(np.sqrt(evals)) @ evecs.conj().T
middle = sqrt_rho @ sigma @ sqrt_rho
middle = 0.5 * (middle + middle.conj().T)
vals = np.linalg.eigvalsh(middle)
vals[vals < 0.0] = 0.0
out = float(np.sum(np.sqrt(vals)) ** 2)
if out < 0.0 and abs(out) < max(state1.tol, state2.tol):
return 0.0
if out > 1.0 and abs(out - 1.0) < max(state1.tol, state2.tol):
return 1.0
return out
[docs]
@invariant_metadata(
display_name="Entanglement entropy",
notation=r"S_{\mathrm{ent}}(\rho)",
category="quantum state invariants",
aliases=("bipartite entanglement entropy",),
definition=(
"For a pure quantum state ρ and a bipartition, the entanglement entropy is the von Neumann entropy of either reduced state across that bipartition."
),
)
def entanglement_entropy(
state: QuantumState,
*,
subsystems: Iterable[int],
base: float = 2.0,
) -> float:
r"""
Return the entanglement entropy across a bipartition for a pure state.
Parameters
----------
state : QuantumState
Input quantum state.
subsystems : iterable of int
Subsystems on one side of the bipartition.
base : float, default=2.0
Logarithm base used in the entropy.
Notes
-----
For a pure state, the entanglement entropy across a bipartition
``A | A^c`` is the von Neumann entropy of the reduced state on either side.
This function is only defined here for pure states. For mixed states,
several inequivalent notions of entanglement measure exist, so this
function raises ``ValueError``.
"""
if not state.is_pure:
raise ValueError("entanglement_entropy is only defined here for pure states.")
reduced = state.reduced_state(subsystems)
return von_neumann_entropy(reduced, base=base)