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

@@ -0,0 +1,670 @@
1
+ """
2
+ Quantum circuit: state simulator for **qudits** (d-level systems).
3
+
4
+ This module provides a high-level `QuditCircuit` API that mirrors `tensorcircuit.circuit.Circuit`
5
+ but targets qudits with dimension `3 <= d <= 36`.
6
+ For string-encoded samples/counts, digits use `0-9A-Z` where `A=10, ..., Z=35`.
7
+
8
+ .. note::
9
+ For qubits (`d=2`) please use :class:`tensorcircuit.circuit.Circuit`.
10
+
11
+ """
12
+
13
+ from functools import partial
14
+ from typing import Any, Dict, List, Optional, Tuple, Sequence, Union, Literal
15
+
16
+ import numpy as np
17
+ import tensornetwork as tn
18
+
19
+ from .gates import Gate
20
+ from .utils import arg_alias
21
+ from .basecircuit import BaseCircuit
22
+ from .circuit import Circuit
23
+ from .quantum import QuOperator, QuVector
24
+ from .quditgates import SINGLE_BUILDERS, TWO_BUILDERS
25
+
26
+
27
+ Tensor = Any
28
+ SAMPLE_FORMAT = Literal["sample_bin", "count_dict_bin"]
29
+
30
+
31
+ class QuditCircuit:
32
+ r"""
33
+ The `QuditCircuit` class provides a d-dimensional state-vector simulator and a thin wrapper
34
+ around :class:`tensorcircuit.circuit.Circuit`, exposing a qudit-friendly API and docstrings.
35
+
36
+ **Quick example (d=3):**
37
+
38
+ >>> c = tc.QuditCircuit(2, dim=3)
39
+ >>> c.h(0)
40
+ >>> c.x(1)
41
+ >>> c.csum(0, 1)
42
+ >>> c.sample(1024, format="count_dict_bin")
43
+
44
+ .. note::
45
+ For `3 <= d <= 36`, string samples and count keys use base-`d` characters `0-9A-Z`
46
+ (`A=10, ..., Z=35`).
47
+
48
+ :param nqudits: Number of qudits (wires) in the circuit.
49
+ :type nqudits: int
50
+ :param dim: Qudit local dimension `d`. Must satisfy `3 <= d <= 36`.
51
+ :type dim: int
52
+ :param inputs: Optional initial state as a wavefunction.
53
+ :type inputs: Optional[Tensor]
54
+ :param mps_inputs: Optional initial state in MPS/MPO-like form.
55
+ :type mps_inputs: Optional[QuOperator]
56
+ :param split: Internal contraction/splitting configuration passed through to
57
+ :class:`~tensorcircuit.circuit.Circuit`.
58
+ :type split: Optional[Dict[str, Any]]
59
+
60
+ :var is_dm: Whether the simulator is a density-matrix simulator (`False` here).
61
+ :vartype is_dm: bool
62
+ :var dim: Property for the local dimension `d`.
63
+ :vartype dim: int
64
+ :var nqudits: Property for the number of qudits.
65
+ :vartype nqudits: int
66
+ """
67
+
68
+ is_dm = False
69
+
70
+ def __init__(
71
+ self,
72
+ nqudits: int,
73
+ dim: int,
74
+ inputs: Optional[Tensor] = None,
75
+ mps_inputs: Optional[QuOperator] = None,
76
+ split: Optional[Dict[str, Any]] = None,
77
+ ):
78
+ self._set_dim(dim=dim)
79
+ self._nqudits = nqudits
80
+
81
+ self._circ = Circuit(
82
+ nqubits=nqudits,
83
+ dim=dim,
84
+ inputs=inputs,
85
+ mps_inputs=mps_inputs,
86
+ split=split,
87
+ )
88
+ self._omega = np.exp(2j * np.pi / self._d)
89
+ self.circuit_param = self._circ.circuit_param
90
+
91
+ def _set_dim(self, dim: int) -> None:
92
+ if not isinstance(dim, int) or dim <= 2:
93
+ raise ValueError(
94
+ f"QuditCircuit is only for qudits (dim>=3). "
95
+ f"You passed dim={dim}. For qubits, please use `Circuit` instead."
96
+ )
97
+ # Require integer d>=2; current string-encoded IO supports d<=36 (0-9A-Z digits).
98
+ if dim > 36:
99
+ raise NotImplementedError(
100
+ "The Qudit interface is only supported for dimension < 36 now."
101
+ )
102
+ self._d = dim
103
+
104
+ @property
105
+ def dim(self) -> int:
106
+ """dimension of the qudit circuit"""
107
+ return self._d
108
+
109
+ @property
110
+ def nqudits(self) -> int:
111
+ """qudit number of the circuit"""
112
+ return self._nqudits
113
+
114
+ def _apply_gate(self, *indices: int, name: str, **kwargs: Any) -> None:
115
+ """
116
+ Apply a single- or two-qudit unitary by name.
117
+
118
+ The gate matrix is looked up by name in either :data:`SINGLE_BUILDERS` (single-qudit)
119
+ or :data:`TWO_BUILDERS` (two-qudit). The registered builder is called with `(d, omega, **kwargs)`
120
+ to produce the unitary, which is then applied at the given indices.
121
+
122
+ :param indices: Qudit indices the gate acts on. One index -> single-qudit gate; two indices -> two-qudit gate.
123
+ :type indices: int
124
+ :param name: Gate name registered in the corresponding builder set.
125
+ :type name: str
126
+ :param kwargs: Extra parameters forwarded to the builder (matched by keyword).
127
+ :type kwargs: Any
128
+ :raises ValueError: If the name is unknown or the arity does not match the number of indices.
129
+ """
130
+ if len(indices) == 1 and name in SINGLE_BUILDERS:
131
+ sig, builder = SINGLE_BUILDERS[name]
132
+ extras = tuple(kwargs.get(k) for k in sig if k != "none")
133
+ builder_kwargs = {k: v for k, v in zip(sig, extras)}
134
+ mat = builder(self._d, self._omega, **builder_kwargs)
135
+ self._circ.unitary(*indices, unitary=mat, name=name, dim=self._d) # type: ignore
136
+ elif len(indices) == 2 and name in TWO_BUILDERS:
137
+ sig, builder = TWO_BUILDERS[name]
138
+ extras = tuple(kwargs.get(k) for k in sig if k != "none")
139
+ builder_kwargs = {k: v for k, v in zip(sig, extras)}
140
+ mat = builder(self._d, self._omega, **builder_kwargs)
141
+ self._circ.unitary( # type: ignore
142
+ *indices, unitary=mat, name=name, dim=self._d
143
+ )
144
+ else:
145
+ raise ValueError(f"Unsupported gate/arity: {name} on {len(indices)} qudits")
146
+
147
+ def any(self, *indices: int, unitary: Tensor, name: str = "any") -> None:
148
+ """
149
+ Apply an arbitrary unitary on one or two qudits.
150
+
151
+ :param indices: Target qudit indices.
152
+ :type indices: int
153
+ :param unitary: Unitary matrix acting on the specified qudit(s), with shape `(d, d)` or `(d^2, d^2)`.
154
+ :type unitary: Tensor
155
+ :param name: Optional label stored in the circuit history.
156
+ :type name: str
157
+ """
158
+ self._circ.unitary(*indices, unitary=unitary, name=name, dim=self._d) # type: ignore
159
+
160
+ unitary = any
161
+
162
+ def i(self, index: int) -> None:
163
+ """
164
+ Apply the generalized identity gate `I` on the given qudit.
165
+
166
+ :param index: Qudit index.
167
+ :type index: int
168
+ """
169
+ self._apply_gate(index, name="I")
170
+
171
+ I = i
172
+
173
+ def x(self, index: int) -> None:
174
+ """
175
+ Apply the generalized shift gate `X` on the given qudit (adds `+1 mod d`).
176
+
177
+ :param index: Qudit index.
178
+ :type index: int
179
+ """
180
+ self._apply_gate(index, name="X")
181
+
182
+ X = x
183
+
184
+ # def y(self, index: int) -> None:
185
+ # """
186
+ # Apply the Y gate on the given qudit index.
187
+ #
188
+ # :param index: Qudit index to apply the gate on.
189
+ # :type index: int
190
+ # """
191
+ # self._apply_gate(index, name="Y")
192
+
193
+ def z(self, index: int) -> None:
194
+ """
195
+ Apply the generalized phase gate `Z` on the given qudit (multiplies by `omega^k`).
196
+
197
+ :param index: Qudit index.
198
+ :type index: int
199
+ """
200
+ self._apply_gate(index, name="Z")
201
+
202
+ Z = z
203
+
204
+ def h(self, index: int) -> None:
205
+ """
206
+ Apply the generalized Hadamard-like gate `H` (DFT on `d` levels) on the given qudit.
207
+
208
+ :param index: Qudit index.
209
+ :type index: int
210
+ """
211
+ self._apply_gate(index, name="H")
212
+
213
+ H = h
214
+
215
+ def u8(
216
+ self, index: int, gamma: float = 2.0, z: float = 1.0, eps: float = 0.0
217
+ ) -> None:
218
+ """
219
+ Apply the parameterized single-qudit gate `U8`.
220
+
221
+ :param index: Qudit index.
222
+ :type index: int
223
+ :param gamma: Gate parameter `gamma`.
224
+ :type gamma: float
225
+ :param z: Gate parameter `z`.
226
+ :type z: float
227
+ :param eps: Gate parameter `eps`.
228
+ :type eps: float
229
+ """
230
+ self._apply_gate(index, name="U8", extra=(gamma, z, eps))
231
+
232
+ U8 = u8
233
+
234
+ def rx(self, index: int, theta: float, j: int = 0, k: int = 1) -> None:
235
+ """
236
+ Single-qudit rotation `RX` on a selected two-level subspace `(j, k)`.
237
+
238
+ :param index: Qudit index.
239
+ :type index: int
240
+ :param theta: Rotation angle.
241
+ :type theta: float
242
+ :param j: Source level of the rotation subspace (0-based).
243
+ :type j: int
244
+ :param k: Target level of the rotation subspace (0-based).
245
+ :type k: int
246
+ :raises ValueError: If `j == k` or indices are outside `[0, d-1]`.
247
+ """
248
+ self._apply_gate(index, name="RX", theta=theta, j=j, k=k)
249
+
250
+ RX = rx
251
+
252
+ def ry(self, index: int, theta: float, j: int = 0, k: int = 1) -> None:
253
+ """
254
+ Single-qudit rotation `RY` on a selected two-level subspace `(j, k)`.
255
+
256
+ :param index: Qudit index.
257
+ :type index: int
258
+ :param theta: Rotation angle.
259
+ :type theta: float
260
+ :param j: Source level of the rotation subspace (0-based).
261
+ :type j: int
262
+ :param k: Target level of the rotation subspace (0-based).
263
+ :type k: int
264
+ :raises ValueError: If `j == k` or indices are outside `[0, d-1]`.
265
+ """
266
+ self._apply_gate(index, name="RY", theta=theta, j=j, k=k)
267
+
268
+ RY = ry
269
+
270
+ def rz(self, index: int, theta: float, j: int = 0) -> None:
271
+ """
272
+ Single-qudit phase rotation `RZ` applied on level `j`.
273
+
274
+ :param index: Qudit index.
275
+ :type index: int
276
+ :param theta: Phase rotation angle around Z.
277
+ :type theta: float
278
+ :param j: Level where the phase is applied (0-based).
279
+ :type j: int
280
+ :raises ValueError: If `j` is outside `[0, d-1]`.
281
+ """
282
+ self._apply_gate(index, name="RZ", theta=theta, j=j)
283
+
284
+ RZ = rz
285
+
286
+ def rxx(
287
+ self,
288
+ *indices: int,
289
+ theta: float,
290
+ j1: int = 0,
291
+ k1: int = 1,
292
+ j2: int = 0,
293
+ k2: int = 1,
294
+ ) -> None:
295
+ """
296
+ Two-qudit interaction `RXX` acting on subspaces `(j1, k1)` and `(j2, k2)`.
297
+
298
+ :param indices: Two qudit indices `(q1, q2)`.
299
+ :type indices: int
300
+ :param theta: Interaction angle.
301
+ :type theta: float
302
+ :param j1: Source level of the first qudit subspace.
303
+ :type j1: int
304
+ :param k1: Target level of the first qudit subspace.
305
+ :type k1: int
306
+ :param j2: Source level of the second qudit subspace.
307
+ :type j2: int
308
+ :param k2: Target level of the second qudit subspace.
309
+ :type k2: int
310
+ :raises ValueError: If levels are invalid or the arity is not two.
311
+ """
312
+ self._apply_gate(*indices, name="RXX", theta=theta, j1=j1, k1=k1, j2=j2, k2=k2)
313
+
314
+ RXX = rxx
315
+
316
+ def rzz(
317
+ self,
318
+ *indices: int,
319
+ theta: float,
320
+ j1: int = 0,
321
+ k1: int = 1,
322
+ j2: int = 0,
323
+ k2: int = 1,
324
+ ) -> None:
325
+ """
326
+ Two-qudit interaction `RZZ` acting on subspaces `(j1, k1)` and `(j2, k2)`.
327
+
328
+ :param indices: Two qudit indices `(q1, q2)`.
329
+ :type indices: int
330
+ :param theta: Interaction angle.
331
+ :type theta: float
332
+ :param j1: Source level of the first qudit subspace.
333
+ :type j1: int
334
+ :param k1: Target level of the first qudit subspace.
335
+ :type k1: int
336
+ :param j2: Source level of the second qudit subspace.
337
+ :type j2: int
338
+ :param k2: Target level of the second qudit subspace.
339
+ :type k2: int
340
+ :raises ValueError: If levels are invalid or the arity is not two.
341
+ """
342
+ self._apply_gate(*indices, name="RZZ", theta=theta, j1=j1, k1=k1, j2=j2, k2=k2)
343
+
344
+ RZZ = rzz
345
+
346
+ def cphase(self, *indices: int, cv: Optional[int] = None) -> None:
347
+ """
348
+ Apply a controlled-phase gate `CPHASE`.
349
+
350
+ :param indices: Two qudit indices `(control, target)`.
351
+ :type indices: int
352
+ :param cv: Optional control value. If `None`, defaults to `1`.
353
+ :type cv: Optional[int]
354
+ :raises ValueError: If arity is not two.
355
+ """
356
+ self._apply_gate(*indices, name="CPHASE", cv=cv)
357
+
358
+ CPHASE = cphase
359
+
360
+ def csum(self, *indices: int, cv: Optional[int] = None) -> None:
361
+ """
362
+ Apply a generalized controlled-sum gate `CSUM` (a.k.a. qudit CNOT).
363
+
364
+ :param indices: Two qudit indices `(control, target)`.
365
+ :type indices: int
366
+ :param cv: Optional control value. If `None`, defaults to `1`.
367
+ :type cv: Optional[int]
368
+ :raises ValueError: If arity is not two.
369
+ """
370
+ self._apply_gate(*indices, name="CSUM", cv=cv)
371
+
372
+ cnot, CSUM, CNOT = csum, csum, csum
373
+
374
+ # Functional
375
+ def wavefunction(self, form: str = "default") -> tn.Node.tensor:
376
+ """
377
+ Compute the output wavefunction from the circuit.
378
+
379
+ :param form: The str indicating the form of the output wavefunction.
380
+ "default": [-1], "ket": [-1, 1], "bra": [1, -1]
381
+ :type form: str, optional
382
+ :return: Tensor with the corresponding shape.
383
+ :rtype: Tensor
384
+ """
385
+ return self._circ.wavefunction(form)
386
+
387
+ state = wavefunction
388
+
389
+ def get_quoperator(self) -> QuOperator:
390
+ """
391
+ Get the ``QuOperator`` MPO like representation of the circuit unitary without contraction.
392
+
393
+ :return: ``QuOperator`` object for the circuit unitary (open indices for the input state)
394
+ :rtype: QuOperator
395
+ """
396
+ return self._circ.quoperator()
397
+
398
+ quoperator = get_quoperator
399
+
400
+ get_circuit_as_quoperator = get_quoperator
401
+ get_state_as_quvector = BaseCircuit.quvector
402
+
403
+ def matrix(self) -> Tensor:
404
+ """
405
+ Get the unitary matrix for the circuit irrespective with the circuit input state.
406
+
407
+ :return: The circuit unitary matrix
408
+ :rtype: Tensor
409
+ """
410
+ return self._circ.matrix()
411
+
412
+ # def measure_reference(
413
+ # self, *index: int, with_prob: bool = False
414
+ # ) -> Tuple[str, float]:
415
+ # return self._circ.measure_reference(*index, with_prob=with_prob)
416
+
417
+ def expectation(
418
+ self,
419
+ *ops: Tuple[tn.Node, List[int]],
420
+ reuse: bool = True,
421
+ enable_lightcone: bool = False,
422
+ nmc: int = 1000,
423
+ **kws: Any,
424
+ ) -> Tensor:
425
+ """
426
+ Compute expectation(s) of local operators.
427
+
428
+ :param ops: Pairs of `(operator_node, [sites])` specifying where each operator acts.
429
+ :type ops: Tuple[tn.Node, List[int]]
430
+ :param reuse: If True, then the wavefunction tensor is cached for further expectation evaluation,
431
+ defaults to be true.
432
+ :type reuse: bool, optional
433
+ :param enable_lightcone: whether enable light cone simplification, defaults to False
434
+ :type enable_lightcone: bool, optional
435
+ :param nmc: repetition time for Monte Carlo sampling for noisfy calculation, defaults to 1000
436
+ :type nmc: int, optional
437
+ :raises ValueError: "Cannot measure two operators in one index"
438
+ :return: Tensor with one element
439
+ :rtype: Tensor
440
+ """
441
+ return self._circ.expectation(
442
+ *ops,
443
+ reuse=reuse,
444
+ enable_lightcone=enable_lightcone,
445
+ noise_conf=None,
446
+ nmc=nmc,
447
+ **kws,
448
+ )
449
+
450
+ def measure_jit(
451
+ self, *index: int, with_prob: bool = False, status: Optional[Tensor] = None
452
+ ) -> Tuple[Tensor, Tensor]:
453
+ """
454
+ Take measurement on the given site indices (computational basis).
455
+ This method is jittable!
456
+
457
+ :param index: Measure on which site (wire) index.
458
+ :type index: int
459
+ :param with_prob: If true, theoretical probability is also returned.
460
+ :type with_prob: bool, optional
461
+ :param status: external randomness, with shape [index], defaults to None
462
+ :type status: Optional[Tensor]
463
+ :return: The sample output and probability (optional) of the quantum line.
464
+ :rtype: Tuple[Tensor, Tensor]
465
+ """
466
+ return self._circ.measure_jit(*index, with_prob=with_prob, status=status)
467
+
468
+ measure = measure_jit
469
+
470
+ def amplitude(self, l: Union[str, Tensor]) -> Tensor:
471
+ r"""
472
+ Return the amplitude for a given base-`d` string `l`.
473
+
474
+ For state simulators, this computes :math:`\langle l \vert \psi \rangle`.
475
+ For density-matrix simulators, it would compute :math:`\operatorname{Tr}(\rho \vert l \rangle \langle l \vert)`
476
+ (note the square in magnitude differs between the two formalisms).
477
+
478
+ **Example**
479
+
480
+ >>> c = tc.QuditCircuit(2, dim=3)
481
+ >>> c.x(0)
482
+ >>> c.x(1)
483
+ >>> c.amplitude("20")
484
+ array(1.+0.j, dtype=complex64)
485
+ >>> c.csum(0, 1, cv=2)
486
+ >>> c.amplitude("21")
487
+ array(1.+0.j, dtype=complex64)
488
+
489
+ :param l: Bitstring in base-`d` using `0-9A-Z`.
490
+ :type l: Union[str, Tensor]
491
+ :return: Complex amplitude.
492
+ :rtype: Tensor
493
+ """
494
+ return self._circ.amplitude(l)
495
+
496
+ def probability(self) -> Tensor:
497
+ """
498
+ Get the length-`d^n` probability vector over the computational basis.
499
+
500
+ :return: Probability vector of shape `[dim^n]`.
501
+ :rtype: Tensor
502
+ """
503
+ return self._circ.probability()
504
+
505
+ @partial(arg_alias, alias_dict={"format": ["format_"]})
506
+ def sample(
507
+ self,
508
+ batch: Optional[int] = None,
509
+ allow_state: bool = False,
510
+ readout_error: Optional[Sequence[Any]] = None,
511
+ format: Optional[SAMPLE_FORMAT] = None,
512
+ random_generator: Optional[Any] = None,
513
+ status: Optional[Tensor] = None,
514
+ jittable: bool = True,
515
+ ) -> Any:
516
+ r"""
517
+ Batched sampling from the circuit or final state.
518
+
519
+ :param batch: Number of samples. If `None`, returns a single draw.
520
+ :type batch: Optional[int]
521
+ :param allow_state: If `True`, sample from the final state (when memory allows). Prefer `True` for speed.
522
+ :type allow_state: bool
523
+ :param readout_error: Optional readout error model.
524
+ :type readout_error: Optional[Sequence[Any]]
525
+ :param format: Output format. See :py:meth:`tensorcircuit.quantum.measurement_results`.
526
+ Supported formats for qudits include:
527
+
528
+ "count_vector": # np.array([2, 0, 0, 0])
529
+
530
+ "count_dict_bin": # {"00": 2, "01": 0, "10": 0, "11": 0}
531
+ for cases :math:`d\in [11, 36]`, use 0-9A-Z digits (e.g., 'A' -> 10, ..., 'Z' -> 35);
532
+
533
+ :type format: Optional[str]
534
+ :param random_generator: random generator, defaults to None
535
+ :type random_generator: Optional[Any], optional
536
+ :param status: external randomness given by tensor uniformly from [0, 1],
537
+ if set, can overwrite random_generator, shape [batch] for `allow_state=True`
538
+ and shape [batch, nqudits] for `allow_state=False` using perfect sampling implementation
539
+ :type status: Optional[Tensor]
540
+ :param jittable: when converting to count, whether keep the full size. if false, may be conflict
541
+ external jit, if true, may fail for large scale system with actual limited count results
542
+ :type jittable: bool, defaults true
543
+ :return: List (if batch) of tuple (binary configuration tensor and corresponding probability)
544
+ if the format is None, and consistent with format when given
545
+ :rtype: Any
546
+ :raises NotImplementedError: For integer-based output formats not suitable for qudits.
547
+ """
548
+ if format in ["sample_int", "count_tuple", "count_dict_int", "count_vector"]:
549
+ raise NotImplementedError(
550
+ "`int` representation is not friendly for d-dimensional systems."
551
+ )
552
+ return self._circ.sample(
553
+ batch=batch,
554
+ allow_state=allow_state,
555
+ readout_error=readout_error,
556
+ format=format,
557
+ random_generator=random_generator,
558
+ status=status,
559
+ jittable=jittable,
560
+ )
561
+
562
+ def projected_subsystem(self, traceout: Tensor, left: Tuple[int, ...]) -> Tensor:
563
+ """
564
+ remaining wavefunction or density matrix on sites in `left`, while fixing the other
565
+ sites to given digits as indicated by `traceout`.
566
+
567
+ :param traceout: A tensor encoding digits (0..d-1) for sites to be fixed; jittable.
568
+ :type traceout: Tensor
569
+ :param left: Tuple of site indices to keep (non-jittable argument).
570
+ :type left: Tuple[int, ...]
571
+ :return: Remaining wavefunction or density matrix on the kept sites.
572
+ :rtype: Tensor
573
+ """
574
+ return self._circ.projected_subsystem(
575
+ traceout=traceout,
576
+ left=left,
577
+ )
578
+
579
+ def replace_inputs(self, inputs: Tensor) -> None:
580
+ """
581
+ Replace the input state with the circuit structure unchanged.
582
+
583
+ :param inputs: Input wavefunction.
584
+ :type inputs: Tensor
585
+ """
586
+ return self._circ.replace_inputs(inputs)
587
+
588
+ def mid_measurement(self, index: int, keep: int = 0) -> Tensor:
589
+ """
590
+ Mid-circuit Z-basis post-selection.
591
+
592
+ The returned state is **not normalized**; normalize manually if needed.
593
+
594
+ :param index: Qudit index where post-selection is applied.
595
+ :type index: int
596
+ :param keep: Post-selected digit in `{0, ..., d-1}`.
597
+ :type keep: int
598
+ :return: Unnormalized post-selected state.
599
+ :rtype: Tensor
600
+ """
601
+ return self._circ.mid_measurement(index, keep=keep)
602
+
603
+ mid_measure = mid_measurement
604
+ post_select = mid_measurement
605
+ post_selection = mid_measurement
606
+
607
+ def get_quvector(self) -> QuVector:
608
+ """
609
+ Get the representation of the output state in the form of ``QuVector``
610
+ while maintaining the circuit uncomputed
611
+
612
+ :return: ``QuVector`` representation of the output state from the circuit
613
+ :rtype: QuVector
614
+ """
615
+ return self._circ.quvector()
616
+
617
+ quvector = get_quvector
618
+
619
+ def replace_mps_inputs(self, mps_inputs: QuOperator) -> None:
620
+ """
621
+ Replace the input state (keeps circuit structure) using an MPS/MPO-like representation.
622
+
623
+ **Example**
624
+
625
+ >>> c = tc.QuditCircuit(2, dim=3)
626
+ >>> c.x(0)
627
+ >>> c2 = tc.QuditCircuit(2, dim=3, mps_inputs=c.quvector())
628
+ >>> c2.x(0); c2.wavefunction()
629
+ array([...], dtype=complex64)
630
+ >>> c3 = tc.QuditCircuit(2, dim=3)
631
+ >>> c3.x(0)
632
+ >>> c3.replace_mps_inputs(c.quvector()); c3.wavefunction()
633
+ array([...], dtype=complex64)
634
+
635
+ :param mps_inputs: (Nodes, dangling Edges) for a MPS like initial wavefunction.
636
+ :type mps_inputs: Tuple[Sequence[Gate], Sequence[Edge]]
637
+ """
638
+ return self._circ.replace_mps_inputs(mps_inputs)
639
+
640
+ def expectation_before(
641
+ self,
642
+ *ops: Tuple[tn.Node, List[int]],
643
+ reuse: bool = True,
644
+ **kws: Any,
645
+ ) -> List[tn.Node]:
646
+ """
647
+ Build (but do not contract) the tensor network for expectation evaluation.
648
+
649
+ :param reuse: _description_, defaults to True
650
+ :type reuse: bool, optional
651
+ :raises ValueError: _description_
652
+ :return: _description_
653
+ :rtype: List[tn.Node]
654
+ """
655
+ return self._circ.expectation_before(*ops, reuse=reuse, **kws)
656
+
657
+ def amplitude_before(self, l: Union[str, Tensor]) -> List[Gate]:
658
+ r"""
659
+ Return the (uncontracted) tensor network nodes for the amplitude of configuration `l`.
660
+
661
+ For state simulators, this corresponds to :math:`\langle l \vert \psi \rangle`.
662
+ For density-matrix simulators,
663
+ it would correspond to :math:`\operatorname{Tr}(\rho \vert l \rangle \langle l \vert)`.
664
+
665
+ :param l: Base-`d` string using `0-9A-Z` or an equivalent tensor index.
666
+ :type l: Union[str, Tensor]
667
+ :return: The tensornetwork nodes for the amplitude of the circuit.
668
+ :rtype: List[Gate]
669
+ """
670
+ return self._circ.amplitude_before(l)