Spaces:
Sleeping
Sleeping
from sympy.core.expr import Expr | |
from sympy.core.symbol import Symbol | |
from sympy.core.sympify import sympify | |
from sympy.matrices.dense import Matrix | |
from sympy.printing.pretty.stringpict import prettyForm | |
from sympy.core.containers import Tuple | |
from sympy.utilities.iterables import is_sequence | |
from sympy.physics.quantum.dagger import Dagger | |
from sympy.physics.quantum.matrixutils import ( | |
numpy_ndarray, scipy_sparse_matrix, | |
to_sympy, to_numpy, to_scipy_sparse | |
) | |
__all__ = [ | |
'QuantumError', | |
'QExpr' | |
] | |
#----------------------------------------------------------------------------- | |
# Error handling | |
#----------------------------------------------------------------------------- | |
class QuantumError(Exception): | |
pass | |
def _qsympify_sequence(seq): | |
"""Convert elements of a sequence to standard form. | |
This is like sympify, but it performs special logic for arguments passed | |
to QExpr. The following conversions are done: | |
* (list, tuple, Tuple) => _qsympify_sequence each element and convert | |
sequence to a Tuple. | |
* basestring => Symbol | |
* Matrix => Matrix | |
* other => sympify | |
Strings are passed to Symbol, not sympify to make sure that variables like | |
'pi' are kept as Symbols, not the SymPy built-in number subclasses. | |
Examples | |
======== | |
>>> from sympy.physics.quantum.qexpr import _qsympify_sequence | |
>>> _qsympify_sequence((1,2,[3,4,[1,]])) | |
(1, 2, (3, 4, (1,))) | |
""" | |
return tuple(__qsympify_sequence_helper(seq)) | |
def __qsympify_sequence_helper(seq): | |
""" | |
Helper function for _qsympify_sequence | |
This function does the actual work. | |
""" | |
#base case. If not a list, do Sympification | |
if not is_sequence(seq): | |
if isinstance(seq, Matrix): | |
return seq | |
elif isinstance(seq, str): | |
return Symbol(seq) | |
else: | |
return sympify(seq) | |
# base condition, when seq is QExpr and also | |
# is iterable. | |
if isinstance(seq, QExpr): | |
return seq | |
#if list, recurse on each item in the list | |
result = [__qsympify_sequence_helper(item) for item in seq] | |
return Tuple(*result) | |
#----------------------------------------------------------------------------- | |
# Basic Quantum Expression from which all objects descend | |
#----------------------------------------------------------------------------- | |
class QExpr(Expr): | |
"""A base class for all quantum object like operators and states.""" | |
# In sympy, slots are for instance attributes that are computed | |
# dynamically by the __new__ method. They are not part of args, but they | |
# derive from args. | |
# The Hilbert space a quantum Object belongs to. | |
__slots__ = ('hilbert_space', ) | |
is_commutative = False | |
# The separator used in printing the label. | |
_label_separator = '' | |
def free_symbols(self): | |
return {self} | |
def __new__(cls, *args, **kwargs): | |
"""Construct a new quantum object. | |
Parameters | |
========== | |
args : tuple | |
The list of numbers or parameters that uniquely specify the | |
quantum object. For a state, this will be its symbol or its | |
set of quantum numbers. | |
Examples | |
======== | |
>>> from sympy.physics.quantum.qexpr import QExpr | |
>>> q = QExpr(0) | |
>>> q | |
0 | |
>>> q.label | |
(0,) | |
>>> q.hilbert_space | |
H | |
>>> q.args | |
(0,) | |
>>> q.is_commutative | |
False | |
""" | |
# First compute args and call Expr.__new__ to create the instance | |
args = cls._eval_args(args, **kwargs) | |
if len(args) == 0: | |
args = cls._eval_args(tuple(cls.default_args()), **kwargs) | |
inst = Expr.__new__(cls, *args) | |
# Now set the slots on the instance | |
inst.hilbert_space = cls._eval_hilbert_space(args) | |
return inst | |
def _new_rawargs(cls, hilbert_space, *args, **old_assumptions): | |
"""Create new instance of this class with hilbert_space and args. | |
This is used to bypass the more complex logic in the ``__new__`` | |
method in cases where you already have the exact ``hilbert_space`` | |
and ``args``. This should be used when you are positive these | |
arguments are valid, in their final, proper form and want to optimize | |
the creation of the object. | |
""" | |
obj = Expr.__new__(cls, *args, **old_assumptions) | |
obj.hilbert_space = hilbert_space | |
return obj | |
#------------------------------------------------------------------------- | |
# Properties | |
#------------------------------------------------------------------------- | |
def label(self): | |
"""The label is the unique set of identifiers for the object. | |
Usually, this will include all of the information about the state | |
*except* the time (in the case of time-dependent objects). | |
This must be a tuple, rather than a Tuple. | |
""" | |
if len(self.args) == 0: # If there is no label specified, return the default | |
return self._eval_args(list(self.default_args())) | |
else: | |
return self.args | |
def is_symbolic(self): | |
return True | |
def default_args(self): | |
"""If no arguments are specified, then this will return a default set | |
of arguments to be run through the constructor. | |
NOTE: Any classes that override this MUST return a tuple of arguments. | |
Should be overridden by subclasses to specify the default arguments for kets and operators | |
""" | |
raise NotImplementedError("No default arguments for this class!") | |
#------------------------------------------------------------------------- | |
# _eval_* methods | |
#------------------------------------------------------------------------- | |
def _eval_adjoint(self): | |
obj = Expr._eval_adjoint(self) | |
if obj is None: | |
obj = Expr.__new__(Dagger, self) | |
if isinstance(obj, QExpr): | |
obj.hilbert_space = self.hilbert_space | |
return obj | |
def _eval_args(cls, args): | |
"""Process the args passed to the __new__ method. | |
This simply runs args through _qsympify_sequence. | |
""" | |
return _qsympify_sequence(args) | |
def _eval_hilbert_space(cls, args): | |
"""Compute the Hilbert space instance from the args. | |
""" | |
from sympy.physics.quantum.hilbert import HilbertSpace | |
return HilbertSpace() | |
#------------------------------------------------------------------------- | |
# Printing | |
#------------------------------------------------------------------------- | |
# Utilities for printing: these operate on raw SymPy objects | |
def _print_sequence(self, seq, sep, printer, *args): | |
result = [] | |
for item in seq: | |
result.append(printer._print(item, *args)) | |
return sep.join(result) | |
def _print_sequence_pretty(self, seq, sep, printer, *args): | |
pform = printer._print(seq[0], *args) | |
for item in seq[1:]: | |
pform = prettyForm(*pform.right(sep)) | |
pform = prettyForm(*pform.right(printer._print(item, *args))) | |
return pform | |
# Utilities for printing: these operate prettyForm objects | |
def _print_subscript_pretty(self, a, b): | |
top = prettyForm(*b.left(' '*a.width())) | |
bot = prettyForm(*a.right(' '*b.width())) | |
return prettyForm(binding=prettyForm.POW, *bot.below(top)) | |
def _print_superscript_pretty(self, a, b): | |
return a**b | |
def _print_parens_pretty(self, pform, left='(', right=')'): | |
return prettyForm(*pform.parens(left=left, right=right)) | |
# Printing of labels (i.e. args) | |
def _print_label(self, printer, *args): | |
"""Prints the label of the QExpr | |
This method prints self.label, using self._label_separator to separate | |
the elements. This method should not be overridden, instead, override | |
_print_contents to change printing behavior. | |
""" | |
return self._print_sequence( | |
self.label, self._label_separator, printer, *args | |
) | |
def _print_label_repr(self, printer, *args): | |
return self._print_sequence( | |
self.label, ',', printer, *args | |
) | |
def _print_label_pretty(self, printer, *args): | |
return self._print_sequence_pretty( | |
self.label, self._label_separator, printer, *args | |
) | |
def _print_label_latex(self, printer, *args): | |
return self._print_sequence( | |
self.label, self._label_separator, printer, *args | |
) | |
# Printing of contents (default to label) | |
def _print_contents(self, printer, *args): | |
"""Printer for contents of QExpr | |
Handles the printing of any unique identifying contents of a QExpr to | |
print as its contents, such as any variables or quantum numbers. The | |
default is to print the label, which is almost always the args. This | |
should not include printing of any brackets or parentheses. | |
""" | |
return self._print_label(printer, *args) | |
def _print_contents_pretty(self, printer, *args): | |
return self._print_label_pretty(printer, *args) | |
def _print_contents_latex(self, printer, *args): | |
return self._print_label_latex(printer, *args) | |
# Main printing methods | |
def _sympystr(self, printer, *args): | |
"""Default printing behavior of QExpr objects | |
Handles the default printing of a QExpr. To add other things to the | |
printing of the object, such as an operator name to operators or | |
brackets to states, the class should override the _print/_pretty/_latex | |
functions directly and make calls to _print_contents where appropriate. | |
This allows things like InnerProduct to easily control its printing the | |
printing of contents. | |
""" | |
return self._print_contents(printer, *args) | |
def _sympyrepr(self, printer, *args): | |
classname = self.__class__.__name__ | |
label = self._print_label_repr(printer, *args) | |
return '%s(%s)' % (classname, label) | |
def _pretty(self, printer, *args): | |
pform = self._print_contents_pretty(printer, *args) | |
return pform | |
def _latex(self, printer, *args): | |
return self._print_contents_latex(printer, *args) | |
#------------------------------------------------------------------------- | |
# Represent | |
#------------------------------------------------------------------------- | |
def _represent_default_basis(self, **options): | |
raise NotImplementedError('This object does not have a default basis') | |
def _represent(self, *, basis=None, **options): | |
"""Represent this object in a given basis. | |
This method dispatches to the actual methods that perform the | |
representation. Subclases of QExpr should define various methods to | |
determine how the object will be represented in various bases. The | |
format of these methods is:: | |
def _represent_BasisName(self, basis, **options): | |
Thus to define how a quantum object is represented in the basis of | |
the operator Position, you would define:: | |
def _represent_Position(self, basis, **options): | |
Usually, basis object will be instances of Operator subclasses, but | |
there is a chance we will relax this in the future to accommodate other | |
types of basis sets that are not associated with an operator. | |
If the ``format`` option is given it can be ("sympy", "numpy", | |
"scipy.sparse"). This will ensure that any matrices that result from | |
representing the object are returned in the appropriate matrix format. | |
Parameters | |
========== | |
basis : Operator | |
The Operator whose basis functions will be used as the basis for | |
representation. | |
options : dict | |
A dictionary of key/value pairs that give options and hints for | |
the representation, such as the number of basis functions to | |
be used. | |
""" | |
if basis is None: | |
result = self._represent_default_basis(**options) | |
else: | |
result = dispatch_method(self, '_represent', basis, **options) | |
# If we get a matrix representation, convert it to the right format. | |
format = options.get('format', 'sympy') | |
result = self._format_represent(result, format) | |
return result | |
def _format_represent(self, result, format): | |
if format == 'sympy' and not isinstance(result, Matrix): | |
return to_sympy(result) | |
elif format == 'numpy' and not isinstance(result, numpy_ndarray): | |
return to_numpy(result) | |
elif format == 'scipy.sparse' and \ | |
not isinstance(result, scipy_sparse_matrix): | |
return to_scipy_sparse(result) | |
return result | |
def split_commutative_parts(e): | |
"""Split into commutative and non-commutative parts.""" | |
c_part, nc_part = e.args_cnc() | |
c_part = list(c_part) | |
return c_part, nc_part | |
def split_qexpr_parts(e): | |
"""Split an expression into Expr and noncommutative QExpr parts.""" | |
expr_part = [] | |
qexpr_part = [] | |
for arg in e.args: | |
if not isinstance(arg, QExpr): | |
expr_part.append(arg) | |
else: | |
qexpr_part.append(arg) | |
return expr_part, qexpr_part | |
def dispatch_method(self, basename, arg, **options): | |
"""Dispatch a method to the proper handlers.""" | |
method_name = '%s_%s' % (basename, arg.__class__.__name__) | |
if hasattr(self, method_name): | |
f = getattr(self, method_name) | |
# This can raise and we will allow it to propagate. | |
result = f(arg, **options) | |
if result is not None: | |
return result | |
raise NotImplementedError( | |
"%s.%s cannot handle: %r" % | |
(self.__class__.__name__, basename, arg) | |
) | |