Overhaul of Qobj dimension handling #1476
Replies: 3 comments 1 reply
-
There's maybe some tricks here to do with multiprocessing and pickle/unpickle, but since the objects are deterministic and completely immutable, I can't see anything inherently wrong with a singleton approach here. These objects are purely data; they must not have behaviour attached to them (methods) only immutable state (properties), so they're inherently thread-safe. |
Beta Was this translation helpful? Give feedback.
-
Using singleton will certainly speed thing up.
|
Beta Was this translation helpful? Give feedback.
-
We could introduce |
Beta Was this translation helpful? Give feedback.
-
This document is a design specification for new dimensions handling. It is only a draft right now; please feel free to offer comments and suggestions.
If you only read one section, read "Overview" inside "Proposal" to get the gist of what will happen.
Background
For an extended discussion of some of the problems in 4.x branch dimension handling, see #1320.
With the new system, we aim to solve a few main problems:
Qobj
type
inference should be instantaneousCurrently, dimension handling is the major overhead in
Qobj
because these problems are not solved.The allowed exception to point 1 is if we include a short-hand notation to represent dimension objects; we may allow a "pure-Python" representation (effectively the current dimension specification) to be parsed into new-form dimension objects for user convenience.
Certain objects, like the excitation-number-restricted spaces (enr) may not have "compatible" dimensions and shapes. This may need further discussion elsewhere.
Proposal
Overview
The principle change is to make dimension objects singleton instances of classes. All
Qobj
of the same dimension will have a reference to the exact same object, which has all the expensive operations already calculated.Internally, dimensions will represented in a very pure linear algebra manner. A dimension object is a single
Dims
object, which is exactly one of the subclasses:Space
representing a vector spaceSpace(size: int)
is a standard ketSpace(Map(...))
is an operator-ketSpace(Map(from, to))
representing an operator in column-stacked format.Map(from, to)
representing some mappingDims -> Dims
Field
used only to represent the absence ofDims
inMap
andCompound
. AQobj
may not have thisdims
; it would simply be a complex number.Compound(dims1, dims2, ...)
for tensor-product spaces.The current
Qobj.type
values (with no tensor products) map like so:Space
Map(Space, Field)
Map(Space, Space)
Space(Map(Space, Space))
Map(Space(Map(Space, Space)), Field)
Map(Space(Map(Space, Space)), Space(Map(Space, Space)))
Users will not have to type out such monstrosities as the mapping for
super
; the current QuTiP dims syntax will be parsed into these types, but internally this form will almost completely remove parsing costs.Some explicit mappings between the current list syntax and the new parsed syntax:
[[2], [1]]
=Space(2)
: a qubit pure state[[2, 10], [1, 1]]
=Compound(Space(2), Space(10))
: a pure state of a qubit space tensor-product a 10-element space[[2, 2], [2, 2]]
=Map(Compound(Space(2), Space(2)), Compound(Space(2), Space(2)))
: a square operator on a two-qubit state[[4, 5], [2, 2]]
=Map(Compound(Space(2), Space(2)), Compound(Space(4), Space(5)))
: (note the reversal) a non-square operator that takes a two-qubit state to a tensor-product space between a 4-element space and 5-element space[[[2], [2]], [[2], [2]]]
=Map(Space(Map(Space(2), Space(2))), Space(Map(Space(2), Space(2))))
: a superoperator acting on square operators on qubit spaces.[[1], [5]]
=Map(Space(5), Field)
is a bra for a 5-element space.The current
Qobj.type
attribute will be stored within the dimension object; unlike the list format, each object is unambiguously one single type (1D spaces are a problem in list form). Similarly, the "size" of a given dimension object is stored within it.How this solves the problems
Type inference is removed as a problem; each
Qobj
type has only one unambiguous representation when expressed as dimension objects. The actual name of the type could be stored as a string attached to the objects to maintain compatibility with the 4.x branch.Dimension compatibility test speed are solved by having dimensions represented by singleton class instances like the Python builtin
None
. The reason to use a singleton class is to replace==
tests withis
tests; the former is structural equality and requires walking the tensor structure, whereas the latter is referential equality, and is true if and only if the two operands are the same object in memory. For example the dimensions test of theadd
operation is nowleft.dims is right.dims
, which is the same speed as comparing two integers.Dimension/shape compatibility is solved by attaching size information into the singleton classes. As the dimension objects are singletons, the size of a dimension object is calculated only on creation of the object. All subsequent
Qobj
that are of the same dimensions as one that came earlier will consequently reuse the same dimensions object, which already calculated its size. This avoids (relatively) expensive calls tonp.prod
on Python lists.The current list syntax allows for invalid dimensions to be represented such as
[2, 1]
(should be[[2], [1]]
, probably). These sorts of failures cannot be represented in the new system. Similarly,[[2], [1], [1]]
cannot be represented as theMap
constructor will take only two arguments.Problems this does not immediately solve
Since QuTiP uses matrices to represent linear algebra objects, we tie ourselves to working in some particular basis. For example, it is invalid to add a vector in the Pauli-Z basis to one in the Pauli-X basis by element-wise addition, but QuTiP has no way of knowing if this is what the user is doing, and will simply allow it because the dimensions will match. This is still the case if the user used
Qobj.transform
to get from one to the other; it is one case where we have to trust that the user is doing the right thing, rather than enforcing correctness. In the future, the system proposed here could be extended to enforce this; thedims
parameter would be renamedbasis
, and some unique identifier would be attached to eachSpace
object. This would allow us to safely define basis-transformation "operators"; they would have the dimensions objectMap(State(2, 'paulix'), State(2, 'pauliz'))
, or something to that effect.In #1320, I mentioned the possibility of a new
'scalar'
type object. Here, this is effectively theField
subtype. There is a choice to be made whetherCompound(Field, Field)
should beField
(implicit contraction of 1D spaces), or whether we should keep track of "missing" spaces. The missing spaces are useful in principle in QIP settings for defining local operations on subsets of the whole system, but right now we do not have the mathematics backend to implement this completely. For now, I propose we keep track of all the missing spaces; it allows this extension in the future, with no cost right now.Implementation details
All objects will be completely immutable, and all their construction arguments will be as well (e.g.
State
will take onlyint
, which is immutable). This means that singleton instances can be found by looking them up in a global store, similar to Python'sbuiltin
package or itsimport
system.The singleton nature of the dimension classes is achieved by defining
__new__
for the instances, and not__init__
. The former is effectively a class method, while the latter is an instance method; since we want instances representing the same object to be unique, we don't want it.In order to maintain referential equality, tensor-product operations must move into a canonical form. Calling
Compound(Compound(x, y), z)
must return the same object asCompound(x, y, z)
. Internally this parsing is easy; if one is using the new object constructors, Python evaluation order guarantees that they will flatten themselves; so long as theCompound
constructor unpacksCompound
objects at a depth of 1, the whole object will always be as flat as possible.The tensor product will be expanded by having
Compound
"thread" overMap
. This effectively expands the mathematicians' definition of the tensor product to allows us to continue to represent "silly" objects such aswhich is an odd object that contracts one element of a tensor-product space down to the field and leaves the other. This will report its
Qobj.type
as'other'
, since it is not a standard operation, but that's ok because we no longer needQobj.type
for fast dimension parsing.The
Compound
threading overMap
goes follows these rules:Compound(Map(x1, y1), Map(x2, y2))
isMap(Compound(x1, x2), Compound(y1, y2))
Compound(Map(x1, y1), Space(z))
isMap(Compound(x1, Field), Compound(y1, Space(z)))
In other words, the
from
andto
fields inside maps areCompound
ed with their counterparts, andSpace
is "promoted" toMap(Field, Space)
. This latter object is not actually valid, butSpace
will behave as if it were withinCompound
. Related but different,Compound(Field, Field)
will exist for the purposes of tensor-product'bra'
types as theto
field ofMap
(to allow us to keep track of empty spaces), but aQobj
whose dimensions would be aCompound
made entirely ofField
will instead become a Python complex number.The dimensions types should be available for advanced users (to allow them to access the full parsing speed-ups), but should not be presented as the standard choice. I propose we place the types inside a nested namespace, such as
qutip.dims
(logically - physically it would bequtip/core/dims.py
), to allow the formfrom qutip.dims import *
where appropriate without forcing the user to do the modern bad practice left over from our MATLAB pastfrom qutip import *
.User impact
In principle, nothing will change for the normal QuTiP user compared to the 4.x branch. You will still be able to supply the
dims
argument to theQobj
constructor as lists in the exact same format, and they will be parsed in the same way. Users do not need to type out the new computer-friendly dimensions objects, but they will be available for advanced users who frequently makeQobj
using the raw constructor with funny dimensions. We will publicly providequtip.dims.parse
to turn a list into the new form, so even advanced users do not need to type out all the nonsense.Qobj
factory functions that take adims
parameter should now also accept the new form. Since almost all of them just pass this directly to thedims
argument in theQobj
constructor, this likely won't involve any developer effort.Qobj
construction overhead should be reduced to near-zero when passed a new dimensions object, which we will always do within the library. Compared to the 4.x branch, the overhead ofQobj
will shrink from ~100µs to ~1µs in library code, even for functions where theQobj
type cannot be cleanly inferred from the input types.Particular points worth commenting on
Qobj
that cannot be represented with this system?dims
andtype
inQobj.__repr__()
?Beta Was this translation helpful? Give feedback.
All reactions