Source code for qhronology.utilities.objects

# 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.

"""
The base class for constructing quantum states and gates.
Not intended to be used directly by the user.
"""

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

import sympy as sp

from qhronology.mechanics.matrices import quantum_object
from qhronology.utilities.classification import (
    COMPATIBILITIES,
    Forms,
    Kinds,
    Shapes,
    arr,
    expr,
    mat,
    matrix_form,
    matrix_shape,
    num,
    sym,
)
from qhronology.utilities.diagrams import VisualizationMixin
from qhronology.utilities.helpers import (
    apply_substitutions,
    cast,
    conjugate_transpose,
    count_systems,
    recursively_simplify,
    stringify,
    symbolize_substitutions,
    symbolize_expression,
)
from qhronology.utilities.symbolics import SymbolicsProperties


[docs] class QuantumObject(VisualizationMixin, SymbolicsProperties): """A base class forming the backbone of the QuantumState and QuantumGate classes. Not intended to be instantiated directly itself, but rather indirectly via the constructors of its derived classes. """ def __init__( self, spec: ( mat | arr | list[list[num | expr | str]] | list[tuple[num | expr | str, int | list[int]]] | None ) = None, form: str | None = None, kind: str | None = None, dim: int | None = None, numerical: bool | None = None, array: bool | None = None, num_systems: int | None = None, symbols: dict[sym | str, dict[str, Any]] | None = None, substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None, conjugate: bool | None = None, label: str | None = None, notation: str | None = None, family: str | None = None, debug: bool | None = None, ): spec = sp.zeros(2) if spec is None else spec form = Forms.MATRIX.value if form is None else form kind = Kinds.MIXED.value if kind is None else kind dim = 2 if dim is None else dim numerical = False if numerical is None else numerical array = False if array is None else array conjugate = False if conjugate is None else conjugate matrix = quantum_object( spec=spec, form=form, kind=kind, dim=dim, numerical=numerical, array=array ) num_systems = count_systems(matrix, dim) if num_systems is None else num_systems label = "A" if label is None else label notation = None if notation is None else notation family = "PUSH" if family is None else family debug = False if debug is None else debug SymbolicsProperties.__init__(self, symbols=symbols, substitutions=substitutions) self.spec = spec self.form = form self.kind = kind self.dim = dim self.array = array self.num_systems = num_systems self.conjugate = conjugate self.numerical = numerical self.label = label self.notation = notation self.family = family self.debug = debug def __str__(self) -> str: return str(self.notation) + " = " + stringify(self.output(), dim=self.dim) def __repr__(self) -> str: return repr(self.output()) @property def spec( self, ) -> ( mat | arr | list[list[num | expr | str]] | list[tuple[num | expr | str, int | list[int]]] ): """The specification of the quantum object. Provides a description of the object's values in a standard :python:`dim`-dimensional basis. """ return self._spec @spec.setter def spec( self, spec: ( mat | arr | list[list[num | expr | str]] | list[tuple[num | expr | str, int | list[int]]] ), ): self._spec = spec @property def current(self) -> mat | arr: """The current (unprocessed) matrix representation of the quantum object.""" return self._current @current.setter def current(self, current: mat | arr): self._current = current
[docs] def matrix( self, numerical: bool | None = None, array: bool | None = None, ) -> mat | arr: """Compute the unprocessed matrix representation of the object. 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 object. """ numerical = self.numerical if numerical is None else numerical array = self.array if array is None else array return quantum_object( spec=self.spec, form=self.form, kind=self.kind, dim=self.dim, 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, ) -> mat | arr: """Compute the processed matrix representation of the object. 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 object. If :python:`False`, does not simplify. Defaults to :python:`False`. conjugate : bool Whether to perform Hermitian conjugation on the object. If :python:`False`, does not conjugate. Defaults to the value of :python:`self.conjugate`. Returns ------- mat | arr The processed matrix representation of the object. """ 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 = self.matrix(numerical=numerical, array=array_intermediate) output = symbolize_expression(output, self.symbols_list) # Conditions substitutions = self.substitutions if substitutions is None else substitutions substitutions = symbolize_substitutions(substitutions, self.symbols_list) output = apply_substitutions(output, substitutions) # Simplification simplify = False if simplify is None else simplify if simplify is True: output = recursively_simplify(output, substitutions) # Conjugation conjugate = self.conjugate if conjugate is None else conjugate if conjugate is True: output = conjugate_transpose(output) return cast(output, numerical=numerical, array=array)
[docs] def print( self, delimiter: str | None = None, product: bool | None = None, return_string: bool | None = None, numerical: bool | None = None, substitutions: list[tuple[num | expr | str, num | expr | str]] | None = None, simplify: bool | None = None, conjugate: bool | None = None, ) -> None | str: """Print or return a mathematical expression of the quantum object as a string. Note that this method is essentially a wrapper on the :py:meth:`~qhronology.utilities.objects.QuantumObject.output` method, and so includes all of its arguments. Arguments --------- delimiter : str A string containing the character(s) with which to delimit (i.e., separate) the values in the ket and/or bra terms in the mathematical expression. Defaults to :python:`","`. product : bool Whether to represent the mathematical expression using tensor products. Only applies if the object is a multipartite composition. Defaults to :python:`False`. return_string : bool Whether to return the mathematical expression as a string. Defaults to :python:`False`. 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`. substitutions : list[tuple[num | expr | str, num | expr | str]] Algebraic substitutions to be applied to the object. Defaults to the value of :python:`self.substitutions`. simplify : bool Whether to perform mathematical simplification on the object. If :python:`False`, does not simplify. Defaults to :python:`False`. conjugate : bool Whether to perform Hermitian conjugation on the object. If :python:`False`, does not conjugate. Defaults to the value of :python:`self.conjugate`. Returns ------- None Returned if :python:`return_string` is :python:`False`. str The constructed mathematical expression. Returned if :python:`return_string` is :python:`True`. """ expression = ( str(self.notation) + " = " + stringify( self.output( numerical=numerical, substitutions=substitutions, simplify=simplify, conjugate=conjugate, ), dim=self.dim, delimiter=delimiter, product=product, ) ) if return_string is True: return expression else: print(expression)
@property def form(self) -> str: """The *form* of the object. Can be either of :python:`"vector"` or :python:`"matrix"`. Only :py:class:`~qhronology.quantum.states.QuantumState` objects can be :python:`"vector"`. """ return matrix_form(self.current) @form.setter def form(self, form: str): if hasattr(self, "_kind"): if form == Forms.VECTOR.value and self.kind == Kinds.MIXED.value: raise AttributeError( f"""The given :python:`form` ('{form}') is incompatible with the given :python:`kind` ('{self.kind}').""" ) self._form = form @property def kind(self) -> str: """The *kind* of quantum object. Can be either of :python:`"mixed"` or :python:`"pure"`.""" return self._kind @kind.setter def kind(self, kind: str): if kind not in COMPATIBILITIES[self.form]: raise AttributeError( f"""The given :python:`kind` ('{kind}') is incompatible with the given :python:`form` ('{self.form}').""" ) self._kind = kind @property def is_vector(self) -> bool: """Test for whether the object is a vector. Returns :python:`True` if so, otherwise :python:`False`.""" is_vector = False if self.form == Forms.VECTOR.value: is_vector = True return is_vector @property def dim(self) -> int: """The dimensionality of the quantum object. Must be a non-negative integer.""" return self._dim @dim.setter def dim(self, dim: int): if hasattr(self, "_dim") is True: raise AttributeError( """The :python:`dim` attribute cannot be set after instancing.""" ) self._dim = dim @property def label(self) -> str: """The unformatted string used to represent the object in mathematical expressions. Must have a non-zero length.""" return self._label @label.setter def label(self, label: str): self._label = label @property def labels(self) -> list[str]: """An ordered list of the object's labels corresponding to its :python:`boundaries`. Used exclusively by the visualization engine.""" return [self.notation] @property def notation(self) -> str: """The formatted string used to represent the object in mathematical expressions. When set, overrides the value of the :python:`label` property. Must have a non-zero length. Not intended to be set by the user in most cases.""" if self._notation is None: if self.is_vector is True: if ( matrix_shape(self.current) == Shapes.COLUMN.value and self.conjugate == False ) or ( matrix_shape(self.current) == Shapes.ROW.value and self.conjugate == True ): notation = "|" + self.label + "⟩" elif ( matrix_shape(self.current) == Shapes.ROW.value and self.conjugate == False ) or ( matrix_shape(self.current) == Shapes.COLUMN.value and self.conjugate == True ): notation = "⟨" + self.label + "|" else: notation = self.label else: notation = self.label if hasattr(self, "_kind"): if self.kind == Kinds.PURE.value: notation = "|" + self.label + "⟩⟨" + self.label + "|" else: notation = self._notation return notation @notation.setter def notation(self, notation: str | None): self._notation = notation @property def family(self) -> str | list[str]: """The code of the block element that the object is to be visualized as. Not intended to be set by the user.""" return self._family @family.setter def family(self, family: str | list[str]): self._family = family @property def boundaries(self) -> list[int]: """An ordered list of indices of the object's boundaries corresponding to its :python:`labels`. Used exclusively by the visualization engine.""" return [self.num_systems] @property def num_systems(self) -> int: """The number of systems that the object spans. Must be a non-negative integer. Should not be set for states.""" return self._num_systems @num_systems.setter def num_systems(self, num_systems: int): self._num_systems = num_systems @property def systems(self) -> list[int]: """Read-only property containing an ordered list of the numerical indices of the object's systems.""" return [k for k in range(0, self.num_systems)] @property def targets(self) -> list[int]: """An ordered list of the numerical indices of the object's target systems.""" return self.systems @property def controls(self) -> list[int]: """An ordered list of the numerical indices of the object's control systems.""" return [] @property def anticontrols(self) -> list[int]: """An ordered list of the numerical indices of the object's anticontrol systems.""" return [] @property def conjugate(self) -> bool: """Whether to perform Hermitian conjugation on the object when it is called.""" return self._conjugate @conjugate.setter def conjugate(self, conjugate: bool): self._conjugate = conjugate @property def numerical(self) -> bool: """Whether to cast the object's matrix elements as floating-point values or exact values.""" return self._numerical @numerical.setter def numerical(self, numerical: bool): self._numerical = numerical @property def array(self) -> bool: """Whether to cast the object's matrix as a NumPy array or SymPy matrix.""" return self._array @array.setter def array(self, array: bool): self._array = array @property def debug(self) -> bool: """Whether to print the object's matrix representation (stored in the :python:`matrix` property) on mutation.""" return self._debug @debug.setter def debug(self, debug: bool): self._debug = debug