Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stim clifford simulator integration #314

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5fb4573
initial commit
elloyd-1qbit Jan 16, 2023
23104ce
Integrate bug fix into main before release (#278) (#279)
ValentinS4t1qbit Feb 17, 2023
8e8ac44
Merge branch 'goodchemistryco:main' into stim_clifford_sim
elloyd-1qbit May 2, 2023
b496d49
circular import fix
elloyd-1qbit May 2, 2023
de5383a
circular import fix, remove clifford_circuits.py
elloyd-1qbit May 2, 2023
0bf16d4
direct tableau
elloyd-1qbit May 17, 2023
7ba2170
noise and circuit sampler
elloyd-1qbit May 24, 2023
10a4a7c
moved decompose clifford gates
elloyd-1qbit May 24, 2023
d691eef
initial commit
elloyd-1qbit Jan 16, 2023
e4e0e04
circular import fix
elloyd-1qbit May 2, 2023
0c78d75
circular import fix, remove clifford_circuits.py
elloyd-1qbit May 2, 2023
0e6dd4b
direct tableau
elloyd-1qbit May 17, 2023
4607fdd
noise and circuit sampler
elloyd-1qbit May 24, 2023
f4ae913
moved decompose clifford gates
elloyd-1qbit May 24, 2023
09a9331
adding tests
elloyd-1qbit Jun 15, 2023
60285e5
Merge branch 'stim_clifford_sim' of https://github.com/elloyd-1qbit/T…
elloyd-1qbit Jun 15, 2023
8201297
small fixes
elloyd-1qbit Jun 16, 2023
24381cb
codestyle
elloyd-1qbit Jun 16, 2023
8a777d5
first small comments
elloyd-1qbit Jun 16, 2023
7ecd76f
clifford decomp tests, and SDAG in cirq
elloyd-1qbit Jun 21, 2023
aa615c2
is_clifford in gate class
elloyd-1qbit Jun 22, 2023
c29cad2
expand clifford decomp to integer values
elloyd-1qbit Jun 23, 2023
ca2e25f
Merge branch 'develop' into stim_clifford_sim
elloyd-1qbit Jun 23, 2023
a2cd8d1
fixes
elloyd-1qbit Jun 23, 2023
21d0ed2
codestyle
elloyd-1qbit Jun 24, 2023
b0a29ff
check stim is installed fix
elloyd-1qbit Jun 25, 2023
d902268
small comments, add pip install stim to workflow
elloyd-1qbit Jun 26, 2023
d9d1dcb
Merge remote-tracking branch 'upstream/develop' into stim_clifford_sim
elloyd-1qbit Jun 26, 2023
95e55ab
addressing comments, changed translation dictionary
elloyd-1qbit Jun 26, 2023
8be842c
added NotImplementedErrors
elloyd-1qbit Jun 27, 2023
6f0ce13
Merge remote-tracking branch 'upstream/develop' into stim_clifford_sim
elloyd-1qbit Jun 27, 2023
1c2f327
added _get_expectation_value_from_frequencies test
elloyd-1qbit Jun 28, 2023
fe6a4ab
removed variance
elloyd-1qbit Jun 28, 2023
5c6b3a2
codestyle
elloyd-1qbit Jun 28, 2023
8de9ca5
bug fix
elloyd-1qbit Jun 28, 2023
41f4e94
docstrings
elloyd-1qbit Jun 28, 2023
cb7357b
docstrings
elloyd-1qbit Jun 28, 2023
7374e8a
Merge remote-tracking branch 'upstream/develop' into stim_clifford_sim
elloyd-1qbit Jun 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
pip install cirq
pip install projectq
pip install pennylane
pip install stim
if: always()

- name: Install Microsoft qsharp/qdk
Expand Down
26 changes: 26 additions & 0 deletions tangelo/helpers/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,29 @@ def bool_col_echelon(bool_array):
pivot -= 1

return bool_array


def arrays_almost_equal_up_to_global_phase(array1, array2, atol=1e-6):
"""
Checks if two arrays are almost equal up to a global phase.

Args:
elloyd-1qbit marked this conversation as resolved.
Show resolved Hide resolved
array1 (array): Self-explanatory.
array2 (array): Self-explanatory.
atol (float) : Optional, absolute tolerance

Returns:
bool : True if arrays are almost equal up to a global phase, False otherwise.
"""
if len(array1) != len(array2):
return False

array1 = np.asarray(array1)
array2 = np.asarray(array2)

if np.allclose(array1, array2, atol=atol):
return True

# Check for global phase difference
phase_diff = np.angle(array1[0] / array2[0])
return np.allclose(array1, array2 * np.exp(1j * phase_diff), atol=atol)
4 changes: 3 additions & 1 deletion tangelo/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ def new_func(*args, **kwargs):


# List all built-in backends supported
all_backends = {"qulacs", "qiskit", "cirq", "braket", "projectq", "qdk", "pennylane", "sympy"}
all_backends = {"qulacs", "qiskit", "cirq", "braket", "projectq", "qdk", "pennylane", "sympy", "stim"}
ValentinS4t1qbit marked this conversation as resolved.
Show resolved Hide resolved
all_backends_simulator = {"qulacs", "qiskit", "cirq", "qdk"}
sv_backends_simulator = {"qulacs", "qiskit", "cirq"}
symbolic_backends = {"sympy"}
chem_backends = {"pyscf", "psi4"}
clifford_backends_simulator = {"stim"}

# Dictionary mapping package names to their identifier in this module
packages = {p: p for p in all_backends}
Expand All @@ -88,6 +89,7 @@ def new_func(*args, **kwargs):
installed_simulator = installed_backends & all_backends_simulator
installed_sv_simulator = installed_backends & sv_backends_simulator
installed_chem_backends = {p_id for p_id in chem_backends if is_package_installed(p_id)}
installed_clifford_simulators = {p_id for p_id in clifford_backends_simulator if is_package_installed(p_id)}

# Check if qulacs installed (better performance for tests). If not, defaults to cirq (always installed with openfermion)
default_simulator = "qulacs" if "qulacs" in installed_sv_simulator else "cirq"
22 changes: 21 additions & 1 deletion tangelo/linq/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
gate operation, without tying it to a particular backend or an underlying
mathematical operation.
"""
from math import pi
from math import pi, isclose
from typing import Union

from numpy import integer, ndarray, floating
Expand All @@ -36,6 +36,8 @@
"CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP",
"CSWAP"}

CLIFFORD_GATES = {"H", "S", "X", "Z", "Y", "SDAG", "CNOT", "CX", "CY", "CZ", "SWAP", "CXSWAP"}


class Gate(dict):
"""An abstract gate class that exposes all the gate information such as gate
Expand Down Expand Up @@ -180,6 +182,24 @@ def inverse(self):
raise AttributeError(f"{self.name} is not an invertible gate when parameter is {self.parameter}")
return Gate(self.name, self.target, self.control, new_parameter, self.is_variational)

def is_clifford(self, abs_tol=1e-4):
"""
Check if a quantum gate is a Clifford gate.

Args:
abs_tol (float) : Optional, Absolute tolerance for the difference between gate parameter clifford parameter values

Returns:
bool: True if the gate is in Clifford gate list or has a parameter corresponding to Clifford points,
False otherwise.
"""
if self.name in CLIFFORD_GATES:
return True
elif self.name in {"RX", "RY", "RZ", "PHASE"}:
return isclose(self.parameter % (pi / 2), 0, abs_tol=abs_tol)
else:
return False

def serialize(self):
return {"type": "Gate",
"params": {"name": self.name, "target": self.target,
Expand Down
89 changes: 89 additions & 0 deletions tangelo/linq/helpers/circuits/clifford_circuits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2023 Good Chemistry Company.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from math import pi, isclose
elloyd-1qbit marked this conversation as resolved.
Show resolved Hide resolved

from tangelo.linq import Gate


def decompose_gate_to_cliffords(gate, abs_tol=1e-4):
"""
Decomposes a single qubit parameterized gate into Clifford gates.

Args:
gate (Gate): The gate to be decomposed.
abs_tol (float): Optional, absolute tolerance for value comparison (default: 1e-4).

Returns:
list: A list of Clifford gates representing the decomposition.

Raises:
ValueError: If parameterized gate cannot be decomposed into Clifford gates.

"""
# Return an error if the gate is not in the Clifford gate set
if not gate.is_clifford(abs_tol):
raise ValueError(f"Error. The following gate cannot be decomposed into Clifford gates:\n {gate}")
# If gate is not in the parameterized gate set, it decomposes to itself
elif gate.name not in {"RX", "RY", "RZ", "PHASE"}:
return gate
# If gate parameter is close to 0, the gate operation is the identity
elif isclose(gate.parameter, 0, abs_tol=abs_tol):
return []

# Find which Clifford parameter gate parameter corresponds to.
clifford_values = [0, pi, pi / 2, -pi / 2]
clifford_parameter = next((value for value in clifford_values if
isclose(gate.parameter % (2 * pi), value % (2 * pi), abs_tol=abs_tol)), None)

if clifford_parameter is None:
raise ValueError(f"Error: Parameterized gate {gate} cannot be decomposed into Clifford gates")

gate_list = []
if gate.name == "RY":
if clifford_parameter == -pi / 2:
gate_list = [Gate("H", gate.target), Gate("Z", gate.target)]
elif clifford_parameter == pi / 2:
gate_list = [Gate("Z", gate.target), Gate("H", gate.target)]

elif clifford_parameter == pi:
gate_list = [Gate("SDAG", gate.target), Gate("Y", gate.target), Gate("SDAG", gate.target)]

elif gate.name == "RX":
if clifford_parameter == -pi / 2:
gate_list = [Gate("S", gate.target), Gate("H", gate.target), Gate("S", gate.target)]
elif clifford_parameter == pi / 2:
gate_list = [Gate("SDAG", gate.target), Gate("H", gate.target), Gate("SDAG", gate.target)]
elif clifford_parameter == pi:
gate_list = [Gate("SDAG", gate.target), Gate("X", gate.target), Gate("SDAG", gate.target)]

elif gate.name == "RZ":
if clifford_parameter == -pi / 2:
gate_list = [Gate("H", gate.target), Gate("S", gate.target), Gate("H", gate.target), Gate("S", gate.target),
Gate("H", gate.target)]
elif clifford_parameter == pi / 2:
gate_list = [Gate("H", gate.target), Gate("SDAG", gate.target), Gate("H", gate.target),
Gate("SDAG", gate.target), Gate("H", gate.target)]
elif clifford_parameter == pi:
gate_list = [Gate("H", gate.target), Gate("SDAG", gate.target), Gate("X", gate.target), Gate("SDAG", gate.target), Gate("H", gate.target)]

elif gate.name == "PHASE":
if clifford_parameter == -pi / 2:
gate_list = [Gate("SDAG", gate.target)]
elif clifford_parameter == pi / 2:
gate_list = [Gate("S", gate.target)]
elif clifford_parameter == pi:
gate_list = [Gate("Z", gate.target)]

return gate_list
43 changes: 43 additions & 0 deletions tangelo/linq/helpers/circuits/tests/test_clifford_circuits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 Good Chemistry Company.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest
elloyd-1qbit marked this conversation as resolved.
Show resolved Hide resolved

import numpy as np

from tangelo.linq import Gate, Circuit
from tangelo.linq.helpers.circuits.clifford_circuits import decompose_gate_to_cliffords
from tangelo.linq.translator.translate_cirq import translate_c_to_cirq
from tangelo.helpers.math import arrays_almost_equal_up_to_global_phase

clifford_angles = [0, np.pi/2, np.pi, -np.pi/2, 3*np.pi/2, 7*np.pi, -5*np.pi]
non_clifford_gate = Gate("RY", 0, parameter=np.pi/3)


class CliffordCircuitTest(unittest.TestCase):

def test_decompose_gate_to_cliffords(self):
"""Test if gate decomposition returns correct sequence. Uses Cirq to validate that the unitaries returned
are equal up to a global phase"""
for gate in ["RY", "RX", "RZ", "PHASE"]:
for angle in clifford_angles:
ref_gate = Gate(gate, 0, parameter=angle)
u_ref = translate_c_to_cirq(Circuit([ref_gate]))
u_decompose = translate_c_to_cirq(Circuit(decompose_gate_to_cliffords(ref_gate)))
arrays_almost_equal_up_to_global_phase(u_ref.unitary(), u_decompose.unitary())

def test_non_clifford_gates(self):
"""Test if non-clifford gate raises value error"""

self.assertRaises(ValueError, decompose_gate_to_cliffords, non_clifford_gate)
5 changes: 3 additions & 2 deletions tangelo/linq/target/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from .target_qulacs import QulacsSimulator
from .target_qdk import QDKSimulator
from .target_sympy import SympySimulator
from tangelo.helpers.utils import all_backends_simulator
from .target_stim import StimSimulator
ValentinS4t1qbit marked this conversation as resolved.
Show resolved Hide resolved
from tangelo.helpers.utils import all_backends_simulator, clifford_backends_simulator


target_dict = {"qiskit": QiskitSimulator, "cirq": CirqSimulator, "qdk": QDKSimulator, "qulacs": QulacsSimulator, "sympy": SympySimulator}
target_dict = {"qiskit": QiskitSimulator, "cirq": CirqSimulator, "qdk": QDKSimulator, "qulacs": QulacsSimulator, "sympy": SympySimulator, "stim": StimSimulator}

# Generate backend info dictionary
backend_info = {sim_id: target_dict[sim_id].backend_info() for sim_id in all_backends_simulator}
1 change: 1 addition & 0 deletions tangelo/linq/target/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ def _get_expectation_value_from_statevector(self, qubit_operator, state_prep_cir
complex: The expectation value of this operator with regards to the
state preparation.
"""

n_qubits = state_prep_circuit.width

expectation_value = 0.
Expand Down
Loading