refactor: added function for converting qibo circuit to quimb circuit directly. modified expectation making use of it. added new test script.
This commit is contained in:
50
examples/quimb_intro/test.py
Normal file
50
examples/quimb_intro/test.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import numpy as np
|
||||||
|
import jax
|
||||||
|
from qibo.backends import construct_backend
|
||||||
|
from qibo import Circuit, gates, hamiltonians
|
||||||
|
from qibo.symbols import Z, X, Y
|
||||||
|
|
||||||
|
# construct qibotn backend
|
||||||
|
quimb_backend = construct_backend(backend="qibotn", platform="quimb")
|
||||||
|
|
||||||
|
quimb_backend.setup_backend_specifics(
|
||||||
|
qimb_backend="jax",
|
||||||
|
optimizer='auto-hq'
|
||||||
|
)
|
||||||
|
|
||||||
|
quimb_backend.configure_tn_simulation(
|
||||||
|
max_bond_dimension=10
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# define Hamiltonian
|
||||||
|
form = 0.5 * Z(0) * Z(1) +- 1.5 * X(0) * Z(2) + Z(3)
|
||||||
|
hamiltonian = hamiltonians.SymbolicHamiltonian(form)
|
||||||
|
|
||||||
|
|
||||||
|
# define circuit
|
||||||
|
def build_circuit(nqubits, nlayers):
|
||||||
|
"""Construct a Qibo parametric quantum circuit."""
|
||||||
|
circ = Circuit(nqubits)
|
||||||
|
for _ in range(nlayers):
|
||||||
|
for q in range(nqubits):
|
||||||
|
circ.add(gates.RY(q=q, theta=0.))
|
||||||
|
circ.add(gates.RZ(q=q, theta=0.))
|
||||||
|
[circ.add(gates.CNOT(q%nqubits, (q+1)%nqubits) for q in range(nqubits))]
|
||||||
|
circ.add(gates.M(*range(nqubits)))
|
||||||
|
return circ
|
||||||
|
|
||||||
|
nqubits = 4
|
||||||
|
circuit = build_circuit(nqubits=nqubits, nlayers=3)
|
||||||
|
|
||||||
|
|
||||||
|
def f(params):
|
||||||
|
circuit.set_parameters(params)
|
||||||
|
return quimb_backend.expectation(
|
||||||
|
circuit=circuit,
|
||||||
|
observable=hamiltonian,
|
||||||
|
)
|
||||||
|
|
||||||
|
parameters = np.random.uniform(-np.pi, np.pi, size=len(circuit.get_parameters()))
|
||||||
|
print(f(parameters))
|
||||||
|
print(jax.value_and_grad(f)(parameters))
|
||||||
@@ -3,6 +3,8 @@ import warnings
|
|||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import jax
|
||||||
|
import jax.numpy as jnp
|
||||||
import quimb as qu
|
import quimb as qu
|
||||||
import quimb.tensor as qtn
|
import quimb.tensor as qtn
|
||||||
from qibo.backends import NumpyBackend
|
from qibo.backends import NumpyBackend
|
||||||
@@ -12,6 +14,43 @@ from qibo.result import QuantumState
|
|||||||
from qibotn.backends.abstract import QibotnBackend
|
from qibotn.backends.abstract import QibotnBackend
|
||||||
from qibotn.result import TensorNetworkResult
|
from qibotn.result import TensorNetworkResult
|
||||||
|
|
||||||
|
GATE_MAP = {
|
||||||
|
"h": "H",
|
||||||
|
"x": "X",
|
||||||
|
"y": "Y",
|
||||||
|
"z": "Z",
|
||||||
|
"s": "S",
|
||||||
|
"sdg": "SDG",
|
||||||
|
"t": "T",
|
||||||
|
"tdg": "TDG",
|
||||||
|
"sx": "SX",
|
||||||
|
"sxdg": "SXDG",
|
||||||
|
"rx": "RX",
|
||||||
|
"ry": "RY",
|
||||||
|
"rz": "RZ",
|
||||||
|
"u1": "U1",
|
||||||
|
"u2": "U2",
|
||||||
|
"u3": "U3",
|
||||||
|
"cx": "CNOT",
|
||||||
|
"cnot": "CNOT",
|
||||||
|
"cy": "CY",
|
||||||
|
"cz": "CZ",
|
||||||
|
"iswap": "ISWAP",
|
||||||
|
"swap": "SWAP",
|
||||||
|
"ccx": "CCX",
|
||||||
|
"toffoli": "CCX",
|
||||||
|
"ccz": "CCZ",
|
||||||
|
"cswap": "CSWAP",
|
||||||
|
"fredkin": "CSWAP",
|
||||||
|
"crx": "CRX",
|
||||||
|
"cry": "CRY",
|
||||||
|
"crz": "CRZ",
|
||||||
|
"fsim": "FSIM",
|
||||||
|
"rxx": "RXX",
|
||||||
|
"ryy": "RYY",
|
||||||
|
"rzz": "RZZ",
|
||||||
|
"m": None, # measurement, skip
|
||||||
|
}
|
||||||
|
|
||||||
class QuimbBackend(QibotnBackend, NumpyBackend):
|
class QuimbBackend(QibotnBackend, NumpyBackend):
|
||||||
|
|
||||||
@@ -147,6 +186,50 @@ class QuimbBackend(QibotnBackend, NumpyBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def expectation(self, circuit, observable):
|
def expectation(self, circuit, observable):
|
||||||
|
"""
|
||||||
|
Compute the expectation value of a Qibo-friendly ``observable`` on the Tensor Network constructed from a Qibo ``circuit``.
|
||||||
|
This method takes a Qibo-style symbolic Hamiltonian (e.g., `X(0)*Z(1) + 2.0*Y(2)*Z(0)`)
|
||||||
|
as the observable, converts it into a Quimb observable and computes its expectation
|
||||||
|
value using the provided circuit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
circuit: A Qibo quantum circuit object on which the expectation value
|
||||||
|
is computed.
|
||||||
|
observable: The observable whose expectation value we want to compute.
|
||||||
|
This must be provided in the symbolic Hamiltonian form supported by Qibo
|
||||||
|
(e.g., `X(0)*Y(1)` or `Z(0)*Z(1) + 1.5*Y(2)`).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The expectation value (real part).
|
||||||
|
"""
|
||||||
|
|
||||||
|
'''Convert Qibo observables to Quimb'''
|
||||||
|
operators_list, sites_list, coeffs_list = self._qiboobs_to_quimbobs(observable)
|
||||||
|
|
||||||
|
'''Convert Qibo circuit to Quimb circuit'''
|
||||||
|
parameters = circuit.get_parameters()
|
||||||
|
quimb_circuit = self._qibo_circuit_to_quimb(
|
||||||
|
circuit, quimb_circuit_type=qtn.Circuit, to_backend=jnp.array, convert_eager=True
|
||||||
|
)
|
||||||
|
quimb_parameters = {
|
||||||
|
key: jnp.asarray(parameters[i]) for i, key in enumerate(quimb_circuit.get_params().keys())
|
||||||
|
}
|
||||||
|
quimb_circuit.set_params(quimb_parameters)
|
||||||
|
|
||||||
|
'''Compute expectation value'''
|
||||||
|
expectation_value = 0.0
|
||||||
|
for ops, sites, coeffs in zip(operators_list, sites_list, coeffs_list):
|
||||||
|
exp_values = quimb_circuit.local_expectation(
|
||||||
|
ops,
|
||||||
|
where=sites,
|
||||||
|
backend=self.backend,
|
||||||
|
optimize=self.optimizer
|
||||||
|
)
|
||||||
|
expectation_value = expectation_value + coeffs * exp_values
|
||||||
|
|
||||||
|
return jnp.real(expectation_value)
|
||||||
|
|
||||||
|
def expectation_old(self, circuit, observable):
|
||||||
"""Compute the expectation value of a Qibo-friendly ``observable`` on the Tensor Network constructed from a Qibo ``circuit``.
|
"""Compute the expectation value of a Qibo-friendly ``observable`` on the Tensor Network constructed from a Qibo ``circuit``.
|
||||||
|
|
||||||
This method takes a Qibo-style symbolic Hamiltonian (e.g., `X(0)*Z(1) + 2.0*Y(2)*Z(0)`)
|
This method takes a Qibo-style symbolic Hamiltonian (e.g., `X(0)*Z(1) + 2.0*Y(2)*Z(0)`)
|
||||||
@@ -191,6 +274,7 @@ class QuimbBackend(QibotnBackend, NumpyBackend):
|
|||||||
else:
|
else:
|
||||||
circ_ansatz = qtn.circuit.Circuit
|
circ_ansatz = qtn.circuit.Circuit
|
||||||
circ = circ_ansatz.from_openqasm2_str(circuit.to_qasm())
|
circ = circ_ansatz.from_openqasm2_str(circuit.to_qasm())
|
||||||
|
|
||||||
expectation_value = 0.0
|
expectation_value = 0.0
|
||||||
for ops, sites, coeffs in zip(
|
for ops, sites, coeffs in zip(
|
||||||
operators_list_grouped, sites_list_grouped, coeffs_list_grouped
|
operators_list_grouped, sites_list_grouped, coeffs_list_grouped
|
||||||
@@ -283,3 +367,42 @@ class QuimbBackend(QibotnBackend, NumpyBackend):
|
|||||||
C_new = list(grouped_C.values())
|
C_new = list(grouped_C.values())
|
||||||
|
|
||||||
return A_new, B_new, C_new
|
return A_new, B_new, C_new
|
||||||
|
|
||||||
|
def _qibo_circuit_to_quimb(self, qibo_circ, quimb_circuit_type=qtn.Circuit, **circuit_kwargs):
|
||||||
|
"""
|
||||||
|
Convert a Qibo Circuit to a Quimb Circuit.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
qibo_circ : qibo.models.circuit.Circuit
|
||||||
|
The circuit to convert.
|
||||||
|
quimb_circuit_type : type
|
||||||
|
The Quimb circuit class to use (Circuit, CircuitMPS, etc).
|
||||||
|
circuit_kwargs : dict
|
||||||
|
Extra arguments to pass to the Quimb circuit constructor.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
circ : quimb.tensor.circuit.Circuit
|
||||||
|
The converted circuit.
|
||||||
|
"""
|
||||||
|
nqubits = qibo_circ.nqubits
|
||||||
|
quimb_gates = []
|
||||||
|
|
||||||
|
for gate in qibo_circ.queue:
|
||||||
|
gname = getattr(gate, "name", None)
|
||||||
|
qname = GATE_MAP.get(gname, None)
|
||||||
|
if qname is None:
|
||||||
|
continue # skip measurements and unknown gates
|
||||||
|
|
||||||
|
# Handle parametrized gates (Qibo: .parameters, Quimb: expects flat tuple)
|
||||||
|
params = getattr(gate, "parameters", ())
|
||||||
|
qubits = getattr(gate, "qubits", ())
|
||||||
|
|
||||||
|
# Quimb expects (*params, *qubits)
|
||||||
|
gate_spec = (qname,) + tuple(params) + tuple(qubits)
|
||||||
|
quimb_gates.append(gate_spec)
|
||||||
|
|
||||||
|
circ = quimb_circuit_type(nqubits, **circuit_kwargs)
|
||||||
|
circ.apply_gates(quimb_gates)
|
||||||
|
return circ
|
||||||
Reference in New Issue
Block a user