From 7afabe812e65dacc663144242996762adc0f4e76 Mon Sep 17 00:00:00 2001 From: GenericP3rson Date: Sun, 28 Apr 2024 15:03:06 -0400 Subject: [PATCH 1/3] [minor] PoC for the inverse function --- torchquantum/module/modules.py | 9 +++++++++ torchquantum/operator/standard_gates/paulix.py | 11 +++++++++++ torchquantum/operator/standard_gates/u3.py | 11 +++++++++++ 3 files changed, 31 insertions(+) diff --git a/torchquantum/module/modules.py b/torchquantum/module/modules.py index e766185c..656d550b 100644 --- a/torchquantum/module/modules.py +++ b/torchquantum/module/modules.py @@ -352,6 +352,15 @@ def get_unitary(self, x=None): unitary = qdev.get_states_1d().T return unitary + + def inverse_module(self): + assert self.Operator_list is not None + inverse_ops = [] + for op in self.Operator_list: + inverse_ops.append(op.inverse_operation(op.params)) + self.Operator_list = tq.QuantumModuleList(inverse_ops[::-1]) + + class QuantumModuleList(nn.ModuleList, QuantumModule, metaclass=ABCMeta): diff --git a/torchquantum/operator/standard_gates/paulix.py b/torchquantum/operator/standard_gates/paulix.py index f42bd81d..34b062bc 100644 --- a/torchquantum/operator/standard_gates/paulix.py +++ b/torchquantum/operator/standard_gates/paulix.py @@ -42,6 +42,17 @@ class CNOT(Operation, metaclass=ABCMeta): def _matrix(cls, params): return cls.matrix + def inverse_operation(self, params): + return CNOT( + has_params=self.has_params, + trainable=self.trainable, + init_params=self.init_params, + n_wires=self.n_wires, + wires=self.wires, + inverse=self.inverse, + ) + # return self + class C4X(Operation, metaclass=ABCMeta): """Class for C4X Gate.""" diff --git a/torchquantum/operator/standard_gates/u3.py b/torchquantum/operator/standard_gates/u3.py index 554c51ce..321d6b50 100644 --- a/torchquantum/operator/standard_gates/u3.py +++ b/torchquantum/operator/standard_gates/u3.py @@ -19,6 +19,17 @@ class U3(Operation, metaclass=ABCMeta): def _matrix(cls, params): return tqf.u3_matrix(params) + def inverse_operation(self, params): + params = self.params.squeeze(0).detach().numpy() + params = [-params[0], -params[2], -params[1]] + return U3( + has_params=self.has_params, + trainable=self.trainable, + init_params=params, + n_wires=self.n_wires, + wires=self.wires, + inverse=self.inverse, + ) U = U3 From d9b8d70006539e2df1211485cea83c5bb000b94f Mon Sep 17 00:00:00 2001 From: GenericP3rson Date: Sun, 28 Apr 2024 15:06:35 -0400 Subject: [PATCH 2/3] [minor] added example with tq-direct cuquantum integration --- .../tq_cuquantum_integration_example.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/cuquantum/tq_cuquantum_integration_example.py diff --git a/examples/cuquantum/tq_cuquantum_integration_example.py b/examples/cuquantum/tq_cuquantum_integration_example.py new file mode 100644 index 00000000..50605e11 --- /dev/null +++ b/examples/cuquantum/tq_cuquantum_integration_example.py @@ -0,0 +1,94 @@ +""" +i +MIT License + +Copyright (c) 2020-present TorchQuantum Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from cuquantum import contract +from cuquantum import CircuitToEinsum +import torchquantum as tq +from torchquantum.measurement import expval_joint_analytical +import cupy as cp + +def expval_joint_analytical_cuquantum(qdev, observable): + """Computes the expectation value of a joint observable using cuquantum. + + Args: + qdev (QuantumDevice): Quantum device to compute the expectation value on. + observable (str): Joint observable to compute the expectation value of. + + Returns: + float: The expectation value of the joint observable. + """ + op_history = qdev.op_history + myconverter = CircuitToEinsum(qdev, dtype='complex128', backend=cp) + expression, operands = myconverter.expectation(observable, lightcone=True) + expec = contract(expression, *operands) + return expec + + +if __name__ == '__main__': + + ops = [ + {'name': 'u3', 'wires': 0, 'trainable': True}, + {'name': 'u3', 'wires': 1, 'trainable': True}, + {'name': 'cx', 'wires': [0, 1]}, + {'name': 'cx', 'wires': [1, 0]}, + {'name': 'u3', 'wires': 0, 'trainable': True}, + {'name': 'u3', 'wires': 1, 'trainable': True}, + {'name': 'cx', 'wires': [0, 1]}, + {'name': 'cx', 'wires': [1, 0]}, + ] + + qmodule = tq.QuantumModule.from_op_history(ops) + + qdev = tq.QuantumDevice(n_wires=2, bsz=1, record_op=True) + + qmodule(qdev) + + op_history = qdev.op_history + + print(qdev.op_history) + + myconverter = CircuitToEinsum(qdev, dtype='complex128', backend=cp) + pauli_string = 'IX' + expression, operands = myconverter.expectation(pauli_string, lightcone=True) + expec = contract(expression, *operands) + print(f'expectation value for {pauli_string}: {expec}') + + print(f"torchquantum expval: {expval_joint_analytical(qdev, pauli_string)}") + print(expval_joint_analytical_cuquantum(qdev, pauli_string)) + + + # # expectation value from reduced density matrix + # qubits = myconverter.qubits + # where = qubits[1:5] + # rdm_expression, rdm_operands = myconverter.reduced_density_matrix(where, lightcone=True) + # rdm = contract(rdm_expression, *rdm_operands) + + # pauli_x = cp.asarray([[0,1],[1,0]], dtype=myconverter.dtype) + # pauli_z = cp.asarray([[1,0],[0,-1]], dtype=myconverter.dtype) + # expec_from_rdm = cp.einsum('abcdABCD,aA,bB,cC,dD->', rdm, pauli_x, pauli_x, pauli_z, pauli_z) + + + # print(f"is expectation value in agreement?", cp.allclose(expec, expec_from_rdm)) + From 7e05eeacb96fa82f1f1fb49ee3c32ff0f063787d Mon Sep 17 00:00:00 2001 From: GenericP3rson Date: Mon, 29 Apr 2024 21:59:13 -0400 Subject: [PATCH 3/3] [minor] preliminary tests for inverse --- test/module/inverse.py | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/module/inverse.py diff --git a/test/module/inverse.py b/test/module/inverse.py new file mode 100644 index 00000000..581e2926 --- /dev/null +++ b/test/module/inverse.py @@ -0,0 +1,53 @@ +import torchquantum as tq +from torchquantum.plugin import op_history2qiskit, qiskit2tq_op_history +from torchquantum.measurement import expval_joint_analytical +from qiskit import QuantumCircuit, Aer, execute +from qiskit.quantum_info import Pauli +import numpy as np + +""" +Testing strategy: + partition on Operation: iterate through all the possible operations + partition on number of gates in module: 1, >1 +""" + +def compare(ops, n_wires): + # construct a normal tq circuit + qmod = tq.QuantumModule.from_op_history(ops) + qdev = tq.QuantumDevice(n_wires=n_wires, record_op=True) + qmod(qdev) + + # turn into qiskit and inverse + qiskit_circuit = op_history2qiskit(n_wires, qdev.op_history) + qiskit_circuit = qiskit_circuit.inverse() + + # inverse the tq circuit + qmod = tq.QuantumModule.from_op_history(ops) + qdev = tq.QuantumDevice(n_wires=n_wires, record_op=True) + qmod.inverse_module() + qmod(qdev) + + qdev_ops = qiskit2tq_op_history(qiskit_circuit) + + for tq_op, qiskit_op in zip(qdev.op_history, qdev_ops): + # TODO: name-wise (but currently need to ensure, e.g., cx == cnot) + if tq_op["params"] is not None and qiskit_op["params"] is not None: + assert np.allclose(np.array(tq_op["params"]), np.array(qiskit_op["params"])) + +def get_random_rotations(num_params): + return 4*np.pi*np.random.rand(num_params) - 2*np.pi + +def test_inverse(): + ops = [ + {'name': 'u3', 'wires': 0, 'trainable': True, 'params': get_random_rotations(3)}, + {'name': 'u3', 'wires': 1, 'trainable': True, 'params': get_random_rotations(3)}, + {'name': 'cx', 'wires': [0, 1]}, + {'name': 'cx', 'wires': [1, 0]}, + {'name': 'u3', 'wires': 0, 'trainable': True, 'params': get_random_rotations(3)}, + {'name': 'u3', 'wires': 1, 'trainable': True, 'params': get_random_rotations(3)}, + {'name': 'cx', 'wires': [0, 1]}, + {'name': 'cx', 'wires': [1, 0]}, + ] + compare(ops, 2) + +# test_inverse()