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 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,134 @@
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
+ 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['backend_options']
22
- simulator_type = backend_options.get('simulator_type', 'default')
23
- if simulator_type == 'default':
24
- return AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
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(f"Simulator type '{simulator_type}' is not supported in Amazon Braket. Using default.")
27
- return AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
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
- def apply_toffoli_gate(circuit, control_qubit_index1, control_qubit_index2, target_qubit_index):
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['backend_options'].get('shots', 1)
58
- task = backend.run(circuit, shots=shots)
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
- raise NotImplementedError("Final state vector calculation is not currently supported with Amazon Braket.")
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 print the circuit object which gives some textual representation.
70
- print(circuit)
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
- circuit.rx(qubit_index, theta)
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
- # 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,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(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
+ 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"]