tensorcircuit-nightly 1.2.0.dev20250326__py3-none-any.whl → 1.4.0.dev20251128__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 (77) 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 +100 -4
  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 +157 -98
  14. tensorcircuit/circuit.py +115 -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 +105 -23
  20. tensorcircuit/densitymatrix.py +16 -11
  21. tensorcircuit/experimental.py +733 -153
  22. tensorcircuit/fgs.py +254 -73
  23. tensorcircuit/gates.py +66 -22
  24. tensorcircuit/interfaces/jax.py +5 -3
  25. tensorcircuit/interfaces/tensortrans.py +6 -2
  26. tensorcircuit/interfaces/torch.py +14 -4
  27. tensorcircuit/keras.py +3 -3
  28. tensorcircuit/mpscircuit.py +154 -65
  29. tensorcircuit/quantum.py +698 -134
  30. tensorcircuit/quditcircuit.py +733 -0
  31. tensorcircuit/quditgates.py +618 -0
  32. tensorcircuit/results/counts.py +131 -18
  33. tensorcircuit/results/readout_mitigation.py +4 -1
  34. tensorcircuit/shadows.py +1 -1
  35. tensorcircuit/simplify.py +3 -1
  36. tensorcircuit/stabilizercircuit.py +29 -17
  37. tensorcircuit/templates/__init__.py +2 -0
  38. tensorcircuit/templates/blocks.py +2 -2
  39. tensorcircuit/templates/hamiltonians.py +174 -0
  40. tensorcircuit/templates/lattice.py +1789 -0
  41. tensorcircuit/timeevol.py +896 -0
  42. tensorcircuit/translation.py +10 -3
  43. tensorcircuit/utils.py +7 -0
  44. {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/METADATA +66 -29
  45. tensorcircuit_nightly-1.4.0.dev20251128.dist-info/RECORD +96 -0
  46. {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/WHEEL +1 -1
  47. {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.dist-info}/top_level.txt +0 -1
  48. tensorcircuit_nightly-1.2.0.dev20250326.dist-info/RECORD +0 -118
  49. tests/__init__.py +0 -0
  50. tests/conftest.py +0 -67
  51. tests/test_backends.py +0 -1035
  52. tests/test_calibrating.py +0 -149
  53. tests/test_channels.py +0 -409
  54. tests/test_circuit.py +0 -1699
  55. tests/test_cloud.py +0 -219
  56. tests/test_compiler.py +0 -147
  57. tests/test_dmcircuit.py +0 -555
  58. tests/test_ensemble.py +0 -72
  59. tests/test_fgs.py +0 -310
  60. tests/test_gates.py +0 -156
  61. tests/test_interfaces.py +0 -562
  62. tests/test_keras.py +0 -160
  63. tests/test_miscs.py +0 -282
  64. tests/test_mpscircuit.py +0 -341
  65. tests/test_noisemodel.py +0 -156
  66. tests/test_qaoa.py +0 -86
  67. tests/test_qem.py +0 -152
  68. tests/test_quantum.py +0 -549
  69. tests/test_quantum_attr.py +0 -42
  70. tests/test_results.py +0 -380
  71. tests/test_shadows.py +0 -160
  72. tests/test_simplify.py +0 -46
  73. tests/test_stabilizer.py +0 -217
  74. tests/test_templates.py +0 -218
  75. tests/test_torchnn.py +0 -99
  76. tests/test_van.py +0 -102
  77. {tensorcircuit_nightly-1.2.0.dev20250326.dist-info → tensorcircuit_nightly-1.4.0.dev20251128.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:
@@ -441,47 +478,34 @@ class BaseCircuit(AbstractCircuit):
441
478
 
442
479
  measure = measure_jit
443
480
 
444
- def amplitude(self, l: Union[str, Tensor]) -> Tensor:
481
+ def amplitude_before(self, l: Union[str, Tensor]) -> List[Gate]:
445
482
  r"""
446
- Returns the amplitude of the circuit given the bitstring l.
483
+ Returns the tensornetwor nodes for the amplitude of the circuit given the bitstring l.
447
484
  For state simulator, it computes :math:`\langle l\vert \psi\rangle`,
448
485
  for density matrix simulator, it computes :math:`Tr(\rho \vert l\rangle \langle 1\vert)`
449
486
  Note how these two are different up to a square operation.
450
487
 
451
- :Example:
452
-
453
- >>> c = tc.Circuit(2)
454
- >>> c.X(0)
455
- >>> c.amplitude("10")
456
- array(1.+0.j, dtype=complex64)
457
- >>> c.CNOT(0, 1)
458
- >>> c.amplitude("11")
459
- array(1.+0.j, dtype=complex64)
460
-
461
488
  :param l: The bitstring of 0 and 1s.
462
489
  :type l: Union[str, Tensor]
463
- :return: The amplitude of the circuit.
464
- :rtype: tn.Node.tensor
490
+ :return: The tensornetwork nodes for the amplitude of the circuit.
491
+ :rtype: List[Gate]
465
492
  """
493
+
466
494
  no, d_edges = self._copy()
467
495
  ms = []
468
496
  if self.is_dm:
469
497
  msconj = []
470
498
  if isinstance(l, str):
471
- for s in l:
472
- if s == "1":
473
- endn = np.array([0, 1], dtype=npdtype)
474
- elif s == "0":
475
- endn = np.array([1, 0], dtype=npdtype)
476
- 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))
477
503
  if self.is_dm:
478
- msconj.append(tn.Node(endn))
479
- else: # l is Tensor
504
+ msconj.append(tn.Node(n))
505
+ else:
480
506
  l = backend.cast(l, dtype=dtypestr)
481
507
  for i in range(self._nqubits):
482
- endn = l[i] * gates.array_to_tensor(np.array([0, 1])) + (
483
- 1 - l[i]
484
- ) * gates.array_to_tensor(np.array([1, 0]))
508
+ endn = onehot_d_tensor(l[i], d=self._d)
485
509
  ms.append(tn.Node(endn))
486
510
  if self.is_dm:
487
511
  msconj.append(tn.Node(endn))
@@ -502,21 +526,48 @@ class BaseCircuit(AbstractCircuit):
502
526
  no.extend(ms)
503
527
  if self.is_dm:
504
528
  no.extend(msconj)
529
+ return no
530
+
531
+ def amplitude(self, l: Union[str, Tensor]) -> Tensor:
532
+ r"""
533
+ Returns the amplitude of the circuit given the bitstring l.
534
+ For state simulator, it computes :math:`\langle l\vert \psi\rangle`,
535
+ for density matrix simulator, it computes :math:`Tr(\rho \vert l\rangle \langle 1\vert)`
536
+ Note how these two are different up to a square operation.
537
+
538
+ :Example:
539
+
540
+ >>> c = tc.Circuit(2)
541
+ >>> c.X(0)
542
+ >>> c.amplitude("10")
543
+ array(1.+0.j, dtype=complex64)
544
+ >>> c.CNOT(0, 1)
545
+ >>> c.amplitude("11")
546
+ array(1.+0.j, dtype=complex64)
547
+
548
+ :param l: The bitstring of 0 and 1s.
549
+ :type l: Union[str, Tensor]
550
+ :return: The amplitude of the circuit.
551
+ :rtype: tn.Node.tensor
552
+ """
553
+ no = self.amplitude_before(l)
554
+
505
555
  return contractor(no).tensor
506
556
 
507
557
  def probability(self) -> Tensor:
508
558
  """
509
- get the 2^n length probability vector over computational basis
559
+ get the d^n length probability vector over computational basis
510
560
 
511
- :return: probability vector
561
+ :return: probability vector of shape [dim**n]
512
562
  :rtype: Tensor
513
563
  """
514
564
  s = self.state() # type: ignore
515
565
  if self.is_dm is False:
516
- p = backend.abs(s) ** 2
517
-
566
+ amp = backend.reshape(s, [-1])
567
+ p = backend.real(backend.abs(amp) ** 2)
518
568
  else:
519
- p = backend.abs(backend.diagonal(s))
569
+ diag = backend.diagonal(s)
570
+ p = backend.real(backend.reshape(diag, [-1]))
520
571
  return p
521
572
 
522
573
  @partial(arg_alias, alias_dict={"format": ["format_"]})
@@ -530,7 +581,7 @@ class BaseCircuit(AbstractCircuit):
530
581
  status: Optional[Tensor] = None,
531
582
  jittable: bool = True,
532
583
  ) -> Any:
