123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- 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 = ''
- @property
- 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
- @classmethod
- 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
- #-------------------------------------------------------------------------
- @property
- 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
- @property
- def is_symbolic(self):
- return True
- @classmethod
- 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
- @classmethod
- def _eval_args(cls, args):
- """Process the args passed to the __new__ method.
- This simply runs args through _qsympify_sequence.
- """
- return _qsympify_sequence(args)
- @classmethod
- 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 parenteses.
- """
- 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)
- #-------------------------------------------------------------------------
- # Methods from Basic and Expr
- #-------------------------------------------------------------------------
- def doit(self, **kw_args):
- return self
- #-------------------------------------------------------------------------
- # 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)
- )
|