tequilahub / tequila

A High-Level Abstraction Framework for Quantum Algorithms
MIT License
362 stars 101 forks source link

How to save and load parameterized circuits? #216

Closed jjgoings closed 2 years ago

jjgoings commented 2 years ago

Sorry if this isn't the best place to ask this. I was hoping to be able to save and load parameterized circuits. For example,

import tequila as tq

geomstring="H 0.0 0.0 0.0\n\
              H 0.0 0.0 0.75"
mol = tq.Molecule(geometry=geomstring, backend='pyscf', basis_set='sto3g')

U = mol.make_ansatz(name='UCCSD')
print(U)

tq.circuit.qasm.export_open_qasm(U,filename='example_circuit.txt', variables={(1, 0, 1, 0) : 1.0, (1, 0):2.0})

gives the circuit

circuit:
X(target=(0,))
X(target=(1,))
FermionicExcitation(target=(0, 1, 2, 3), control=(), parameter=f([(1, 0, 1, 0)]))
FermionicExcitation(target=(0, 1, 2), control=(), parameter=f([(1, 0)]))
FermionicExcitation(target=(1, 2, 3), control=(), parameter=f([(1, 0)]))

and I've attached the saved qasm 2.0 file (example_circuit.txt.) But in the saved output it does not seem the variables are passed correctly, if at all. Is there a way to do this? I wasn't sure if I needed to pass in a tq.Variable object, but this does not seem to change anything and I get an error in convert_to_open_qasm_2 saying TypeError: Argument 'a' of type <class 'tequila.objective.objective.Variable'> is not a valid JAX type.

Similarly, is there a way to read in parameterized circuits into Tequila?

If QASM is not the way to go, that's fine too ... just would like some way to save / load / share circuits.

jjgoings commented 2 years ago

example_circuit.txt

kottmanj commented 2 years ago

It is probably the best place to ask - especially since that question arises often. Then we have it online for others to find.

We currently don't have a standardised procedure since circuits can be different in structure (mostly the angles make it difficult as they can be plain numbers, variables, but also to.Objectives i.e. arbitrary complicated objects).

I personally always reconstruct instead of save/load as I find it more convenient. But it depends on the task at hand.

I'll give some examples for different circuit types that maybe are helpful:

  1. Simple circuits: All parameters are numbers or plain Variables. In this case it's easiest to just use pickle
import pickle
import tequila as tq

U = tq.gates.Ry("a",0) + tq.gates.Ry(1.0,1)
with open("circuit.pickle", "wb") as f:
    pickle.dump(U,f)

with open("circuit.pickle", "rb") as f:
    U2 = pickle.load(f)
  1. More complicated: Often works with dill instead of pickle (reason: in the construction of the tq.Objective lambdas are used and they can not be pickled)
    
    import dill
    import tequila as tq

a = tq.Variable("a") # this is a variable f = a**2 + 5.0 # this is a tq.Objective U = tq.gates.Ry(f,0) + tq.gates.Ry(1.0,1) with open("circuit.pickle", "wb") as f: dill.dump(U,f)

with open("circuit.pickle", "rb") as f: U2 = dill.load(f)


3. Complicated: e.g. when you used tq.grad at this point jax complied gradients are in the circuit parameters. Here I don't have a good way to save/load (instead of saving the entire jax tree, but this is more complicated than reconstruction)

Potentially useful for workarounds
```python

# U is a circuit with complicated parameters

# map all to primitive variables
variables=U.extract_variables()
M = {variables[i]:tq.Variable((i,))}
U2 = U.map_variables(M)

now U2 can be saved and loaded with pickle and the variables can be changed again after loading.

Regarding qasm: If I remember correctly, the issue was that, at least back then, open qasm 2 did not support variables. So you always need to pass a variables dictionary to the export function that sets explicit numbers. I heard it can be done now with open qasm 3.

If you have a good idea on how to integrate save/load or if you want to give it a go with the suggestion here + some if/else business or qasm3 support feel free to make a PR :-).

Disclaimer: Trying to write from my phone (internet issues at home) .... Don't know how the formatting will look and I might have some syntax errors in the code.

kottmanj commented 2 years ago

Maybe also helpful: I Had to write a small serialiser once for a project, but since it was not able to handle all types of parameters I did not integrate it into tq. Maybe it gives some inspiration

https://github.com/kottmanj/genencoder

jjgoings commented 2 years ago

Hey thanks! What worked for me (thanks to your help) was

import dill
import tequila as tq

geomstring="H 0.0 0.0 0.0\nH 0.0 0.0 0.75"
mol = tq.Molecule(geometry=geomstring, backend='pyscf', basis_set='sto6g')

# get the qubit hamiltonian
H = mol.make_hamiltonian()

# get the ansatz (circuit)
U = mol.make_ansatz(name='UCCSD')

with open("circuit.pickle", "wb") as f:
    dill.dump(U,f)

with open("circuit.pickle", "rb") as f:
    U2 = dill.load(f)

variables = U2.extract_variables()
M = {variables[i]:tq.Variable(i) for i in range(len(variables))}
U2 = U2.map_variables(M)

# define the expectation value
E = tq.ExpectationValue(H=H, U=U2)

# minimize the expectation value
result = tq.minimize(E, method='COBYLA')
print("VQE : {:+2.8}".format(result.energy))