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 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"]
@@ -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 AwsQuantumSimulator, AwsDevice
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['backend_options']
22
- simulator_type = backend_options.get('simulator_type', 'default')
23
- if simulator_type == 'default':
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(f"Simulator type '{simulator_type}' is not supported in Amazon Braket. Using default.")
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
- def create_empty_circuit(num_qubits):
30
- return Circuit()
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
- def apply_toffoli_gate(circuit, control_qubit_index1, control_qubit_index2, target_qubit_index):
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['backend_options'].get('shots', 1)
58
- task = backend.run(circuit, shots=shots)
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
- raise NotImplementedError("Final state vector calculation is not currently supported with Amazon Braket.")
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 print the circuit object which gives some textual representation.
70
- print(circuit)
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
- circuit.rx(qubit_index, theta)
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
- # Assuming 'simulator_type' specifies the type of simulator in Cirq
22
- simulator_type = backend_config.get('backend_options', {}).get('simulator_type', 'default')
23
- if simulator_type != 'default':
24
- print(f"Simulator type '{simulator_type}' is not supported in Cirq. Ignoring this argument")
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
- return cirq.Circuit()
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
- def apply_toffoli_gate(circuit, control_qubit_index1, control_qubit_index2, target_qubit_index):
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='result'))
111
+ circuit.append(cirq.measure(*circuit.all_qubits(), key="result"))
73
112
  simulator = cirq.Simulator()
74
- parameter_values = backend_config.get('parameter_values', None)
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(circuit, repetitions=backend_config['backend_options'].get('shots', 1), params=res)
79
- return [result.histogram(key='result') for result in results]
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(circuit, repetitions=backend_config['backend_options'].get('shots', 1))
82
- return [result.histogram(key='result')]
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
- print(circuit)
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(phi).on(qubit))
108
- circuit.append(cirq.rx(theta).on(qubit))
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['backend_options']
21
- simulator_type = backend_options['simulator_type']
22
- shots = backend_options['shots']
23
- backend = qiskit.Aer.get_backend(simulator_type)
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
- return qiskit.QuantumCircuit(num_qubits)
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
- def apply_toffoli_gate(circuit, control_qubit_index1,
45
- control_qubit_index2,
46
- target_qubit_index):
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
- control_qubit_index2,
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 not circuit.cregs:
72
- circuit.measure_all()
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 circuit.parameters:
119
+ if working_circuit.parameters:
76
120
  # Parse the global parameter configuration
77
- parameter_bindings = {param: backend_config['parameter_values'][str(param)] for param in circuit.parameters}
78
- transpiled_circuit = qiskit.transpile(circuit, backend)
79
- qobj = qiskit.assemble(transpiled_circuit, parameter_binds=[parameter_bindings], shots=backend_config['backend_options']['shots'])
80
- job = backend.run(qobj)
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(circuit, backend)
85
- job = qiskit.execute(transpiled_circuit, backend, shots=backend_config['backend_options']['shots'])
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
- simulator = qiskit.Aer.get_backend('statevector_simulator')
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
- job = qiskit.execute(circuit, simulator)
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
- print(circuit.draw())
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