[RFC][Ecosystem] Hooks for integrating with the Qobj constructor : `__qobj__` interface
See original GitHub issueHello, I’m the Lead developer of netket, a jax-based framework for using Machine-Learning techiques to study quantum systems (open and closed).
We are finalising the next release and as I am a big fan of extensibility, I would like our two frameworks to coexist and provide a sensible, easy api to work together. Mainly, what I would like to do is provide an easy-to-use no-documentation-needed to convert NetKet’s types to Qutip, so that it becomes easy to use it to check our variational calculations.
TLDR: I propose creating and documenting a __qobj__
interface so that arbitrary objects having this method will support conversion to a QuTiP Qobj.
–
NetKet has mainly three types that are concerned: hilbert spaces, operators and variational states.
Hilbert spaces describe the space every object is defined upon, and can be easily converted to your dims
format.
Operators are used to represent operators and super-operators acting on hilbert spaces. We use a very custom format roughly corresponding to a lazy kronecker product. Those object satisfy the numpy __array__
interface and can be converted by calling np.array(netket_operator)
or np.asarray(netket_operator)
. We can also obtain sparse representations (but there is no nice api to do that, so we simply provide a method netket_operator.to_sparse()
.
Variational states can also be converted to vectors or matrices (kets and density operators) as they also support the __array__
interface.
I would like our users to be able to call Qobj(netket_operator)
or Qobj(netket_state)
and obtain the corresponding qutip object. We could, in principle support a netket_operator.to_qobj()
conversion method, but I am strongly opposed to that as this is an ugly design pattern: a way to construct a Qobj
already exists, it’s Qobj(...)
, and the simplest-to-use api is, in my opinion, to overload this constructor to perform the conversion.
This is a standard design pattern in Julia, which I believe has the best ecosystem inter-compatibility thanks to a standardisation of this pattern through (multiple)dispatch.
Numpy too, does the same, through the __array__
interface: any object that defines a __array__(self, dtype=None)->np.ndarray
method will be supported by np.asarray
and np.array
.
Proposal: I would like QuTiP to support a __qobj__
interface, meaning that your Qobj
constructor should check if an object has this method, and if it has, then use it to convert it to a Qobj.
It should be relatively simple and involve adding another else-if in your constructor
if isinstance(inpt, Qobj):
...
elif inpt is None:
...
...
elif hasattr(inpt, "__qobj__"):
data = input.__qobj(options)
self.shape = data.shape
...
The only thing to do would be discussing the interface itself and document what it should return.
This will allow other software in the future to integrate with qutip, too, and I believe will help shape the ecosystem.
Issue Analytics
- State:
- Created 2 years ago
- Comments:22 (11 by maintainers)
Top GitHub Comments
Apologies for another long delay. I’d be happy to hop on a video call sometime to discuss the design.
I think the
__qutip_qobj__
interface is likely to remain fairly simple and be just:and we can figure out how to make that work on our side.
How that looks under the hood depends quite a bit on whether the
Data
object will growdims
support or not. We have another use case fordims
on the data layer (a tensor network data backend that we’re starting work on) but it is a bit of a philosophical shift in what theData
layer is, so we’ll need to think carefully (e.g. allData
operations would have to keep track ofdims
which is a big change; and it’s also a bit strange because the operations themselves are just 2D matrix operations, where dims are meaningless).@PhilipVinc I’m not sure if we discussed this before, but do you have some short code snippets showing how you see this Qobj support being used in user code? Apologies if I missed such examples elsewhere in the discussion already.
— Let me begin by saying that I completely understand your concerns. I am not trying to enforce a view on the QuTiP project, but rather as I see I would like the various frameworks in the vast ‘quantum’ ecosystem to coexist in such a way that makes easy for users to jump from one tool to the other easily.
I personally envisage an ecosystem where one can write down an hamiltonian in the tool of choice, obtain the exact time evolution with qutip, and maybe have a look at it’s semiclassical trajectory. Jump into netket and check if a variational representation can properly capture the correlations. Trotterize the hamiltonian and get a circuit, without worrying about how to convert from one framework to the other.
I want to start the discussion on this topic eagerly because I am aware that it will take a fairly long time. If there is anything I can do to make your future internal discussions easier, do let me know.
Indeed. I was just trying to play around with this.
I completely agree with your approach. I’m simply trying to push for this to be something that will come out in a reasonable timeframe and not be something forgotten.
I do understand your point, and I see how you want to be backward and forward compatible, however I don’t think that my proposal would break different tools in the same environment. What I propose would simply prevent
qutip.Qobj(otherlibraryobject)
from working (with an explicative error message) ifotherlibrary
does not support the same qobj interface version. Everything else would work the same. You can still import and use the two libraries independently without issues.It’s indeed possible to design around this and supporting at the same time different versions of the interface, however, and there’s value in doing so.
I don’t know enough of the history behind numpy current api, but I see your point. however I’d like to reiterate that this is not what I am proposing. I think there is considerable difference between supporting arbitrary operations
new_obj = Qobj + netket_operator*5
(though if you are going to export dispatch hooks in5.0
, this would be possible in a consistent way, I think) andqutip.mesolve(netket_operator, ...)
. In the first, I do agree with your point that the nature python’s__add__/__radd__
is not commutative, leading to surprising behavior. From my point of view, no package really owns+
so any conversion would be implicit. Butqutip.groundstate(netket_operator)
is in itself an explicit cast. I am explicitly asking to qutipplease give me the ground state of this object
. I do expect to get a Qobj out, and qutip to make an effort converting this object to whatever format he internally wants to work with. Same goes with time evolution.I do prefer such an approach. I just did not think that was an option in QuTiP. In fact we have redesigned NetKet to allow for something similar using multiple dispatch. (I’m not familiar with Tensorflow either.)
I do agree with all your points, especially the first.
As for your last point, about downstream packages having to import qutip: This is indeed a potential issue. I’ll try to investigate this: maybe there is a solution whereby a callback executed on package load can be registered with importlib?