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

tensorcircuit/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "1.3.0.dev20250728"
1
+ __version__ = "1.3.0.dev20250730"
2
2
  __author__ = "TensorCircuit Authors"
3
3
  __creator__ = "refraction-ray"
4
4
 
@@ -52,6 +52,7 @@ from . import compiler
52
52
  from . import cloud
53
53
  from . import fgs
54
54
  from .fgs import FGSSimulator
55
+ from . import timeevol
55
56
 
56
57
  FGSCircuit = FGSSimulator
57
58
 
@@ -700,6 +700,9 @@ class ExtendedBackend:
700
700
  "Backend '{}' has not implemented `is_tensor`.".format(self.name)
701
701
  )
702
702
 
703
+ def matvec(self: Any, A: Tensor, x: Tensor) -> Tensor:
704
+ return self.tensordot(A, x, axes=[[1], [0]])
705
+
703
706
  def cast(self: Any, a: Tensor, dtype: str) -> Tensor:
704
707
  """
705
708
  Cast the tensor dtype of a ``a``.
@@ -1386,9 +1389,21 @@ class ExtendedBackend:
1386
1389
  :rtype: Tensor
1387
1390
  """
1388
1391
  carry = init
1389
- for x in xs:
1390
- carry = f(carry, x)
1392
+ # Check if `xs` is a PyTree (tuple or list) of arrays.
1393
+ if isinstance(xs, (tuple, list)):
1394
+ for x_slice_tuple in zip(*xs):
1395
+ # x_slice_tuple will be (k_elems[i], j_elems[i]) at each step.
1396
+ carry = f(carry, x_slice_tuple)
1397
+ else:
1398
+ # If xs is a single array, iterate normally.
1399
+ for x in xs:
1400
+ carry = f(carry, x)
1401
+
1391
1402
  return carry
1403
+ # carry = init
1404
+ # for x in xs:
1405
+ # carry = f(carry, x)
1406
+ # return carry
1392
1407
 
1393
1408
  def stop_gradient(self: Any, a: Tensor) -> Tensor:
1394
1409
  """
@@ -200,6 +200,7 @@ class NumpyBackend(numpy_backend.NumPyBackend, ExtendedBackend): # type: ignore
200
200
  return softmax(a, axis=axis)
201
201
 
202
202
  def onehot(self, a: Tensor, num: int) -> Tensor:
203
+ a = np.asarray(a)
203
204
  res = np.eye(num)[a.reshape([-1])]
204
205
  return res.reshape(list(a.shape) + [num])
205
206
  # https://stackoverflow.com/questions/38592324/one-hot-encoding-using-numpy
@@ -678,7 +678,14 @@ class TensorFlowBackend(tensorflow_backend.TensorFlowBackend, ExtendedBackend):
678
678
  sp_a: Tensor,
679
679
  b: Tensor,
680
680
  ) -> Tensor:
681
- return tf.sparse.sparse_dense_matmul(sp_a, b)
681
+ is_vec = False
682
+ if len(b.shape) == 1:
683
+ b = self.reshape(b, [-1, 1])
684
+ is_vec = True
685
+ r = tf.sparse.sparse_dense_matmul(sp_a, b)
686
+ if is_vec:
687
+ return self.reshape(r, [-1])
688
+ return r
682
689
 
683
690
  def _densify(self) -> Tensor:
684
691
  @partial(self.jit, jit_compile=True)
@@ -712,7 +719,10 @@ class TensorFlowBackend(tensorflow_backend.TensorFlowBackend, ExtendedBackend):
712
719
  def scan(
713
720
  self, f: Callable[[Tensor, Tensor], Tensor], xs: Tensor, init: Tensor
714
721
  ) -> Tensor:
715
- return tf.scan(f, xs, init)[-1]
722
+ stacked_results = tf.scan(f, xs, init)
723
+ final_state = tf.nest.map_structure(lambda x: x[-1], stacked_results)
724
+ return final_state
725
+ # return tf.scan(f, xs, init)[-1]
716
726
 
717
727
  def device(self, a: Tensor) -> str:
718
728
  dev = a.device
@@ -2,14 +2,20 @@
2
2
  Experimental features
3
3
  """
4
4
 
5
+ # pylint: disable=unused-import
6
+
5
7
  from functools import partial
6
8
  import logging
7
9
  from typing import Any, Callable, Dict, Optional, Tuple, List, Sequence, Union
8
10
 
9
11
  import numpy as np
10
12
 
11
- from .cons import backend, dtypestr, contractor, rdtypestr, get_tn_info
13
+ from .cons import backend, dtypestr, rdtypestr, get_tn_info
12
14
  from .gates import Gate
15
+ from .timeevol import hamiltonian_evol, evol_global, evol_local
16
+
17
+
18
+ # for backward compatibility
13
19
 
14
20
  Tensor = Any
15
21
  Circuit = Any
