qumat 0.0.1__py3-none-any.whl → 0.5.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qumat/__init__.py +20 -0
- qumat/amazon_braket_backend.py +95 -15
- qumat/cirq_backend.py +100 -16
- qumat/qdp.py +63 -0
- qumat/qiskit_backend.py +117 -24
- qumat/qumat.py +487 -13
- qumat-0.5.0rc1.dist-info/METADATA +111 -0
- qumat-0.5.0rc1.dist-info/RECORD +11 -0
- {qumat-0.0.1.dist-info → qumat-0.5.0rc1.dist-info}/WHEEL +1 -1
- {qumat-0.0.1.dist-info → qumat-0.5.0rc1.dist-info/licenses}/LICENSE +0 -112
- qumat-0.5.0rc1.dist-info/licenses/NOTICE +7 -0
- qumat-0.0.1.dist-info/METADATA +0 -77
- qumat-0.0.1.dist-info/NOTICE +0 -41
- qumat-0.0.1.dist-info/RECORD +0 -10
qumat/__init__.py
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
3
|
+
# contributor license agreements. See the NOTICE file distributed with
|
|
4
|
+
# this work for additional information regarding copyright ownership.
|
|
5
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
6
|
+
# (the "License"); you may not use this file except in compliance with
|
|
7
|
+
# the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
from .qumat import QuMat
|
|
18
|
+
from . import qdp
|
|
19
|
+
|
|
20
|
+
__all__ = ["QuMat", "qdp"]
|
qumat/amazon_braket_backend.py
CHANGED
|
@@ -14,60 +14,109 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
#
|
|
17
|
-
from braket.aws import
|
|
17
|
+
from braket.aws import AwsDevice
|
|
18
|
+
from braket.devices import LocalSimulator
|
|
18
19
|
from braket.circuits import Circuit, FreeParameter
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
def initialize_backend(backend_config):
|
|
21
|
-
backend_options = backend_config[
|
|
22
|
-
simulator_type = backend_options.get(
|
|
23
|
-
if simulator_type ==
|
|
23
|
+
backend_options = backend_config["backend_options"]
|
|
24
|
+
simulator_type = backend_options.get("simulator_type", "default")
|
|
25
|
+
if simulator_type == "local":
|
|
26
|
+
return LocalSimulator()
|
|
27
|
+
elif simulator_type == "default":
|
|
24
28
|
return AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
|
|
25
29
|
else:
|
|
26
|
-
print(
|
|
30
|
+
print(
|
|
31
|
+
f"Simulator type '{simulator_type}' is not supported in Amazon Braket. Using default."
|
|
32
|
+
)
|
|
27
33
|
return AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
|
|
36
|
+
def create_empty_circuit(num_qubits: int | None = None):
|
|
37
|
+
circuit = Circuit()
|
|
38
|
+
if num_qubits is not None:
|
|
39
|
+
for i in range(num_qubits):
|
|
40
|
+
circuit.i(i)
|
|
41
|
+
return circuit
|
|
42
|
+
|
|
31
43
|
|
|
32
44
|
def apply_not_gate(circuit, qubit_index):
|
|
33
45
|
circuit.x(qubit_index)
|
|
34
46
|
|
|
47
|
+
|
|
35
48
|
def apply_hadamard_gate(circuit, qubit_index):
|
|
36
49
|
circuit.h(qubit_index)
|
|
37
50
|
|
|
51
|
+
|
|
38
52
|
def apply_cnot_gate(circuit, control_qubit_index, target_qubit_index):
|
|
39
53
|
circuit.cnot(control_qubit_index, target_qubit_index)
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
|
|
56
|
+
def apply_toffoli_gate(
|
|
57
|
+
circuit, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
58
|
+
):
|
|
42
59
|
circuit.ccnot(control_qubit_index1, control_qubit_index2, target_qubit_index)
|
|
43
60
|
|
|
61
|
+
|
|
44
62
|
def apply_swap_gate(circuit, qubit_index1, qubit_index2):
|
|
45
63
|
circuit.swap(qubit_index1, qubit_index2)
|
|
46
64
|
|
|
65
|
+
|
|
66
|
+
def apply_cswap_gate(
|
|
67
|
+
circuit, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
68
|
+
):
|
|
69
|
+
circuit.cswap(control_qubit_index, target_qubit_index1, target_qubit_index2)
|
|
70
|
+
|
|
71
|
+
|
|
47
72
|
def apply_pauli_x_gate(circuit, qubit_index):
|
|
48
73
|
circuit.x(qubit_index)
|
|
49
74
|
|
|
75
|
+
|
|
50
76
|
def apply_pauli_y_gate(circuit, qubit_index):
|
|
51
77
|
circuit.y(qubit_index)
|
|
52
78
|
|
|
79
|
+
|
|
53
80
|
def apply_pauli_z_gate(circuit, qubit_index):
|
|
54
81
|
circuit.z(qubit_index)
|
|
55
82
|
|
|
83
|
+
|
|
84
|
+
def apply_t_gate(circuit, qubit_index):
|
|
85
|
+
circuit.t(qubit_index)
|
|
86
|
+
|
|
87
|
+
|
|
56
88
|
def execute_circuit(circuit, backend, backend_config):
|
|
57
|
-
shots = backend_config[
|
|
58
|
-
|
|
89
|
+
shots = backend_config["backend_options"].get("shots", 1)
|
|
90
|
+
parameter_values = backend_config.get("parameter_values", {})
|
|
91
|
+
if parameter_values and circuit.parameters:
|
|
92
|
+
# Braket accepts parameter names as strings in inputs dict
|
|
93
|
+
inputs = {
|
|
94
|
+
param_name: value
|
|
95
|
+
for param_name, value in parameter_values.items()
|
|
96
|
+
if param_name in {p.name for p in circuit.parameters}
|
|
97
|
+
}
|
|
98
|
+
task = backend.run(circuit, shots=shots, inputs=inputs)
|
|
99
|
+
else:
|
|
100
|
+
task = backend.run(circuit, shots=shots)
|
|
59
101
|
result = task.result()
|
|
60
102
|
return result.measurement_counts
|
|
61
103
|
|
|
104
|
+
|
|
62
105
|
# placeholder method for use in the testing suite
|
|
63
106
|
def get_final_state_vector(circuit, backend, backend_config):
|
|
64
|
-
|
|
107
|
+
circuit.state_vector()
|
|
108
|
+
result = backend.run(circuit, shots=0).result()
|
|
109
|
+
state_vector = result.values[0]
|
|
110
|
+
|
|
111
|
+
return state_vector
|
|
112
|
+
|
|
65
113
|
|
|
66
114
|
def draw_circuit(circuit):
|
|
67
115
|
# Unfortunately, Amazon Braket does not have direct support for drawing circuits in the same way
|
|
68
116
|
# as Qiskit and Cirq. You would typically visualize Amazon Braket circuits using external tools.
|
|
69
|
-
# For simplicity, we'll
|
|
70
|
-
|
|
117
|
+
# For simplicity, we'll return the circuit object's string representation.
|
|
118
|
+
return str(circuit)
|
|
119
|
+
|
|
71
120
|
|
|
72
121
|
def apply_rx_gate(circuit, qubit_index, angle):
|
|
73
122
|
if isinstance(angle, (int, float)):
|
|
@@ -92,7 +141,38 @@ def apply_rz_gate(circuit, qubit_index, angle):
|
|
|
92
141
|
param = FreeParameter(angle)
|
|
93
142
|
circuit.rz(qubit_index, param)
|
|
94
143
|
|
|
144
|
+
|
|
95
145
|
def apply_u_gate(circuit, qubit_index, theta, phi, lambd):
|
|
96
|
-
|
|
97
|
-
circuit.ry(qubit_index, phi)
|
|
146
|
+
# U(θ, φ, λ) = Rz(φ) · Ry(θ) · Rz(λ)
|
|
98
147
|
circuit.rz(qubit_index, lambd)
|
|
148
|
+
circuit.ry(qubit_index, theta)
|
|
149
|
+
circuit.rz(qubit_index, phi)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def calculate_prob_zero(results, ancilla_qubit, num_qubits):
|
|
153
|
+
"""
|
|
154
|
+
Calculate the probability of measuring the ancilla qubit in |0> state.
|
|
155
|
+
|
|
156
|
+
Amazon Braket uses big-endian qubit ordering with string format results,
|
|
157
|
+
where the leftmost bit corresponds to qubit 0.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
results: Measurement results from execute_circuit() (dict with string keys)
|
|
161
|
+
ancilla_qubit: Index of the ancilla qubit
|
|
162
|
+
num_qubits: Total number of qubits in the circuit
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
float: Probability of measuring ancilla in |0> state
|
|
166
|
+
"""
|
|
167
|
+
if isinstance(results, list):
|
|
168
|
+
results = results[0]
|
|
169
|
+
|
|
170
|
+
total_shots = sum(results.values())
|
|
171
|
+
count_zero = 0
|
|
172
|
+
|
|
173
|
+
for state, count in results.items():
|
|
174
|
+
# Braket: big-endian, leftmost bit is qubit 0
|
|
175
|
+
if len(state) > ancilla_qubit and state[ancilla_qubit] == "0":
|
|
176
|
+
count_zero += count
|
|
177
|
+
|
|
178
|
+
return count_zero / total_shots if total_shots > 0 else 0.0
|
qumat/cirq_backend.py
CHANGED
|
@@ -17,72 +17,120 @@
|
|
|
17
17
|
import cirq
|
|
18
18
|
import sympy
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
def initialize_backend(backend_config):
|
|
21
|
-
|
|
22
|
-
simulator_type = backend_config.get(
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# Assuming 'simulator_type' specifies the type of simulator in Cirq
|
|
23
|
+
simulator_type = backend_config.get("backend_options", {}).get(
|
|
24
|
+
"simulator_type", "default"
|
|
25
|
+
)
|
|
26
|
+
if simulator_type != "default":
|
|
27
|
+
print(
|
|
28
|
+
f"Simulator type '{simulator_type}' is not supported in Cirq. Ignoring this argument"
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
return cirq.Simulator()
|
|
27
32
|
|
|
28
33
|
|
|
29
|
-
def create_empty_circuit(num_qubits):
|
|
30
|
-
|
|
34
|
+
def create_empty_circuit(num_qubits: int | None = None):
|
|
35
|
+
circuit = cirq.Circuit()
|
|
36
|
+
if num_qubits is not None:
|
|
37
|
+
qubits = [cirq.LineQubit(i) for i in range(num_qubits)]
|
|
38
|
+
for qubit in qubits:
|
|
39
|
+
circuit.append(cirq.I(qubit))
|
|
40
|
+
return circuit
|
|
41
|
+
|
|
31
42
|
|
|
32
43
|
def apply_not_gate(circuit, qubit_index):
|
|
33
44
|
qubit = cirq.LineQubit(qubit_index)
|
|
34
45
|
circuit.append(cirq.X(qubit))
|
|
35
46
|
|
|
47
|
+
|
|
36
48
|
def apply_hadamard_gate(circuit, qubit_index):
|
|
37
49
|
qubit = cirq.LineQubit(qubit_index)
|
|
38
50
|
circuit.append(cirq.H(qubit))
|
|
39
51
|
|
|
52
|
+
|
|
40
53
|
def apply_cnot_gate(circuit, control_qubit_index, target_qubit_index):
|
|
41
54
|
control_qubit = cirq.LineQubit(control_qubit_index)
|
|
42
55
|
target_qubit = cirq.LineQubit(target_qubit_index)
|
|
43
56
|
circuit.append(cirq.CNOT(control_qubit, target_qubit))
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
|
|
59
|
+
def apply_toffoli_gate(
|
|
60
|
+
circuit, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
61
|
+
):
|
|
46
62
|
control_qubit1 = cirq.LineQubit(control_qubit_index1)
|
|
47
63
|
control_qubit2 = cirq.LineQubit(control_qubit_index2)
|
|
48
64
|
target_qubit = cirq.LineQubit(target_qubit_index)
|
|
49
65
|
circuit.append(cirq.CCX(control_qubit1, control_qubit2, target_qubit))
|
|
50
66
|
|
|
67
|
+
|
|
51
68
|
def apply_swap_gate(circuit, qubit_index1, qubit_index2):
|
|
52
69
|
qubit1 = cirq.LineQubit(qubit_index1)
|
|
53
70
|
qubit2 = cirq.LineQubit(qubit_index2)
|
|
54
71
|
circuit.append(cirq.SWAP(qubit1, qubit2))
|
|
55
72
|
|
|
73
|
+
|
|
74
|
+
def apply_cswap_gate(
|
|
75
|
+
circuit, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
76
|
+
):
|
|
77
|
+
control_qubit = cirq.LineQubit(control_qubit_index)
|
|
78
|
+
target_qubit1 = cirq.LineQubit(target_qubit_index1)
|
|
79
|
+
target_qubit2 = cirq.LineQubit(target_qubit_index2)
|
|
80
|
+
circuit.append(cirq.CSWAP(control_qubit, target_qubit1, target_qubit2))
|
|
81
|
+
|
|
82
|
+
|
|
56
83
|
def apply_pauli_x_gate(circuit, qubit_index):
|
|
57
84
|
qubit = cirq.LineQubit(qubit_index)
|
|
58
85
|
circuit.append(cirq.X(qubit))
|
|
59
86
|
|
|
87
|
+
|
|
60
88
|
def apply_pauli_y_gate(circuit, qubit_index):
|
|
61
89
|
qubit = cirq.LineQubit(qubit_index)
|
|
62
90
|
circuit.append(cirq.Y(qubit))
|
|
63
91
|
|
|
92
|
+
|
|
64
93
|
def apply_pauli_z_gate(circuit, qubit_index):
|
|
65
94
|
qubit = cirq.LineQubit(qubit_index)
|
|
66
95
|
circuit.append(cirq.Z(qubit))
|
|
67
96
|
|
|
68
97
|
|
|
98
|
+
def apply_t_gate(circuit, qubit_index):
|
|
99
|
+
qubit = cirq.LineQubit(qubit_index)
|
|
100
|
+
circuit.append(cirq.T(qubit))
|
|
101
|
+
|
|
102
|
+
|
|
69
103
|
def execute_circuit(circuit, backend, backend_config):
|
|
104
|
+
# handle 0-qubit circuits before adding measurements
|
|
105
|
+
if not circuit.all_qubits():
|
|
106
|
+
shots = backend_config["backend_options"].get("shots", 1)
|
|
107
|
+
return [{0: shots}]
|
|
108
|
+
|
|
70
109
|
# Ensure measurement is added to capture the results
|
|
71
110
|
if not circuit.has_measurements():
|
|
72
|
-
circuit.append(cirq.measure(*circuit.all_qubits(), key=
|
|
111
|
+
circuit.append(cirq.measure(*circuit.all_qubits(), key="result"))
|
|
73
112
|
simulator = cirq.Simulator()
|
|
74
|
-
parameter_values = backend_config.get(
|
|
113
|
+
parameter_values = backend_config.get("parameter_values", None)
|
|
75
114
|
if parameter_values:
|
|
76
115
|
# Convert parameter_values to applicable resolvers
|
|
77
116
|
res = [cirq.ParamResolver(parameter_values)]
|
|
78
|
-
results = simulator.run_sweep(
|
|
79
|
-
|
|
117
|
+
results = simulator.run_sweep(
|
|
118
|
+
circuit,
|
|
119
|
+
repetitions=backend_config["backend_options"].get("shots", 1),
|
|
120
|
+
params=res,
|
|
121
|
+
)
|
|
122
|
+
return [result.histogram(key="result") for result in results]
|
|
80
123
|
else:
|
|
81
|
-
result = simulator.run(
|
|
82
|
-
|
|
124
|
+
result = simulator.run(
|
|
125
|
+
circuit, repetitions=backend_config["backend_options"].get("shots", 1)
|
|
126
|
+
)
|
|
127
|
+
return [result.histogram(key="result")]
|
|
128
|
+
|
|
83
129
|
|
|
84
130
|
def draw_circuit(circuit):
|
|
85
|
-
|
|
131
|
+
# Use Cirq's string representation for circuit visualization
|
|
132
|
+
return str(circuit)
|
|
133
|
+
|
|
86
134
|
|
|
87
135
|
def apply_rx_gate(circuit, qubit_index, angle):
|
|
88
136
|
param = sympy.Symbol(angle) if isinstance(angle, str) else angle
|
|
@@ -101,8 +149,44 @@ def apply_rz_gate(circuit, qubit_index, angle):
|
|
|
101
149
|
qubit = cirq.LineQubit(qubit_index)
|
|
102
150
|
circuit.append(cirq.rz(param).on(qubit))
|
|
103
151
|
|
|
152
|
+
|
|
104
153
|
def apply_u_gate(circuit, qubit_index, theta, phi, lambd):
|
|
105
154
|
qubit = cirq.LineQubit(qubit_index)
|
|
106
155
|
circuit.append(cirq.rz(lambd).on(qubit))
|
|
107
|
-
circuit.append(cirq.ry(
|
|
108
|
-
circuit.append(cirq.
|
|
156
|
+
circuit.append(cirq.ry(theta).on(qubit))
|
|
157
|
+
circuit.append(cirq.rz(phi).on(qubit))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_final_state_vector(circuit, backend, backend_config):
|
|
161
|
+
simulator = cirq.Simulator()
|
|
162
|
+
result = simulator.simulate(circuit)
|
|
163
|
+
return result.final_state_vector
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def calculate_prob_zero(results, ancilla_qubit, num_qubits):
|
|
167
|
+
"""
|
|
168
|
+
Calculate the probability of measuring the ancilla qubit in |0> state.
|
|
169
|
+
|
|
170
|
+
Cirq uses big-endian qubit ordering with integer format results,
|
|
171
|
+
where qubit i corresponds to bit (num_qubits - 1 - i).
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
results: Measurement results from execute_circuit() (list of dicts with integer keys)
|
|
175
|
+
ancilla_qubit: Index of the ancilla qubit
|
|
176
|
+
num_qubits: Total number of qubits in the circuit
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
float: Probability of measuring ancilla in |0> state
|
|
180
|
+
"""
|
|
181
|
+
if isinstance(results, list):
|
|
182
|
+
results = results[0]
|
|
183
|
+
|
|
184
|
+
total_shots = sum(results.values())
|
|
185
|
+
count_zero = 0
|
|
186
|
+
|
|
187
|
+
for state, count in results.items():
|
|
188
|
+
bit_position = num_qubits - 1 - ancilla_qubit
|
|
189
|
+
if ((state >> bit_position) & 1) == 0:
|
|
190
|
+
count_zero += count
|
|
191
|
+
|
|
192
|
+
return count_zero / total_shots if total_shots > 0 else 0.0
|
qumat/qdp.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
|
3
|
+
# contributor license agreements. See the NOTICE file distributed with
|
|
4
|
+
# this work for additional information regarding copyright ownership.
|
|
5
|
+
# The ASF licenses this file to You under the Apache License, Version 2.0
|
|
6
|
+
# (the "License"); you may not use this file except in compliance with
|
|
7
|
+
# the License. You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Quantum Data Plane (QDP) - GPU-accelerated quantum state encoding.
|
|
19
|
+
|
|
20
|
+
This module provides a unified interface to the QDP engine, enabling
|
|
21
|
+
GPU-accelerated encoding of classical data into quantum states with
|
|
22
|
+
zero-copy PyTorch integration via DLPack.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> import qumat.qdp as qdp
|
|
26
|
+
>>> engine = qdp.QdpEngine(device_id=0)
|
|
27
|
+
>>> qtensor = engine.encode([1.0, 2.0, 3.0, 4.0], num_qubits=2, encoding_method="amplitude")
|
|
28
|
+
>>> import torch
|
|
29
|
+
>>> torch_tensor = torch.from_dlpack(qtensor)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_INSTALL_MSG = (
|
|
33
|
+
"QDP requires the qumat-qdp native extension. "
|
|
34
|
+
"Build and install it with: cd qdp/qdp-python && maturin develop --release"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _make_stub(name: str) -> type:
|
|
39
|
+
"""Create a stub class that raises ImportError on instantiation."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def]
|
|
42
|
+
raise ImportError(_INSTALL_MSG)
|
|
43
|
+
|
|
44
|
+
return type(name, (), {"__init__": __init__, "__doc__": f"Stub class - {name}"})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
from _qdp import QdpEngine as QdpEngine
|
|
49
|
+
from _qdp import QuantumTensor as QuantumTensor
|
|
50
|
+
except ImportError as e:
|
|
51
|
+
import warnings
|
|
52
|
+
|
|
53
|
+
warnings.warn(
|
|
54
|
+
f"QDP module not available: {e}. "
|
|
55
|
+
"QDP requires the qumat-qdp native extension which needs to be built with maturin. "
|
|
56
|
+
"See qdp/qdp-python/README.md for installation instructions.",
|
|
57
|
+
ImportWarning,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
QdpEngine = _make_stub("QdpEngine") # type: ignore[misc]
|
|
61
|
+
QuantumTensor = _make_stub("QuantumTensor") # type: ignore[misc]
|
|
62
|
+
|
|
63
|
+
__all__ = ["QdpEngine", "QuantumTensor"]
|
qumat/qiskit_backend.py
CHANGED
|
@@ -15,103 +15,196 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
#
|
|
17
17
|
import qiskit
|
|
18
|
+
from qiskit_aer import AerSimulator
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
def initialize_backend(backend_config):
|
|
20
|
-
backend_options = backend_config[
|
|
21
|
-
simulator_type = backend_options[
|
|
22
|
-
shots = backend_options
|
|
23
|
-
|
|
22
|
+
backend_options = backend_config["backend_options"]
|
|
23
|
+
simulator_type = backend_options["simulator_type"]
|
|
24
|
+
shots = backend_options.get("shots", 1)
|
|
25
|
+
|
|
26
|
+
# Map legacy simulator types to AerSimulator methods
|
|
27
|
+
simulator_methods = {
|
|
28
|
+
"aer_simulator": "automatic",
|
|
29
|
+
"statevector_simulator": "statevector",
|
|
30
|
+
"qasm_simulator": "automatic",
|
|
31
|
+
"unitary_simulator": "unitary",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if simulator_type in simulator_methods:
|
|
35
|
+
backend = AerSimulator(method=simulator_methods[simulator_type])
|
|
36
|
+
else:
|
|
37
|
+
backend = AerSimulator(method=simulator_type)
|
|
38
|
+
|
|
24
39
|
backend.shots = shots
|
|
25
40
|
return backend
|
|
26
41
|
|
|
27
42
|
|
|
28
|
-
def create_empty_circuit(num_qubits):
|
|
29
|
-
|
|
43
|
+
def create_empty_circuit(num_qubits: int | None = None):
|
|
44
|
+
if num_qubits is not None:
|
|
45
|
+
return qiskit.QuantumCircuit(num_qubits)
|
|
46
|
+
else:
|
|
47
|
+
return qiskit.QuantumCircuit()
|
|
48
|
+
|
|
30
49
|
|
|
31
50
|
def apply_not_gate(circuit, qubit_index):
|
|
32
51
|
# Apply a NOT gate (X gate) on the specified qubit
|
|
33
52
|
circuit.x(qubit_index)
|
|
34
53
|
|
|
54
|
+
|
|
35
55
|
def apply_hadamard_gate(circuit, qubit_index):
|
|
36
56
|
# Apply a Hadamard gate on the specified qubit
|
|
37
57
|
circuit.h(qubit_index)
|
|
38
58
|
|
|
59
|
+
|
|
39
60
|
def apply_cnot_gate(circuit, control_qubit_index, target_qubit_index):
|
|
40
61
|
# Apply a CNOT gate (controlled-X gate) with the specified control and
|
|
41
62
|
# target qubits
|
|
42
63
|
circuit.cx(control_qubit_index, target_qubit_index)
|
|
43
64
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
|
|
66
|
+
def apply_toffoli_gate(
|
|
67
|
+
circuit, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
68
|
+
):
|
|
47
69
|
# Apply a Toffoli gate (controlled-controlled-X gate) with the
|
|
48
70
|
# specified control and target qubits
|
|
49
|
-
circuit.ccx(control_qubit_index1,
|
|
50
|
-
|
|
51
|
-
target_qubit_index)
|
|
71
|
+
circuit.ccx(control_qubit_index1, control_qubit_index2, target_qubit_index)
|
|
72
|
+
|
|
52
73
|
|
|
53
74
|
def apply_swap_gate(circuit, qubit_index1, qubit_index2):
|
|
54
75
|
# Apply a SWAP gate to exchange the states of two qubits
|
|
55
76
|
circuit.swap(qubit_index1, qubit_index2)
|
|
56
77
|
|
|
78
|
+
|
|
79
|
+
def apply_cswap_gate(
|
|
80
|
+
circuit, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
81
|
+
):
|
|
82
|
+
# Apply a controlled-SWAP (Fredkin) gate with the specified control and target qubits
|
|
83
|
+
circuit.cswap(control_qubit_index, target_qubit_index1, target_qubit_index2)
|
|
84
|
+
|
|
85
|
+
|
|
57
86
|
def apply_pauli_x_gate(circuit, qubit_index):
|
|
58
87
|
# Apply a Pauli X gate on the specified qubit
|
|
59
88
|
circuit.x(qubit_index)
|
|
60
89
|
|
|
90
|
+
|
|
61
91
|
def apply_pauli_y_gate(circuit, qubit_index):
|
|
62
92
|
# Apply a Pauli Y gate on the specified qubit
|
|
63
93
|
circuit.y(qubit_index)
|
|
64
94
|
|
|
95
|
+
|
|
65
96
|
def apply_pauli_z_gate(circuit, qubit_index):
|
|
66
97
|
# Apply a Pauli Z gate on the specified qubit
|
|
67
98
|
circuit.z(qubit_index)
|
|
68
99
|
|
|
100
|
+
|
|
101
|
+
def apply_t_gate(circuit, qubit_index):
|
|
102
|
+
# Apply a T gate (π/8 gate) on the specified qubit
|
|
103
|
+
circuit.t(qubit_index)
|
|
104
|
+
|
|
105
|
+
|
|
69
106
|
def execute_circuit(circuit, backend, backend_config):
|
|
107
|
+
working_circuit = circuit.copy()
|
|
108
|
+
|
|
70
109
|
# Add measurements if they are not already present
|
|
71
|
-
if
|
|
72
|
-
|
|
110
|
+
# Check if circuit already has measurement operations
|
|
111
|
+
has_measurements = any(
|
|
112
|
+
isinstance(inst.operation, qiskit.circuit.Measure)
|
|
113
|
+
for inst in working_circuit.data
|
|
114
|
+
)
|
|
115
|
+
if not has_measurements:
|
|
116
|
+
working_circuit.measure_all()
|
|
73
117
|
|
|
74
118
|
# Ensure the circuit is parameterized properly
|
|
75
|
-
if
|
|
119
|
+
if working_circuit.parameters:
|
|
76
120
|
# Parse the global parameter configuration
|
|
77
|
-
parameter_bindings = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
parameter_bindings = {
|
|
122
|
+
param: backend_config["parameter_values"][str(param)]
|
|
123
|
+
for param in working_circuit.parameters
|
|
124
|
+
}
|
|
125
|
+
transpiled_circuit = qiskit.transpile(working_circuit, backend)
|
|
126
|
+
bound_circuit = transpiled_circuit.assign_parameters(parameter_bindings)
|
|
127
|
+
job = backend.run(
|
|
128
|
+
bound_circuit, shots=backend_config["backend_options"].get("shots", 1)
|
|
129
|
+
)
|
|
81
130
|
result = job.result()
|
|
82
131
|
return result.get_counts()
|
|
83
132
|
else:
|
|
84
|
-
transpiled_circuit = qiskit.transpile(
|
|
85
|
-
job =
|
|
133
|
+
transpiled_circuit = qiskit.transpile(working_circuit, backend)
|
|
134
|
+
job = backend.run(
|
|
135
|
+
transpiled_circuit, shots=backend_config["backend_options"].get("shots", 1)
|
|
136
|
+
)
|
|
86
137
|
result = job.result()
|
|
87
138
|
return result.get_counts()
|
|
88
139
|
|
|
140
|
+
|
|
89
141
|
# placeholder method for use in the testing suite
|
|
90
142
|
def get_final_state_vector(circuit, backend, backend_config):
|
|
91
|
-
|
|
143
|
+
working_circuit = circuit.copy()
|
|
144
|
+
|
|
145
|
+
simulator = AerSimulator(method="statevector")
|
|
146
|
+
|
|
147
|
+
# Add save_statevector instruction
|
|
148
|
+
working_circuit.save_statevector()
|
|
92
149
|
|
|
93
150
|
# Simulate the circuit
|
|
94
|
-
|
|
151
|
+
transpiled_circuit = qiskit.transpile(working_circuit, simulator)
|
|
152
|
+
job = simulator.run(transpiled_circuit)
|
|
95
153
|
result = job.result()
|
|
96
154
|
|
|
97
155
|
return result.get_statevector()
|
|
98
156
|
|
|
157
|
+
|
|
99
158
|
def draw_circuit(circuit):
|
|
100
159
|
# Use Qiskit's built-in drawing function
|
|
101
|
-
|
|
160
|
+
return circuit.draw()
|
|
161
|
+
|
|
102
162
|
|
|
103
163
|
def apply_rx_gate(circuit, qubit_index, angle):
|
|
104
164
|
param = qiskit.circuit.Parameter(angle) if isinstance(angle, str) else angle
|
|
105
165
|
circuit.rx(param, qubit_index)
|
|
106
166
|
|
|
167
|
+
|
|
107
168
|
def apply_ry_gate(circuit, qubit_index, angle):
|
|
108
169
|
param = qiskit.circuit.Parameter(angle) if isinstance(angle, str) else angle
|
|
109
170
|
circuit.ry(param, qubit_index)
|
|
110
171
|
|
|
172
|
+
|
|
111
173
|
def apply_rz_gate(circuit, qubit_index, angle):
|
|
112
174
|
param = qiskit.circuit.Parameter(angle) if isinstance(angle, str) else angle
|
|
113
175
|
circuit.rz(param, qubit_index)
|
|
114
176
|
|
|
177
|
+
|
|
115
178
|
def apply_u_gate(circuit, qubit_index, theta, phi, lambd):
|
|
116
179
|
# Apply the U gate directly with specified parameters
|
|
117
180
|
circuit.u(theta, phi, lambd, qubit_index)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def calculate_prob_zero(results, ancilla_qubit, num_qubits):
|
|
184
|
+
"""
|
|
185
|
+
Calculate the probability of measuring the ancilla qubit in |0> state.
|
|
186
|
+
|
|
187
|
+
Qiskit uses little-endian qubit ordering with string format results,
|
|
188
|
+
where the rightmost bit corresponds to qubit 0.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
results: Measurement results from execute_circuit() (dict with string keys)
|
|
192
|
+
ancilla_qubit: Index of the ancilla qubit
|
|
193
|
+
num_qubits: Total number of qubits in the circuit
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
float: Probability of measuring ancilla in |0> state
|
|
197
|
+
"""
|
|
198
|
+
# Handle different result formats from different backends
|
|
199
|
+
if isinstance(results, list):
|
|
200
|
+
results = results[0]
|
|
201
|
+
|
|
202
|
+
total_shots = sum(results.values())
|
|
203
|
+
count_zero = 0
|
|
204
|
+
|
|
205
|
+
for state, count in results.items():
|
|
206
|
+
# Qiskit: little-endian, rightmost bit is qubit 0
|
|
207
|
+
if len(state) > ancilla_qubit and state[-(ancilla_qubit + 1)] == "0":
|
|
208
|
+
count_zero += count
|
|
209
|
+
|
|
210
|
+
return count_zero / total_shots if total_shots > 0 else 0.0
|