533
- """
584
+ r"""
534
585
  batched sampling from state or circuit tensor network directly
535
586
 
536
587
  :param batch: number of samples, defaults to None
@@ -553,6 +604,7 @@ class BaseCircuit(AbstractCircuit):
553
604
  "count_tuple": # (np.array([0]), np.array([2]))
554
605
 
555
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);
556
608
 
557
609
  "count_dict_int": # {0: 2, 1: 0, 2: 0, 3: 0}
558
610
 
@@ -560,7 +612,8 @@ class BaseCircuit(AbstractCircuit):
560
612
  :param random_generator: random generator, defaults to None
561
613
  :type random_generator: Optional[Any], optional
562
614
  :param status: external randomness given by tensor uniformly from [0, 1],
563
- 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
564
617
  :type status: Optional[Tensor]
565
618
  :param jittable: when converting to count, whether keep the full size. if false, may be conflict
566
619
  external jit, if true, may fail for large scale system with actual limited count results
@@ -581,25 +634,29 @@ class BaseCircuit(AbstractCircuit):
581
634
  return r
582
635
  r = [r] # type: ignore
583
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
584
642
 
585
- @backend.jit
586
- def perfect_sampling(key: Any) -> Any:
587
- backend.set_random_state(key)
588
- return self.perfect_sampling()
643
+ else:
589
644
 
590
- # 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()
591
649
 
