tensorcircuit-nightly 1.3.0.dev20250728__py3-none-any.whl → 1.4.0.dev20251103__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.

Potentially problematic release.


This version of tensorcircuit-nightly might be problematic. Click here for more details.

Files changed (72) hide show
  1. tensorcircuit/__init__.py +5 -1
  2. tensorcircuit/abstractcircuit.py +4 -0
  3. tensorcircuit/analogcircuit.py +413 -0
  4. tensorcircuit/applications/layers.py +1 -1
  5. tensorcircuit/applications/van.py +1 -1
  6. tensorcircuit/backends/abstract_backend.py +312 -5
  7. tensorcircuit/backends/cupy_backend.py +3 -1
  8. tensorcircuit/backends/jax_backend.py +92 -3
  9. tensorcircuit/backends/jax_ops.py +108 -0
  10. tensorcircuit/backends/numpy_backend.py +49 -3
  11. tensorcircuit/backends/pytorch_backend.py +92 -3
  12. tensorcircuit/backends/tensorflow_backend.py +102 -3
  13. tensorcircuit/basecircuit.py +123 -82
  14. tensorcircuit/circuit.py +67 -57
  15. tensorcircuit/cloud/local.py +1 -1
  16. tensorcircuit/cloud/quafu_provider.py +1 -1
  17. tensorcircuit/cloud/tencent.py +1 -1
  18. tensorcircuit/compiler/simple_compiler.py +2 -2
  19. tensorcircuit/cons.py +1 -0
  20. tensorcircuit/densitymatrix.py +16 -11
  21. tensorcircuit/experimental.py +7 -152
  22. tensorcircuit/fgs.py +5 -6
  23. tensorcircuit/gates.py +66 -22
  24. tensorcircuit/keras.py +3 -3
  25. tensorcircuit/mpscircuit.py +109 -61
  26. tensorcircuit/quantum.py +697 -133
  27. tensorcircuit/quditcircuit.py +733 -0
  28. tensorcircuit/quditgates.py +618 -0
  29. tensorcircuit/results/counts.py +45 -31
  30. tensorcircuit/shadows.py +1 -1
  31. tensorcircuit/simplify.py +3 -1
  32. tensorcircuit/stabilizercircuit.py +4 -2
  33. tensorcircuit/templates/blocks.py +2 -2
  34. tensorcircuit/templates/hamiltonians.py +29 -8
  35. tensorcircuit/templates/lattice.py +676 -335
  36. tensorcircuit/timeevol.py +896 -0
  37. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/METADATA +50 -25
  38. tensorcircuit_nightly-1.4.0.dev20251103.dist-info/RECORD +96 -0
  39. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/top_level.txt +0 -1
  40. tensorcircuit_nightly-1.3.0.dev20250728.dist-info/RECORD +0 -122
  41. tests/__init__.py +0 -0
  42. tests/conftest.py +0 -67
  43. tests/test_backends.py +0 -1035
  44. tests/test_calibrating.py +0 -149
  45. tests/test_channels.py +0 -409
  46. tests/test_circuit.py +0 -1713
  47. tests/test_cloud.py +0 -219
  48. tests/test_compiler.py +0 -147
  49. tests/test_dmcircuit.py +0 -555
  50. tests/test_ensemble.py +0 -72
  51. tests/test_fgs.py +0 -318
  52. tests/test_gates.py +0 -156
  53. tests/test_hamiltonians.py +0 -159
  54. tests/test_interfaces.py +0 -557
  55. tests/test_keras.py +0 -160
  56. tests/test_lattice.py +0 -1666
  57. tests/test_miscs.py +0 -334
  58. tests/test_mpscircuit.py +0 -341
  59. tests/test_noisemodel.py +0 -156
  60. tests/test_qaoa.py +0 -86
  61. tests/test_qem.py +0 -152
  62. tests/test_quantum.py +0 -549
  63. tests/test_quantum_attr.py +0 -42
  64. tests/test_results.py +0 -379
  65. tests/test_shadows.py +0 -160
  66. tests/test_simplify.py +0 -46
  67. tests/test_stabilizer.py +0 -226
  68. tests/test_templates.py +0 -218
  69. tests/test_torchnn.py +0 -99
  70. tests/test_van.py +0 -102
  71. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/WHEEL +0 -0
  72. {tensorcircuit_nightly-1.3.0.dev20250728.dist-info → tensorcircuit_nightly-1.4.0.dev20251103.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,9 @@
1
1
  """
2
2
  Quantum circuit: common methods for all circuit classes as MixIn
3
+
4
+ Note:
5
+ - Supports qubit (d = 2) and qudit (d >= 2) systems.
6
+ - For string-encoded samples/counts when d <= 36, digits use base-d characters 0-9A-Z (A = 10, ..., Z = 35).
3
7
  """
4
8
 
5
9
  # pylint: disable=invalid-name
@@ -19,8 +23,10 @@ from .quantum import (
19
23
  correlation_from_counts,
20
24
  measurement_counts,
21
25
  sample_int2bin,
22
- sample_bin2int,
23
26
  sample2all,
27
+ _infer_num_sites,
28
+ _decode_basis_label,
29
+ onehot_d_tensor,
24
30
  )
25
31
  from .abstractcircuit import AbstractCircuit
26
32
  from .cons import npdtype, backend, dtypestr, contractor, rdtypestr
@@ -41,8 +47,9 @@ class BaseCircuit(AbstractCircuit):
41
47
  is_mps = False
42
48
 
43
49
  @staticmethod
44
- def all_zero_nodes(n: int, d: int = 2, prefix: str = "qb-") -> List[tn.Node]:
45
- l = [0.0 for _ in range(d)]
50
+ def all_zero_nodes(n: int, prefix: str = "qb-", dim: int = 2) -> List[tn.Node]:
51
+ prefix = "qd-" if dim > 2 else prefix
52
+ l = [0.0 for _ in range(dim)]
46
53
  l[0] = 1.0
47
54
  nodes = [
48
55
  tn.Node(
@@ -289,7 +296,7 @@ class BaseCircuit(AbstractCircuit):
289
296
  for op, index in ops:
290
297
  if not isinstance(op, tn.Node):
291
298
  # op is only a matrix
292
- op = backend.reshape2(op)
299
+ op = backend.reshaped(op, d=self._d)
293
300
  op = backend.cast(op, dtype=dtypestr)
294
301
  op = gates.Gate(op)
295
302
  else:
@@ -355,12 +362,12 @@ class BaseCircuit(AbstractCircuit):
355
362
 
356
363
  def perfect_sampling(self, status: Optional[Tensor] = None) -> Tuple[str, float]:
357
364
  """
358
- Sampling bistrings from the circuit output based on quantum amplitudes.
365
+ Sampling base-d strings (0-9A-Z when d <= 36) from the circuit output based on quantum amplitudes.
359
366
  Reference: arXiv:1201.3974.
360
367
 
361
368
  :param status: external randomness, with shape [nqubits], defaults to None
362
369
  :type status: Optional[Tensor]
363
- :return: Sampled bit string and the corresponding theoretical probability.
370
+ :return: Sampled base-d string and the corresponding theoretical probability.
364
371
  :rtype: Tuple[str, float]
365
372
  """
366
373
  return self.measure_jit(*range(self._nqubits), with_prob=True, status=status)
@@ -369,10 +376,10 @@ class BaseCircuit(AbstractCircuit):
369
376
  self, *index: int, with_prob: bool = False, status: Optional[Tensor] = None
370
377
  ) -> Tuple[Tensor, Tensor]:
371
378
  """
372
- Take measurement to the given quantum lines.
379
+ Take measurement on the given site indices (computational basis).
373
380
  This method is jittable is and about 100 times faster than unjit version!
374
381
 
375
- :param index: Measure on which quantum line.
382
+ :param index: Measure on which site (wire) index.
376
383
  :type index: int
377
384
  :param with_prob: If true, theoretical probability is also returned.
378
385
  :type with_prob: bool, optional
@@ -383,9 +390,8 @@ class BaseCircuit(AbstractCircuit):
383
390
  """
384
391
  # finally jit compatible ! and much faster than unjit version ! (100x)
385
392
  sample: List[Tensor] = []
386
- p = 1.0
387
- p = backend.convert_to_tensor(p)
388
- p = backend.cast(p, dtype=rdtypestr)
393
+ one_r = backend.cast(backend.convert_to_tensor(1.0), rdtypestr)
394
+ p = one_r
389
395
  for k, j in enumerate(index):
390
396
  if self.is_dm is False:
391
397
  nodes1, edge1 = self._copy()
@@ -400,40 +406,71 @@ class BaseCircuit(AbstractCircuit):
400
406
  if i != j:
401
407
  e ^ edge2[i]
402
408
  for i in range(k):
403
- m = (1 - sample[i]) * gates.array_to_tensor(np.array([1, 0])) + sample[
404
- i
405
- ] * gates.array_to_tensor(np.array([0, 1]))
406
- newnodes.append(Gate(m))
407
- newnodes[-1].id = id(newnodes[-1])
408
- newnodes[-1].is_dagger = False
409
- newnodes[-1].flag = "measurement"
410
- newnodes[-1].get_edge(0) ^ edge1[index[i]]
411
- newnodes.append(Gate(m))
412
- newnodes[-1].id = id(newnodes[-1])
413
- newnodes[-1].is_dagger = True
414
- newnodes[-1].flag = "measurement"
415
- newnodes[-1].get_edge(0) ^ edge2[index[i]]
409
+ if self._d == 2:
410
+ m = (1 - sample[i]) * gates.array_to_tensor(
411
+ np.array([1, 0])
412
+ ) + sample[i] * gates.array_to_tensor(np.array([0, 1]))
413
+ else:
414
+ m = onehot_d_tensor(sample[i], d=self._d)
415
+ g1 = Gate(m)
416
+ g1.id = id(g1)
417
+ g1.is_dagger = False
418
+ g1.flag = "measurement"
419
+ newnodes.append(g1)
420
+ g1.get_edge(0) ^ edge1[index[i]]
421
+ g2 = Gate(m)
422
+ g2.id = id(g2)
423
+ g2.is_dagger = True
424
+ g2.flag = "measurement"
425
+ newnodes.append(g2)
426
+ g2.get_edge(0) ^ edge2[index[i]]
427
+
416
428
  rho = (
417
429
  1
418
430
  / backend.cast(p, dtypestr)
419
431
  * contractor(newnodes, output_edge_order=[edge1[j], edge2[j]]).tensor
420
432
  )
421
- pu = backend.real(rho[0, 0])
422
- if status is None:
423
- r = backend.implicit_randu()[0]
433
+ if self._d == 2:
434
+ pu = backend.real(rho[0, 0])
435
+ if status is None:
436
+ r = backend.implicit_randu()[0]
437
+ else:
438
+ r = status[k]
439
+ r = backend.real(backend.cast(r, dtypestr))
440
+ eps = 0.31415926 * 1e-12
441
+ sign = (
442
+ backend.sign(r - pu + eps) / 2 + 0.5
443
+ ) # in case status is exactly 0.5
444
+ sign = backend.convert_to_tensor(sign)
445
+ sign = backend.cast(sign, dtype=rdtypestr)
446
+ sign_complex = backend.cast(sign, dtypestr)
447
+ sample.append(sign_complex)
448
+ p = p * (pu * (-1) ** sign + sign)
424
449
  else:
425
- r = status[k]
426
- r = backend.real(backend.cast(r, dtypestr))
427
- eps = 0.31415926 * 1e-12
428
- sign = backend.sign(r - pu + eps) / 2 + 0.5 # in case status is exactly 0.5
429
- sign = backend.convert_to_tensor(sign)
430
- sign = backend.cast(sign, dtype=rdtypestr)
431
- sign_complex = backend.cast(sign, dtypestr)
432
- sample.append(sign_complex)
433
- p = p * (pu * (-1) ** sign + sign)
434
-
435
- sample = backend.stack(sample)
436
- sample = backend.real(sample)
450
+ pu = backend.clip(
451
+ backend.real(backend.diagonal(rho)),
452
+ backend.convert_to_tensor(0.0),
453
+ backend.convert_to_tensor(1.0),
454
+ )
455
+ pu = pu / backend.sum(pu)
456
+ if status is None:
457
+ ind = backend.implicit_randc(
458
+ a=backend.arange(self._d),
459
+ shape=1,
460
+ p=backend.cast(pu, rdtypestr),
461
+ )
462
+ else:
463
+ one_r = backend.cast(backend.convert_to_tensor(1.0), rdtypestr)
464
+ st = backend.cast(status[k : k + 1], rdtypestr)
465
+ ind = backend.probability_sample(
466
+ shots=1,
467
+ p=backend.cast(pu, rdtypestr),
468
+ status=one_r - st,
469
+ )
470
+ k_out = backend.cast(ind[0], "int32")
471
+ sample.append(backend.cast(k_out, rdtypestr))
472
+ p = p * backend.cast(pu[k_out], rdtypestr)
473
+ sample = backend.real(backend.stack(sample))
437
474
  if with_prob:
438
475
  return sample, p
439
476
  else:
@@ -453,25 +490,22 @@ class BaseCircuit(AbstractCircuit):
453
490
  :return: The tensornetwork nodes for the amplitude of the circuit.
454
491
  :rtype: List[Gate]
455
492
  """
493
+
456
494
  no, d_edges = self._copy()
457
495
  ms = []
458
496
  if self.is_dm:
459
497
  msconj = []
460
498
  if isinstance(l, str):
461
- for s in l:
462
- if s == "1":
463
- endn = np.array([0, 1], dtype=npdtype)
464
- elif s == "0":
465
- endn = np.array([1, 0], dtype=npdtype)
466
- ms.append(tn.Node(endn))
499
+ symbols = _decode_basis_label(l, n=self._nqubits, dim=self._d)
500
+ for k in symbols:
501
+ n = onehot_d_tensor(k, d=self._d)
502
+ ms.append(tn.Node(n))
467
503
  if self.is_dm:
468
- msconj.append(tn.Node(endn))
469
- else: # l is Tensor
504
+ msconj.append(tn.Node(n))
505
+ else:
470
506
  l = backend.cast(l, dtype=dtypestr)
471
507
  for i in range(self._nqubits):
472
- endn = l[i] * gates.array_to_tensor(np.array([0, 1])) + (
473
- 1 - l[i]
474
- ) * gates.array_to_tensor(np.array([1, 0]))
508
+ endn = onehot_d_tensor(l[i], d=self._d)
475
509
  ms.append(tn.Node(endn))
476
510
  if self.is_dm:
477
511
  msconj.append(tn.Node(endn))
@@ -522,17 +556,18 @@ class BaseCircuit(AbstractCircuit):
522
556
 
523
557
  def probability(self) -> Tensor:
524
558
  """
525
- get the 2^n length probability vector over computational basis
559
+ get the d^n length probability vector over computational basis
526
560
 
527
- :return: probability vector
561
+ :return: probability vector of shape [dim**n]
528
562
  :rtype: Tensor
529
563
  """
530
564
  s = self.state() # type: ignore
531
565
  if self.is_dm is False:
532
- p = backend.abs(s) ** 2
533
-
566
+ amp = backend.reshape(s, [-1])
567
+ p = backend.real(backend.abs(amp) ** 2)
534
568
  else:
535
- p = backend.abs(backend.diagonal(s))
569
+ diag = backend.diagonal(s)
570
+ p = backend.real(backend.reshape(diag, [-1]))
536
571
  return p
537
572
 
538
573
  @partial(arg_alias, alias_dict={"format": ["format_"]})
@@ -546,7 +581,7 @@ class BaseCircuit(AbstractCircuit):
546
581
  status: Optional[Tensor] = None,
547
582
  jittable: bool = True,
548
583
  ) -> Any:
549
- """
584
+ r"""
550
585
  batched sampling from state or circuit tensor network directly
551
586
 
552
587
  :param batch: number of samples, defaults to None
@@ -569,6 +604,7 @@ class BaseCircuit(AbstractCircuit):
569
604
  "count_tuple": # (np.array([0]), np.array([2]))
570
605
 
571
606
  "count_dict_bin": # {"00": 2, "01": 0, "10": 0, "11": 0}
607
+ for cases d\in [11, 36], use 0-9A-Z digits (e.g., 'A' -> 10, ..., 'Z' -> 35);
572
608
 
573
609
  "count_dict_int": # {0: 2, 1: 0, 2: 0, 3: 0}
574
610
 
@@ -576,7 +612,8 @@ class BaseCircuit(AbstractCircuit):
576
612
  :param random_generator: random generator, defaults to None
577
613
  :type random_generator: Optional[Any], optional
578
614
  :param status: external randomness given by tensor uniformly from [0, 1],
579
- if set, can overwrite random_generator
615
+ if set, can overwrite random_generator, shape [batch] for `allow_state=True`
616
+ and shape [batch, nqubits] for `allow_state=False` using perfect sampling implementation
580
617
  :type status: Optional[Tensor]
581
618
  :param jittable: when converting to count, whether keep the full size. if false, may be conflict
582
619
  external jit, if true, may fail for large scale system with actual limited count results
@@ -597,25 +634,29 @@ class BaseCircuit(AbstractCircuit):
597
634
  return r
598
635
  r = [r] # type: ignore
599
636
  else:
637
+ r = [] # type: ignore
638
+ if status is not None:
639
+ assert backend.shape_tuple(status)[0] == batch
640
+ for seed in status:
641
+ r.append(self.perfect_sampling(seed)) # type: ignore
600
642
 
601
- @backend.jit
602
- def perfect_sampling(key: Any) -> Any:
603
- backend.set_random_state(key)
604
- return self.perfect_sampling()
643
+ else:
605
644
 
606
- # TODO(@refraction-ray): status is not used here
645
+ @backend.jit
646
+ def perfect_sampling(key: Any) -> Any:
647
+ backend.set_random_state(key)
648
+ return self.perfect_sampling()
607
649
 
608
- r = [] # type: ignore
650
+ subkey = random_generator
651
+ for _ in range(batch):
652
+ key, subkey = backend.random_split(subkey)
653
+ r.append(perfect_sampling(key)) # type: ignore
609
654
 
610
- subkey = random_generator
611
- for _ in range(batch):
612
- key, subkey = backend.random_split(subkey)
613
- r.append(perfect_sampling(key)) # type: ignore
614
655
  if format is None:
615
656
  return r
616
657
  r = backend.stack([ri[0] for ri in r]) # type: ignore
617
- r = backend.cast(r, "int32")
618
- ch = sample_bin2int(r, self._nqubits)
658
+ ch = backend.cast(r, "int32")
659
+ # ch = sample_bin2int(r, self._nqubits, dim=self._d)
619
660
  else: # allow_state
620
661
  if batch is None:
621
662
  nbatch = 1
@@ -640,7 +681,7 @@ class BaseCircuit(AbstractCircuit):
640
681
  # 2,
641
682
  # )
642
683
  if format is None: # for backward compatibility
643
- confg = sample_int2bin(ch, self._nqubits)
684
+ confg = sample_int2bin(ch, self._nqubits, dim=self._d)
644
685
  prob = backend.gather1d(p, ch)
645
686
  r = list(zip(confg, prob)) # type: ignore
646
687
  if batch is None:
@@ -648,7 +689,9 @@ class BaseCircuit(AbstractCircuit):
648
689
  return r
649
690
  if self._nqubits > 35:
650
691
  jittable = False
651
- return sample2all(sample=ch, n=self._nqubits, format=format, jittable=jittable)
692
+ return sample2all(
693
+ sample=ch, n=self._nqubits, format=format, jittable=jittable, dim=self._d
694
+ )
652
695
 
653
696
  def sample_expectation_ps(
654
697
  self,
@@ -848,9 +891,9 @@ class BaseCircuit(AbstractCircuit):
848
891
  """
849
892
  inputs = backend.reshape(inputs, [-1])
850
893
  N = inputs.shape[0]
851
- n = int(np.log(N) / np.log(2))
894
+ n = _infer_num_sites(N, self._d)
852
895
  assert n == self._nqubits
853
- inputs = backend.reshape(inputs, [2 for _ in range(n)])
896
+ inputs = backend.reshape(inputs, [self._d for _ in range(n)])
854
897
  if self.inputs is not None:
855
898
  self._nodes[0].tensor = inputs
856
899
  if self.is_dm:
@@ -881,9 +924,9 @@ class BaseCircuit(AbstractCircuit):
881
924
 
882
925
 
883
926
 
884
- :param index: the qubit for the z-basis measurement
927
+ :param index: the site index for the Z-basis measurement
885
928
  :type index: int
886
- :return: 0 or 1 for z measurement on up and down freedom
929
+ :return: 0 or 1 for Z-basis measurement outcome
887
930
  :rtype: Tensor
888
931
  """
889
932
  return self.general_kraus( # type: ignore
@@ -962,8 +1005,8 @@ class BaseCircuit(AbstractCircuit):
962
1005
 
963
1006
  def projected_subsystem(self, traceout: Tensor, left: Tuple[int, ...]) -> Tensor:
964
1007
  """
965
- remaining wavefunction or density matrix on qubits in left, with other qubits
966
- fixed in 0 or 1 indicated by traceout
1008
+ remaining wavefunction or density matrix on sites in ``left``, with other sites
1009
+ fixed to given digits (0..d-1) as indicated by ``traceout``
967
1010
 
968
1011
  :param traceout: can be jitted
969
1012
  :type traceout: Tensor
@@ -972,15 +1015,14 @@ class BaseCircuit(AbstractCircuit):
972
1015
  :return: _description_
973
1016
  :rtype: Tensor
974
1017
  """
975
- end0, end1 = gates.array_to_tensor(np.array([1.0, 0]), np.array([0, 1.0]))
1018
+
976
1019
  traceout = backend.cast(traceout, dtypestr)
977
1020
  nodes, front = self._copy()
978
1021
  L = self._nqubits
979
1022
  edges = []
980
1023
  for i in range(len(traceout)):
981
1024
  if i not in left:
982
- b = traceout[i]
983
- n = gates.Gate((1 - b) * end0 + b * end1)
1025
+ n = Gate(onehot_d_tensor(traceout[i], d=self._d))
984
1026
  nodes.append(n)
985
1027
  front[i] ^ n[0]
986
1028
  else:
@@ -989,8 +1031,7 @@ class BaseCircuit(AbstractCircuit):
989
1031
  if self.is_dm:
990
1032
  for i in range(len(traceout)):
991
1033
  if i not in left:
992
- b = traceout[i]
993
- n = gates.Gate((1 - b) * end0 + b * end1)
1034
+ n = Gate(onehot_d_tensor(traceout[i], d=self._d))
994
1035
  nodes.append(n)
995
1036
  front[i + L] ^ n[0]
996
1037
  else: