# 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.
"""
A class for the creation of quantum circuits containing closed timelike curves.
Classes and functions implementing quantum prescriptions of time travel.
"""
# https://peps.python.org/pep-0649/
# https://peps.python.org/pep-0749/
from __future__ import annotations
import copy
import numpy as np
import sympy as sp
from qhronology.mechanics.operations import columnify, densify, partial_trace
from qhronology.mechanics.quantities import entropy, trace
from qhronology.quantum.circuits import QuantumCircuit
from qhronology.quantum.gates import QuantumGate
from qhronology.quantum.states import QuantumState
from qhronology.utilities.classification import (
Forms,
Kinds,
arr,
expr,
mat,
matrix_form,
num,
sym,
)
from qhronology.utilities.helpers import (
adjust_targets,
assemble_composition,
cast,
conjugate_transpose,
count_dims,
count_systems,
dtype,
extract_substitutions,
extract_matrix,
flatten_list,
matrix_multiplication,
recursively_simplify,
)
[docs]
class QuantumCTC(QuantumCircuit):
"""A class for creating quantum circuit models of quantum interactions near closed timelike curves and storing their metadata.
This is built upon the :py:class:`~qhronology.quantum.circuits.QuantumCircuit` class, and so inherits all of its attributes, properties, and methods.
Instances provide complete descriptions of quantum circuits involving antichronological time travel. The class however does not possess any ability to compute the output state (e.g., resolve temporal paradoxes) of the circuit; that functionality is associated with the specific prescriptions of quantum time travel, which are implemented as subclasses.
Arguments
---------
*args
Variable length argument list, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.circuits.QuantumCircuit`.
circuit : QuantumCircuit
An instance of the :py:class:`~qhronology.quantum.circuits.QuantumCircuit` class.
The values of its attributes override any other values specified in :python:`*args` and :python:`**kwargs`.
Defaults to :python:`None`.
systems_respecting : int | list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
Defaults to :python:`[]`.
systems_violating : int | list[int]
The numerical indices of the chronology-violating (CV) subsystems.
Defaults to :python:`[]`.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.circuits.QuantumCircuit`.
Note
----
The :python:`circuit` argument can be used to merge the value of every attribute from a pre-existing :py:class:`~qhronology.quantum.circuits.QuantumCircuit` instance into the :py:class:`~qhronology.quantum.prescriptions.QuantumCTC` instance. Any such merges override the values of the attributes associated with the other arguments specified in the constructor. It is therefore best practice to specify the circuit via either of the following two ways:
- the :python:`circuit` argument
- :python:`*args` and :python:`**kwargs` like a typical initialization of a :py:class:`~qhronology.quantum.circuits.QuantumCircuit` instance (without using :python:`circuit`)
Note that this is in addition to specifying either of :python:`systems_respecting` or :python:`systems_violating`.
Note
----
The lists of indices specified in either of :python:`systems_respecting` or :python:`systems_violating` must be contiguous.
Additionally, the circuit's inputs (:python:`inputs`) are treated as one contiguous total state, with the indices of its subsystems exactly matching those specified in :python:`systems_respecting`.
Note
----
It is best practice to specify only one of either :python:`systems_violating` or :python:`systems_violating`, never both.
The properties associated with both of these constructor arguments automatically ensure that they are always complementary (with respect to the entire system space), and so only one needs to be specified.
Note
----
The total interaction between the CR and CV systems is expected to be unitary, and so the sequence of gates in :python:`gates` cannot contain any non-unitary gates (e.g., measurement operations).
Note
----
Post-processing (e.g., traces and postselections) cannot be performed on any chronology-violating (CV) systems (i.e., those corresponding to indices specified in :python:`systems_violating`).
"""
def __init__(
self,
*args,
circuit: QuantumCircuit | None = None,
systems_respecting: list[int] | None = None,
systems_violating: list[int] | None = None,
**kwargs,
):
super().__init__(*args, **kwargs)
if circuit is not None:
self.__dict__ = copy.deepcopy(circuit.__dict__)
for key, value in kwargs.items():
setattr(self, key, value)
if hasattr(self, "_systems_respecting") is False:
systems_respecting = (
[] if systems_respecting is None else systems_respecting
)
systems_violating = [] if systems_violating is None else systems_violating
if len(systems_respecting) == 0 and len(systems_violating) == 0:
raise ValueError(
"""Either :python:`systems_respecting` or :python:`systems_violating` must be set."""
)
if len(systems_respecting) != 0 and len(systems_violating) != 0:
if set(self.systems) != set(systems_respecting) | set(
systems_violating
):
raise ValueError(
"""The union of :python:`systems_respecting` and :python:`systems_violating` is inequivalent to the entire system's structure."""
)
self.systems_respecting = systems_respecting
def __repr__(self) -> str:
return repr(self.output_respecting())
def matrix(
self,
numerical: bool | None = None,
array: bool | None = None,
) -> mat | arr:
"""Compute the unprocessed matrix representation of the circuit's total output state (combined CR and CV systems).
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
Returns
-------
mat | arr
The unprocessed matrix representation of the circuit's total output state.
"""
return QuantumCircuit(
inputs=self.inputs,
gates=self.gates,
traces=self.traces,
postselections=self.postselections,
numerical=self.numerical,
array=self.array,
symbols=self.symbols,
substitutions=self.substitutions,
).matrix(numerical=numerical, array=array)
@property
def systems_respecting(self) -> list[int]:
"""The numerical indices of the chronology-respecting (CR) subsystems."""
return self._systems_respecting
@systems_respecting.setter
def systems_respecting(self, systems_respecting: list[int]):
self._systems_respecting = systems_respecting
self._systems_respecting = list(set(self._systems_respecting))
@property
def systems_violating(self) -> list[int]:
"""The numerical indices of the chronology-violating (CV) subsystems."""
return list(set(self.systems) ^ set(self.systems_respecting))
@systems_violating.setter
def systems_violating(self, systems_violating: list[int]):
systems_respecting = list(set(self.systems) ^ set(systems_violating))
self.systems_respecting = systems_respecting
@property
def input_is_vector(self) -> bool:
"""Whether all states in :python:`inputs` are vector states."""
is_vector = False
if all(state.is_vector for state in self.inputs):
is_vector = True
return is_vector
# The four methods below merely output the reduced states, so the :python:`systems_respective` and :python:`systems_violating` of the base class acts just like extra traces.
def output_violating(self) -> mat | arr:
return (
QuantumCircuit(
inputs=self.inputs,
gates=self.gates,
traces=self.traces,
postselections=self.postselections,
numerical=self.numerical,
array=self.array,
symbols=self.symbols,
substitutions=self.substitutions,
)
.state(traces=self.systems_respecting)
.output()
)
def output_respecting(self) -> mat | arr:
return (
QuantumCircuit(
inputs=self.inputs,
gates=self.gates,
traces=self.traces,
postselections=self.postselections,
numerical=self.numerical,
array=self.array,
symbols=self.symbols,
substitutions=self.substitutions,
)
.state(traces=self.systems_violating)
.output()
)
def state_violating(self) -> QuantumState:
return QuantumCircuit(
inputs=self.inputs,
gates=self.gates,
traces=self.traces,
postselections=self.postselections,
numerical=self.numerical,
array=self.array,
symbols=self.symbols,
substitutions=self.substitutions,
).state(traces=self.systems_respecting)
def state_respecting(self) -> QuantumState:
return QuantumCircuit(
inputs=self.inputs,
gates=self.gates,
traces=self.traces,
postselections=self.postselections,
numerical=self.numerical,
array=self.array,
symbols=self.symbols,
substitutions=self.substitutions,
).state(traces=self.systems_violating)
[docs]
def dctc_violating(
input_respecting: mat | arr | QuantumState,
gate: mat | arr | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> mat | arr:
"""Calculate the chronology-violating (CV) state according to the D-CTC prescription by computing fixed points of the map
.. math::
\\MapDCTCsCV_{\\Unitary}[\\StateCR,\\StateCV]
= \\trace_\\CR\\bigl[\\Unitary(\\StateCR \\otimes \\StateCV)\\Unitary^\\dagger\\bigr]
given the chronology-respecting (CR) input state :python:`input_respecting` (:math:`\\StateCR`) and (unitary) interaction described by :python:`gate` (:math:`\\Unitary`).
Arguments
---------
input_respecting : mat | arr | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | arr | QuantumGate
The matrix representation of the gate describing the (unitary) interaction between the CR and CV systems.
systems_respecting : list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
systems_violating : list[int]
The numerical indices of the chronology-violating (CV) subsystems.
free_symbol : sym | str
The representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to :python:`"g"`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CV state that possesses the most (von Neumann) entropy, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to :python:`False`.
Returns
-------
mat | arr
The fixed-point solution(s) of the D-CTC CV map.
Note
----
Please note that this function in its current form is considered to be *highly* experimental.
"""
free_symbol = "g" if free_symbol is None else free_symbol
maximum_entropy = False if maximum_entropy is None else maximum_entropy
if isinstance(input_respecting, mat | arr) is True:
matrix_num = True if issubclass(dtype(input_respecting), num) is True else False
matrix_arr = True if isinstance(input_respecting, arr) is True else False
else:
try:
matrix_num = (
True
if issubclass(dtype(input_respecting.output()), num) is True
else False
)
matrix_arr = (
True if isinstance(input_respecting.output(), arr) is True else False
)
except:
raise TypeError(
"""A matrix or array cannot be extracted from the given input state."""
)
systems_respecting = list(set(systems_respecting))
systems_violating = list(set(systems_violating))
try:
dim = input_respecting.dim
except:
dim = count_dims(
matrix=densify(extract_matrix(input_respecting)),
num_systems=len(systems_respecting),
)
substitutions_respecting = extract_substitutions(input_respecting)
input_respecting = densify(extract_matrix(input_respecting))
trace_respecting = input_respecting.trace()
gate = densify(extract_matrix(gate))
# Use :python:`Symbol` for persistent (structurally bound) variables.
# Use :python:`Dummy` for non-persistent (not structurally bound) variables.
# The latter should be used so as to not interfere with any user-predefined variables.
input_violating = sp.Matrix(
[
[sp.Dummy(f"τ_{i}{j}") for j in range(0, dim ** len(systems_violating))]
for i in range(0, dim ** len(systems_violating))
]
)
input_total = assemble_composition(
(input_respecting, systems_respecting), (input_violating, systems_violating)
)
output_total = matrix_multiplication(gate, input_total, conjugate_transpose(gate))
output_violating = partial_trace(
matrix=output_total, targets=systems_respecting, dim=dim
)
output_violating = recursively_simplify(output_violating, substitutions_respecting)
equations = []
unknowns_violating = [*input_violating]
unknowns = list(unknowns_violating)
for n in range(0, (dim ** len(systems_violating)) ** 2):
equations.append(sp.Eq(output_violating[n], input_violating[n], evaluate=False))
equations += [sp.Eq(trace(input_respecting), trace_respecting, evaluate=False)]
equations += [sp.Eq(trace(input_violating), trace_respecting, evaluate=False)]
# Designed to loop twice: once without the trace equation, then once with it.
counter = 0
while True:
solutions = sp.solve(equations, unknowns, set=True)
if solutions[1] == set() or solutions[1] == {tuple(0 for _ in unknowns)}:
solutions = sp.nonlinsolve(equations, unknowns)
if isinstance(solutions, sp.sets.sets.EmptySet) is True or solutions == {
tuple(0 for _ in unknowns) or tuple(0.0 for _ in unknowns)
}:
solutions = set()
solutions = (unknowns, solutions)
if len(solutions[1]) == 1:
values = list(solutions[1])[0]
if not (len(set(values)) == 1 and values[0] in [0, 0.0]):
break
elif len(solutions[1]) == 0:
# Remove final equation (initially a trace condition) and try again.
del equations[-1]
else:
raise NotImplementedError(
"""Support for multiple non-parametrized D-CTC CV solutions has not yet been implemented."""
)
counter += 1
if counter == 4:
raise NotImplementedError(
"""The D-CTC CV algorithm was unable to determine a solution (fixed point) to the CV map.
If you are certain that your circuit does indeed have a solution, you are welcome to file a bug report."""
)
solutions = list(solutions[1])[0]
solutions = {key: value for key, value in zip(unknowns, solutions)}
free_variables = []
# Check for all zeroes solution and, if found, replace it with the CR input state.
# This assumes that a zero solution corresponds to the CR input.
if any(value != 0 for value in solutions.values()) is False:
pairs = {
key: value
for key, value in zip(list(input_violating), list(input_respecting))
}
for key, value in solutions.items():
solutions[key] = pairs[key]
solutions = list(solutions.values())
else:
# Invert solution about τ_00 in the case of a single free parameter so that the final result is consistent with the analytical approach.
if len(solutions[input_violating[0]].free_symbols) == 1:
inverting_symbol = list(solutions[input_violating[0]].free_symbols)[0]
if inverting_symbol in solutions.keys():
inverting_equation = [
sp.Eq(
input_violating[0],
solutions[input_violating[0]],
evaluate=False,
)
]
inverting_solution = sp.nonlinsolve(
inverting_equation, [inverting_symbol]
)
inverting_solution = list(inverting_solution)[0]
inverting_solution = {
key: value
for key, value in zip([inverting_symbol], inverting_solution)
}
for key, value in solutions.items():
solutions[key] = sp.simplify(
value.subs(
inverting_symbol, inverting_solution[inverting_symbol]
)
)
unknowns = unknowns_violating
solutions = [
sp.simplify(solutions[unknown]) if unknown in solutions.keys() else unknown
for unknown in unknowns
]
free_symbols = list(
set().union(*[element.free_symbols for element in solutions])
)
unknowns_free = [symbol for symbol in free_symbols if symbol in unknowns]
if len(unknowns_free) == 1:
free_variables = [sp.Symbol(f"{free_symbol}")]
if len(unknowns_free) > 1:
free_variables = [
sp.Symbol(f"{free_symbol}_{i}") for i in range(0, len(unknowns_free))
]
if len(free_variables) > 0:
solutions = [
solution.subs(dict(zip(unknowns_free, free_variables)))
for solution in solutions
]
output_violating = sp.Matrix(
np.array(solutions).reshape(
dim ** len(systems_violating), dim ** len(systems_violating)
)
)
if maximum_entropy is True and len(free_variables) > 0:
S = entropy(output_violating, base=dim)
dS = [sp.diff(S, variable) for variable in free_variables]
ddS = [sp.diff(first, variable) for first in dS for variable in free_variables]
hessian_matrix = sp.Matrix(len(free_variables), len(free_variables), ddS)
stationary_points = sp.solve(dS, free_variables, set=False)
maxima = []
for point in stationary_points:
substitutions = [
(free_variables[n], point[n]) for n in range(0, len(point))
]
hessian_matrix_point = hessian_matrix.subs(substitutions)
eigenvalues = list(hessian_matrix_point.eigenvals().keys())
if all(
eigenvalue < 0 for eigenvalue in eigenvalues
): # Check for a local maximum.
maxima.append(point)
if len(maxima) == 0:
print("No maximally entropic D-CTC CV state found.")
if len(maxima) > 1:
print("More than one maximally entropic D-CTC CV state found.")
if len(maxima) == 1:
output_violating = output_violating.subs(
[
(free_variables[n], maxima[0][n])
for n in range(0, len(free_variables))
]
)
return cast(output_violating, numerical=matrix_num, array=matrix_arr)
[docs]
def dctc_respecting(
input_respecting: mat | arr | QuantumState,
input_violating: mat | arr | QuantumState,
gate: mat | arr | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat | arr:
"""Calculate the chronology-respecting (CR) state according to the D-CTC prescription's CR map
.. math::
\\MapDCTCsCR_{\\Unitary}[\\StateCR,\\StateCV]
= \\trace_\\CV\\bigl[\\Unitary(\\StateCR \\otimes \\StateCV)\\Unitary^\\dagger\\bigr]
given the chronology-respecting (CR) input state :python:`input_respecting` (:math:`\\StateCR`), chronology-violating (CV) solution state :python:`input_violating` (:math:`\\StateCV`), and (unitary) interaction described by :python:`gate` (:math:`\\Unitary`).
Arguments
---------
input_respecting : mat | arr | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
input_violating : mat | arr | QuantumState
The matrix representation of the chronology-violating (CR) solution state.
gate : mat | arr | QuantumGate
The matrix representation of the gate describing the (unitary) interaction between the CR and CV systems.
systems_respecting : list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
systems_violating : list[int]
The numerical indices of the chronology-violating (CV) subsystems.
Returns
-------
mat | arr
The solution(s) of the D-CTC CR map.
"""
if isinstance(input_respecting, mat | arr) is True:
matrix_num = True if issubclass(dtype(input_respecting), num) is True else False
matrix_arr = True if isinstance(input_respecting, arr) is True else False
else:
try:
matrix_num = (
True
if issubclass(dtype(input_respecting.output()), num) is True
else False
)
matrix_arr = (
True if isinstance(input_respecting.output(), arr) is True else False
)
except:
raise TypeError(
"""A matrix or array cannot be extracted from the given input state."""
)
systems_respecting = list(set(systems_respecting))
systems_violating = list(set(systems_violating))
try:
dim = input_respecting.dim
except:
dim = count_dims(
matrix=densify(extract_matrix(input_respecting)),
num_systems=len(systems_respecting),
)
input_respecting = densify(extract_matrix(input_respecting))
input_violating = densify(extract_matrix(input_violating))
input_total = assemble_composition(
(input_respecting, systems_respecting), (input_violating, systems_violating)
)
gate = densify(extract_matrix(gate))
output_total = matrix_multiplication(gate, input_total, conjugate_transpose(gate))
output_respecting = partial_trace(
matrix=output_total, targets=systems_violating, dim=dim
)
return cast(output_respecting, numerical=matrix_num, array=matrix_arr)
[docs]
class DCTC(QuantumCTC):
"""A subclass for creating closed timelike curves described by Deutsch's prescription (D-CTCs) of quantum time travel.
This is built upon the :py:class:`~qhronology.quantum.prescriptions.QuantumCTC` class, and so inherits all of its attributes, properties, and methods.
Arguments
---------
*args
Positional arguments, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
free_symbol : sym | str
The representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to :python:`"g"`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return solutions that correspond to the maximally entropic CV state, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to :python:`False`.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
"""
def __init__(
self,
*args,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
**kwargs,
):
super().__init__(*args, **kwargs)
free_symbol = "g" if free_symbol is None else free_symbol
maximum_entropy = False if maximum_entropy is None else maximum_entropy
self.free_symbol = free_symbol
self.maximum_entropy = maximum_entropy
@property
def free_symbol(self) -> sym | str:
"""The representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points."""
return self._free_symbol
@free_symbol.setter
def free_symbol(self, free_symbol: sym | str):
self._free_symbol = free_symbol
@property
def maximum_entropy(self) -> bool:
"""Whether to, in the case of solution multiplicity, return solutions that correspond to the maximally entropic CV state, in accordance with Deutsch's original prescription."""
return self._maximum_entropy
@maximum_entropy.setter
def maximum_entropy(self, maximum_entropy: bool):
self._maximum_entropy = maximum_entropy
@property
def input_is_vector(self) -> bool:
return False
# D-CTC prescription requires density matrix inputs.
@property
def output_is_vector(self) -> bool:
return False
# The CR and CV maps of a D-CTC are non-linear, non-unitary (mixing) operations in general.
[docs]
def matrix(
self,
numerical: bool | None = None,
array: bool | None = None,
) -> mat | arr:
"""Compute the unprocessed matrix representation of the D-CTC chronology-respecting (CR) output state.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
Returns
-------
mat | arr
The unprocessed matrix representation of the D-CTC CR output state.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
output_respecting = dctc_respecting(
input_respecting=self.input(substitutions=[]),
input_violating=self.state_violating(
free_symbol=self.free_symbol,
maximum_entropy=self.maximum_entropy,
),
gate=self.gate(substitutions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
return cast(output_respecting, numerical=numerical, array=array)
[docs]
def output_violating(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> mat | arr:
"""Compute the processed matrix representation of the D-CTC chronology-violating (CV) state.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
free_symbol : str
The string representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to the value of :python:`self.free_symbol`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CV state that possesses the most (von Neumann) entropy, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to the value of :python:`self.maximum_entropy`.
Returns
-------
mat | arr
The processed matrix representation of the D-CTC CV output state.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
free_symbol = self.free_symbol if free_symbol is None else free_symbol
maximum_entropy = (
self.maximum_entropy if maximum_entropy is None else maximum_entropy
)
output_violating = dctc_violating(
input_respecting=self.input(substitutions=[]),
gate=self.gate(substitutions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
free_symbol=free_symbol,
maximum_entropy=maximum_entropy,
)
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
output_violating = QuantumState(
spec=output_violating,
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
# Normalization
norm = False if norm is None else norm
norm = 1 if norm is True else norm
if norm is not False:
output_violating.normalize(norm)
# Simplification
simplify = False if simplify is None else simplify
if simplify is True:
output_violating.simplify()
# Conjugation
conjugate = False if conjugate is None else conjugate
if conjugate is True:
output_violating.dagger()
output_violating = QuantumState(
spec=output_violating.output(numerical=numerical, array=array_intermediate),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return output_violating.output(numerical=numerical, array=array)
[docs]
def output_respecting(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
postprocess: bool | None = None,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> mat | arr:
"""Compute the matrix representation of the D-CTC chronology-respecting (CR) output state (including any post-processing, i.e., traces and postselections).
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
free_symbol : str
The string representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to the value of :python:`self.free_symbol`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CR solution that corresponds to the maximally entropic CV state, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to the value of :python:`maximum_entropy`.
Returns
-------
mat | arr
The processed matrix representation of the D-CTC CR output state.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
free_symbol = self.free_symbol if free_symbol is None else free_symbol
maximum_entropy = (
self.maximum_entropy if maximum_entropy is None else maximum_entropy
)
output_respecting = dctc_respecting(
input_respecting=self.input(
numerical=numerical, array=array_intermediate, substitutions=[]
),
input_violating=self.state_violating(
numerical=numerical,
array=array_intermediate,
free_symbol=free_symbol,
maximum_entropy=maximum_entropy,
),
gate=self.gate(
numerical=numerical, array=array_intermediate, substitutions=[]
),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
output_respecting = QuantumState(
spec=output_respecting,
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
postprocess = True if postprocess is None else postprocess
if postprocess is True:
systems_removed = []
# Partial traces
traces = adjust_targets(self.systems_traces, systems_removed)
output_respecting.partial_trace(targets=traces)
systems_removed += traces
# Postselections
for postselection in self.postselections:
length = count_systems(extract_matrix(postselection[0]), self.dim)
listed = flatten_list([postselection[1]])
systems = [(min(listed) + n) for n in range(0, length)]
targets_postselection = adjust_targets(systems, systems_removed)
output_respecting.postselect(
postselections=[(postselection[0], targets_postselection)]
)
systems_removed += systems
# Normalization
norm = False if norm is None else norm
norm = 1 if norm is True else norm
if norm is not False:
output_respecting.normalize(norm)
# Simplification
simplify = False if simplify is None else simplify
if simplify is True:
output_respecting.simplify()
# Conjugation
conjugate = False if conjugate is None else conjugate
if conjugate is True:
output_respecting.dagger()
output_respecting = QuantumState(
spec=output_respecting.output(
numerical=numerical, array=array_intermediate
),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return output_respecting.output(numerical=numerical, array=array)
[docs]
def output(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
postprocess: bool | None = None,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> mat | arr:
"""An alias for the :py:meth:`~qhronology.quantum.prescriptions.DCTC.output_respecting` method.
Useful for polymorphism.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
free_symbol : str
The string representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to the value of :python:`self.free_symbol`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CR solution that corresponds to the maximally entropic CV state, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to the value of :python:`self.maximum_entropy`.
Returns
-------
mat | arr
The processed matrix representation of the D-CTC CR output state.
"""
return self.output_respecting(
numerical=numerical,
array=array,
substitutions=substitutions,
simplify=simplify,
conjugate=conjugate,
norm=norm,
postprocess=postprocess,
free_symbol=free_symbol,
maximum_entropy=maximum_entropy,
)
[docs]
def state_violating(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
label: str | None = None,
notation: str | None = None,
traces: list[int] | None = None,
debug: bool | None = None,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> QuantumState:
"""Compute the D-CTC chronology-violating (CV) state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
numerical : bool
Whether to cast the state's matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the state's matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state before committing it to the :python:`matrix` property.
If :python:`False`, does not simplify.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
If :python:`False`, does not conjugate.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to :python:`"ρ"` (if :python:`form == "matrix"`) or :python:`"ψ"` (if :python:`form == "vector"`).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not :python:`None`, overrides the value passed to :python:`label`.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to :python:`None`.
traces : list[int]
A list of indices of the CV systems (relative to the entire circuit) on which to perform partial traces.
Defaults to :python:`[]`.
free_symbol : str
The string representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to the value of :python:`self.free_symbol`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CV state that possesses the most (von Neumann) entropy, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to the value of :python:`self.maximum_entropy`.
debug : bool
Whether to print the internal state (held in :python:`matrix`) on change.
If :python:`False`, does not print.
Defaults to :python:`False`.
Returns
-------
QuantumState
The D-CTC CV output state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
traces = [] if traces is None else traces
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
state = QuantumState(
form=form,
kind=kind,
spec=self.output_violating(
numerical=numerical,
array=array_intermediate,
substitutions=substitutions,
simplify=simplify,
conjugate=False,
norm=norm,
free_symbol=free_symbol,
maximum_entropy=maximum_entropy,
),
dim=self.dim,
numerical=numerical,
array=array,
symbols=self.symbols,
substitutions=substitutions,
conjugate=conjugate,
norm=False,
label=label,
notation=notation,
debug=debug,
)
traces = adjust_targets(traces, self.systems_removed + self.systems_respecting)
state.partial_trace(targets=traces)
return state
[docs]
def state_respecting(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
label: str | None = None,
notation: str | None = None,
traces: list[int] | None = None,
postprocess: bool | None = None,
debug: bool | None = None,
free_symbol: sym | str | None = None,
maximum_entropy: bool | None = None,
) -> QuantumState:
"""Compute the D-CTC chronology-respecting (CR) state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
numerical : bool
Whether to cast the state's matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the state's matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state before committing it to the :python:`matrix` property.
If :python:`False`, does not simplify.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
If :python:`False`, does not conjugate.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to :python:`"ρ"` (if :python:`form == "matrix"`) or :python:`"ψ"` (if :python:`form == "vector"`).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not :python:`None`, overrides the value passed to :python:`label`.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to :python:`None`.
traces : list[int]
A list of indices of the CR systems (relative to the entire circuit) on which to perform partial traces.
Performed regardless of the value of :python:`postprocess`.
Defaults to :python:`[]`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
free_symbol : str
The string representation of the algebraic symbol to be used as the free parameter in the case where the CV map has a multiplicity of fixed points.
Defaults to the value of :python:`self.free_symbol`.
maximum_entropy : bool
Whether to, in the case of solution multiplicity, return the CR solution that corresponds to the maximally entropic CV state, in accordance with Deutsch's original prescription.
If :python:`False`, simply returns the ordinary (single or parametrized) solution.
Defaults to the value of :python:`self.maximum_entropy`.
debug : bool
Whether to print the internal state (held in :python:`matrix`) on change.
If :python:`False`, does not print.
Defaults to :python:`False`.
Returns
-------
QuantumState
The D-CTC CR output state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
traces = [] if traces is None else traces
postprocess = True if postprocess is None else postprocess
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
if postprocess is True:
traces = adjust_targets(
traces, self.systems_removed + self.systems_violating
)
else:
traces = adjust_targets(traces, self.systems_violating)
state = QuantumState(
spec=self.output_respecting(
numerical=numerical,
array=array_intermediate,
substitutions=substitutions,
simplify=simplify,
conjugate=False,
norm=norm,
postprocess=postprocess,
free_symbol=free_symbol,
maximum_entropy=maximum_entropy,
),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array,
symbols=self.symbols,
substitutions=substitutions,
conjugate=conjugate,
norm=False,
label=label,
notation=notation,
debug=debug,
)
state.partial_trace(targets=traces)
return state
[docs]
def pctc_violating(
input_respecting: mat | arr | QuantumState,
gate: mat | arr | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat | arr:
"""Calculate the chronology-violating (CV) state according to the P-CTC weak-measurement tomography expression for the prescription's CV map
.. math::
\\MapPCTCsCV_{\\Unitary}[\\StateCR]
= \\trace_\\CR\\bigl[\\Unitary(\\StateCR \\otimes \\tfrac{1}{\\Dimension}\\Identity)
\\Unitary^\\dagger\\bigr]
given the chronology-respecting (CR) input state :python:`input_respecting` (:math:`\\StateCR`) and interaction described by :python:`gate` (:math:`\\Unitary`).
Here, :math:`\\Dimension` is the dimensionality of the CV Hilbert space (assumed to be equivalent to that of its CR counterpart), while :math:`\\Identity` is the :math:`\\Dimension \\times \\Dimension` identity matrix.
Arguments
---------
input_respecting : mat | arr | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | arr | QuantumGate
The matrix representation of the gate describing the (unitary) interaction between the CR and CV systems.
systems_respecting : list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
systems_violating : list[int]
The numerical indices of the chronology-violating (CV) subsystems.
Returns
-------
mat
The weak-measurement tomography expression for the P-CTC CV state.
Note
----
The validity of the expression used in this function to compute the P-CTC CV state for *non-qubit* systems has not been proven.
"""
if isinstance(input_respecting, mat | arr) is True:
matrix_num = True if issubclass(dtype(input_respecting), num) is True else False
matrix_arr = True if isinstance(input_respecting, arr) is True else False
else:
try:
matrix_num = (
True
if issubclass(dtype(input_respecting.output()), num) is True
else False
)
matrix_arr = (
True if isinstance(input_respecting.output(), arr) is True else False
)
except:
raise TypeError(
"""A matrix or array cannot be extracted from the given input state."""
)
systems_respecting = list(set(systems_respecting))
systems_violating = list(set(systems_violating))
try:
dim = input_respecting.dim
except:
dim = count_dims(
matrix=densify(extract_matrix(input_respecting)),
num_systems=len(systems_respecting),
)
input_respecting = densify(extract_matrix(input_respecting))
identity = (sp.Rational(1, dim) ** len(systems_violating)) * sp.eye(
dim ** len(systems_violating)
)
input_total = assemble_composition(
(input_respecting, systems_respecting), (identity, systems_violating)
)
gate = densify(extract_matrix(gate))
output_total = matrix_multiplication(gate, input_total, conjugate_transpose(gate))
output_violating = partial_trace(
matrix=output_total, targets=systems_respecting, dim=dim
)
return cast(output_violating, numerical=matrix_num, array=matrix_arr)
[docs]
def pctc_respecting(
input_respecting: mat | arr | QuantumState,
gate: mat | arr | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat | arr:
"""Calculate the (non-renormalized) chronology-respecting (CR) state according to the P-CTC prescription's non-renormalizing CR map
.. math::
\\MapPCTCsCR_{\\Unitary}[\\StateCR]
\\propto \\OperatorPCTC \\StateCR \\OperatorPCTC^\\dagger
given the chronology-respecting (CR) input state :python:`input_respecting` (:math:`\\StateCR`) and (unitary) interaction described by :python:`gate` (:math:`\\Unitary`).
Here,
.. math:: \\OperatorPCTC \\equiv \\trace_\\CV[\\Unitary]
is the P-CTC operator.
Note
----
This function does not renormalize the output state as per the renormalized P-CTC map
.. math::
\\MapPCTCsCR_{\\Unitary}[\\StateCR]
= \\frac{\\OperatorPCTC \\StateCR \\OperatorPCTC^\\dagger}
{\\trace\\bigl[ \\OperatorPCTC \\StateCR \\OperatorPCTC^\\dagger\\bigr]}.
Arguments
---------
input_respecting : mat | arr | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | arr | QuantumGate
The matrix representation of the gate describing the (unitary) interaction between the CR and CV systems.
systems_respecting : list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
systems_violating : list[int]
The numerical indices of the chronology-violating (CV) subsystems.
Returns
-------
mat
The solution of the P-CTC CR map.
"""
if isinstance(input_respecting, mat | arr) is True:
matrix_num = True if issubclass(dtype(input_respecting), num) is True else False
matrix_arr = True if isinstance(input_respecting, arr) is True else False
else:
try:
matrix_num = (
True
if issubclass(dtype(input_respecting.output()), num) is True
else False
)
matrix_arr = (
True if isinstance(input_respecting.output(), arr) is True else False
)
except:
raise TypeError(
"""A matrix or array cannot be extracted from the given input state."""
)
systems_respecting = list(set(systems_respecting))
systems_violating = list(set(systems_violating))
try:
dim = input_respecting.dim
except:
dim = count_dims(
matrix=densify(extract_matrix(input_respecting)),
num_systems=len(systems_respecting),
)
input_respecting = extract_matrix(input_respecting)
gate = densify(extract_matrix(gate))
gate_reduced = partial_trace(matrix=gate, targets=systems_violating, dim=dim)
if matrix_form(input_respecting) == Forms.VECTOR.value:
input_respecting = columnify(input_respecting)
output_respecting = gate_reduced * input_respecting
# renormalization = recursively_simplify(sp.sqrt(1/trace(output_respecting)))
# output_respecting = renormalization * output_respecting
else:
output_respecting = (
gate_reduced * densify(input_respecting) * conjugate_transpose(gate_reduced)
)
# renormalization = recursively_simplify(1/trace(output_respecting))
# output_respecting = renormalization * output_respecting
return cast(output_respecting, numerical=matrix_num, array=matrix_arr)
[docs]
class PCTC(QuantumCTC):
"""A subclass for creating closed timelike curves described by the postselected teleportation prescription (P-CTCs) of quantum time travel.
This is built upon the :py:class:`~qhronology.quantum.prescriptions.QuantumCTC` class, and so inherits all of its attributes, properties, and methods.
Arguments
---------
*args
Positional arguments, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor :python:`__init__` of the superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
[docs]
def matrix(
self,
numerical: bool | None = None,
array: bool | None = None,
) -> mat | arr:
"""Compute the unprocessed matrix representation of the P-CTC chronology-respecting (CR) output state.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
Returns
-------
mat | arr
The unprocessed matrix representation of the P-CTC CR output state.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
output_respecting = pctc_respecting(
input_respecting=self.input(substitutions=[]),
gate=self.gate(substitutions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
return cast(output_respecting, numerical=numerical, array=array)
[docs]
def output_violating(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
) -> mat | arr:
"""Compute the processed matrix representation of the P-CTC chronology-violating (CV) state.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
Returns
-------
mat | arr
The processed matrix representation of the D-CTC CV output state.
Note
----
The validity of the expression used in this method to compute the P-CTC CV state for *non-qubit* systems has not been proven.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
output_violating = pctc_violating(
input_respecting=self.input(substitutions=[]),
gate=self.gate(substitutions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
output_violating = QuantumState(
spec=output_violating,
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
# Normalization
norm = False if norm is None else norm
norm = 1 if norm is True else norm
if norm is not False:
output_violating.normalize(norm)
# Simplification
simplify = False if simplify is None else simplify
if simplify is True:
output_violating.simplify()
# Conjugation
conjugate = False if conjugate is None else conjugate
if conjugate is True:
output_violating.dagger()
output_violating = QuantumState(
spec=output_violating.output(numerical=numerical, array=array_intermediate),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return output_violating.output(numerical=numerical, array=array)
[docs]
def output_respecting(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
postprocess: bool | None = None,
) -> mat | arr:
"""Compute the matrix representation of the P-CTC chronology-respecting (CR) output state (including any post-processing, i.e., traces and postselections).
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
Returns
-------
mat | arr
The processed matrix representation of the P-CTC CR output state.
Note
----
The output state is not renormalized.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
output_respecting = pctc_respecting(
input_respecting=self.input(substitutions=[]),
gate=self.gate(substitutions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
if self.input_is_vector is True:
form = Forms.VECTOR.value
kind = Kinds.PURE.value
if self.gate_is_linear is False:
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
output_respecting = QuantumState(
spec=output_respecting,
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
postprocess = True if postprocess is None else postprocess
if postprocess is True:
systems_removed = []
# Partial traces
traces = adjust_targets(self.systems_traces, systems_removed)
output_respecting.partial_trace(targets=traces)
systems_removed += traces
# Postselections
for postselection in self.postselections:
length = count_systems(extract_matrix(postselection[0]), self.dim)
listed = flatten_list([postselection[1]])
systems = [(min(listed) + n) for n in range(0, length)]
targets_postselection = adjust_targets(systems, systems_removed)
output_respecting.postselect(
postselections=[(postselection[0], targets_postselection)]
)
systems_removed += systems
# Normalization
norm = False if norm is None else norm
norm = 1 if norm is True else norm
if norm is not False:
output_respecting.normalize(norm)
# Simplification
simplify = False if simplify is None else simplify
if simplify is True:
output_respecting.simplify()
# Conjugation
conjugate = False if conjugate is None else conjugate
if conjugate is True:
output_respecting.dagger()
output_respecting = QuantumState(
spec=output_respecting.output(
numerical=numerical, array=array_intermediate
),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array_intermediate,
symbols=self.symbols,
substitutions=substitutions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return output_respecting.output(numerical=numerical, array=array)
[docs]
def output(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
postprocess: bool | None = None,
) -> mat | arr:
"""An alias for the :py:meth:`~qhronology.quantum.prescriptions.PCTC.output_respecting` method.
Useful for polymorphism.
Arguments
---------
numerical : bool
Whether to cast the matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
Returns
-------
mat | arr
The processed matrix representation of the P-CTC CR output state.
Note
----
The output state is not renormalized.
"""
return self.output_respecting(
numerical=numerical,
array=array,
substitutions=substitutions,
simplify=simplify,
conjugate=conjugate,
norm=norm,
postprocess=postprocess,
)
[docs]
def state_violating(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
label: str | None = None,
notation: str | None = None,
traces: list[int] | None = None,
debug: bool | None = None,
) -> QuantumState:
"""Compute the P-CTC chronology-violating (CV) state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
numerical : bool
Whether to cast the state's matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the state's matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state before committing it in the :python:`matrix` property.
If :python:`False`, does not simplify.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
If :python:`False`, does not conjugate.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to :python:`"ρ"` (if :python:`form == "matrix"`) or :python:`"ψ"` (if :python:`form == "vector"`).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not :python:`None`, overrides the value passed to :python:`label`.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to :python:`None`.
traces : list[int]
A list of indices of the CV systems (relative to the entire circuit) on which to perform partial traces.
Defaults to :python:`[]`.
debug : bool
Whether to print the internal state (held in :python:`matrix`) on change.
If :python:`False`, does not print.
Defaults to :python:`False`.
Returns
-------
QuantumState
The P-CTC CV output state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Note
----
The validity of the expression used in this method to compute the P-CTC CV state for *non-qubit* systems has not been proven.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
traces = [] if traces is None else traces
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
state = QuantumState(
form=form,
kind=kind,
spec=self.output_violating(
numerical=numerical,
array=array_intermediate,
substitutions=substitutions,
simplify=simplify,
conjugate=False,
norm=norm,
),
dim=self.dim,
numerical=numerical,
array=array,
symbols=self.symbols,
substitutions=substitutions,
conjugate=conjugate,
norm=False,
label=label,
notation=notation,
debug=debug,
)
traces = adjust_targets(traces, self.systems_removed + self.systems_respecting)
state.partial_trace(targets=traces)
return state
[docs]
def state_respecting(
self,
numerical: bool | None = None,
array: bool | None = None,
substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | expr | str | None = None,
label: str | None = None,
notation: str | None = None,
traces: list[int] | None = None,
postprocess: bool | None = None,
debug: bool | None = None,
) -> QuantumState:
"""Compute the P-CTC chronology-respecting (CR) state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
numerical : bool
Whether to cast the state's matrix elements as floating-point values (:python:`True`) (if possible) or exact values (:python:`False`).
Defaults to the value of :python:`self.numerical`.
array : bool
Whether to cast the state's matrix as a NumPy array (:python:`True`) or SymPy matrix (:python:`False`).
Defaults to the value of :python:`self.array`.
substitutions : list[tuple[num | expr | str, num | expr | str]]
Algebraic substitutions to be applied to the state.
Defaults to the value of :python:`self.substitutions`.
simplify : bool
Whether to perform mathematical simplification on the state before committing it to the :python:`matrix` property.
If :python:`False`, does not simplify.
Defaults to :python:`False`.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
If :python:`False`, does not conjugate.
Defaults to :python:`False`.
norm : bool | num | expr | str
The value to which the state is normalized.
If :python:`True`, normalizes to a value of :math:`1`.
If :python:`False`, does not normalize.
Defaults to :python:`False`.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to :python:`"ρ"` (if :python:`form == "matrix"`) or :python:`"ψ"` (if :python:`form == "vector"`).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not :python:`None`, overrides the value passed to :python:`label`.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to :python:`None`.
traces : list[int]
A list of indices of the CR systems (relative to the entire circuit) on which to perform partial traces.
Performed regardless of the value of :python:`postprocess`.
Defaults to :python:`[]`.
postprocess : bool
Whether to post-process the state (i.e., perform the circuit's traces and postselections).
Defaults to :python:`True`.
debug : bool
Whether to print the internal state (held in :python:`matrix`) on change.
If :python:`False`, does not print.
Defaults to :python:`False`.
Returns
-------
QuantumState
The P-CTC CR output state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
Note
----
The output state is not renormalized if :python:`norm` is :python:`False`.
"""
numerical = self.numerical if numerical is None else numerical
array = self.array if array is None else array
array_intermediate = True if numerical is True else False
substitutions = self.substitutions if substitutions is None else substitutions
traces = [] if traces is None else traces
postprocess = True if postprocess is None else postprocess
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
if postprocess is True:
if self.output_is_vector is True and len(traces) == 0:
form = Forms.VECTOR.value
kind = Kinds.PURE.value
traces = adjust_targets(
traces, self.systems_removed + self.systems_violating
)
else:
if (
self.input_is_vector is True
and self.gate_is_linear is True
and len(traces) == 0
):
form = Forms.VECTOR.value
kind = Kinds.PURE.value
traces = adjust_targets(traces, self.systems_violating)
state = QuantumState(
spec=self.output_respecting(
numerical=numerical,
array=array_intermediate,
substitutions=substitutions,
simplify=simplify,
conjugate=False,
norm=norm,
postprocess=postprocess,
),
form=form,
kind=kind,
dim=self.dim,
numerical=numerical,
array=array,
symbols=self.symbols,
substitutions=substitutions,
conjugate=conjugate,
norm=False,
label=label,
notation=notation,
debug=debug,
)
state.partial_trace(targets=traces)
return state