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) | |
| ) | |