Skip to content

Commit 343b119

Browse files
authored
Add rotation gates to stabilizer (#1938)
* Add rotation gates to stabilizer * format tests * set method=statevctor to sample tests * set method=statevector to Estimator test, fix ry gate * format test
1 parent 39487db commit 343b119

File tree

9 files changed

+448
-11
lines changed

9 files changed

+448
-11
lines changed

qiskit_aer/backends/backend_utils.py

+3
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@
211211
"delay",
212212
"pauli",
213213
"ecr",
214+
"rx",
215+
"ry",
216+
"rz",
214217
]
215218
),
216219
"extended_stabilizer": sorted(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
upgrade:
3+
- |
4+
Adding support of rotation gates (rx, ry and rz gates) to stabilizer method
5+
when input theta is multiple of pi/2.
6+
If ``method=automatic`` is specified (this is default), if all the input
7+
theta of rotation gates are multiple of pi/2 ``method=stabilizer``
8+
is selected. Of when user sets ``method=stabilizer`` and any of theta
9+
is not multiple of pi/2, Aer raises an exception.

src/simulators/circuit_executor.hpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -933,13 +933,20 @@ bool Executor<state_t>::validate_state(const Config &config,
933933

934934
JSON::get_value(circ_name, "name", circ.header);
935935

936+
state.set_config(config);
936937
// Check if a circuit is valid for state ops
937938
bool circ_valid = state.opset().contains(circ.opset());
938939
if (throw_except && !circ_valid) {
939940
error_msg << "Circuit " << circ_name << " contains invalid instructions ";
940941
error_msg << state.opset().difference(circ.opset());
941942
error_msg << " for \"" << state.name() << "\" method.";
942943
}
944+
// check parameters set inf ops
945+
circ_valid &= state.validate_parameters(circ.ops);
946+
if (throw_except && !circ_valid) {
947+
error_msg << "Circuit " << circ_name << " contains invalid parameters ";
948+
error_msg << " for \"" << state.name() << "\" method.";
949+
}
943950

944951
// Check if a noise model valid for state ops
945952
bool noise_valid = noise.is_ideal() || state.opset().contains(noise.opset());
@@ -952,8 +959,8 @@ bool Executor<state_t>::validate_state(const Config &config,
952959
// Validate memory requirements
953960
bool memory_valid = true;
954961
if (max_memory_mb_ > 0) {
955-
size_t required_mb =
956-
required_memory_mb(config, circ, noise) / num_process_per_experiment_;
962+
size_t required_mb = state.required_memory_mb(circ.num_qubits, circ.ops) /
963+
num_process_per_experiment_;
957964
size_t mem_size = (sim_device_ == Device::GPU)
958965
? max_memory_mb_ + max_gpu_memory_mb_
959966
: max_memory_mb_;

src/simulators/stabilizer/stabilizer_state.hpp

+78-4
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ const Operations::OpSet StateOpSet(
3838
OpType::save_amps_sq, OpType::save_stabilizer, OpType::save_clifford,
3939
OpType::save_state, OpType::set_stabilizer, OpType::jump, OpType::mark},
4040
// Gates
41-
{"CX", "cx", "cy", "cz", "swap", "id", "x", "y", "z", "h", "s", "sdg", "sx",
42-
"sxdg", "delay", "pauli", "ecr"});
41+
{"CX", "cx", "cy", "cz", "swap", "id", "x", "y", "z", "h",
42+
"s", "sdg", "sx", "sxdg", "delay", "pauli", "ecr", "rx", "ry", "rz"});
4343

4444
enum class Gates {
4545
id,
@@ -56,7 +56,10 @@ enum class Gates {
5656
cz,
5757
swap,
5858
pauli,
59-
ecr
59+
ecr,
60+
rx,
61+
ry,
62+
rz
6063
};
6164

6265
//============================================================================
@@ -101,6 +104,9 @@ class State : public QuantumState::State<Clifford::Clifford> {
101104
virtual std::vector<reg_t> sample_measure(const reg_t &qubits, uint_t shots,
102105
RngEngine &rng) override;
103106

107+
bool
108+
validate_parameters(const std::vector<Operations::Op> &ops) const override;
109+
104110
protected:
105111
//-----------------------------------------------------------------------
106112
// Apply instructions
@@ -203,7 +209,10 @@ const stringmap_t<Gates> State::gateset_({
203209
{"cz", Gates::cz}, // Controlled-Z gate
204210
{"swap", Gates::swap}, // SWAP gate
205211
{"pauli", Gates::pauli}, // Pauli gate
206-
{"ecr", Gates::ecr} // ECR gate
212+
{"ecr", Gates::ecr}, // ECR gate
213+
{"rx", Gates::rx}, // RX gate (only support k * pi/2 cases)
214+
{"ry", Gates::ry}, // RY gate (only support k * pi/2 cases)
215+
{"rz", Gates::rz} // RZ gate (only support k * pi/2 cases)
207216
});
208217

209218
//============================================================================
@@ -245,6 +254,23 @@ void State::set_config(const Config &config) {
245254
max_qubits_snapshot_probs_ = std::max<uint_t>(max_qubits_snapshot_probs_, 64);
246255
}
247256

257+
bool State::validate_parameters(const std::vector<Operations::Op> &ops) const {
258+
for (int_t i = 0; i < ops.size(); i++) {
259+
if (ops[i].type == OpType::gate) {
260+
// check parameter of R gates
261+
if (ops[i].name == "rx" || ops[i].name == "ry" || ops[i].name == "rz") {
262+
double pi2 = std::real(ops[i].params[0]) * 2.0 / M_PI;
263+
double pi2_int = (double)std::round(pi2);
264+
265+
if (!AER::Linalg::almost_equal(pi2, pi2_int)) {
266+
return false;
267+
}
268+
}
269+
}
270+
}
271+
return true;
272+
}
273+
248274
//=========================================================================
249275
// Implementation: apply operations
250276
//=========================================================================
@@ -298,6 +324,7 @@ void State::apply_op(const Operations::Op &op, ExperimentResult &result,
298324
}
299325

300326
void State::apply_gate(const Operations::Op &op) {
327+
int_t pi2;
301328
// Check Op is supported by State
302329
auto it = gateset_.find(op.name);
303330
if (it == gateset_.end())
@@ -369,6 +396,53 @@ void State::apply_gate(const Operations::Op &op) {
369396
BaseState::qreg_.append_x(op.qubits[0]);
370397
BaseState::qreg_.append_x(op.qubits[1]);
371398
break;
399+
case Gates::rx:
400+
pi2 = (int_t)std::round(std::real(op.params[0]) * 2.0 / M_PI) & 3;
401+
if (pi2 == 1) {
402+
// HSH
403+
BaseState::qreg_.append_h(op.qubits[0]);
404+
BaseState::qreg_.append_s(op.qubits[0]);
405+
BaseState::qreg_.append_h(op.qubits[0]);
406+
} else if (pi2 == 2) {
407+
// X
408+
BaseState::qreg_.append_x(op.qubits[0]);
409+
} else if (pi2 == 3) {
410+
// HSdgH
411+
BaseState::qreg_.append_h(op.qubits[0]);
412+
BaseState::qreg_.append_z(op.qubits[0]);
413+
BaseState::qreg_.append_s(op.qubits[0]);
414+
BaseState::qreg_.append_h(op.qubits[0]);
415+
}
416+
break;
417+
case Gates::ry:
418+
pi2 = (int_t)std::round(std::real(op.params[0]) * 2.0 / M_PI) & 3;
419+
if (pi2 == 1) {
420+
// HX
421+
BaseState::qreg_.append_x(op.qubits[0]);
422+
BaseState::qreg_.append_h(op.qubits[0]);
423+
} else if (pi2 == 2) {
424+
// Y
425+
BaseState::qreg_.append_y(op.qubits[0]);
426+
} else if (pi2 == 3) {
427+
// Hdg
428+
BaseState::qreg_.append_h(op.qubits[0]);
429+
BaseState::qreg_.append_x(op.qubits[0]);
430+
}
431+
break;
432+
case Gates::rz:
433+
pi2 = (int_t)std::round(std::real(op.params[0]) * 2.0 / M_PI) & 3;
434+
if (pi2 == 1) {
435+
// S
436+
BaseState::qreg_.append_s(op.qubits[0]);
437+
} else if (pi2 == 2) {
438+
// Z
439+
BaseState::qreg_.append_z(op.qubits[0]);
440+
} else if (pi2 == 3) {
441+
// Sdg
442+
BaseState::qreg_.append_z(op.qubits[0]);
443+
BaseState::qreg_.append_s(op.qubits[0]);
444+
}
445+
break;
372446
default:
373447
// We shouldn't reach here unless there is a bug in gateset
374448
throw std::invalid_argument(

src/simulators/state.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ class Base {
115115
// Typically this is the n-qubit all |0> state
116116
virtual void initialize_qreg(uint_t num_qubits) = 0;
117117

118+
// validate parameters in input operations
119+
virtual bool
120+
validate_parameters(const std::vector<Operations::Op> &ops) const {
121+
return true;
122+
}
123+
118124
//-----------------------------------------------------------------------
119125
// ClassicalRegister methods
120126
//-----------------------------------------------------------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2018, 2019.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
"""
13+
AerSimulator Integration Tests
14+
"""
15+
from ddt import ddt
16+
from test.terra.reference import ref_rotation
17+
from qiskit import transpile
18+
from test.terra.backends.simulator_test_case import SimulatorTestCase, supported_methods
19+
20+
SUPPORTED_METHODS = [
21+
"automatic",
22+
"stabilizer",
23+
"statevector",
24+
"density_matrix",
25+
"matrix_product_state",
26+
"tensor_network",
27+
]
28+
29+
30+
@ddt
31+
class TestRotation(SimulatorTestCase):
32+
"""AerSimulator Rotation gate tests"""
33+
34+
SEED = 12345
35+
36+
# ---------------------------------------------------------------------
37+
# Test rx-gate
38+
# ---------------------------------------------------------------------
39+
@supported_methods(SUPPORTED_METHODS)
40+
def test_rx_gate_deterministic(self, method, device):
41+
"""Test rx-gate circuits"""
42+
backend = self.backend(method=method, device=device, seed_simulator=self.SEED)
43+
shots = 1000
44+
circuits = ref_rotation.rx_gate_circuits_deterministic(final_measure=True)
45+
targets = ref_rotation.rx_gate_counts_deterministic(shots)
46+
result = backend.run(circuits, shots=shots).result()
47+
self.assertSuccess(result)
48+
self.compare_counts(result, circuits, targets, delta=0.05 * shots)
49+
50+
# ---------------------------------------------------------------------
51+
# Test rz-gate
52+
# ---------------------------------------------------------------------
53+
@supported_methods(SUPPORTED_METHODS)
54+
def test_rz_gate_deterministic(self, method, device):
55+
"""Test rz-gate circuits"""
56+
backend = self.backend(method=method, device=device, seed_simulator=self.SEED)
57+
shots = 1000
58+
circuits = ref_rotation.rz_gate_circuits_deterministic(final_measure=True)
59+
targets = ref_rotation.rz_gate_counts_deterministic(shots)
60+
result = backend.run(circuits, shots=shots).result()
61+
self.assertSuccess(result)
62+
self.compare_counts(result, circuits, targets, delta=0.05 * shots)
63+
64+
# ---------------------------------------------------------------------
65+
# Test ry-gate
66+
# ---------------------------------------------------------------------
67+
@supported_methods(SUPPORTED_METHODS)
68+
def test_ry_gate_deterministic(self, method, device):
69+
"""Test ry-gate circuits"""
70+
backend = self.backend(method=method, device=device, seed_simulator=self.SEED)
71+
shots = 1000
72+
circuits = ref_rotation.ry_gate_circuits_deterministic(final_measure=True)
73+
targets = ref_rotation.ry_gate_counts_deterministic(shots)
74+
result = backend.run(circuits, shots=shots).result()
75+
self.assertSuccess(result)
76+
self.compare_counts(result, circuits, targets, delta=0.05 * shots)

test/terra/primitives/test_estimator.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def test_estimator(self, abelian_grouping):
5757
with self.subTest("PauliSumOp"):
5858
observable = PauliSumOp.from_list(lst)
5959
ansatz = RealAmplitudes(num_qubits=2, reps=2)
60-
est = Estimator(abelian_grouping=abelian_grouping)
60+
est = Estimator(
61+
backend_options={"method": "statevector"}, abelian_grouping=abelian_grouping
62+
)
6163
result = est.run(
6264
ansatz, observable, parameter_values=[[0, 1, 1, 2, 3, 5]], seed=15
6365
).result()
@@ -67,7 +69,9 @@ def test_estimator(self, abelian_grouping):
6769
with self.subTest("SparsePauliOp"):
6870
observable = SparsePauliOp.from_list(lst)
6971
ansatz = RealAmplitudes(num_qubits=2, reps=2)
70-
est = Estimator(abelian_grouping=abelian_grouping)
72+
est = Estimator(
73+
backend_options={"method": "statevector"}, abelian_grouping=abelian_grouping
74+
)
7175
result = est.run(
7276
ansatz, observable, parameter_values=[[0, 1, 1, 2, 3, 5]], seed=15
7377
).result()
@@ -84,7 +88,9 @@ def test_estimator(self, abelian_grouping):
8488
]
8589
)
8690
ansatz = RealAmplitudes(num_qubits=2, reps=2)
87-
est = Estimator(abelian_grouping=abelian_grouping)
91+
est = Estimator(
92+
backend_options={"method": "statevector"}, abelian_grouping=abelian_grouping
93+
)
8894
result = est.run(ansatz, observable, parameter_values=[[0] * 6], seed=15).result()
8995
self.assertIsInstance(result, EstimatorResult)
9096
np.testing.assert_allclose(result.values, [-0.4], rtol=0.02)

test/terra/primitives/test_sampler.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def test_sampler_param_order(self):
110110
qc.measure(1, 1)
111111
qc.measure(2, 2)
112112

113-
sampler = Sampler(backend_options={"seed_simulator": 15})
113+
sampler = Sampler(backend_options={"method": "statevector", "seed_simulator": 15})
114114
result = sampler.run([qc] * 4, [[0, 0], [0, 0], [np.pi / 2, 0], [0, np.pi / 2]]).result()
115115
self.assertIsInstance(result, SamplerResult)
116116
self.assertEqual(len(result.quasi_dists), 4)
@@ -140,7 +140,7 @@ def test_sampler_reverse_meas_order(self):
140140
qc.measure(1, 1)
141141
qc.measure(2, 0)
142142

143-
sampler = Sampler()
143+
sampler = Sampler(backend_options={"method": "statevector"})
144144
result = sampler.run(
145145
[qc, qc, qc, qc], [[0, 0], [0, 0], [np.pi / 2, 0], [0, np.pi / 2]], seed=15
146146
).result()

0 commit comments

Comments
 (0)