from abc import ABCMeta, abstractmethod, abstractproperty
import warnings
import numpy as np
import sympy
import sympy.physics.quantum as sq
from parampy import Parameters
from .operator import Operator
[docs]class Basis(object):
'''
A Basis instance describes a particular basis, and allows transformations
of objects (such as `Operator`s) from one basis to another. A Basis
is an abstract class, and must be subclassed to be useful.
:param dim: The dimension of the basis. If not specified, the dimension will
be extracted from the Operator returned by Basis.operator; except during
`Basis.init`, where `Basis.dim` will return the raw value stored (e.g. None).
:type dim: int or None
:param parameters: A Parameters instance, if required.
:type parameters: parampy.Parameters
:param kwargs: Additional keyword arguments to pass to `Basis.init`.
:type kwargs: dict
Subclassing Basis:
Subclasses of Basis must implement the following methods, which function according to
their respective documentation below:
- init
- operator
Subclasses may optionally implement:
- state_info
- state_toString
- state_fromString
- state_latex
These latter methods are used to allow convenient conversion of strings to states
and also later representation of states as strings/LaTeX. Otherwise, these
methods are not required. Since they are not used except when the user desires
to change the state's representation, the implementer has a lot of freedom
about the way these functions work, and what they return. The documentation
for these methods indicates the way in which the original author intended
for them to function.
'''
__metaclass__ = ABCMeta
def __init__(self, dim=None, parameters=None, **kwargs):
self.dim = dim
self.p = parameters
self._basis_initialising = True
self.init(**kwargs)
del self._basis_initialising
@abstractmethod
[docs] def init(self, **kwargs):
'''
This method should do whatever is necessary to prepare the
Basis instance for use. When this method is called by the
Python __init__ method, you can use `Basis.dim` to access
the raw value of `dim`. If `dim` is necessary to construct
the operator, and it is not set, this method should raise an
exception. All keyword arguments except `dim` and `parameters`
passed to the `Basis` instance constructor will also be passed to
this method.
'''
pass
def __repr__(self):
return "<%s(Basis) of dimension %d>" % (self.__class__.__name__, self.dim)
@property
def dim(self):
'''
The dimension of the basis; or equivalently, the number of basis states.
'''
if self.__dim == None and not getattr(self,'_basis_initialising',False):
self.__dim = self.operator().shape[0]
return self.__dim
@dim.setter
def dim(self, dim):
try:
if self.__dim is not None:
raise ValueError("Attempt to change Basis dimension after initialisation. This is not supported.")
except AttributeError:
pass
self.__dim = int(dim) if dim is not None else None
@property
def p(self):
'''
A reference to the Parameters instance used by this object.
'''
if self.__p is None:
raise ValueError("Parameters instance required by Basis object, a Parameters object has not been configured.")
return self.__p
@p.setter
def p(self, parameters):
if parameters is not None and not isinstance(parameters, Parameters):
raise ValueError("Parameters reference must be an instance of Parameters or None.")
self.__p = parameters
def __p_ref(self):
'''
This method allows child Operator objects to automatically be kept up to date with
changes to the Parameters instance associated with the StateOperator object. This
cannot be used directly, but is used by the StateOperator.Operator method.
'''
return self.__p
[docs] def Operator(self, components, basis=None, exact=False):
'''
This method is a shorthand for constructing Operator objects which refer
to the same Parameters instance as this Basis instance.
:param components: Specification for Operator.
:type components: Operator, dict, numpy.ndarray or sympy.MatrixBase
:param basis: The basis in which the Operator is represented.
:type basis: Basis or None
:param exact: True if Operator should maintain exact representations of numbers,
and False otherwise.
:type exact: bool
If *components* is already an Operator object, it is returned with its
Parameters reference updated to point the Parameters instance associated
with this Basis instance. Otherwise, a new Operator is constructed according
to the specifications, again with a reference to this Basis's
Parameters instance.
For more information, refer to the documentation for Operator.
'''
if not isinstance(components, Operator):
operator = Operator(components)
operator.p = self.__p_ref
return operator
@abstractproperty
def operator(self):
'''
This method should return a two dimensional `Operator` object, with basis states as
columns. The `Operator` object should use the `Parameters` instance provided by the
Basis instance. The simplest way to ensure this is to use the `Basis.Operator` method.
'''
raise NotImplementedError("Basis operator has not been implemented.")
[docs] def states(self, **params):
'''
This method returns the basis states (columns of the `Operator` returned by
`basis.operator`) as a list. The Operator is first evaluated with the
parameter overrides in params.
:param params: A dictionary of parameter overrides. (see `parampy.Parameters`)
:type params: dict
'''
i = xrange(self.dim)
O = self.operator(**params)
return map(lambda i: O[:, i], i)
[docs] def state_info(self, state, params={}):
'''
This method (if implemented) should return a dictionary with more information
about the state provided. There are no further constraints upon what might be
returned.
:param state: The state about which information should be returned.
:type state: str or iterable
:param params: A dictionary of parameter overrides. (see `parampy.Parameters`)
:type params: dict
'''
return NotImplementedError("Basis.state_info has not been implemented.")
[docs] def state_toString(self, state, params={}):
'''
This method (if implemented) should return a string representation of the
provided state, which should then be able to be converted back into the same
state using `Basis.state_fromString`.
:param state: The state which should be represented as a string.
:type state: iterable
:param params: A dictionary of parameter overrides. (see `parampy.Parameters`)
:type params: dict
'''
raise NotImplementedError("Basis.state_toString has not been implemented.")
[docs] def state_fromString(self, string, params={}):
'''
This method (if implemented) should return the state as a numerical array that
is represented as a string in `string`. Calling `basis.state_toString` should then
return the same (or equivalent) string representation.
:param string: A string representation of a state.
:type state: str
:param params: A dictionary of parameter overrides. (see `parampy.Parameters`)
:type params: dict
'''
raise NotImplementedError("Basis.state_fromString has not been implemented.")
[docs] def state_latex(self, state, params={}):
'''
This method (if implemented) should return string that when compiled by
LaTeX would represent the state.
:param state: The state which should be represented as a string.
:type state: iterable
:param params: A dictionary of parameter overrides. (see `parampy.Parameters`)
:type params: dict
'''
raise NotImplementedError("Basis.state_latex has not been implemented.")
[docs] def state_toSymbolic(self, state):
'''
This method is a stub, and may be implemented in the future to provide the
logical inverse of `Basis.state_fromSymbolic`.
'''
raise NotImplementedError("Conversion of a state to a symbolic representation has not yet been implemented.")
[docs] def state_fromSymbolic(self, expr):
'''
This method converts a sympy representation of a quantum state into
an array or vector (as used by QuBricks). It uses internally `Basis.state_fromString`
to recognise ket and bra names, and to substitute them appropriately with the right
state vectors.
.. warning:: Support for conversion from symbolic representations is not fully
baked, but seems to work reasonably well.
'''
r = np.array(sq.represent(expr, basis=self.__sympy_basis).tolist(), dtype=object)
if len(r.shape) == 2:
if r.shape[0] == 1:
r = r[0, :]
else:
r = r[:, 0]
return r
@property
def __sympy_basis(self):
'''
Sympy requires an operator instance to determine how states should represent
themselves. This property provides such an instance.
'''
op = QubricksBasis(self.__class__.__name__)
op.qubricks = self
return op
#
# Transform vector and matrix elements from the standard basis to this basis
def __filter(self, output, state=None, threshold=False, params={}):
'''
Basis.__filter is a private method that implements the thresholding described in
the documentation for Basis.transform .
'''
def __filter_threshold(a):
# TODO: DO THIS MORE NEATLY?
'''
Determine the minimum threshold over real and imaginary components which should capture everything.
'''
try:
real_ind = np.where(np.abs(a.real) > 1e-15)
t_real = np.min(np.abs(a[real_ind])) if len(real_ind[0]) > 0 else np.inf
imag_ind = np.where(np.abs(a.imag) > 1e-15)
t_imag = np.min(np.abs(a[imag_ind])) if len(imag_ind[0]) > 0 else np.inf
return min(t_real, t_imag)
except:
return False
if threshold is False:
return output
warnings.warn("Be careful with auto-thresholding.")
threshold = __filter_threshold(self.operator(**params))
# print "op threshold", threshold
if state is not None:
if isinstance(state, Operator):
threshold = np.min(map(__filter_threshold, state.components.values())) # TODO: do more abstractly?
else:
threshold = min(__filter_threshold(state), threshold)
# print "w/ state threshold", threshold
if threshold is False:
return output
threshold *= 1e-8
# print "final threshold", threshold
if isinstance(state, Operator):
output.clean(threshold)
else:
ind = np.where(np.abs(output) < threshold)
output[ind] = 0
return output
[docs]class QubricksBasis(sq.Operator):
'''
This object is used internally to support symbolic representations of states.
'''
pass
# TODO: Flesh out sympy symbolic representation
[docs]class QubricksKet(sq.Ket):
'''
This object is used to represent states analytically.
For example:
>>> ket = QubricksKet('0')
These objects then obey standard arithmetic, for example:
>>> 2*ket
2|0>
You can convert from a symbolic representation of states
to a QuBricks array using `Basis.state_fromSymbolic`.
'''
def _represent_QubricksBasis(self, basis, **options):
if getattr(basis, 'qubricks') is None:
raise ValueError("The `qubricks` attribute must be set on the basis object for ket representation.")
# print str(self)
# print sympy.Matrix( basis.qubricks.state_fromString(str(self)) ).applyfunc(sympy.nsimplify)
return sympy.Matrix(basis.qubricks.state_fromString(str(self))).applyfunc(sympy.nsimplify)
def _eval_innerproduct_QubricksBra(self, bra, **hints):
return 1 if bra.label == self.label else 0
def _eval_conjugate(self):
return QubricksBra(*self.label)
@property
def strlabel(self):
return ",".join(map(str, self.label))
def __mul__(self, other):
if isinstance(other, QubricksKet):
return QubricksKet(",".join((self.strlabel, other.strlabel)))
return super(QubricksKet, self).__mul__(other)
def __rmul__(self, other):
if isinstance(other, QubricksKet):
return QubricksKet(",".join((other.strlabel, self.strlabel)))
return super(QubricksKet, self).__rmul__(other)
def __pow__(self, power):
r = 1
for _ in xrange(power):
r *= self
return r
@classmethod
[docs] def dual_class(self):
return QubricksBra
[docs]class QubricksBra(sq.Bra):
'''
This object is used to represent states analytically.
For example:
>>> bra = QubricksBra('0')
These objects then obey standard arithmetic, for example:
>>> 2*bra
2<0|
You can convert from a symbolic representation of states
to a QuBricks array using `Basis.state_fromSymbolic`.
'''
@classmethod
[docs] def dual_class(self):
return QubricksKet
def _represent_QubricksBasis(self, basis, **options):
if 'qubricks_basis' not in options:
raise ValueError("Qubricks basis object must be passed to ket for representation.")
basis = options['qubricks_basis']
l = ",".join(map(str, self.label))
return sympy.Matrix(basis.state_fromString("|%s>" % l).transpose()).applyfunc(sympy.nsimplify)