592
- 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
593
654
 
594
- subkey = random_generator
595
- for _ in range(batch):
596
- key, subkey = backend.random_split(subkey)
597
- r.append(perfect_sampling(key)) # type: ignore
598
655
  if format is None:
599
656
  return r
600
657
  r = backend.stack([ri[0] for ri in r]) # type: ignore
601
- r = backend.cast(r, "int32")
602
- ch = sample_bin2int(r, self._nqubits)
658
+ ch = backend.cast(r, "int32")
659
+ # ch = sample_bin2int(r, self._nqubits, dim=self._d)
603
660
  else: # allow_state
604
661
  if batch is None:
605
662
  nbatch = 1
@@ -624,7 +681,7 @@ class BaseCircuit(AbstractCircuit):
624
681
  # 2,
625
682
  # )
626
683
  if format is None: # for backward compatibility
627
- confg = sample_int2bin(ch, self._nqubits)
684
+ confg = sample_int2bin(ch, self._nqubits, dim=self._d)
628
685
  prob = backend.gather1d(p, ch)
629
686
  r = list(zip(confg, prob)) # type: ignore
630
687
  if batch is None:
@@ -632,7 +689,9 @@ class BaseCircuit(AbstractCircuit):
632
689
  return r
633
690
  if self._nqubits > 35:
634
691
  jittable = False
635
- 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
+ )
636
695
 
637
696
  def sample_expectation_ps(
638
697
  self,
@@ -796,8 +855,10 @@ class BaseCircuit(AbstractCircuit):
796
855
  """
797
856
  # if isinstance(readout_error, tuple):
798
857
  # readout_error = list[readout_error] # type: ignore
799
-
800
- nqubit = len(readout_error) # type: ignore
858
+ try:
859
+ nqubit = int(readout_error.shape[0]) # type: ignore
860
+ except AttributeError:
861
+ nqubit = len(readout_error) # type: ignore
801
862
  readoutlist = []
802
863
  for i in range(nqubit):
803
864
  readoutlist.append(
@@ -830,9 +891,9 @@ class BaseCircuit(AbstractCircuit):
830
891
  """
831
892
  inputs = backend.reshape(inputs, [-1])
832
893
  N = inputs.shape[0]
833
- n = int(np.log(N) / np.log(2))
894
+ n = _infer_num_sites(N, self._d)
834
895
  assert n == self._nqubits
835
- inputs = backend.reshape(inputs, [2 for _ in range(n)])
896
+ inputs = backend.reshape(inputs, [self._d for _ in range(n)])
836
897
  if self.inputs is not None:
837
898
  self._nodes[0].tensor = inputs
838
899
  if self.is_dm:
@@ -863,9 +924,9 @@ class BaseCircuit(AbstractCircuit):
863
924
 
864
925
 
865
926
 
866
- :param index: the qubit for the z-basis measurement
927
+ :param index: the site index for the Z-basis measurement
867
928
  :type index: int
868
- :return: 0 or 1 for z measurement on up and down freedom
929
+ :return: 0 or 1 for Z-basis measurement outcome
869
930
  :rtype: Tensor
870
931
  """
871
932
  return self.general_kraus( # type: ignore
@@ -944,8 +1005,8 @@ class BaseCircuit(AbstractCircuit):
944
1005
 
945
1006
  def projected_subsystem(self, traceout: Tensor, left: Tuple[int, ...]) -> Tensor:
946
1007
  """
947
- remaining wavefunction or density matrix on qubits in left, with other qubits
948
- 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``
949
1010
 
950
1011
  :param traceout: can be jitted
951
1012
  :type traceout: Tensor
@@ -954,15 +1015,14 @@ class BaseCircuit(AbstractCircuit):
954
1015
  :return: _description_
955
1016
  :rtype: Tensor
956
1017
  """
957
- end0, end1 = gates.array_to_tensor(np.array([1.0, 0]), np.array([0, 1.0]))
1018
+
958
1019
  traceout = backend.cast(traceout, dtypestr)
959
1020
  nodes, front = self._copy()
960
1021
  L = self._nqubits
961
1022
  edges = []
962
1023
  for i in range(len(traceout)):
963
1024
  if i not in left:
964
- b = traceout[i]
965
- n = gates.Gate((1 - b) * end0 + b * end1)
1025
+ n = Gate(onehot_d_tensor(traceout[i], d=self._d))
966
1026
  nodes.append(n)
967
1027
  front[i] ^ n[0]
968
1028
  else:
@@ -971,8 +1031,7 @@ class BaseCircuit(AbstractCircuit):
971
1031
  if self.is_dm:
972
1032
  for i in range(len(traceout)):
973
1033
  if i not in left:
974
- b = traceout[i]
975
- n = gates.Gate((1 - b) * end0 + b * end1)
1034
+ n = Gate(onehot_d_tensor(traceout[i], d=self._d))
976
1035
  nodes.append(n)
977
1036
  front[i + L] ^ n[0]
978
1037
  else: