qilisdk 0.1.3__py3-none-any.whl → 0.1.4__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.
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
  from __future__ import annotations
15
15
 
16
+ import math
16
17
  import string
17
18
  from typing import Literal
18
19
 
@@ -141,31 +142,95 @@ class QuantumObject:
141
142
  out = QuantumObject(self._data.conj().T)
142
143
  return out
143
144
 
144
- def ptrace(self, dims: list[int], keep: list[int]) -> "QuantumObject":
145
+ def ptrace(self, keep: list[int], dims: list[int] | None = None) -> "QuantumObject":
145
146
  """
146
147
  Compute the partial trace over subsystems not in 'keep'.
147
148
 
148
149
  This method calculates the reduced density matrix by tracing out
149
150
  the subsystems that are not specified in the 'keep' parameter.
150
- The input 'dims' represents the dimensions of each subsystem,
151
- and 'keep' indicates the indices of the subsystems to be retained.
151
+ The input 'dims' represents the dimensions of each subsystem (optional),
152
+ and 'keep' indicates the indices of those subsystems to be retained.
153
+
154
+ If the QuantumObject is a ket or bra, it will first be converted to a density matrix.
152
155
 
153
156
  Args:
154
- dims (list[int]): A list specifying the dimensions of each subsystem.
155
157
  keep (list[int]): A list of indices corresponding to the subsystems to retain.
158
+ The order of the indices in 'keep' is not important, since dimensions will
159
+ be returned in the tensor original order, but the indices must be unique.
160
+ dims (list[int], optional): A list specifying the dimensions of each subsystem.
161
+ If not specified, a density matrix of qubit states is assumed, and the
162
+ dimensions are inferred accordingly (i.e. we split the state in dim 2 states).
156
163
 
157
164
  Raises:
158
165
  ValueError: If the product of the dimensions in dims does not match the
159
- shape of the QuantumObject's dense representation.
166
+ shape of the QuantumObject's dense representation or if any dimension is non-positive.
167
+ ValueError: If the indices in 'keep' are not unique or are out of range.
168
+ ValueError: If the QuantumObject is not a valid density matrix or state vector.
169
+ ValueError: If the number of subsystems exceeds the available ASCII letters.
160
170
 
161
171
  Returns:
162
172
  QuantumObject: A new QuantumObject representing the reduced density matrix
163
173
  for the subsystems specified in 'keep'.
164
174
  """
165
- rho = self.dense
166
- total_dim = np.prod(dims)
167
- if rho.shape != (total_dim, total_dim):
168
- raise ValueError("Dimension mismatch between provided dims and QuantumObject shape")
175
+ # 1) Get the density matrix representation:
176
+ rho = self.dense if self.is_operator() else self.to_density_matrix().dense
177
+
178
+ # 2.a) If `dims` is not provided, we assume a density matrix of qubit states (we split in subsystems of dim = 2):
179
+ if dims is None:
180
+ # The to_density_matrix() should check its a square matrix, with size being a power of 2, so we can do:
181
+ number_of_qubits_in_state = int(math.log2(rho.shape[0]))
182
+ dims = [2 for _ in range(number_of_qubits_in_state)]
183
+ # 2.b) If `dims` is provided, we run checks on it:
184
+ else:
185
+ total_dim = int(np.prod(dims))
186
+ if rho.shape != (total_dim, total_dim):
187
+ raise ValueError(
188
+ f"Dimension mismatch: QuantumObject shape {rho.shape} does not match the expected shape ({total_dim}, {total_dim}), given by the product of all passed `dims`: (np.prod(dims), np.prod(dims))."
189
+ )
190
+ if any(d <= 0 for d in dims):
191
+ raise ValueError("All subsystem dimensions must be positive")
192
+
193
+ # 3) Validate & sort `keep`
194
+ keep_set = set(keep)
195
+ if any(i < 0 or i >= len(dims) for i in keep_set):
196
+ raise ValueError("keep indices out of range (0, len(dims))")
197
+ if len(keep_set) != len(keep):
198
+ raise ValueError("duplicate indices in keep")
199
+
200
+ # 4) Trace out the subsystems not in `keep`.
201
+ rho_t = self._compute_traced_tensor_via_einstein_summation(rho, keep_set, dims)
202
+
203
+ # 5) The resulting tensor has separate indices for each subsystem kept.
204
+ # Reshape it into a matrix (i.e. combine the row indices and column indices).
205
+ dims_keep = [dims[i] for i in keep_set]
206
+ new_dim = int(np.prod(dims_keep)) if dims_keep else 1
207
+
208
+ return QuantumObject(rho_t.reshape((new_dim, new_dim)))
209
+
210
+ @staticmethod
211
+ def _compute_traced_tensor_via_einstein_summation(rho: np.ndarray, keep: set[int], dims: list[int]) -> np.ndarray:
212
+ """Helper function called in `ptrace`, which computes the partial trace over subsystems not in 'keep'.
213
+
214
+ This function generates the appropriate einsum subscript strings for the input tensor
215
+ and performs the summation over the indices corresponding to the subsystems being traced out.
216
+
217
+ Args:
218
+ rho (np.ndarray): The input density matrix to be traced out.
219
+ keep (set[int]): A list of indices corresponding to the subsystems to retain.
220
+ The order of the indices in 'keep' is not important, since dimensions will
221
+ be returned in the tensor original order, but the indices must be unique.
222
+ dims (list[int]): A list specifying the dimensions of each subsystem.
223
+
224
+ Returns:
225
+ np.ndarray: The resulting tensor after tracing out the specified subsystems.
226
+
227
+ Raises:
228
+ ValueError: If the number of subsystems exceeds the available ASCII letters.
229
+ """
230
+ # Check that the number of subsystems is not too large, that we run out of ascii letters.
231
+ needed, MAX_LABELS = len(dims) + len(keep), len(string.ascii_letters)
232
+ if needed > MAX_LABELS:
233
+ raise ValueError(f"Not enough einsum labels (dims + keep): need {needed}, but only {MAX_LABELS} available.")
169
234
 
170
235
  # Use letters from the ASCII alphabet (both cases) for einsum indices.
171
236
  # For each subsystem, assign two letters: one for the row index and one for the column index.
@@ -196,15 +261,7 @@ class QuantumObject:
196
261
  # Reshape rho into a tensor with shape dims + dims.
197
262
  reshaped = rho.reshape(dims + dims)
198
263
  # Use einsum to sum over the indices that appear twice (i.e. those being traced out).
199
- reduced_tensor = np.einsum(f"{input_subscript}->{output_subscript}", reshaped)
200
-
201
- # The resulting tensor has separate indices for each subsystem kept.
202
- # Reshape it into a matrix (i.e. combine the row indices and column indices).
203
- dims_keep = [dims[i] for i in keep]
204
- new_dim = np.prod(dims_keep)
205
- reduced_matrix = reduced_tensor.reshape(new_dim, new_dim)
206
-
207
- return QuantumObject(reduced_matrix)
264
+ return np.einsum(f"{input_subscript}->{output_subscript}", reshaped)
208
265
 
209
266
  def norm(self, order: int | Literal["fro", "tr"] = 1) -> float:
210
267
  """
@@ -282,6 +339,7 @@ class QuantumObject:
282
339
 
283
340
  Raises:
284
341
  ValueError: If the QuantumObject is a scalar, as a density matrix cannot be derived.
342
+ ValueError: If the QuantumObject is an operator that is not a density matrix.
285
343
 
286
344
  Returns:
287
345
  QuantumObject: A new QuantumObject representing the density matrix.
@@ -289,15 +347,20 @@ class QuantumObject:
289
347
  if self.is_scalar():
290
348
  raise ValueError("Cannot make a density matrix from scalar.")
291
349
 
292
- if self.is_density_matrix():
293
- return self
294
-
295
350
  if self.is_bra():
296
351
  return (self.adjoint() @ self).unit()
297
352
 
298
353
  if self.is_ket():
299
354
  return (self @ self.adjoint()).unit()
300
355
 
356
+ if self.is_density_matrix():
357
+ return self
358
+
359
+ if self.is_operator():
360
+ raise ValueError(
361
+ "Cannot make a density matrix from an operator, which is not a density matrix already (trace=1 and hermitian)."
362
+ )
363
+
301
364
  raise ValueError(
302
365
  "Cannot make a density matrix from this QuantumObject. "
303
366
  "It must be either a ket, a bra or already a density matrix."
@@ -20,7 +20,7 @@ __all__ = []
20
20
  OPTIONAL_FEATURES: list[OptionalFeature] = [
21
21
  OptionalFeature(
22
22
  name="cuda",
23
- dependencies=["cudaq"],
23
+ dependencies=["cuda-quantum-cu12"],
24
24
  symbols=[Symbol(path="qilisdk.extras.cuda.cuda_backend", name="CudaBackend")],
25
25
  ),
26
26
  OptionalFeature(
@@ -17,9 +17,8 @@ from typing import TYPE_CHECKING, Callable, Type, TypeVar
17
17
 
18
18
  import cudaq
19
19
  import numpy as np
20
- from cudaq import State
21
- from cudaq.operator import ElementaryOperator, OperatorSum, ScalarOperator, evolve, spin
22
- from cudaq.operator import Schedule as cuda_schedule
20
+ from cudaq import ElementaryOperator, OperatorSum, ScalarOperator, State, evolve, spin
21
+ from cudaq import Schedule as cuda_schedule
23
22
 
24
23
  from qilisdk.analog.analog_backend import AnalogBackend
25
24
  from qilisdk.analog.hamiltonian import Hamiltonian, PauliI, PauliOperator, PauliX, PauliY, PauliZ
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qilisdk
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: qilisdk is a Python framework for writing digital and analog quantum algorithms and executing them across multiple quantum backends. Its modular design streamlines the development process and enables easy integration with a variety of quantum platforms.
5
5
  Author-email: Qilimanjaro Quantum Tech <info@qilimanjaro.tech>
6
6
  License-File: LICENCE
@@ -26,7 +26,7 @@ Requires-Dist: pydantic>=2.10.6
26
26
  Requires-Dist: ruamel-yaml>=0.18.10
27
27
  Requires-Dist: scipy>=1.15.1
28
28
  Provides-Extra: cuda
29
- Requires-Dist: cudaq==0.10.0; extra == 'cuda'
29
+ Requires-Dist: cuda-quantum-cu12; extra == 'cuda'
30
30
  Provides-Extra: qaas
31
31
  Requires-Dist: httpx>=0.28.1; extra == 'qaas'
32
32
  Requires-Dist: keyring>=25.6.0; extra == 'qaas'
@@ -227,7 +227,7 @@ For analog simulations, the new `TimeEvolution` and `Schedule` classes allow you
227
227
 
228
228
  ```python
229
229
  import numpy as np
230
- from qilisdk.analog import TimeEvolution, Schedule, tensor, ket, X, Z, Y
230
+ from qilisdk.analog import Schedule, TimeEvolution, ket, X, Z, Y, tensor_prod
231
231
  from qilisdk.extras import CudaBackend
232
232
 
233
233
  T = 10 # Total evolution time
@@ -251,16 +251,15 @@ schedule = Schedule(
251
251
  )
252
252
 
253
253
  # Prepare an initial state (equal superposition)
254
- state = tensor([(ket(0) + ket(1)).unit() for _ in range(nqubits)]).unit()
254
+ state = tensor_prod([(ket(0) + ket(1)).unit() for _ in range(nqubits)]).unit()
255
255
 
256
256
  # Perform time evolution on the CUDA backend with observables to monitor
257
257
  time_evolution = TimeEvolution(
258
- backend=CudaBackend(),
259
258
  schedule=schedule,
260
259
  initial_state=state,
261
260
  observables=[Z(0), X(0), Y(0)],
262
261
  )
263
- results = time_evolution.evolve(store_intermediate_results=True)
262
+ results = time_evolution.evolve(backend=CudaBackend(), store_intermediate_results=True)
264
263
  print("Time Evolution Results:", results)
265
264
  ```
266
265
 
@@ -9,7 +9,7 @@ qilisdk/analog/analog_backend.py,sha256=GRCqlCslo8F0iB8N6Mn-_0IFJos6xRbadwVvk3lY
9
9
  qilisdk/analog/analog_result.py,sha256=aaTGQmKm8-cuFvs_lhCAP0JOaUxsmsNcVbn_M5rvkm4,4705
10
10
  qilisdk/analog/exceptions.py,sha256=66nF1b3God6LgJ1IQd0thC3iyr6z1iMcZTfmDsSr4LE,686
11
11
  qilisdk/analog/hamiltonian.py,sha256=ELQXIauk_foOQbOChTFIV4fYzTwqHhSP1tMw8PwytaQ,25126
12
- qilisdk/analog/quantum_objects.py,sha256=jXKaPQV3ZV0so1BzzB9zCCAjOhgSLFCaTQtwGaYwwfU,20641
12
+ qilisdk/analog/quantum_objects.py,sha256=toYJZmgnAq06vHSpJbyl6bXEC1jRLjBrN9tJ2lV_9bc,24478
13
13
  qilisdk/analog/schedule.py,sha256=AHpiBPN1YZb9J2WNw6MyRPjRWAyqLVIiGFj5y_rwDmA,12807
14
14
  qilisdk/common/__init__.py,sha256=sB9yxB1512KdJuWGSHx7TWvIxLMFBX1m7zm6NVkWEAA,657
15
15
  qilisdk/common/algorithm.py,sha256=_MBdMHIJogTPnR8QRCvpf364lxQNDqGWWyZpsgplcyI,636
@@ -27,11 +27,11 @@ qilisdk/digital/digital_result.py,sha256=PmqVOp4W1COZOFYcROxuLG7WAYjBAB6EM5BeY0J
27
27
  qilisdk/digital/exceptions.py,sha256=21RnaSBkzZOuQWcIWVB303F4nyU1ABOykhAYow4m7lA,871
28
28
  qilisdk/digital/gates.py,sha256=Ylf9iwDNONaatzIrrPpXi6H59C7bMvrq-tWyIK5ZykU,29614
29
29
  qilisdk/digital/vqe.py,sha256=HdxueeBO6MQYKyy8xo2_x50hqHd693RPvXaxEFIcEJI,6648
30
- qilisdk/extras/__init__.py,sha256=jAfA4d8AaTWXqAsQTpv_Rf1tQ0kcfQ_mmxF6rA-Mlro,1510
30
+ qilisdk/extras/__init__.py,sha256=MdNUZ8Fd0hYFcRzUQWrhHGGEhdHlldd61fWL8iIPe_Y,1522
31
31
  qilisdk/extras/__init__.pyi,sha256=xQ4zOPeSaNM5zGfb07fK4lcbAu4y95YfgSlHs5TkR3Y,717
32
32
  qilisdk/extras/cuda/__init__.py,sha256=LDSTo7NstSbHMvvqvx7ZLxh0HFTEXg1O_B76SyEEHk8,588
33
33
  qilisdk/extras/cuda/cuda_analog_result.py,sha256=0IoBZpkPfxS5CDps1Z6uRH1ATWG689klYR1RvGyUhBU,737
34
- qilisdk/extras/cuda/cuda_backend.py,sha256=jnzdMkCUpQQhJ6S9dQgVTOJSciWcl5VhnG84VdZqgYA,16414
34
+ qilisdk/extras/cuda/cuda_backend.py,sha256=AuoWuwlFQfloEE6krMP_1ZHsQZJyxwcnB7g56YTW1U0,16379
35
35
  qilisdk/extras/cuda/cuda_digital_result.py,sha256=bgHCrmcUVCBngo443PhfLkMxAT8Kk24FRubU3bJsdsQ,742
36
36
  qilisdk/extras/qaas/__init__.py,sha256=LDSTo7NstSbHMvvqvx7ZLxh0HFTEXg1O_B76SyEEHk8,588
37
37
  qilisdk/extras/qaas/keyring.py,sha256=xjJj6wrRALj31foctYTGZ07wgusVyIGuzeKxU-MNAA0,1774
@@ -45,7 +45,7 @@ qilisdk/extras/qaas/qaas_vqe_result.py,sha256=tQUJ904aW7BzSzd-Dxi8TvL6AIQAnDsiOG
45
45
  qilisdk/utils/__init__.py,sha256=cFdezrFwesp9azZEBG_CWq3_Qp1yH8do_PyJbIIdSkU,920
46
46
  qilisdk/utils/openqasm2.py,sha256=QGQi2rrkYB_cqRgCyp3V3uDyfnVvcdMxykIiK0-sqXM,8316
47
47
  qilisdk/utils/serialization.py,sha256=vp-q2SZ9cBq3NX-gfT3ZDt0tzF3KnxkhV0cM7imJ2zo,3870
48
- qilisdk-0.1.3.dist-info/METADATA,sha256=IwKsI6lbnMmSkdb-zRYDrGQOAwqTktRKOvVcP6N5jTs,18006
49
- qilisdk-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- qilisdk-0.1.3.dist-info/licenses/LICENCE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
51
- qilisdk-0.1.3.dist-info/RECORD,,
48
+ qilisdk-0.1.4.dist-info/METADATA,sha256=TyXixdKgm05tUHh5HArhbZh5U2cUE63hGTFPY6msU1M,18016
49
+ qilisdk-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
+ qilisdk-0.1.4.dist-info/licenses/LICENCE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
51
+ qilisdk-0.1.4.dist-info/RECORD,,