Spaces:
Sleeping
Sleeping
File size: 14,005 Bytes
6a86ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
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 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)
)
|