From e9b2ad6f5107e802f219d71b86280bf730eff153 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 3 Jun 2024 21:20:27 +0900 Subject: [PATCH 01/33] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20isort/black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/noisy_mbqc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/noisy_mbqc.py b/examples/noisy_mbqc.py index 97e7ea5a..efd3f96a 100644 --- a/examples/noisy_mbqc.py +++ b/examples/noisy_mbqc.py @@ -43,7 +43,6 @@ class NoisyGraphState(NoiseModel): - def __init__(self, p_z=0.1): self.p_z = p_z From fdde361a01cb5e989a4ab916492b7ae4081fe49f Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 10 Jun 2024 20:33:57 +0900 Subject: [PATCH 02/33] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20type=20checks=20&=20?= =?UTF-8?q?text=20length?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 30d942dc..7d535006 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -317,7 +317,7 @@ def add_qubits(self, indices, states="plus"): "plus", "minus", "zero", "one", "iplus", "iminus", or 1*2 np.ndarray (arbitrary state). list of the above, to specify the initial state of each qubit. """ - if type(states) != list: + if isinstance(states, list): states = [states] * len(indices) for i, ind in enumerate(indices): self.add_qubit(ind, state=states[i]) @@ -352,7 +352,7 @@ def measure_single(self, index, basis="Z", bypass_probability_calculation=True, else: result = np.random.choice([0, 1]) # Basis state to be projected - if type(basis) == np.ndarray: + if isinstance(basis, np.ndarray): if outcome is not None: raise Warning("Measurement outcome is chosen but the basis state was given.") proj_vec = basis @@ -435,14 +435,15 @@ def get_basis_coefficient(self, basis, normalize=True, indices=None, **kwagrs): normalize (optional): bool if True, normalize the coefficient by the norm of the entire state. indices (optional): list of int - target qubit indices to compute the coefficients, default is the MBQC output nodes (self.default_output_nodes). + target qubit indices to compute the coefficients, + default is the MBQC output nodes (self.default_output_nodes). Returns ------- coef : complex coefficient """ - if indices == None: + if indices is None: indices = self.default_output_nodes if isinstance(basis, str): basis = int(basis, 2) @@ -504,7 +505,7 @@ def to_statevector(self, indices=None, **kwagrs): numpy.ndarray : statevector """ - if indices == None: + if indices is None: n_qubit = len(self.default_output_nodes) else: n_qubit = len(indices) @@ -546,8 +547,8 @@ def expectation_value(self, op, qubit_indices, output_node_indices=None, **kwagr float : Expectation value """ - if output_node_indices == None: - if self.default_output_nodes == None: + if output_node_indices is None: + if self.default_output_nodes is None: raise ValueError("output_nodes is not set.") else: target_nodes = [self.default_output_nodes[ind] for ind in qubit_indices] @@ -556,7 +557,7 @@ def expectation_value(self, op, qubit_indices, output_node_indices=None, **kwagr target_nodes = [output_node_indices[ind] for ind in qubit_indices] out_inds = output_node_indices op_dim = len(qubit_indices) - op = op.reshape([2 for _ in range(2 * op_dim)]) + op = op.reshape([2,] * (2 * op_dim)) new_ind_left = [gen_str() for _ in range(op_dim)] new_ind_right = [gen_str() for _ in range(op_dim)] tn_cp_left = self.copy() @@ -594,7 +595,8 @@ def evolve(self, operator, qubit_indices, decompose=True, **kwagrs): Applied positions of **logical** qubits. decompose : bool, optional default True - whether a given operator will be decomposed or not. If True, operator is decomposed into Matrix Product Operator(MPO) + whether a given operator will be decomposed or not. + If True, operator is decomposed into Matrix Product Operator(MPO) """ if len(operator.shape) != len(qubit_indices) * 2: shape = [2 for _ in range(2 * len(qubit_indices))] From f9d34cc0b4b10a60b98ab8b2e17688521546bddb Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 10 Jun 2024 20:37:59 +0900 Subject: [PATCH 03/33] =?UTF-8?q?=F0=9F=8E=A8=20Add=20psi=20argument=20in?= =?UTF-8?q?=20Statevec.=5F=5Finit=5F=5F=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/statevec.py | 44 +++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 8ebbc5f0..808f433b 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -1,9 +1,10 @@ from copy import deepcopy import numpy as np +from numpy.typing import NDArray import graphix.sim.base_backend -from graphix.clifford import CLIFFORD, CLIFFORD_CONJ, CLIFFORD_MUL +from graphix.clifford import CLIFFORD, CLIFFORD_CONJ from graphix.ops import Ops @@ -182,21 +183,19 @@ def meas_op(angle, vop=0, plane="XY", choice=0): class Statevec: """Simple statevector simulator""" - def __init__(self, nqubit=1, plus_states=True): + def __init__(self, nqubit=1, psi=None, plus_states=True): """Initialize statevector Parameters ---------- nqubit : int, optional: number of qubits. Defaults to 1. + psi : numpy.ndarray, optional + statevector. Defaults to None. plus_states : bool, optional whether or not to start all qubits in + state or 0 state. Defaults to + """ - if plus_states: - self.psi = np.ones((2,) * nqubit) / 2 ** (nqubit / 2) - else: - self.psi = np.zeros((2,) * nqubit) - self.psi[(0,) * nqubit] = 1 + self.psi = _initial_state(nqubit, psi, plus_states) def __repr__(self): return f"Statevec, data={self.psi}, shape={self.dims()}" @@ -225,8 +224,7 @@ def evolve(self, op, qargs): target qubits' indices """ op_dim = int(np.log2(len(op))) - # TODO shape = (2,)* 2 * op_dim - shape = [2 for _ in range(2 * op_dim)] + shape = [2,] * 2 * op_dim op_tensor = op.reshape(shape) self.psi = np.tensordot( op_tensor, @@ -409,6 +407,32 @@ def expectation_value(self, op, qargs): return np.dot(st2.psi.flatten().conjugate(), st1.psi.flatten()) -def _get_statevec_norm(psi): +def _get_statevec_norm(psi) -> float: """returns norm of the state""" return np.sqrt(np.sum(psi.flatten().conj() * psi.flatten())) + + +def _initial_state(nqubit=1, psi=None, plus_states=True) -> NDArray: + """Create initial state + + Parameters + ---------- + nqubit : int, optional: + number of qubits. Defaults to 1. + psi : numpy.ndarray, optional + statevector. Defaults to None. + plus_states : bool, optional + whether or not to start all qubits in + state or 0 state. Defaults to + + + Returns + ------- + numpy.ndarray + statevector + """ + if psi is not None: + return psi + if plus_states: + return np.ones((2,) * nqubit) / 2 ** (nqubit / 2) + psi = np.zeros((2,) * nqubit) + psi[(0,) * nqubit] = 1 + return psi From bc18da577a0b13831e9b9a4e8dca9f3c34dacc0f Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 10 Jun 2024 20:59:45 +0900 Subject: [PATCH 04/33] =?UTF-8?q?=F0=9F=90=9B=20Fix=20type=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 7d535006..a2480c11 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -317,7 +317,7 @@ def add_qubits(self, indices, states="plus"): "plus", "minus", "zero", "one", "iplus", "iminus", or 1*2 np.ndarray (arbitrary state). list of the above, to specify the initial state of each qubit. """ - if isinstance(states, list): + if not isinstance(states, list): states = [states] * len(indices) for i, ind in enumerate(indices): self.add_qubit(ind, state=states[i]) From 7fb608542fdd8a12de324764af31560daf59698b Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 10 Jun 2024 22:00:54 +0900 Subject: [PATCH 05/33] =?UTF-8?q?=E2=9C=A8Improve=20to=5Fstatevector=20by?= =?UTF-8?q?=20introducing=20efficient=20contraction=20order=20calculation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 29 ++++++++++++++++------------- tests/random_circuit.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index a2480c11..075e4618 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -2,6 +2,7 @@ from copy import deepcopy import numpy as np +from numpy.typing import NDArray import quimb.tensor as qtn from quimb.tensor import Tensor, TensorNetwork @@ -491,28 +492,30 @@ def get_basis_amplitude(self, basis, **kwagrs): coef = self.get_basis_coefficient(basis, **kwagrs) return abs(coef) ** 2 - def to_statevector(self, indices=None, **kwagrs): - """Retrieve the statevector from the tensornetwork. - This method tends to be slow however we plan to parallelize this. + def to_statevector(self, skip=False, **kwagrs) -> NDArray: + """Take outer product of the tensors in the network and return the statevector. Parameters ---------- - indices (optional): list of int - target qubit indices. Default is the MBQC output nodes (self.default_output_nodes). + skip : bool + if True, skip the simplification process. Returns ------- numpy.ndarray : statevector """ - if indices is None: - n_qubit = len(self.default_output_nodes) - else: - n_qubit = len(indices) - statevec = np.zeros(2**n_qubit, np.complex128) - for i in range(len(statevec)): - statevec[i] = self.get_basis_coefficient(i, normalize=False, indices=indices, **kwagrs) - return statevec / np.linalg.norm(statevec) + if skip: + tn = self.copy() + output_inds = [self._dangling[str(index)] for index in self.default_output_nodes] + psi = tn.contract(output_inds=output_inds, **kwagrs).data.reshape(-1) + return psi + + tn = self.copy() + tn_simplified = tn.full_simplify("ADCR") + output_inds = [self._dangling[str(index)] for index in self.default_output_nodes] + psi = tn_simplified.contract(output_inds=output_inds, **kwagrs) + return np.array([psi]) if isinstance(psi, (np.complex128, np.float64)) else psi.data.reshape(-1) def get_norm(self, **kwagrs): """Calculate the norm of the state. diff --git a/tests/random_circuit.py b/tests/random_circuit.py index ae73579c..81a8577f 100644 --- a/tests/random_circuit.py +++ b/tests/random_circuit.py @@ -84,6 +84,21 @@ def get_rand_circuit( use_rzz: bool = False, use_ccx: bool = False, ) -> Circuit: + """Generate a random circuit. + + Parameters + ---------- + nqubits : int + Number of qubits in the circuit. Must be at least 2. + depth : int + Number of layers in the circuit. + rng : Generator + Random number generator. + use_rzz : bool, optional + Whether to include RZZ gates, by default False. + use_ccx : bool, optional + Whether to include CCX gates, by default False. + """ circuit = Circuit(nqubits) gate_choice = ( functools.partial(circuit.ry, angle=np.pi / 4), From eb7f77d6131817ef34135628677545b1eda19496 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 19:38:51 +0900 Subject: [PATCH 06/33] =?UTF-8?q?=F0=9F=8E=A8=20Change=20max=5Fqubit=5Fnum?= =?UTF-8?q?=20as=20optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/statevec.py | 24 +++++++++++++++++------- tests/test_statevec_backend.py | 15 ++++++++++++++- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 808f433b..5c935004 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -11,7 +11,7 @@ class StatevectorBackend(graphix.sim.base_backend.Backend): """MBQC simulator with statevector method.""" - def __init__(self, pattern, max_qubit_num=20, pr_calc=True): + def __init__(self, pattern, pr_calc=True, max_qubit_num=None): """ Parameters ----------- @@ -19,12 +19,12 @@ def __init__(self, pattern, max_qubit_num=20, pr_calc=True): MBQC pattern to be simulated. backend : str, 'statevector' optional argument for simulation. - max_qubit_num : int - optional argument specifying the maximum number of qubits - to be stored in the statevector at a time. pr_calc : bool whether or not to compute the probability distribution before choosing the measurement result. if False, measurements yield results 0/1 with 50% probabilities each. + max_qubit_num : int, optional + optional argument specifying the maximum number of qubits + to be stored in the statevector at a time. """ # check that pattern has output nodes configured # assert len(pattern.output_nodes) > 0 @@ -35,9 +35,7 @@ def __init__(self, pattern, max_qubit_num=20, pr_calc=True): self.Nqubit = 0 self.to_trace = [] self.to_trace_loc = [] - self.max_qubit_num = max_qubit_num - if pattern.max_space() > max_qubit_num: - raise ValueError("Pattern.max_space is larger than max_qubit_num. Increase max_qubit_num and try again") + self.max_qubit_num = _validate_max_qubit_num(max_qubit_num, pattern.max_space) super().__init__(pr_calc) def qubit_dim(self): @@ -436,3 +434,15 @@ def _initial_state(nqubit=1, psi=None, plus_states=True) -> NDArray: psi = np.zeros((2,) * nqubit) psi[(0,) * nqubit] = 1 return psi + + +def _validate_max_qubit_num(max_qubit_num: int | None, max_space: int) -> int | None: + if max_qubit_num is None: + return + if not isinstance(max_qubit_num, int): + raise ValueError("max_qubit_num must be an integer") + if max_qubit_num < 1: + raise ValueError("max_qubit_num must be a positive integer") + if max_qubit_num < max_space: + raise ValueError("Pattern.max_space is larger than max_qubit_num. Increase max_qubit_num and try again") + return max_qubit_num diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index aba2efd1..7e9cb917 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -7,10 +7,23 @@ import pytest from graphix.ops import States -from graphix.sim.statevec import Statevec, meas_op +from graphix.sim.statevec import Statevec, meas_op, _validate_max_qubit_num class TestStatevec: + @pytest.mark.parametrize("max_qubit_num, max_space", [ + (1.0, 2), (1.1, 2), (0, 2), (1, 2), ("2", 1) + ]) + def test_validate_max_qubit_num_fail(self, max_qubit_num: float | int, max_space: int): + with pytest.raises(ValueError): + _validate_max_qubit_num(max_qubit_num, max_space) + + @pytest.mark.parametrize("max_qubit_num, max_space, expect", [ + (None, 2, None), (None, 3, None), (1, 1, 1), (10, 1, 10), (10, 2, 10) + ]) + def test_validate_max_qubit_num_pass(self, max_qubit_num: int | None, max_space: int, expect: int | None): + assert _validate_max_qubit_num(max_qubit_num, max_space) == expect + def test_remove_one_qubit(self) -> None: n = 10 k = 3 From abc5c87e6c6b50175aec7c162513a421caea4029 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 20:09:04 +0900 Subject: [PATCH 07/33] =?UTF-8?q?=F0=9F=8E=A8=20Move=20random=5Fcircuit.py?= =?UTF-8?q?=20to=20the=20graphix=20dir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {tests => graphix}/random_circuit.py | 0 tests/test_generator.py | 2 +- tests/test_gflow.py | 2 +- tests/test_pattern.py | 2 +- tests/test_tnsim.py | 2 +- tests/test_transpiler.py | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename {tests => graphix}/random_circuit.py (100%) diff --git a/tests/random_circuit.py b/graphix/random_circuit.py similarity index 100% rename from tests/random_circuit.py rename to graphix/random_circuit.py diff --git a/tests/test_generator.py b/tests/test_generator.py index 0f707b9b..b2daea26 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -6,7 +6,7 @@ import numpy as np import pytest -import tests.random_circuit as rc +import graphix.random_circuit as rc from graphix.generator import generate_from_graph if TYPE_CHECKING: diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 0c0bb7b0..3c6b4b9d 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -9,7 +9,7 @@ from numpy.random import Generator from graphix.gflow import find_flow, find_gflow, find_pauliflow, verify_flow, verify_gflow, verify_pauliflow -from tests.random_circuit import get_rand_circuit +from graphix.random_circuit import get_rand_circuit if TYPE_CHECKING: from collections.abc import Iterable, Iterator diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 2308a00a..e6f988cf 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -7,7 +7,7 @@ import numpy as np import pytest -import tests.random_circuit as rc +import graphix.random_circuit as rc from graphix.pattern import CommandNode, Pattern from graphix.simulator import PatternSimulator from graphix.transpiler import Circuit diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index 5c16e7be..6099c5a2 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -8,7 +8,7 @@ from numpy.random import PCG64, Generator from quimb.tensor import Tensor -import tests.random_circuit as rc +import graphix.random_circuit as rc from graphix.clifford import CLIFFORD from graphix.ops import Ops, States from graphix.sim.tensornet import MBQCTensorNet, gen_str diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index ff2c0a87..5c167a29 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -6,7 +6,7 @@ import graphix.pauli import graphix.simulator -import tests.random_circuit as rc +import graphix.random_circuit as rc from graphix.transpiler import Circuit From a41ac08e5558af36af9448f8fec64c207d55095e Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 20:17:29 +0900 Subject: [PATCH 08/33] =?UTF-8?q?=F0=9F=8E=A8=20Move=20StatevectorBackend?= =?UTF-8?q?=20test=20location?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_statevec_backend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index 7e9cb917..73b6b934 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -10,7 +10,7 @@ from graphix.sim.statevec import Statevec, meas_op, _validate_max_qubit_num -class TestStatevec: +class TestStatevectorBackend: @pytest.mark.parametrize("max_qubit_num, max_space", [ (1.0, 2), (1.1, 2), (0, 2), (1, 2), ("2", 1) ]) @@ -24,6 +24,8 @@ def test_validate_max_qubit_num_fail(self, max_qubit_num: float | int, max_space def test_validate_max_qubit_num_pass(self, max_qubit_num: int | None, max_space: int, expect: int | None): assert _validate_max_qubit_num(max_qubit_num, max_space) == expect + +class TestStatevec: def test_remove_one_qubit(self) -> None: n = 10 k = 3 From f833447e24b74a6d476270c9a4cd78f9f5416679 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 20:49:26 +0900 Subject: [PATCH 09/33] =?UTF-8?q?=F0=9F=90=9B=20Fix=20StatevectorBackend?= =?UTF-8?q?=20=5F=5Finit=5F=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/statevec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 5c935004..96f45e06 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -35,7 +35,7 @@ def __init__(self, pattern, pr_calc=True, max_qubit_num=None): self.Nqubit = 0 self.to_trace = [] self.to_trace_loc = [] - self.max_qubit_num = _validate_max_qubit_num(max_qubit_num, pattern.max_space) + self.max_qubit_num = _validate_max_qubit_num(max_qubit_num, pattern.max_space()) super().__init__(pr_calc) def qubit_dim(self): From fa4965f89da5ae5424722966f7a9bb8005c95694 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 21:16:11 +0900 Subject: [PATCH 10/33] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20=5Finitia?= =?UTF-8?q?l=5Fstate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_statevec_backend.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index 73b6b934..82499362 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -7,7 +7,7 @@ import pytest from graphix.ops import States -from graphix.sim.statevec import Statevec, meas_op, _validate_max_qubit_num +from graphix.sim.statevec import Statevec, meas_op, _validate_max_qubit_num, _initial_state class TestStatevectorBackend: @@ -26,6 +26,22 @@ def test_validate_max_qubit_num_pass(self, max_qubit_num: int | None, max_space: class TestStatevec: + @pytest.mark.parametrize("nqubit, error", [ + (1.0, TypeError), (-1, ValueError), + ]) + def test_init_fail(self, nqubit: float | int, error: Exception): + with pytest.raises(error): + _initial_state(nqubit=nqubit) + + @pytest.mark.parametrize("nqubit, psi, plus_states, expect", [ + (1, States.zero, False, States.zero), + (1, States.zero, True, States.zero), + (10, States.zero, False, States.zero), + (2, None, True, np.tensordot(States.plus, States.plus, 0)), + ]) + def test_init_pass(self, nqubit: int, psi: npt.NDArray | None, plus_states: bool, expect: npt.NDArray): + np.testing.assert_allclose(_initial_state(nqubit=nqubit, psi=psi, plus_states=plus_states), expect) + def test_remove_one_qubit(self) -> None: n = 10 k = 3 From d086fbfc21e1945cbef7999455bb64891c6abaf1 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 21:16:53 +0900 Subject: [PATCH 11/33] =?UTF-8?q?=F0=9F=8E=A8=20Mod=20=5Finitial=5Fstate?= =?UTF-8?q?=20func?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/statevec.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 96f45e06..0d3249f7 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -410,7 +410,7 @@ def _get_statevec_norm(psi) -> float: return np.sqrt(np.sum(psi.flatten().conj() * psi.flatten())) -def _initial_state(nqubit=1, psi=None, plus_states=True) -> NDArray: +def _initial_state(nqubit: int = 1, psi: NDArray = None, plus_states: bool = True) -> NDArray: """Create initial state Parameters @@ -427,8 +427,12 @@ def _initial_state(nqubit=1, psi=None, plus_states=True) -> NDArray: numpy.ndarray statevector """ - if psi is not None: + if isinstance(psi, np.ndarray): return psi + if not isinstance(nqubit, int): + raise TypeError("nqubit must be an integer") + if nqubit < 0: + raise ValueError("nqubit must be a non-negative integer") if plus_states: return np.ones((2,) * nqubit) / 2 ** (nqubit / 2) psi = np.zeros((2,) * nqubit) From 9ad4674fe530a94ce59dbd6f1e4c38356b9999bb Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 21:43:41 +0900 Subject: [PATCH 12/33] =?UTF-8?q?=F0=9F=93=9D=20Add=20benchmarking=20code?= =?UTF-8?q?=20for=20eco-sim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 benchmarks/efficient_contraction_order_statevec.py diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py new file mode 100644 index 00000000..d0b29de4 --- /dev/null +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -0,0 +1,130 @@ +""" +Efficient contraction order statevector simulation of MBQC patterns +=================================================================== + +Here we benchmark our efficient contraction order statevector simulator for MBQC, +which uses the TensorNetwork backend. + +The methods and modules we use are the followings: + 1. :meth:`graphix.pattern.Pattern.simulate_pattern` + Pattern simulation with Statevector backend. + :meth:`graphix.pattern.Pattern.minimize_space` locally minimizes the space of the pattern. + 2. :mod:`graphix.sim.tensornet` + Pattern simulation with TensorNetwork backend. + This enables the efficient contraction order simulation of the pattern. + Here we use the `cotengra` optimizer for the contraction order optimization. +""" + +# %% +# Firstly, let us import relevant modules: + +from copy import deepcopy +from time import perf_counter + +import cotengra as ctg +from numpy.random import Generator, PCG64 +import quimb as qu + +from graphix.random_circuit import get_rand_circuit + +# %% +# Next, set global seed and number of thread workers +GLOBAL_SEED = 2 +qu.core._NUM_THREAD_WORKERS = 1 + +# %% +# We then run simulations. +# Let's benchmark the simulation time of the statevector simulator and the efficient contraction order simulator. + +n_qubit_list = list(range(2, 31)) + +sv_sim = [] +eco_sim = [] +eco_sim_wo_ctg = [] +max_space_ls = [] + +for n_qubit in n_qubit_list: + rng = Generator(PCG64(GLOBAL_SEED)) + circuit = get_rand_circuit(n_qubit, n_qubit, rng) + pattern = circuit.transpile().pattern + pattern.standardize() + pat_original = deepcopy(pattern) + start = perf_counter() + pat_original.minimize_space() + max_sp = pat_original.max_space() + max_space_ls.append(max_sp) + sv = pat_original.simulate_pattern(max_qubit_num=max_sp) + end = perf_counter() + sv_sim.append(end - start) + del sv + + # efficient contraction order simulation (eco-sim): tn + tn = pattern.simulate_pattern("tensornetwork") + output_inds = [tn._dangling[str(index)] for index in tn.default_output_nodes] + + start = perf_counter() + tn_sv = tn.to_statevector( + backend="numpy", + skip=False, + optimize=ctg.HyperOptimizer(minimize="combo", max_time=600, progbar=True), + ) + end = perf_counter() + eco_sim.append(end - start) + del tn_sv + + # eco-sim: tn without cotengra optimizer + start = perf_counter() + tn_sv = tn.to_statevector( + backend="numpy", + skip=False, + optimize=None, + ) + end = perf_counter() + eco_sim_wo_ctg.append(end - start) + del tn_sv, tn + +# %% +# Finally, we save the results to a text file. +with open("sqrqcresults.txt", "w") as f: + f.write("n_qubit, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") + for i in range(len(n_qubit_list)): + f.write( + f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n" + ) + +# %% +# We can now plot the simulation time results. +import matplotlib.pyplot as plt +import numpy as np + + +data = np.loadtxt("sqrqcresults.txt", delimiter=",", skiprows=1) +n_qubits = data[:, 0].astype(int) +sv_sim = data[:, 1].astype(float) +eco_sim = data[:, 2].astype(float) +eco_sim_wo_ctg = data[:, 3].astype(float) +max_sp = data[:, 4].astype(int) + +fig, ax1 = plt.subplots(figsize=(8, 5)) +color = "tab:red" +ax1.set_xlabel("Original Circuit Size [qubit]") +ax1.set_ylabel("Simulation time [sec]") +ax1.set_yscale("log") +ax1.scatter(n_qubits, sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") +ax1.scatter(n_qubits, eco_sim, marker="x", label="MBQC TN base (with cotengra)") +ax1.scatter(n_qubits, eco_sim_wo_ctg, marker="x", label="MBQC TN base (without cotengra)") +ax1.tick_params(axis="y") +ax1.legend(loc="upper left") +ax1.set_title("Simulation time (Square RQC)") +plt.grid(True, which="Major") + +ax2 = ax1.twinx() +color = "tab:blue" +ax2.set_ylabel("Max Space [qubit]") +ax2.plot(n_qubits, max_sp, color=color, linestyle="--", label="Max Space") +ax2.tick_params(axis="y") +ax2.legend(loc="lower right") + +plt.rcParams["svg.fonttype"] = "none" +plt.savefig("simulation_time_wo_p.png") +plt.close() From cb3f2b0be911cbfed47ce02c4f70a1fd9f4a3f3f Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 17 Jun 2024 22:11:36 +0900 Subject: [PATCH 13/33] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20black=20&=20isort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 7 +--- examples/MBQCvqe.py | 8 +++- graphix/sim/statevec.py | 8 +++- graphix/sim/tensornet.py | 9 ++++- tests/test_statevec_backend.py | 37 +++++++++++-------- tests/test_transpiler.py | 2 +- 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index d0b29de4..4ba9b663 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -22,8 +22,8 @@ from time import perf_counter import cotengra as ctg -from numpy.random import Generator, PCG64 import quimb as qu +from numpy.random import PCG64, Generator from graphix.random_circuit import get_rand_circuit @@ -88,16 +88,13 @@ with open("sqrqcresults.txt", "w") as f: f.write("n_qubit, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") for i in range(len(n_qubit_list)): - f.write( - f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n" - ) + f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n") # %% # We can now plot the simulation time results. import matplotlib.pyplot as plt import numpy as np - data = np.loadtxt("sqrqcresults.txt", delimiter=",", skiprows=1) n_qubits = data[:, 0].astype(int) sv_sim = data[:, 1].astype(float) diff --git a/examples/MBQCvqe.py b/examples/MBQCvqe.py index 05eefdbc..5d548ab2 100644 --- a/examples/MBQCvqe.py +++ b/examples/MBQCvqe.py @@ -22,9 +22,11 @@ import networkx as nx import numpy as np from scipy.optimize import minimize + from graphix import Circuit from graphix.simulator import PatternSimulator + # %% # Define the Hamiltonian for the VQE problem (Example: H = Z0Z1 + X0 + X1) def create_hamiltonian(): @@ -33,6 +35,7 @@ def create_hamiltonian(): H = np.kron(Z, Z) + np.kron(X, np.eye(2)) + np.kron(np.eye(2), X) return H + # %% # Function to build the VQE circuit def build_vqe_circuit(n_qubits, params): @@ -45,6 +48,7 @@ def build_vqe_circuit(n_qubits, params): circuit.cnot(i, i + 1) return circuit + # %% class MBQCVQE: def __init__(self, n_qubits, hamiltonian): @@ -85,6 +89,7 @@ def compute_energy(self, params): energy = tn.expectation_value(self.hamiltonian, qubit_indices=range(self.n_qubits)) return energy + # %% # Set parameters for VQE n_qubits = 2 @@ -99,13 +104,14 @@ def compute_energy(self, params): def cost_function(params): return mbqc_vqe.compute_energy(params) + # %% # Random initial parameters initial_params = np.random.rand(n_qubits * 3) # %% # Perform the optimization using COBYLA -result = minimize(cost_function, initial_params, method='COBYLA', options={'maxiter': 100}) +result = minimize(cost_function, initial_params, method="COBYLA", options={"maxiter": 100}) print(f"Optimized parameters: {result.x}") print(f"Optimized energy: {result.fun}") diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 0d3249f7..44435a6d 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -222,7 +222,13 @@ def evolve(self, op, qargs): target qubits' indices """ op_dim = int(np.log2(len(op))) - shape = [2,] * 2 * op_dim + shape = ( + [ + 2, + ] + * 2 + * op_dim + ) op_tensor = op.reshape(shape) self.psi = np.tensordot( op_tensor, diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 075e4618..d27e646e 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -2,8 +2,8 @@ from copy import deepcopy import numpy as np -from numpy.typing import NDArray import quimb.tensor as qtn +from numpy.typing import NDArray from quimb.tensor import Tensor, TensorNetwork from graphix.clifford import CLIFFORD, CLIFFORD_CONJ, CLIFFORD_MUL @@ -560,7 +560,12 @@ def expectation_value(self, op, qubit_indices, output_node_indices=None, **kwagr target_nodes = [output_node_indices[ind] for ind in qubit_indices] out_inds = output_node_indices op_dim = len(qubit_indices) - op = op.reshape([2,] * (2 * op_dim)) + op = op.reshape( + [ + 2, + ] + * (2 * op_dim) + ) new_ind_left = [gen_str() for _ in range(op_dim)] new_ind_right = [gen_str() for _ in range(op_dim)] tn_cp_left = self.copy() diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index 82499362..fd34f400 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -7,38 +7,43 @@ import pytest from graphix.ops import States -from graphix.sim.statevec import Statevec, meas_op, _validate_max_qubit_num, _initial_state +from graphix.sim.statevec import Statevec, _initial_state, _validate_max_qubit_num, meas_op class TestStatevectorBackend: - @pytest.mark.parametrize("max_qubit_num, max_space", [ - (1.0, 2), (1.1, 2), (0, 2), (1, 2), ("2", 1) - ]) + @pytest.mark.parametrize("max_qubit_num, max_space", [(1.0, 2), (1.1, 2), (0, 2), (1, 2), ("2", 1)]) def test_validate_max_qubit_num_fail(self, max_qubit_num: float | int, max_space: int): with pytest.raises(ValueError): _validate_max_qubit_num(max_qubit_num, max_space) - @pytest.mark.parametrize("max_qubit_num, max_space, expect", [ - (None, 2, None), (None, 3, None), (1, 1, 1), (10, 1, 10), (10, 2, 10) - ]) + @pytest.mark.parametrize( + "max_qubit_num, max_space, expect", [(None, 2, None), (None, 3, None), (1, 1, 1), (10, 1, 10), (10, 2, 10)] + ) def test_validate_max_qubit_num_pass(self, max_qubit_num: int | None, max_space: int, expect: int | None): assert _validate_max_qubit_num(max_qubit_num, max_space) == expect class TestStatevec: - @pytest.mark.parametrize("nqubit, error", [ - (1.0, TypeError), (-1, ValueError), - ]) + @pytest.mark.parametrize( + "nqubit, error", + [ + (1.0, TypeError), + (-1, ValueError), + ], + ) def test_init_fail(self, nqubit: float | int, error: Exception): with pytest.raises(error): _initial_state(nqubit=nqubit) - @pytest.mark.parametrize("nqubit, psi, plus_states, expect", [ - (1, States.zero, False, States.zero), - (1, States.zero, True, States.zero), - (10, States.zero, False, States.zero), - (2, None, True, np.tensordot(States.plus, States.plus, 0)), - ]) + @pytest.mark.parametrize( + "nqubit, psi, plus_states, expect", + [ + (1, States.zero, False, States.zero), + (1, States.zero, True, States.zero), + (10, States.zero, False, States.zero), + (2, None, True, np.tensordot(States.plus, States.plus, 0)), + ], + ) def test_init_pass(self, nqubit: int, psi: npt.NDArray | None, plus_states: bool, expect: npt.NDArray): np.testing.assert_allclose(_initial_state(nqubit=nqubit, psi=psi, plus_states=plus_states), expect) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 5c167a29..a5be9f73 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -5,8 +5,8 @@ from numpy.random import PCG64, Generator import graphix.pauli -import graphix.simulator import graphix.random_circuit as rc +import graphix.simulator from graphix.transpiler import Circuit From 2814ae082d3aa05d65291c669c052b557118f708 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sun, 14 Jul 2024 19:34:55 +0900 Subject: [PATCH 14/33] =?UTF-8?q?=F0=9F=94=A5=20Delete=20tests=20for=20old?= =?UTF-8?q?=20Statevec.init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_statevec_backend.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index 6ed28aad..60b8ff8f 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -4,12 +4,10 @@ from typing import TYPE_CHECKING import numpy as np -import numpy.typing as npt import pytest -from graphix.ops import States import graphix.pauli -from graphix.sim.statevec import Statevec, StatevectorBackend, meas_op, _initial_state, _validate_max_qubit_num +from graphix.sim.statevec import Statevec, StatevectorBackend, _validate_max_qubit_num, meas_op from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: @@ -30,29 +28,6 @@ def test_validate_max_qubit_num_pass(self, max_qubit_num: int | None, max_space: class TestStatevec: - @pytest.mark.parametrize( - "nqubit, error", - [ - (1.0, TypeError), - (-1, ValueError), - ], - ) - def test_init_fail(self, nqubit: float | int, error: Exception): - with pytest.raises(error): - _initial_state(nqubit=nqubit) - - @pytest.mark.parametrize( - "nqubit, psi, plus_states, expect", - [ - (1, States.zero, False, States.zero), - (1, States.zero, True, States.zero), - (10, States.zero, False, States.zero), - (2, None, True, np.tensordot(States.plus, States.plus, 0)), - ], - ) - def test_init_pass(self, nqubit: int, psi: npt.NDArray | None, plus_states: bool, expect: npt.NDArray): - np.testing.assert_allclose(_initial_state(nqubit=nqubit, psi=psi, plus_states=plus_states), expect) - def test_remove_one_qubit(self) -> None: n = 10 k = 3 From a8379209fac883da5a644c0a03f7a87dbfabaa38 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sun, 14 Jul 2024 19:35:18 +0900 Subject: [PATCH 15/33] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20isort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 4 ++-- tests/test_pattern.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e0947f8e..3cb46d84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,8 +3,8 @@ import pytest from numpy.random import PCG64, Generator +import graphix.random_circuit as rc import graphix.transpiler -import tests.random_circuit SEED = 25 DEPTH = 1 @@ -34,7 +34,7 @@ def nqb(fx_rng: Generator) -> int: @pytest.fixture def rand_circ(nqb, fx_rng: Generator) -> graphix.transpiler.Circuit: - return tests.random_circuit.get_rand_circuit(nqb, DEPTH, fx_rng) + return rc.get_rand_circuit(nqb, DEPTH, fx_rng) @pytest.fixture diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 58c72bf2..bd013bdf 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -8,9 +8,9 @@ import graphix.ops import graphix.pauli +import graphix.random_circuit as rc import graphix.sim.base_backend import graphix.states -import graphix.random_circuit as rc from graphix.command import M, N from graphix.pattern import CommandNode, Pattern from graphix.sim.density_matrix import DensityMatrix From 264e2bb193d611178ce8951faef72577bf73421d Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sun, 14 Jul 2024 19:59:12 +0900 Subject: [PATCH 16/33] =?UTF-8?q?=F0=9F=99=88=20Ignore=20generated=20cover?= =?UTF-8?q?age=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5bc66443..b303af32 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ graphix/sim/graphix.code-workspace .vscode/settings.json .pre-commit-config.yaml graphix/_version.py +.coverage +coverage.xml From bafcbad040cd913f2272363b4da87e55d9f74e98 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sun, 14 Jul 2024 20:15:04 +0900 Subject: [PATCH 17/33] =?UTF-8?q?=F0=9F=9A=9A=20Move=20random=5Fcircuits.p?= =?UTF-8?q?y=20content=20into=20random=5Fobjects.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 2 +- graphix/random_objects.py | 122 ++++++++++++++++++ tests/conftest.py | 4 +- tests/test_generator.py | 4 +- tests/test_gflow.py | 2 +- tests/test_pattern.py | 40 +++--- tests/test_tnsim.py | 14 +- tests/test_transpiler.py | 12 +- 8 files changed, 161 insertions(+), 39 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index 4ba9b663..523d2da4 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -25,7 +25,7 @@ import quimb as qu from numpy.random import PCG64, Generator -from graphix.random_circuit import get_rand_circuit +from graphix.random_objects import get_rand_circuit # %% # Next, set global seed and number of thread workers diff --git a/graphix/random_objects.py b/graphix/random_objects.py index ca5bd62f..e74deb30 100644 --- a/graphix/random_objects.py +++ b/graphix/random_objects.py @@ -1,5 +1,8 @@ from __future__ import annotations +import functools +from typing import TYPE_CHECKING + import numpy as np import scipy.linalg from scipy.stats import unitary_group @@ -7,6 +10,12 @@ from graphix.channels import KrausChannel from graphix.ops import Ops from graphix.sim.density_matrix import DensityMatrix +from graphix.transpiler import Circuit + +if TYPE_CHECKING: + from collections.abc import Iterable, Iterator + + from numpy.random import Generator def rand_herm(sz: int): @@ -173,3 +182,116 @@ def rand_Pauli_channel_kraus(dim: int, rank: int | None = None) -> KrausChannel: # think we don't really care return KrausChannel(data) + + +def first_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: + for qubit in range(nqubits): + circuit.rx(qubit, rng.random()) + + +def mid_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: + for qubit in range(nqubits): + circuit.rx(qubit, rng.random()) + circuit.rz(qubit, rng.random()) + + +def last_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: + for qubit in range(nqubits): + circuit.rz(qubit, rng.random()) + + +def entangler(circuit: Circuit, pairs: Iterable[tuple[int, int]]) -> None: + for a, b in pairs: + circuit.cnot(a, b) + + +def entangler_rzz(circuit: Circuit, pairs: Iterable[tuple[int, int]], rng: Generator) -> None: + for a, b in pairs: + circuit.rzz(a, b, rng.random()) + + +def generate_gate( + nqubits: int, + depth: int, + pairs: Iterable[tuple[int, int]], + rng: Generator, + *, + use_rzz: bool = False, +) -> Circuit: + circuit = Circuit(nqubits) + first_rotation(circuit, nqubits, rng) + entangler(circuit, pairs) + for _ in range(depth - 1): + mid_rotation(circuit, nqubits, rng) + if use_rzz: + entangler_rzz(circuit, pairs, rng) + else: + entangler(circuit, pairs) + last_rotation(circuit, nqubits, rng) + return circuit + + +def genpair(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int]]: + choice = list(range(n_qubits)) + for _ in range(count): + rng.shuffle(choice) + x, y = choice[:2] + yield (x, y) + + +def gentriplet(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int, int]]: + choice = list(range(n_qubits)) + for _ in range(count): + rng.shuffle(choice) + x, y, z = choice[:3] + yield (x, y, z) + + +def get_rand_circuit( + nqubits: int, + depth: int, + rng: Generator, + *, + use_rzz: bool = False, + use_ccx: bool = False, +) -> Circuit: + """Generate a random circuit. + + Parameters + ---------- + nqubits : int + Number of qubits in the circuit. Must be at least 2. + depth : int + Number of layers in the circuit. + rng : Generator + Random number generator. + use_rzz : bool, optional + Whether to include RZZ gates, by default False. + use_ccx : bool, optional + Whether to include CCX gates, by default False. + """ + circuit = Circuit(nqubits) + gate_choice = ( + functools.partial(circuit.ry, angle=np.pi / 4), + functools.partial(circuit.rz, angle=-np.pi / 4), + functools.partial(circuit.rx, angle=-np.pi / 4), + circuit.h, + circuit.s, + circuit.x, + circuit.z, + circuit.y, + ) + for _ in range(depth): + for j, k in genpair(nqubits, 2, rng): + circuit.cnot(j, k) + if use_rzz: + for j, k in genpair(nqubits, 2, rng): + circuit.rzz(j, k, np.pi / 4) + if use_ccx: + for j, k, l in gentriplet(nqubits, 2, rng): + circuit.ccx(j, k, l) + for j, k in genpair(nqubits, 4, rng): + circuit.swap(j, k) + for j in range(nqubits): + rng.choice(gate_choice)(j) + return circuit diff --git a/tests/conftest.py b/tests/conftest.py index 3cb46d84..69889d60 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,7 @@ import pytest from numpy.random import PCG64, Generator -import graphix.random_circuit as rc +import graphix.random_objects as ro import graphix.transpiler SEED = 25 @@ -34,7 +34,7 @@ def nqb(fx_rng: Generator) -> int: @pytest.fixture def rand_circ(nqb, fx_rng: Generator) -> graphix.transpiler.Circuit: - return rc.get_rand_circuit(nqb, DEPTH, fx_rng) + return ro.get_rand_circuit(nqb, DEPTH, fx_rng) @pytest.fixture diff --git a/tests/test_generator.py b/tests/test_generator.py index 0a36d0c5..147c1fc3 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -7,7 +7,7 @@ import pytest import graphix.pauli -import graphix.random_circuit as rc +import graphix.random_objects as ro from graphix.generator import generate_from_graph if TYPE_CHECKING: @@ -57,7 +57,7 @@ def test_pattern_generation_flow(self, fx_rng: Generator) -> None: nqubits = 3 depth = 2 pairs = [(0, 1), (1, 2)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng) # transpile into graph pattern = circuit.transpile().pattern pattern.standardize() diff --git a/tests/test_gflow.py b/tests/test_gflow.py index 1957ba1e..bd86f291 100644 --- a/tests/test_gflow.py +++ b/tests/test_gflow.py @@ -19,7 +19,7 @@ verify_pauliflow, ) from graphix.pattern import Pattern -from graphix.random_circuit import get_rand_circuit +from graphix.random_objects import get_rand_circuit if TYPE_CHECKING: from collections.abc import Iterable, Iterator diff --git a/tests/test_pattern.py b/tests/test_pattern.py index bd013bdf..7e517ac5 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -8,7 +8,7 @@ import graphix.ops import graphix.pauli -import graphix.random_circuit as rc +import graphix.random_objects as ro import graphix.sim.base_backend import graphix.states from graphix.command import M, N @@ -44,7 +44,7 @@ def test_manual_generation(self) -> None: def test_standardize(self, fx_rng: Generator) -> None: nqubits = 2 depth = 1 - circuit = rc.get_rand_circuit(nqubits, depth, fx_rng) + circuit = ro.get_rand_circuit(nqubits, depth, fx_rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") assert pattern.is_standard() @@ -55,7 +55,7 @@ def test_standardize(self, fx_rng: Generator) -> None: def test_minimize_space(self, fx_rng: Generator) -> None: nqubits = 5 depth = 5 - circuit = rc.get_rand_circuit(nqubits, depth, fx_rng) + circuit = ro.get_rand_circuit(nqubits, depth, fx_rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.minimize_space() @@ -69,7 +69,7 @@ def test_minimize_space_with_gflow(self, fx_bg: PCG64, jumps: int, use_rustworkx nqubits = 3 depth = 3 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, rng) + circuit = ro.generate_gate(nqubits, depth, pairs, rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -105,7 +105,7 @@ def test_minimize_space_graph_maxspace_with_flow(self, fx_rng: Generator) -> Non for nqubits in range(2, max_qubits): depth = 5 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.minimize_space() @@ -114,7 +114,7 @@ def test_minimize_space_graph_maxspace_with_flow(self, fx_rng: Generator) -> Non def test_parallelize_pattern(self, fx_rng: Generator) -> None: nqubits = 2 depth = 1 - circuit = rc.get_rand_circuit(nqubits, depth, fx_rng) + circuit = ro.get_rand_circuit(nqubits, depth, fx_rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.parallelize_pattern() @@ -127,7 +127,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 2 depth = 1 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -145,7 +145,7 @@ def test_pauli_measurement_random_circuit( rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -162,7 +162,7 @@ def test_pauli_measurement_leave_input_random_circuit( rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -177,7 +177,7 @@ def test_pauli_measurement_opt_gate(self, fx_bg: PCG64, jumps: int, use_rustwork rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 - circuit = rc.get_rand_circuit(nqubits, depth, rng, use_rzz=True) + circuit = ro.get_rand_circuit(nqubits, depth, rng, use_rzz=True) pattern = circuit.transpile(opt=True).pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -192,7 +192,7 @@ def test_pauli_measurement_opt_gate_transpiler(self, fx_bg: PCG64, jumps: int, u rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 - circuit = rc.get_rand_circuit(nqubits, depth, rng, use_rzz=True) + circuit = ro.get_rand_circuit(nqubits, depth, rng, use_rzz=True) pattern = circuit.standardize_and_transpile(opt=True).pattern pattern.standardize(method="global") pattern.shift_signals(method="global") @@ -212,7 +212,7 @@ def test_pauli_measurement_opt_gate_transpiler_without_signalshift( rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 - circuit = rc.get_rand_circuit(nqubits, depth, rng, use_rzz=True) + circuit = ro.get_rand_circuit(nqubits, depth, rng, use_rzz=True) pattern = circuit.standardize_and_transpile(opt=True).pattern pattern.perform_pauli_measurements(use_rustworkx=use_rustworkx) pattern.minimize_space() @@ -369,7 +369,7 @@ def test_get_graph(self, fx_rng: Generator) -> None: nqubits = 5 depth = 4 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng) pattern = circuit.transpile().pattern nodes_ref, edges_ref = pattern.get_graph() @@ -411,7 +411,7 @@ def test_standardize(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern localpattern = pattern.get_local_pattern() localpattern.standardize() @@ -427,7 +427,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern localpattern = pattern.get_local_pattern() localpattern.standardize() @@ -444,7 +444,7 @@ def test_standardize_and_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern pattern.standardize_and_shift_signals() assert pattern.is_standard() @@ -468,7 +468,7 @@ def test_mixed_pattern_operations(self, fx_bg: PCG64, jumps: int) -> None: ] nqubits = 3 depth = 2 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) state_ref = circuit.simulate_statevector().statevec for process in processes: pattern = circuit.transpile().pattern @@ -487,7 +487,7 @@ def test_opt_transpile_standardize(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile(opt=True).pattern pattern.standardize(method="local") assert pattern.is_standard() @@ -501,7 +501,7 @@ def test_opt_transpile_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) pattern = circuit.transpile(opt=True).pattern pattern.standardize(method="local") pattern.shift_signals(method="local") @@ -531,7 +531,7 @@ def test_localpattern_is_standard(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 4 - circuit = rc.get_rand_circuit(nqubits, depth, rng) + circuit = ro.get_rand_circuit(nqubits, depth, rng) localpattern = circuit.transpile().pattern.get_local_pattern() result1 = localpattern.is_standard() localpattern.standardize() diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index b1deef1d..81d84bda 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -8,7 +8,7 @@ from numpy.random import PCG64, Generator from quimb.tensor import Tensor -import graphix.random_circuit as rc +import graphix.random_objects as ro from graphix.clifford import CLIFFORD from graphix.command import C, E, X, Z from graphix.ops import Ops @@ -302,7 +302,7 @@ def test_cnot(self, fx_rng: Generator) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(4, 6, rng) + circuit = ro.get_rand_circuit(4, 6, rng) circuit.ccx(0, 1, 2) pattern = circuit.transpile().pattern pattern.minimize_space() @@ -316,7 +316,7 @@ def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) def test_with_graphtrans(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(4, 6, rng) + circuit = ro.get_rand_circuit(4, 6, rng) pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() @@ -333,7 +333,7 @@ def test_with_graphtrans(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) def test_with_graphtrans_sequential(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(4, 6, rng) + circuit = ro.get_rand_circuit(4, 6, rng) pattern = circuit.transpile().pattern pattern = circuit.transpile().pattern pattern.standardize() @@ -351,7 +351,7 @@ def test_with_graphtrans_sequential(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) def test_coef_state(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(4, 2, rng) + circuit = ro.get_rand_circuit(4, 2, rng) pattern = circuit.standardize_and_transpile().pattern @@ -367,7 +367,7 @@ def test_coef_state(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize(("nqubits", "jumps"), itertools.product(range(2, 6), range(1, 6))) def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(nqubits, 3, rng) + circuit = ro.get_rand_circuit(nqubits, 3, rng) pattern = circuit.standardize_and_transpile().pattern statevec_ref = circuit.simulate_statevector().statevec @@ -380,7 +380,7 @@ def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) def test_evolve(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) - circuit = rc.get_rand_circuit(4, 6, rng) + circuit = ro.get_rand_circuit(4, 6, rng) pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 23dd588e..1af9cfc1 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -5,7 +5,7 @@ from numpy.random import PCG64, Generator import graphix.pauli -import graphix.random_circuit as rc +import graphix.random_objects as ro import graphix.simulator from graphix.transpiler import Circuit @@ -99,7 +99,7 @@ def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 4 depth = 6 - circuit = rc.get_rand_circuit(nqubits, depth, rng, use_ccx=True) + circuit = ro.get_rand_circuit(nqubits, depth, rng, use_ccx=True) pattern = circuit.transpile().pattern pattern.minimize_space() state = circuit.simulate_statevector().statevec @@ -113,7 +113,7 @@ def test_ccx_opt(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 4 depth = 6 - circuit = rc.get_rand_circuit(nqubits, depth, rng, use_ccx=True) + circuit = ro.get_rand_circuit(nqubits, depth, rng, use_ccx=True) circuit.ccx(0, 1, 2) pattern = circuit.transpile(opt=True).pattern pattern.minimize_space() @@ -125,7 +125,7 @@ def test_transpile_opt(self, fx_rng: Generator) -> None: nqubits = 2 depth = 1 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) pattern = circuit.transpile(opt=True).pattern state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern() @@ -135,7 +135,7 @@ def test_standardize_and_transpile(self, fx_rng: Generator) -> None: nqubits = 3 depth = 2 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) pattern = circuit.standardize_and_transpile().pattern state = circuit.simulate_statevector().statevec pattern.minimize_space() @@ -146,7 +146,7 @@ def test_standardize_and_transpile_opt(self, fx_rng: Generator) -> None: nqubits = 3 depth = 2 pairs = [(i, np.mod(i + 1, nqubits)) for i in range(nqubits)] - circuit = rc.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) + circuit = ro.generate_gate(nqubits, depth, pairs, fx_rng, use_rzz=True) pattern = circuit.standardize_and_transpile(opt=True).pattern state = circuit.simulate_statevector().statevec pattern.minimize_space() From 0a7f6019441b04baf42850495f1f7d7249dfb7e1 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Sun, 14 Jul 2024 20:15:40 +0900 Subject: [PATCH 18/33] =?UTF-8?q?=F0=9F=94=A5=20Delete=20random=5Fcircuit.?= =?UTF-8?q?py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/random_circuit.py | 126 -------------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 graphix/random_circuit.py diff --git a/graphix/random_circuit.py b/graphix/random_circuit.py deleted file mode 100644 index 81a8577f..00000000 --- a/graphix/random_circuit.py +++ /dev/null @@ -1,126 +0,0 @@ -from __future__ import annotations - -import functools -from typing import TYPE_CHECKING - -import numpy as np - -from graphix.transpiler import Circuit - -if TYPE_CHECKING: - from collections.abc import Iterable, Iterator - - from numpy.random import Generator - - -def first_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: - for qubit in range(nqubits): - circuit.rx(qubit, rng.random()) - - -def mid_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: - for qubit in range(nqubits): - circuit.rx(qubit, rng.random()) - circuit.rz(qubit, rng.random()) - - -def last_rotation(circuit: Circuit, nqubits: int, rng: Generator) -> None: - for qubit in range(nqubits): - circuit.rz(qubit, rng.random()) - - -def entangler(circuit: Circuit, pairs: Iterable[tuple[int, int]]) -> None: - for a, b in pairs: - circuit.cnot(a, b) - - -def entangler_rzz(circuit: Circuit, pairs: Iterable[tuple[int, int]], rng: Generator) -> None: - for a, b in pairs: - circuit.rzz(a, b, rng.random()) - - -def generate_gate( - nqubits: int, - depth: int, - pairs: Iterable[tuple[int, int]], - rng: Generator, - *, - use_rzz: bool = False, -) -> Circuit: - circuit = Circuit(nqubits) - first_rotation(circuit, nqubits, rng) - entangler(circuit, pairs) - for _ in range(depth - 1): - mid_rotation(circuit, nqubits, rng) - if use_rzz: - entangler_rzz(circuit, pairs, rng) - else: - entangler(circuit, pairs) - last_rotation(circuit, nqubits, rng) - return circuit - - -def genpair(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int]]: - choice = list(range(n_qubits)) - for _ in range(count): - rng.shuffle(choice) - x, y = choice[:2] - yield (x, y) - - -def gentriplet(n_qubits: int, count: int, rng: Generator) -> Iterator[tuple[int, int, int]]: - choice = list(range(n_qubits)) - for _ in range(count): - rng.shuffle(choice) - x, y, z = choice[:3] - yield (x, y, z) - - -def get_rand_circuit( - nqubits: int, - depth: int, - rng: Generator, - *, - use_rzz: bool = False, - use_ccx: bool = False, -) -> Circuit: - """Generate a random circuit. - - Parameters - ---------- - nqubits : int - Number of qubits in the circuit. Must be at least 2. - depth : int - Number of layers in the circuit. - rng : Generator - Random number generator. - use_rzz : bool, optional - Whether to include RZZ gates, by default False. - use_ccx : bool, optional - Whether to include CCX gates, by default False. - """ - circuit = Circuit(nqubits) - gate_choice = ( - functools.partial(circuit.ry, angle=np.pi / 4), - functools.partial(circuit.rz, angle=-np.pi / 4), - functools.partial(circuit.rx, angle=-np.pi / 4), - circuit.h, - circuit.s, - circuit.x, - circuit.z, - circuit.y, - ) - for _ in range(depth): - for j, k in genpair(nqubits, 2, rng): - circuit.cnot(j, k) - if use_rzz: - for j, k in genpair(nqubits, 2, rng): - circuit.rzz(j, k, np.pi / 4) - if use_ccx: - for j, k, l in gentriplet(nqubits, 2, rng): - circuit.ccx(j, k, l) - for j, k in genpair(nqubits, 4, rng): - circuit.swap(j, k) - for j in range(nqubits): - rng.choice(gate_choice)(j) - return circuit From c6e2bb76973b5786696e1917fbf63656756b9284 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 22 Jul 2024 17:37:05 +0900 Subject: [PATCH 19/33] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index a522d454..059b4791 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -572,12 +572,7 @@ def expectation_value(self, op, qubit_indices, output_node_indices=None, **kwagr target_nodes = [output_node_indices[ind] for ind in qubit_indices] out_inds = output_node_indices op_dim = len(qubit_indices) - op = op.reshape( - [ - 2, - ] - * (2 * op_dim) - ) + op = op.reshape([2,] * (2 * op_dim)) new_ind_left = [gen_str() for _ in range(op_dim)] new_ind_right = [gen_str() for _ in range(op_dim)] tn_cp_left = self.copy() From 020fc037e658eacb2886d37d45df74025cca4f34 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 22 Jul 2024 17:56:11 +0900 Subject: [PATCH 20/33] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20in=20tensornet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 059b4791..b24e99a4 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -730,7 +730,7 @@ def proj_basis(angle, vop, plane, choice): vec = BasicStates.VEC[4 + choice].get_statevector() rotU = Ops.Rx(angle) elif plane == Plane.XZ: - vec = States.VEC[0 + choice].get_statevector() + vec = BasicStates.VEC[0 + choice].get_statevector() rotU = Ops.Ry(-angle) vec = np.matmul(rotU, vec) vec = np.matmul(CLIFFORD[CLIFFORD_CONJ[vop]], vec) From 7d4d62f5a5eb0388d5ed97a7cae87378e8602a9b Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 22 Jul 2024 20:50:46 +0900 Subject: [PATCH 21/33] =?UTF-8?q?=F0=9F=8E=A8=20Add=20detailed=20descripti?= =?UTF-8?q?on=20on=20to=5Fstatevector=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index b24e99a4..7a5cd268 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -504,20 +504,30 @@ def get_basis_amplitude(self, basis, **kwagrs): coef = self.get_basis_coefficient(basis, **kwagrs) return abs(coef) ** 2 - def to_statevector(self, skip=False, **kwagrs) -> NDArray: + def to_statevector(self, skip_tn_simp=False, **kwagrs) -> NDArray: """Take outer product of the tensors in the network and return the statevector. + This method uses the contract method and full_simplify method of quimb.tensor.TensorNetwork, + which enables the efficient contraction order search and simplification of the tensor network. + This method maintains the same computational capabilities as the conventional statevector simulation, + as both calculate the same tensor network. + The difference is only in the contraction order. + This eco simulation is not always exactly optimal, but it is efficient enough for most cases. + The current implementation does NOT support intermediate measurements according to the probability distribution. Parameters ---------- - skip : bool - if True, skip the simplification process. + skip_tn_simp : bool + If True, skip the tensor simplification process. + For n_qubits \lessapprox 25, set skip_tn_simp=True is recommended to reduce the simulation time, + while for n_qubits \gtrapprox 25, set skip_tn_simp=False is recommended. + See benchmarks/efficient_contraction_order_statevec.py for the detail. Returns ------- - numpy.ndarray : + : numpy.ndarray statevector """ - if skip: + if skip_tn_simp: tn = self.copy() output_inds = [self._dangling[str(index)] for index in self.default_output_nodes] psi = tn.contract(output_inds=output_inds, **kwagrs).data.reshape(-1) From fae1f4ac032a6e2e80a1d4d2b22f8710150dca38 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 22 Jul 2024 20:51:52 +0900 Subject: [PATCH 22/33] =?UTF-8?q?=F0=9F=8E=A8=20Add=20benchmark=20for=20co?= =?UTF-8?q?ntraction=20skipped=20results?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index 523d2da4..32d6e91d 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -39,6 +39,7 @@ n_qubit_list = list(range(2, 31)) sv_sim = [] +neco_sim = [] eco_sim = [] eco_sim_wo_ctg = [] max_space_ls = [] @@ -49,14 +50,30 @@ pattern = circuit.transpile().pattern pattern.standardize() pat_original = deepcopy(pattern) + + # statevector simulation: sv + if n_qubit > 17: + sv_sim.append(-1) + max_space_ls.append(-1) + else: + start = perf_counter() + pat_original.minimize_space() + max_sp = pat_original.max_space() + max_space_ls.append(max_sp) + sv = pat_original.simulate_pattern(max_qubit_num=max_sp) + end = perf_counter() + sv_sim.append(end - start) + del sv + + # no-contraction order search simulation: tn + tn = pattern.simulate_pattern("tensornetwork") + output_inds = [tn._dangling[str(index)] for index in tn.default_output_nodes] + start = perf_counter() - pat_original.minimize_space() - max_sp = pat_original.max_space() - max_space_ls.append(max_sp) - sv = pat_original.simulate_pattern(max_qubit_num=max_sp) + tn_sv = tn.to_statevector(backend="numpy", skip_tn_simp=True, optimize=None) end = perf_counter() - sv_sim.append(end - start) - del sv + neco_sim.append(end - start) + del tn_sv # efficient contraction order simulation (eco-sim): tn tn = pattern.simulate_pattern("tensornetwork") @@ -65,7 +82,7 @@ start = perf_counter() tn_sv = tn.to_statevector( backend="numpy", - skip=False, + skip_tn_simp=False, optimize=ctg.HyperOptimizer(minimize="combo", max_time=600, progbar=True), ) end = perf_counter() @@ -76,7 +93,7 @@ start = perf_counter() tn_sv = tn.to_statevector( backend="numpy", - skip=False, + skip_tn_simp=False, optimize=None, ) end = perf_counter() @@ -86,9 +103,9 @@ # %% # Finally, we save the results to a text file. with open("sqrqcresults.txt", "w") as f: - f.write("n_qubit, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") + f.write("n_qubit, neco_sim, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") for i in range(len(n_qubit_list)): - f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n") + f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {neco_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n") # %% # We can now plot the simulation time results. @@ -97,17 +114,19 @@ data = np.loadtxt("sqrqcresults.txt", delimiter=",", skiprows=1) n_qubits = data[:, 0].astype(int) -sv_sim = data[:, 1].astype(float) -eco_sim = data[:, 2].astype(float) -eco_sim_wo_ctg = data[:, 3].astype(float) -max_sp = data[:, 4].astype(int) +sv_sim = data[:16, 1].astype(float) +neco_sim = data[:, 2].astype(float) +eco_sim = data[:, 3].astype(float) +eco_sim_wo_ctg = data[:, 4].astype(float) +max_sp = data[:16, 5].astype(int) fig, ax1 = plt.subplots(figsize=(8, 5)) color = "tab:red" ax1.set_xlabel("Original Circuit Size [qubit]") ax1.set_ylabel("Simulation time [sec]") ax1.set_yscale("log") -ax1.scatter(n_qubits, sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") +ax1.scatter(n_qubits[:16], sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") +ax1.scatter(n_qubits, neco_sim, marker="x", label="MBQC TN base (contraction skipped)") ax1.scatter(n_qubits, eco_sim, marker="x", label="MBQC TN base (with cotengra)") ax1.scatter(n_qubits, eco_sim_wo_ctg, marker="x", label="MBQC TN base (without cotengra)") ax1.tick_params(axis="y") @@ -118,10 +137,12 @@ ax2 = ax1.twinx() color = "tab:blue" ax2.set_ylabel("Max Space [qubit]") -ax2.plot(n_qubits, max_sp, color=color, linestyle="--", label="Max Space") +ax2.plot(n_qubits[:16], max_sp, color=color, linestyle="--", label="Max Space") ax2.tick_params(axis="y") ax2.legend(loc="lower right") plt.rcParams["svg.fonttype"] = "none" plt.savefig("simulation_time_wo_p.png") plt.close() + +# %% From 2a028cebddc23a2031d17398904cb15e040033ae Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 22 Jul 2024 21:14:17 +0900 Subject: [PATCH 23/33] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmarks/efficient_contraction_order_statevec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index 32d6e91d..9a8cfe65 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -105,7 +105,9 @@ with open("sqrqcresults.txt", "w") as f: f.write("n_qubit, neco_sim, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") for i in range(len(n_qubit_list)): - f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {neco_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n") + f.write( + f"{n_qubit_list[i]}, {sv_sim[i]}, {neco_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n" + ) # %% # We can now plot the simulation time results. From 40471f9b8b6b2eb7313b50af1927358262b3f493 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Thu, 25 Jul 2024 17:35:51 +0900 Subject: [PATCH 24/33] =?UTF-8?q?=F0=9F=94=A5=20Delete=20skip=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 7a5cd268..87db7311 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -504,7 +504,7 @@ def get_basis_amplitude(self, basis, **kwagrs): coef = self.get_basis_coefficient(basis, **kwagrs) return abs(coef) ** 2 - def to_statevector(self, skip_tn_simp=False, **kwagrs) -> NDArray: + def to_statevector(self, **kwagrs) -> NDArray: """Take outer product of the tensors in the network and return the statevector. This method uses the contract method and full_simplify method of quimb.tensor.TensorNetwork, which enables the efficient contraction order search and simplification of the tensor network. @@ -514,25 +514,11 @@ def to_statevector(self, skip_tn_simp=False, **kwagrs) -> NDArray: This eco simulation is not always exactly optimal, but it is efficient enough for most cases. The current implementation does NOT support intermediate measurements according to the probability distribution. - Parameters - ---------- - skip_tn_simp : bool - If True, skip the tensor simplification process. - For n_qubits \lessapprox 25, set skip_tn_simp=True is recommended to reduce the simulation time, - while for n_qubits \gtrapprox 25, set skip_tn_simp=False is recommended. - See benchmarks/efficient_contraction_order_statevec.py for the detail. - Returns ------- : numpy.ndarray statevector """ - if skip_tn_simp: - tn = self.copy() - output_inds = [self._dangling[str(index)] for index in self.default_output_nodes] - psi = tn.contract(output_inds=output_inds, **kwagrs).data.reshape(-1) - return psi - tn = self.copy() tn_simplified = tn.full_simplify("ADCR") output_inds = [self._dangling[str(index)] for index in self.default_output_nodes] From 06512cc25fdf962a4ede42882ccbe970139e7dfd Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Thu, 25 Jul 2024 17:36:43 +0900 Subject: [PATCH 25/33] =?UTF-8?q?=F0=9F=94=A5=20Remove=20not=20essential?= =?UTF-8?q?=20simulations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 74 +++++-------------- 1 file changed, 17 insertions(+), 57 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index 9a8cfe65..c1864b71 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -36,13 +36,10 @@ # We then run simulations. # Let's benchmark the simulation time of the statevector simulator and the efficient contraction order simulator. -n_qubit_list = list(range(2, 31)) +n_qubit_list = list(range(2, 25)) sv_sim = [] -neco_sim = [] eco_sim = [] -eco_sim_wo_ctg = [] -max_space_ls = [] for n_qubit in n_qubit_list: rng = Generator(PCG64(GLOBAL_SEED)) @@ -54,59 +51,35 @@ # statevector simulation: sv if n_qubit > 17: sv_sim.append(-1) - max_space_ls.append(-1) else: start = perf_counter() pat_original.minimize_space() max_sp = pat_original.max_space() - max_space_ls.append(max_sp) sv = pat_original.simulate_pattern(max_qubit_num=max_sp) end = perf_counter() sv_sim.append(end - start) del sv - # no-contraction order search simulation: tn - tn = pattern.simulate_pattern("tensornetwork") - output_inds = [tn._dangling[str(index)] for index in tn.default_output_nodes] - - start = perf_counter() - tn_sv = tn.to_statevector(backend="numpy", skip_tn_simp=True, optimize=None) - end = perf_counter() - neco_sim.append(end - start) - del tn_sv - - # efficient contraction order simulation (eco-sim): tn + # efficient contraction order simulation (eco-sim) tn = pattern.simulate_pattern("tensornetwork") output_inds = [tn._dangling[str(index)] for index in tn.default_output_nodes] start = perf_counter() tn_sv = tn.to_statevector( backend="numpy", - skip_tn_simp=False, optimize=ctg.HyperOptimizer(minimize="combo", max_time=600, progbar=True), ) end = perf_counter() eco_sim.append(end - start) - del tn_sv - - # eco-sim: tn without cotengra optimizer - start = perf_counter() - tn_sv = tn.to_statevector( - backend="numpy", - skip_tn_simp=False, - optimize=None, - ) - end = perf_counter() - eco_sim_wo_ctg.append(end - start) del tn_sv, tn # %% # Finally, we save the results to a text file. -with open("sqrqcresults.txt", "w") as f: - f.write("n_qubit, neco_sim, sv_sim, eco_sim, eco_sim_wo_ctg, max_space_ls\n") +with open("results.txt", "w") as f: + f.write("n_qubit, sv_sim\n") for i in range(len(n_qubit_list)): f.write( - f"{n_qubit_list[i]}, {sv_sim[i]}, {neco_sim[i]}, {eco_sim[i]}, {eco_sim_wo_ctg[i]}, {max_space_ls[i]}\n" + f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}\n" ) # %% @@ -114,37 +87,24 @@ import matplotlib.pyplot as plt import numpy as np -data = np.loadtxt("sqrqcresults.txt", delimiter=",", skiprows=1) +data = np.loadtxt("results.txt", delimiter=",", skiprows=1) n_qubits = data[:, 0].astype(int) sv_sim = data[:16, 1].astype(float) -neco_sim = data[:, 2].astype(float) -eco_sim = data[:, 3].astype(float) -eco_sim_wo_ctg = data[:, 4].astype(float) -max_sp = data[:16, 5].astype(int) +eco_sim = data[:, 2].astype(float) -fig, ax1 = plt.subplots(figsize=(8, 5)) +fig, ax = plt.subplots(figsize=(8, 5)) color = "tab:red" -ax1.set_xlabel("Original Circuit Size [qubit]") -ax1.set_ylabel("Simulation time [sec]") -ax1.set_yscale("log") -ax1.scatter(n_qubits[:16], sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") -ax1.scatter(n_qubits, neco_sim, marker="x", label="MBQC TN base (contraction skipped)") -ax1.scatter(n_qubits, eco_sim, marker="x", label="MBQC TN base (with cotengra)") -ax1.scatter(n_qubits, eco_sim_wo_ctg, marker="x", label="MBQC TN base (without cotengra)") -ax1.tick_params(axis="y") -ax1.legend(loc="upper left") -ax1.set_title("Simulation time (Square RQC)") +ax.set_xlabel("Original Circuit Size [qubit]") +ax.set_ylabel("Simulation time [sec]") +ax.set_yscale("log") +ax.scatter(n_qubits[:16], sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") +ax.scatter(n_qubits, eco_sim, marker="x", label="MBQC TN base") +ax.tick_params(axis="y") +ax.legend(loc="upper left") +ax.set_title("Simulation time") plt.grid(True, which="Major") - -ax2 = ax1.twinx() -color = "tab:blue" -ax2.set_ylabel("Max Space [qubit]") -ax2.plot(n_qubits[:16], max_sp, color=color, linestyle="--", label="Max Space") -ax2.tick_params(axis="y") -ax2.legend(loc="lower right") - plt.rcParams["svg.fonttype"] = "none" -plt.savefig("simulation_time_wo_p.png") +plt.savefig("simulation_time.png") plt.close() # %% From cf14228c7018bad976e910e547ff1247527e6cae Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Thu, 25 Jul 2024 17:44:54 +0900 Subject: [PATCH 26/33] =?UTF-8?q?=F0=9F=8E=A8=20Reformat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmarks/efficient_contraction_order_statevec.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index c1864b71..c2b37a1d 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -48,7 +48,7 @@ pattern.standardize() pat_original = deepcopy(pattern) - # statevector simulation: sv + # statevector simulation if n_qubit > 17: sv_sim.append(-1) else: @@ -78,9 +78,7 @@ with open("results.txt", "w") as f: f.write("n_qubit, sv_sim\n") for i in range(len(n_qubit_list)): - f.write( - f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}\n" - ) + f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}\n") # %% # We can now plot the simulation time results. From a1b0e8d23416cf54dff6ff61d22eb59272ff1453 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Thu, 25 Jul 2024 17:47:06 +0900 Subject: [PATCH 27/33] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 87db7311..6a2bc061 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -568,7 +568,12 @@ def expectation_value(self, op, qubit_indices, output_node_indices=None, **kwagr target_nodes = [output_node_indices[ind] for ind in qubit_indices] out_inds = output_node_indices op_dim = len(qubit_indices) - op = op.reshape([2,] * (2 * op_dim)) + op = op.reshape( + [ + 2, + ] + * (2 * op_dim) + ) new_ind_left = [gen_str() for _ in range(op_dim)] new_ind_right = [gen_str() for _ in range(op_dim)] tn_cp_left = self.copy() From 405194c48ce7dd8d516354f222afcb5041d4faa8 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 17:45:20 +0900 Subject: [PATCH 28/33] =?UTF-8?q?=F0=9F=8E=A8=20Ignore=20E402=20in=20bench?= =?UTF-8?q?marking=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmarks/efficient_contraction_order_statevec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index c2b37a1d..a68b2deb 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -82,8 +82,8 @@ # %% # We can now plot the simulation time results. -import matplotlib.pyplot as plt -import numpy as np +import matplotlib.pyplot as plt # noqa: E402 +import numpy as np # noqa: E402 data = np.loadtxt("results.txt", delimiter=",", skiprows=1) n_qubits = data[:, 0].astype(int) From b6f20caeec7fd4470424506eea9a2f58a300af23 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 19:02:17 +0900 Subject: [PATCH 29/33] =?UTF-8?q?=F0=9F=8E=A8=20Apply=20ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphix/sim/tensornet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index d8343a8b..1da39956 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -4,9 +4,9 @@ from copy import deepcopy import numpy as np +import numpy.typing as npt import quimb.tensor as qtn import typing_extensions -from numpy.typing import NDArray from quimb.tensor import Tensor, TensorNetwork import graphix.clifford @@ -524,7 +524,7 @@ def get_basis_amplitude(self, basis, **kwagrs): coef = self.get_basis_coefficient(basis, **kwagrs) return abs(coef) ** 2 - def to_statevector(self, **kwagrs) -> NDArray: + def to_statevector(self, **kwagrs) -> npt.NDArray: """Take outer product of the tensors in the network and return the statevector. This method uses the contract method and full_simplify method of quimb.tensor.TensorNetwork, which enables the efficient contraction order search and simplification of the tensor network. From ea52373d142201ecf630351828810f03b0cbddaf Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 19:37:33 +0900 Subject: [PATCH 30/33] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20benchmark?= =?UTF-8?q?=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index a68b2deb..a456a876 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -25,6 +25,7 @@ import quimb as qu from numpy.random import PCG64, Generator +import graphix from graphix.random_objects import get_rand_circuit # %% @@ -38,32 +39,33 @@ n_qubit_list = list(range(2, 25)) -sv_sim = [] -eco_sim = [] -for n_qubit in n_qubit_list: +def prepare_pattern(n_qubit: int) -> tuple[graphix.pattern.Pattern, graphix.pattern.Pattern]: rng = Generator(PCG64(GLOBAL_SEED)) circuit = get_rand_circuit(n_qubit, n_qubit, rng) pattern = circuit.transpile().pattern pattern.standardize() pat_original = deepcopy(pattern) + pattern.standardize() + pat_original = deepcopy(pattern) + return pattern, pat_original + - # statevector simulation +def statevector_sim(n_qubit: int, sv_sim: list, pat_original: graphix.pattern.Pattern) -> None: if n_qubit > 17: sv_sim.append(-1) else: start = perf_counter() pat_original.minimize_space() max_sp = pat_original.max_space() - sv = pat_original.simulate_pattern(max_qubit_num=max_sp) + sv = pat_original.simulate_pattern(max_qubit_num=max_sp) # noqa: F841 end = perf_counter() sv_sim.append(end - start) del sv - # efficient contraction order simulation (eco-sim) - tn = pattern.simulate_pattern("tensornetwork") - output_inds = [tn._dangling[str(index)] for index in tn.default_output_nodes] +def efficient_contraction_order_sim(eco_sim: list, pattern: graphix.pattern.Pattern) -> None: + tn = pattern.simulate_pattern("tensornetwork") start = perf_counter() tn_sv = tn.to_statevector( backend="numpy", @@ -73,10 +75,28 @@ eco_sim.append(end - start) del tn_sv, tn + +def single_iter(n_qubit: int, sv_sim: list, eco_sim: list) -> None: + pattern, pat_original = prepare_pattern(n_qubit) + statevector_sim(n_qubit, sv_sim, pat_original) + efficient_contraction_order_sim(eco_sim, pattern) + + +def benchmark(n_qubit_list: list) -> tuple[list, list]: + sv_sim = [] + eco_sim = [] + for n_qubit in n_qubit_list: + single_iter(n_qubit, sv_sim, eco_sim) + return sv_sim, eco_sim + + +sv_sim, eco_sim = benchmark(n_qubit_list) + + # %% # Finally, we save the results to a text file. with open("results.txt", "w") as f: - f.write("n_qubit, sv_sim\n") + f.write("n_qubit, sv_sim, eco_sim\n") for i in range(len(n_qubit_list)): f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}\n") From c9f0358d5d8582eb2ef396f6bd59036d6cbf4b88 Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 20:19:01 +0900 Subject: [PATCH 31/33] =?UTF-8?q?=E2=9C=A8=20Show=20error=20bars=20in=20be?= =?UTF-8?q?nchmarking=20results?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../efficient_contraction_order_statevec.py | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index a456a876..a168a9bd 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -22,6 +22,8 @@ from time import perf_counter import cotengra as ctg +import numpy as np +import numpy.typing as npt import quimb as qu from numpy.random import PCG64, Generator @@ -37,8 +39,6 @@ # We then run simulations. # Let's benchmark the simulation time of the statevector simulator and the efficient contraction order simulator. -n_qubit_list = list(range(2, 25)) - def prepare_pattern(n_qubit: int) -> tuple[graphix.pattern.Pattern, graphix.pattern.Pattern]: rng = Generator(PCG64(GLOBAL_SEED)) @@ -82,44 +82,59 @@ def single_iter(n_qubit: int, sv_sim: list, eco_sim: list) -> None: efficient_contraction_order_sim(eco_sim, pattern) -def benchmark(n_qubit_list: list) -> tuple[list, list]: +def benchmark(n_qubit_list: list) -> tuple[npt.NDArray, npt.NDArray]: sv_sim = [] eco_sim = [] for n_qubit in n_qubit_list: single_iter(n_qubit, sv_sim, eco_sim) - return sv_sim, eco_sim + return np.array(sv_sim), np.array(eco_sim) + + +# %% +# Here, we calculate the simulation multiple times so that we can make error bars. +n_qubit_list = list(range(2, 25)) +sv_sim_results: list[npt.NDArray] = [] +eco_sim_results: list[npt.NDArray] = [] +for _ in range(6): + sv_sim, eco_sim = benchmark(n_qubit_list) + sv_sim_results.append(sv_sim) + eco_sim_results.append(eco_sim) -sv_sim, eco_sim = benchmark(n_qubit_list) +sv_sim_means = np.mean(sv_sim_results, axis=0) +eco_sim_means = np.mean(eco_sim_results, axis=0) +sv_sim_std = np.std(sv_sim_results, axis=0) +eco_sim_std = np.std(eco_sim_results, axis=0) # %% # Finally, we save the results to a text file. with open("results.txt", "w") as f: - f.write("n_qubit, sv_sim, eco_sim\n") + f.write("n_qubit, sv_sim_mean, sv_sim_std, eco_sim_mean, eco_sim_std\n") for i in range(len(n_qubit_list)): - f.write(f"{n_qubit_list[i]}, {sv_sim[i]}, {eco_sim[i]}\n") + f.write(f"{n_qubit_list[i]}, {sv_sim_means[i]}, {sv_sim_std[i]}, {eco_sim_means[i]}, {eco_sim_std[i]}\n") # %% # We can now plot the simulation time results. import matplotlib.pyplot as plt # noqa: E402 -import numpy as np # noqa: E402 data = np.loadtxt("results.txt", delimiter=",", skiprows=1) n_qubits = data[:, 0].astype(int) -sv_sim = data[:16, 1].astype(float) -eco_sim = data[:, 2].astype(float) +sv_sim_mean = data[:16, 1].astype(float) +sv_sim_std = data[:16, 2].astype(float) +eco_sim_mean = data[:, 3].astype(float) +eco_sim_std = data[:, 4].astype(float) fig, ax = plt.subplots(figsize=(8, 5)) color = "tab:red" -ax.set_xlabel("Original Circuit Size [qubit]") -ax.set_ylabel("Simulation time [sec]") +ax.set_xlabel("Original Circuit Size [qubit]", fontsize=20) +ax.set_ylabel("Simulation time [sec]", fontsize=20) ax.set_yscale("log") -ax.scatter(n_qubits[:16], sv_sim, marker="x", label="MBQC Statevector (minimizing sp)") -ax.scatter(n_qubits, eco_sim, marker="x", label="MBQC TN base") +ax.errorbar(n_qubits[:16], sv_sim_mean, yerr=sv_sim_std, color=color, fmt="o", label="MBQC Statevector (minimizing sp)") +ax.errorbar(n_qubits, eco_sim_mean, yerr=eco_sim_std, fmt="x", label="MBQC TN base") ax.tick_params(axis="y") ax.legend(loc="upper left") -ax.set_title("Simulation time") +ax.set_title("Simulation time", fontsize=20) plt.grid(True, which="Major") plt.rcParams["svg.fonttype"] = "none" plt.savefig("simulation_time.png") From 175e026bb2dc6d73deefd9a096a9968862613eee Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 20:22:32 +0900 Subject: [PATCH 32/33] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20import=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmarks/efficient_contraction_order_statevec.py | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index a168a9bd..6263cf7f 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -17,6 +17,7 @@ # %% # Firstly, let us import relevant modules: +from __future__ import annotations from copy import deepcopy from time import perf_counter From d12470075e107f71ef30371b207fa20d627b683f Mon Sep 17 00:00:00 2001 From: Yuki Watanabe Date: Mon, 19 Aug 2024 20:34:03 +0900 Subject: [PATCH 33/33] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20TYPE=5FCHECKING=20im?= =?UTF-8?q?port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- benchmarks/efficient_contraction_order_statevec.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benchmarks/efficient_contraction_order_statevec.py b/benchmarks/efficient_contraction_order_statevec.py index 6263cf7f..9e8defec 100644 --- a/benchmarks/efficient_contraction_order_statevec.py +++ b/benchmarks/efficient_contraction_order_statevec.py @@ -21,6 +21,7 @@ from copy import deepcopy from time import perf_counter +from typing import TYPE_CHECKING import cotengra as ctg import numpy as np @@ -28,9 +29,11 @@ import quimb as qu from numpy.random import PCG64, Generator -import graphix from graphix.random_objects import get_rand_circuit +if TYPE_CHECKING: + import graphix + # %% # Next, set global seed and number of thread workers GLOBAL_SEED = 2 @@ -59,7 +62,7 @@ def statevector_sim(n_qubit: int, sv_sim: list, pat_original: graphix.pattern.Pa start = perf_counter() pat_original.minimize_space() max_sp = pat_original.max_space() - sv = pat_original.simulate_pattern(max_qubit_num=max_sp) # noqa: F841 + sv = pat_original.simulate_pattern(max_qubit_num=max_sp) end = perf_counter() sv_sim.append(end - start) del sv