# 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.
"""
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 sympy.physics.quantum import TensorProduct
from sympy.physics.quantum.dagger import Dagger
from qhronology.utilities.classification import num, sym, mat, Forms, Kinds, matrix_form
from qhronology.utilities.helpers import (
adjust_targets,
count_systems,
count_dims,
extract_matrix,
extract_conditions,
flatten_list,
assemble_composition,
recursively_simplify,
)
from qhronology.quantum.states import QuantumState
from qhronology.quantum.gates import QuantumGate
from qhronology.quantum.circuits import QuantumCircuit
from qhronology.mechanics.operations import densify, columnify, partial_trace
from qhronology.mechanics.quantities import trace
[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;
this is functionality that is associated with the specific prescriptions of quantum time travel,
and such prescriptions are implemented as subclasses.
Arguments
---------
*args
Variable length argument list, passed directly to the constructor ``__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 ``*args`` and
``**kwargs``.
Defaults to ``None``.
systems_respecting : int | list[int]
The numerical indices of the chronology-respecting (CR) subsystems.
Defaults to ``[]``.
systems_violating : int | list[int]
The numerical indices of the chronology-violating (CV) subsystems.
Defaults to ``[]``.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor ``__init__`` of the
superclass :py:class:`~qhronology.quantum.circuits.QuantumCircuit`.
Note
----
The lists of indices specified in ``systems_respecting`` and ``systems_violating`` must both be
contiguous.
Additionally, the circuit's inputs (``inputs``) are treated as one contiguous total state,
with the indices of its subsystems exactly matching those specified in ``systems_respecting``.
Note
----
It is best practice to specify only one of either ``systems_violating`` or
``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 ``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 mergers override the values of the attributes associated with the other arguments
specified in the constructor. It is best practice to specify either of:
- only ``circuit`` and one of either ``systems_respecting`` or ``systems_violating``
- ``*args`` and ``**kwargs`` (like a typical initialization of a
:py:class:`~qhronology.quantum.circuits.QuantumCircuit` instance)
without specifying ``circuit``
Note
----
The total interaction between the CR and CV systems is expected to be unitary,
and so the sequence of gates in ``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
``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__)
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 ``systems_respecting`` or ``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 ``systems_respecting`` and ``systems_violating`` is \
inequivalent to the entire system's structure."
)
self.systems_respecting = systems_respecting
@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 ``inputs`` are vector states."""
is_vector = True
if any(
form != Forms.VECTOR.value for form in [state.form for state in self.inputs]
):
is_vector = False
return is_vector
# The four methods below merely output the reduced states, so the ``systems_respective`` and
# ``systems_violating`` of the base class acts just like extra traces.
def output_violating(self) -> mat:
return self.state(traces=self.systems_respecting).output()
def output_respecting(self) -> mat:
return self.state(traces=self.systems_violating).output()
def state_violating(self) -> QuantumState:
return self.state(traces=self.systems_respecting)
def state_respecting(self) -> QuantumState:
return self.state(traces=self.systems_violating)
[docs]
def dctc_violating(
input_respecting: mat | QuantumState,
gate: mat | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
free_symbol: sym | str | None = None,
) -> mat:
"""Calculate the chronology-violating (CV) state(s) 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 ``input_respecting`` (:math:`\\StateCR`)
and (unitary) interaction described by ``gate`` (:math:`\\Unitary`).
Arguments
---------
input_respecting : mat | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | 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 ``"g"``.
Returns
-------
mat
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
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)), systems=systems_respecting
)
conditions_respecting = extract_conditions(input_respecting)
trace_respecting = input_respecting.trace()
input_respecting = densify(extract_matrix(input_respecting))
# Use ``Symbol`` for persistent (structurally bound) variables.
# Use ``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)
)
gate = densify(extract_matrix(gate))
output_total = gate * input_total * Dagger(gate)
output_violating = partial_trace(
matrix=output_total, targets=systems_respecting, dim=dim
)
output_violating = recursively_simplify(output_violating, conditions_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))
if trace_respecting == 1:
equations += [sp.Eq(trace(input_respecting), 1, evaluate=False)]
equations += [sp.Eq(trace(input_violating), 1, 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)
}:
solutions = set()
solutions = (unknowns, solutions)
if len(solutions[1]) == 1:
break
elif len(solutions[1]) == 0:
# Remove final equation (possibly 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 == 2:
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)}
# Check for all zeroes solution and replace with CR input state if so
# 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]
free_variables = []
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)
)
)
return output_violating
[docs]
def dctc_respecting(
input_respecting: mat | QuantumState,
input_violating: mat | QuantumState,
gate: mat | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat:
"""Calculate the chronology-respecting (CR) state(s) 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 ``input_respecting`` (:math:`\\StateCR`),
chronology-violating (CV) solution state ``input_violating`` (:math:`\\StateCV`),
and (unitary) interaction described by ``gate`` (:math:`\\Unitary`).
Arguments
---------
input_respecting : mat | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
input_violating : mat | QuantumState
The matrix representation of the chronology-violating (CR) solution state.
gate : mat | 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(s) of the D-CTC CR map.
"""
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)), systems=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 = gate * input_total * Dagger(gate)
output_respecting = partial_trace(
matrix=output_total, targets=systems_violating, dim=dim
)
return output_respecting
[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
Variable-length argument list, passed directly to the constructor ``__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 ``"g"``.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor ``__init__`` of the
superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
"""
def __init__(self, *args, free_symbol: sym | str | None = None, **kwargs):
super().__init__(*args, **kwargs)
free_symbol = "g" if free_symbol is None else free_symbol
self.free_symbol = free_symbol
@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 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.
@property
def matrix(self) -> mat:
"""The matrix representation of the total D-CTC chronology-respecting (CR) output state
prior to any post-processing."""
output_respecting = dctc_respecting(
input_respecting=self.input(conditions=[]),
input_violating=self.state_violating(free_symbol=self.free_symbol),
gate=self.gate(conditions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
return output_respecting
[docs]
def output_violating(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
free_symbol: sym | str | None = None,
) -> mat:
"""Compute the matrix representation of the D-CTC chronology-violating (CV) state(s).
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``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 ``self.free_symbol``.
Returns
-------
mat
The matrix representation of the CV output state.
"""
free_symbol = self.free_symbol if free_symbol is None else free_symbol
output_violating = dctc_violating(
input_respecting=self.input(conditions=[]),
gate=self.gate(conditions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
free_symbol=free_symbol,
)
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
output_violating = QuantumState(
spec=output_violating,
form=form,
kind=kind,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
# 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(),
form=form,
kind=kind,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return sp.Matrix(output_violating.output())
[docs]
def output_respecting(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
postprocess: bool | None = None,
free_symbol: sym | str | None = None,
) -> mat:
"""Compute the matrix representation of the D-CTC chronology-respecting (CR) state(s)
(including any post-processing).
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``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 ``self.free_symbol``.
Returns
-------
mat
The matrix representation of the (post-processed) CR output state.
"""
conditions = self.conditions if conditions is None else conditions
free_symbol = self.free_symbol if free_symbol is None else free_symbol
output_respecting = dctc_respecting(
input_respecting=self.input(conditions=[]),
input_violating=self.state_violating(free_symbol=free_symbol),
gate=self.gate(conditions=[]),
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,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
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
# 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(),
form=form,
kind=kind,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return sp.Matrix(output_respecting.output())
[docs]
def output(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
postprocess: bool | None = None,
free_symbol: sym | str | None = None,
) -> mat:
"""An alias for the :py:meth:`~qhronology.quantum.prescriptions.DCTC.output_respecting` method.
Useful for polymorphism.
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``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 ``self.free_symbol``.
Returns
-------
mat
The matrix representation of the (post-processed) CR output state.
"""
return self.output_respecting(
conditions=conditions,
simplify=simplify,
conjugate=conjugate,
postprocess=postprocess,
free_symbol=free_symbol,
)
[docs]
def state_violating(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | sym | 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,
) -> QuantumState:
"""Compute the D-CTC chronology-violating (CV) state(s) as a
:py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state before committing it to the
``matrix`` property.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
norm : bool | num | sym | str
The value to which the state is normalized.
If ``True``, normalizes to a value of :math:`1`.
If ``False``, does not normalize.
Defaults to ``False``.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to ``"ρ"`` (if ``form == "matrix"``) or ``"ψ"`` (if ``form == "vector"``).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not ``None``, overrides the value passed to ``label``.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to ``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 ``[]``.
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 ``self.free_symbol``.
debug : bool
Whether to print the internal state (held in ``matrix``) on change.
Defaults to ``False``.
Returns
-------
QuantumState
The CV output state as a :py:class:`~qhronology.quantum.states.QuantumState` instance.
"""
conditions = self.conditions if conditions is None else conditions
traces = [] if traces is None else traces
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
state = QuantumState(
form=form,
kind=kind,
spec=sp.Matrix(
self.output_violating(
conditions=conditions,
simplify=simplify,
conjugate=False,
free_symbol=free_symbol,
)
),
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
conjugate=conjugate,
norm=norm,
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,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | sym | 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,
) -> QuantumState:
"""Compute the D-CTC chronology-respecting (CR) state(s) as a
:py:class:`~qhronology.quantum.states.QuantumState` instance.
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state before committing it to the
``matrix`` property.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
norm : bool | num | sym | str
The value to which the state is normalized.
If ``True``, normalizes to a value of :math:`1`.
If ``False``, does not normalize.
Defaults to ``False``.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to ``"ρ"`` (if ``form == "matrix"``) or ``"ψ"`` (if ``form == "vector"``).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not ``None``, overrides the value passed to ``label``.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to ``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 ``postprocess``.
Defaults to ``[]``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``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 ``self.free_symbol``.
debug : bool
Whether to print the internal state (held in ``matrix``) on change.
Defaults to ``False``.
Returns
-------
QuantumState
The (post-processed) CR output state as a
:py:class:`~qhronology.quantum.states.QuantumState` instance.
"""
conditions = self.conditions if conditions is None else conditions
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)
matrix = sp.Matrix(
self.output_respecting(
conditions=conditions,
simplify=simplify,
conjugate=False,
postprocess=postprocess,
free_symbol=free_symbol,
)
)
state = QuantumState(
form=form,
kind=kind,
spec=matrix,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
conjugate=conjugate,
norm=norm,
label=label,
notation=notation,
debug=debug,
)
state.partial_trace(targets=traces)
return state
[docs]
def pctc_violating(
input_respecting: mat | QuantumState,
gate: mat | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat:
"""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 ``input_respecting`` (:math:`\\StateCR`)
and (unitary) interaction described by ``gate`` (:math:`\\Unitary`).
Here, :math:`\\Dimension` is the dimensionality of the CV system's 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 | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | 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's 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.
"""
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)), systems=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 = gate * input_total * Dagger(gate)
output_violating = partial_trace(
matrix=output_total, targets=systems_respecting, dim=dim
)
return output_violating
[docs]
def pctc_respecting(
input_respecting: mat | QuantumState,
gate: mat | QuantumGate,
systems_respecting: list[int],
systems_violating: list[int],
) -> mat:
"""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 ``input_respecting`` (:math:`\\StateCR`)
and (unitary) interaction described by ``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 | QuantumState
The matrix representation of the chronology-respecting (CR) input state.
gate : mat | 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.
"""
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)), systems=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 * input_respecting * Dagger(gate_reduced)
# renormalization = recursively_simplify(1/trace(output_respecting))
# output_respecting = renormalization*output_respecting
return output_respecting
[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
Variable-length argument list, passed directly to the constructor ``__init__`` of the
superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
**kwargs
Arbitrary keyword arguments, passed directly to the constructor ``__init__`` of the
superclass :py:class:`~qhronology.quantum.gates.QuantumGate`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@property
def matrix(self) -> mat:
"""The matrix representation of the total P-CTC CR output state prior to any
post-processing."""
output_respecting = pctc_respecting(
input_respecting=self.input(conditions=[]),
gate=self.gate(conditions=[]),
systems_respecting=self.systems_respecting,
systems_violating=self.systems_violating,
)
return output_respecting
[docs]
def output_violating(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
) -> mat:
"""Compute the matrix representation of the P-CTC chronology-violating (CV) state.
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
Returns
-------
mat
The matrix representation of the 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.
"""
output_violating = pctc_violating(
input_respecting=self.input(conditions=[]),
gate=self.gate(conditions=[]),
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,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
# 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(),
form=form,
kind=kind,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return sp.Matrix(output_violating.output())
[docs]
def output_respecting(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
postprocess: bool | None = None,
) -> mat:
"""Compute the matrix representation of the P-CTC chronology-respecting (CR) state
(including any post-processing).
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``True``.
Returns
-------
mat
The matrix representation of the (post-processed) CR output state.
Note
----
The output state is not renormalized.
"""
conditions = self.conditions if conditions is None else conditions
output_respecting = pctc_respecting(
input_respecting=self.input(conditions=[]),
gate=self.gate(conditions=[]),
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,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
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
# 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(),
form=form,
kind=kind,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
norm=False,
conjugate=False,
label=None,
notation=None,
debug=False,
)
return sp.Matrix(output_respecting.output())
[docs]
def output(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
postprocess: bool | None = None,
) -> mat:
"""An alias for the :py:meth:`~qhronology.quantum.prescriptions.PCTC.output_respecting` method.
Useful for polymorphism.
Arguments
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``True``.
Returns
-------
mat
The matrix representation of the (post-processed) CR output state.
Note
----
The output state is not renormalized.
"""
return self.output_respecting(
conditions=conditions,
simplify=simplify,
conjugate=conjugate,
postprocess=postprocess,
)
[docs]
def state_violating(
self,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | sym | 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
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state before committing it in the
``matrix`` property.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
norm : bool | num | sym | str
The value to which the state is normalized.
If ``True``, normalizes to a value of :math:`1`.
If ``False``, does not normalize.
Defaults to ``False``.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to ``"ρ"`` (if ``form == "matrix"``) or ``"ψ"`` (if ``form == "vector"``).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not ``None``, overrides the value passed to ``label``.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to ``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 ``[]``.
debug : bool
Whether to print the internal state (held in ``matrix``) on change.
Defaults to ``False``.
Returns
-------
QuantumState
The 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.
"""
traces = [] if traces is None else traces
form = Forms.MATRIX.value
kind = Kinds.MIXED.value
state = QuantumState(
form=form,
kind=kind,
spec=sp.Matrix(
self.output_violating(
conditions=conditions, simplify=simplify, conjugate=False
)
),
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
conjugate=conjugate,
norm=norm,
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,
conditions: list[tuple[num | sym | str, num | sym | str]] | None = None,
simplify: bool | None = None,
conjugate: bool | None = None,
norm: bool | num | sym | 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
---------
conditions : list[tuple[num | sym | str, num | sym | str]]
Algebraic conditions to be applied to the state.
Defaults to the value of ``self.conditions``.
simplify : bool
Whether to perform algebraic simplification on the state before committing it to the
``matrix`` property.
Defaults to ``False``.
conjugate : bool
Whether to perform Hermitian conjugation on the state.
Defaults to ``False``.
norm : bool | num | sym | str
The value to which the state is normalized.
If ``True``, normalizes to a value of :math:`1`.
If ``False``, does not normalize.
Defaults to ``False``.
label : str
The unformatted string used to represent the state in mathematical expressions.
Must have a non-zero length.
Defaults to ``"ρ"`` (if ``form == "matrix"``) or ``"ψ"`` (if ``form == "vector"``).
notation : str
The formatted string used to represent the state in mathematical expressions.
When not ``None``, overrides the value passed to ``label``.
Must have a non-zero length.
Not intended to be set by the user in most cases.
Defaults to ``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 ``postprocess``.
Defaults to ``[]``.
postprocess : bool
Whether to post-process the state
(i.e., perform the circuit's traces and postselections).
Defaults to ``True``.
debug : bool
Whether to print the internal state (held in ``matrix``) on change.
Defaults to ``False``.
Returns
-------
QuantumState
The (post-processed) CR output state as a
:py:class:`~qhronology.quantum.states.QuantumState` instance.
Note
----
The output state is not renormalized if ``norm`` is ``False``.
"""
conditions = self.conditions if conditions is None else conditions
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)
matrix = sp.Matrix(
self.output_respecting(
conditions=conditions,
simplify=simplify,
conjugate=False,
postprocess=postprocess,
)
)
state = QuantumState(
form=form,
kind=kind,
spec=matrix,
symbols=self.symbols,
dim=self.dim,
conditions=conditions,
conjugate=conjugate,
norm=norm,
label=label,
notation=notation,
debug=debug,
)
state.partial_trace(targets=traces)
return state