qumat 0.0.1__py3-none-any.whl → 0.5.0__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 +122 -17
- qumat/cirq_backend.py +105 -16
- qumat/qdp.py +63 -0
- qumat/qiskit_backend.py +125 -24
- qumat/qumat.py +508 -13
- qumat-0.5.0.dist-info/METADATA +111 -0
- qumat-0.5.0.dist-info/RECORD +11 -0
- {qumat-0.0.1.dist-info → qumat-0.5.0.dist-info}/WHEEL +1 -1
- {qumat-0.0.1.dist-info → qumat-0.5.0.dist-info/licenses}/LICENSE +0 -112
- qumat-0.5.0.dist-info/licenses/NOTICE +5 -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,134 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
#
|
|
17
|
-
|
|
17
|
+
import boto3
|
|
18
|
+
from braket.aws import AwsDevice, AwsSession
|
|
19
|
+
from braket.devices import LocalSimulator
|
|
18
20
|
from braket.circuits import Circuit, FreeParameter
|
|
19
21
|
|
|
22
|
+
|
|
20
23
|
def initialize_backend(backend_config):
|
|
21
|
-
backend_options = backend_config[
|
|
22
|
-
simulator_type = backend_options.get(
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
backend_options = backend_config["backend_options"]
|
|
25
|
+
simulator_type = backend_options.get("simulator_type", "default")
|
|
26
|
+
region = backend_options.get("region")
|
|
27
|
+
|
|
28
|
+
# Create AWS session with region if specified
|
|
29
|
+
aws_session = None
|
|
30
|
+
if region:
|
|
31
|
+
boto_session = boto3.Session(region_name=region)
|
|
32
|
+
aws_session = AwsSession(boto_session=boto_session)
|
|
33
|
+
|
|
34
|
+
if simulator_type == "local":
|
|
35
|
+
return LocalSimulator()
|
|
36
|
+
elif simulator_type == "default":
|
|
37
|
+
return AwsDevice(
|
|
38
|
+
"arn:aws:braket:::device/quantum-simulator/amazon/sv1",
|
|
39
|
+
aws_session=aws_session,
|
|
40
|
+
)
|
|
25
41
|
else:
|
|
26
|
-
print(
|
|
27
|
-
|
|
42
|
+
print(
|
|
43
|
+
f"Simulator type '{simulator_type}' is not supported in Amazon Braket. Using default."
|
|
44
|
+
)
|
|
45
|
+
return AwsDevice(
|
|
46
|
+
"arn:aws:braket:::device/quantum-simulator/amazon/sv1",
|
|
47
|
+
aws_session=aws_session,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_empty_circuit(num_qubits: int | None = None):
|
|
52
|
+
circuit = Circuit()
|
|
53
|
+
if num_qubits is not None:
|
|
54
|
+
for i in range(num_qubits):
|
|
55
|
+
circuit.i(i)
|
|
56
|
+
return circuit
|
|
28
57
|
|
|
29
|
-
def create_empty_circuit(num_qubits):
|
|
30
|
-
return Circuit()
|
|
31
58
|
|
|
32
59
|
def apply_not_gate(circuit, qubit_index):
|
|
33
60
|
circuit.x(qubit_index)
|
|
34
61
|
|
|
62
|
+
|
|
35
63
|
def apply_hadamard_gate(circuit, qubit_index):
|
|
36
64
|
circuit.h(qubit_index)
|
|
37
65
|
|
|
66
|
+
|
|
38
67
|
def apply_cnot_gate(circuit, control_qubit_index, target_qubit_index):
|
|
39
68
|
circuit.cnot(control_qubit_index, target_qubit_index)
|
|
40
69
|
|
|
41
|
-
|
|
70
|
+
|
|
71
|
+
def apply_toffoli_gate(
|
|
72
|
+
circuit, control_qubit_index1, control_qubit_index2, target_qubit_index
|
|
73
|
+
):
|
|
42
74
|
circuit.ccnot(control_qubit_index1, control_qubit_index2, target_qubit_index)
|
|
43
75
|
|
|
76
|
+
|
|
44
77
|
def apply_swap_gate(circuit, qubit_index1, qubit_index2):
|
|
45
78
|
circuit.swap(qubit_index1, qubit_index2)
|
|
46
79
|
|
|
80
|
+
|
|
81
|
+
def apply_cswap_gate(
|
|
82
|
+
circuit, control_qubit_index, target_qubit_index1, target_qubit_index2
|
|
83
|
+
):
|
|
84
|
+
circuit.cswap(control_qubit_index, target_qubit_index1, target_qubit_index2)
|
|
85
|
+
|
|
86
|
+
|
|
47
87
|
def apply_pauli_x_gate(circuit, qubit_index):
|
|
48
88
|
circuit.x(qubit_index)
|
|
49
89
|
|
|
90
|
+
|
|
50
91
|
def apply_pauli_y_gate(circuit, qubit_index):
|
|
51
92
|
circuit.y(qubit_index)
|
|
52
93
|
|
|
94
|
+
|
|
53
95
|
def apply_pauli_z_gate(circuit, qubit_index):
|
|
54
96
|
circuit.z(qubit_index)
|
|
55
97
|
|
|
98
|
+
|
|
99
|
+
def apply_t_gate(circuit, qubit_index):
|
|
100
|
+
circuit.t(qubit_index)
|
|
101
|
+
|
|
102
|
+
|
|
56
103
|
def execute_circuit(circuit, backend, backend_config):
|
|
57
|
-
shots = backend_config[
|
|
58
|
-
|
|
104
|
+
shots = backend_config["backend_options"].get("shots", 1)
|
|
105
|
+
parameter_values = backend_config.get("parameter_values", {})
|
|
106
|
+
if parameter_values and circuit.parameters:
|
|
107
|
+
# Braket accepts parameter names as strings in inputs dict
|
|
108
|
+
inputs = {
|
|
109
|
+
param_name: value
|
|
110
|
+
for param_name, value in parameter_values.items()
|
|
111
|
+
if param_name in {p.name for p in circuit.parameters}
|
|
112
|
+
}
|
|
113
|
+
task = backend.run(circuit, shots=shots, inputs=inputs)
|
|
114
|
+
else:
|
|
115
|
+
task = backend.run(circuit, shots=shots)
|
|
59
116
|
result = task.result()
|
|
60
117
|
return result.measurement_counts
|
|
61
118
|
|
|
119
|
+
|
|
62
120
|
# placeholder method for use in the testing suite
|
|
63
121
|
def get_final_state_vector(circuit, backend, backend_config):
|
|
64
|
-
|
|
122
|
+
circuit.state_vector()
|
|
123
|
+
parameter_values = backend_config.get("parameter_values", {})
|
|
124
|
+
if parameter_values and circuit.parameters:
|
|
125
|
+
# Braket accepts parameter names as strings in inputs dict
|
|
126
|
+
inputs = {
|
|
127
|
+
param_name: value
|
|
128
|
+
for param_name, value in parameter_values.items()
|
|
129
|
+
if param_name in {p.name for p in circuit.parameters}
|
|
130
|
+
}
|
|
131
|
+
result = backend.run(circuit, shots=0, inputs=inputs).result()
|
|
132
|
+
else:
|
|
133
|
+
result = backend.run(circuit, shots=0).result()
|
|
134
|
+
state_vector = result.values[0]
|
|
135
|
+
|
|
136
|
+
return state_vector
|
|
137
|
+
|
|
65
138
|
|
|
66
139
|
def draw_circuit(circuit):
|
|
67
140
|
# Unfortunately, Amazon Braket does not have direct support for drawing circuits in the same way
|
|
68
141
|
# as Qiskit and Cirq. You would typically visualize Amazon Braket circuits using external tools.
|
|
69
|
-
# For simplicity, we'll
|
|
70
|
-
|
|
142
|
+
# For simplicity, we'll return the circuit object's string representation.
|
|
143
|
+
return str(circuit)
|
|
144
|
+
|
|
71
145
|
|
|
72
146
|
def apply_rx_gate(circuit, qubit_index, angle):
|
|
73
147
|
if isinstance(angle, (int, float)):
|
|
@@ -92,7 +166,38 @@ def apply_rz_gate(circuit, qubit_index, angle):
|
|
|
92
166
|
param = FreeParameter(angle)
|
|
93
167
|
circuit.rz(qubit_index, param)
|
|
94
168
|
|
|
169
|
+
|
|
95
170
|
def apply_u_gate(circuit, qubit_index, theta, phi, lambd):
|
|
96
|
-
|
|
97
|
-
circuit.ry(qubit_index, phi)
|
|
171
|
+
# U(θ, φ, λ) = Rz(φ) · Ry(θ) · Rz(λ)
|
|
98
172
|
circuit.rz(qubit_index, lambd)
|
|
173
|
+
circuit.ry(qubit_index, theta)
|
|
174
|
+
circuit.rz(qubit_index, phi)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def calculate_prob_zero(results, ancilla_qubit, num_qubits):
|
|
178
|
+
"""
|
|
179
|
+
Calculate the probability of measuring the ancilla qubit in |0> state.
|
|
180
|
+
|
|
181
|
+
Amazon Braket uses big-endian qubit ordering with string format results,
|
|
182
|
+
where the leftmost bit corresponds to qubit 0.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
results: Measurement results from execute_circuit() (dict with string keys)
|
|
186
|
+
ancilla_qubit: Index of the ancilla qubit
|
|
187
|
+
num_qubits: Total number of qubits in the circuit
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
float: Probability of measuring ancilla in |0> state
|
|
191
|
+
"""
|
|
192
|
+
if isinstance(results, list):
|
|
193
|
+
results = results[0]
|
|
194
|
+
|
|
195
|
+
total_shots = sum(results.values())
|
|
196
|
+
count_zero = 0
|
|
197
|
+
|
|
198
|
+
for state, count in results.items():
|
|
199
|
+
# Braket: big-endian, leftmost bit is qubit 0
|
|
200
|
+
if len(state) > ancilla_qubit and state[ancilla_qubit] == "0":
|
|
201
|
+
count_zero += count
|
|
202
|
+
|
|
203
|
+
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,49 @@ 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
|
+
parameter_values = backend_config.get("parameter_values", None)
|
|
163
|
+
if parameter_values:
|
|
164
|
+
resolver = cirq.ParamResolver(parameter_values)
|
|
165
|
+
result = simulator.simulate(circuit, param_resolver=resolver)
|
|
166
|
+
else:
|
|
167
|
+
result = simulator.simulate(circuit)
|
|
168
|
+
return result.final_state_vector
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def calculate_prob_zero(results, ancilla_qubit, num_qubits):
|
|
172
|
+
"""
|
|
173
|
+
Calculate the probability of measuring the ancilla qubit in |0> state.
|
|
174
|
+
|
|
175
|
+
Cirq uses big-endian qubit ordering with integer format results,
|
|
176
|
+
where qubit i corresponds to bit (num_qubits - 1 - i).
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
results: Measurement results from execute_circuit() (list of dicts with integer keys)
|
|
180
|
+
ancilla_qubit: Index of the ancilla qubit
|
|
181
|
+
num_qubits: Total number of qubits in the circuit
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
float: Probability of measuring ancilla in |0> state
|
|
185
|
+
"""
|
|
186
|
+
if isinstance(results, list):
|
|
187
|
+
results = results[0]
|
|
188
|
+
|
|
189
|
+
total_shots = sum(results.values())
|
|
190
|
+
count_zero = 0
|
|
191
|
+
|
|
192
|
+
for state, count in results.items():
|
|
193
|
+
bit_position = num_qubits - 1 - ancilla_qubit
|
|
194
|
+
if ((state >> bit_position) & 1) == 0:
|
|
195
|
+
count_zero += count
|
|
196
|
+
|
|
197
|
+
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"]
|