@@ -435,157 +441,6 @@ def finite_difference_differentiator(
435
441
  return tf_function # type: ignore
436
442
 
437
443
 
438
- def hamiltonian_evol(
439
- tlist: Tensor,
440
- h: Tensor,
441
- psi0: Tensor,
442
- callback: Optional[Callable[..., Any]] = None,
443
- ) -> Tensor:
444
- """
445
- Fast implementation of time independent Hamiltonian evolution using eigendecomposition.
446
- By default, performs imaginary time evolution.
447
-
448
- :param tlist: Time points for evolution
449
- :type tlist: Tensor
450
- :param h: Time-independent Hamiltonian matrix
451
- :type h: Tensor
452
- :param psi0: Initial state vector
453
- :type psi0: Tensor
454
- :param callback: Optional function to process state at each time point
455
- :type callback: Optional[Callable[..., Any]], optional
456
- :return: Evolution results at each time point. If callback is None, returns state vectors;
457
- otherwise returns callback results
458
- :rtype: Tensor
459
-
460
- :Example:
461
-
462
- >>> import tensorcircuit as tc
463
- >>> import numpy as np
464
- >>> # Define a simple 2-qubit Hamiltonian
465
- >>> h = tc.array_to_tensor([
466
- ... [1.0, 0.0, 0.0, 0.0],
467
- ... [0.0, -1.0, 2.0, 0.0],
468
- ... [0.0, 2.0, -1.0, 0.0],
469
- ... [0.0, 0.0, 0.0, 1.0]
470
- ... ])
471
- >>> # Initial state |00⟩
472
- >>> psi0 = tc.array_to_tensor([1.0, 0.0, 0.0, 0.0])
473
- >>> # Evolution times
474
- >>> times = tc.array_to_tensor([0.0, 0.5, 1.0])
475
- >>> # Evolve and get states
476
- >>> states = tc.experimental.hamiltonian_evol(times, h, psi0)
477
- >>> print(states.shape) # (3, 4)
478
-
479
-
480
- Note:
481
- 1. The Hamiltonian must be time-independent
482
- 2. For time-dependent Hamiltonians, use ``evol_local`` or ``evol_global`` instead
483
- 3. The evolution is performed in imaginary time by default (factor -t in exponential)
484
- 4. The state is automatically normalized at each time point
485
- """
486
- es, u = backend.eigh(h)
487
- utpsi0 = backend.reshape(
488
- backend.transpose(u) @ backend.reshape(psi0, [-1, 1]), [-1]
489
- )
490
-
491
- @backend.jit
492
- def _evol(t: Tensor) -> Tensor:
493
- ebetah_utpsi0 = backend.exp(-t * es) * utpsi0
494
- psi_exact = backend.conj(u) @ backend.reshape(ebetah_utpsi0, [-1, 1])
495
- psi_exact = backend.reshape(psi_exact, [-1])
496
- psi_exact = psi_exact / backend.norm(psi_exact)
497
- if callback is None:
498
- return psi_exact
499
- return callback(psi_exact)
500
-
501
- return backend.stack([_evol(t) for t in tlist])
502
-
503
-
504
- def evol_local(
505
- c: Circuit,
506
- index: Sequence[int],
507
- h_fun: Callable[..., Tensor],
508
- t: float,
509
- *args: Any,
510
- **solver_kws: Any,
511
- ) -> Circuit:
512
- """
513
- ode evolution of time dependent Hamiltonian on circuit of given indices
514
- [only jax backend support for now]
515
-
516
- :param c: _description_
517
- :type c: Circuit
518
- :param index: qubit sites to evolve
519
- :type index: Sequence[int]
520
- :param h_fun: h_fun should return a dense Hamiltonian matrix
521
- with input arguments time and *args
522
- :type h_fun: Callable[..., Tensor]
523
- :param t: evolution time
524
- :type t: float
525
- :return: _description_
526
- :rtype: Circuit
527
- """
528
- from jax.experimental.ode import odeint
529
-
530
- s = c.state()
531
- n = c._nqubits
532
- l = len(index)
533
-
534
- def f(y: Tensor, t: Tensor, *args: Any) -> Tensor:
535
- y = backend.reshape2(y)
536
- y = Gate(y)
537
- h = -1.0j * h_fun(t, *args)
538
- h = backend.reshape2(h)
539
- h = Gate(h)
540
- edges = []
541
- for i in range(n):
542
- if i not in index:
543
- edges.append(y[i])
544
- else:
545
- j = index.index(i)
546
- edges.append(h[j])
547
- h[j + l] ^ y[i]
548
- y = contractor([y, h], output_edge_order=edges)
549
- return backend.reshape(y.tensor, [-1])
550
-
551
- ts = backend.stack([0.0, t])
552
- ts = backend.cast(ts, dtype=rdtypestr)
553
- s1 = odeint(f, s, ts, *args, **solver_kws)
554
- return type(c)(n, inputs=s1[-1])
555
-
556
-
557
- def evol_global(
558
- c: Circuit, h_fun: Callable[..., Tensor], t: float, *args: Any, **solver_kws: Any
559
- ) -> Circuit:
560
- """
561
- ode evolution of time dependent Hamiltonian on circuit of all qubits
562
- [only jax backend support for now]
563
-
564
- :param c: _description_
565
- :type c: Circuit
566
- :param h_fun: h_fun should return a **SPARSE** Hamiltonian matrix
567
- with input arguments time and *args
568
- :type h_fun: Callable[..., Tensor]
569
- :param t: _description_
570
- :type t: float
571
- :return: _description_
572
- :rtype: Circuit
573
- """
574
- from jax.experimental.ode import odeint
575
-
576
- s = c.state()
577
- n = c._nqubits
578
-
579
- def f(y: Tensor, t: Tensor, *args: Any) -> Tensor:
580
- h = -1.0j * h_fun(t, *args)
581
- return backend.sparse_dense_matmul(h, y)
582
-
583
- ts = backend.stack([0.0, t])
584
- ts = backend.cast(ts, dtype=rdtypestr)
585
- s1 = odeint(f, s, ts, *args, **solver_kws)
586
- return type(c)(n, inputs=s1[-1])
587
-
588
-
589
444
  def jax_jitted_function_save(filename: str, f: Callable[..., Any], *args: Any) -> None:
590
445
  """
591
446
  save a jitted jax function as a file