Skip to content

Commit e8cdd62

Browse files
update time evol api and add new examples
1 parent bb4405a commit e8cdd62

File tree

5 files changed

+571
-45
lines changed

5 files changed

+571
-45
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
- Fixed `scan` in tensorflow backend and numpy backend.
1414

15+
### Changed
16+
17+
- The order of arguments of `tc.timeevol.ed_evol` are changed for consistent interface with other evolution methods.
18+
1519
## v1.3.0
1620

1721
### Added

docs/source/advance.rst

Lines changed: 153 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ These methods are essential for studying quantum dynamics, particularly in many-
157157

158158
**Exact Diagonalization:**
159159

160-
For small systems where full diagonalization is feasible, the :py:meth:`tensorcircuit.timeevol.ed_evol` method provides exact time evolution by directly computing matrix exponentials:
160+
For small systems where full diagonalization is feasible, the :py:meth:`tensorcircuit.timeevol.ed_evol` method provides exact time evolution by directly computing matrix exponentials
161+
(alias :py:meth:`tensorcircuit.timeevol.hamiltonian_evol`):
161162

162163
.. code-block:: python
163164
164165
import tensorcircuit as tc
165166
166-
# Create Heisenberg Hamiltonian for a 4-site chain
167167
n = 4
168168
g = tc.templates.graphs.Line1D(n, pbc=False)
169169
h = tc.quantum.heisenberg_hamiltonian(g, hzz=1.0, hxx=1.0, hyy=1.0, sparse=False)
@@ -177,22 +177,30 @@ For small systems where full diagonalization is feasible, the :py:meth:`tensorci
177177
times = tc.backend.convert_to_tensor([0.0, 0.5, 1.0, 2.0])
178178
179179
# Evolve and get states
180-
states = tc.timeevol.ed_evol(times, h, psi0)
180+
states = tc.timeevol.ed_evol(h, psi0, times)
181+
print(states)
181182
182183
183184
def evolve_and_measure(params):
184185
# Parametrized Hamiltonian
185186
h_param = tc.quantum.heisenberg_hamiltonian(
186187
g, hzz=params[0], hxx=params[1], hyy=params[2], sparse=False
187188
)
188-
states = tc.timeevol.hamiltonian_evol(times, h_param, psi0)
189+
states = tc.timeevol.ed_evol(h_param, psi0, times)
189190
# Measure observable on final state
190191
circuit = tc.Circuit(n, inputs=states[-1])
191192
return tc.backend.real(circuit.expectation_ps(z=[0]))
192193
194+
evolve_and_measure(tc.backend.ones([3]))
195+
193196
This method is particularly efficient for time-independent Hamiltonians as it uses eigendecomposition to compute the evolution.
194197
It provides exact results but is limited to small systems (typically <16 qubits) due to the exponential growth of the Hilbert space.
195198

199+
.. note::
200+
201+
For real time evolution, the time should be chosen as ``times = 1.j * tc.backend.convert_to_tensor([0.0, 0.5, 1.0, 2.0])``
202+
203+
196204
**Krylov Subspace Methods:**
197205

198206
For larger systems where exact diagonalization becomes intractable, the Krylov subspace method provides an efficient approximation.
@@ -246,9 +254,147 @@ It supports both standard and scan-based jit-friendly implementations:
246254
**ODE-Based Evolution:**
247255

248256
For time-dependent Hamiltonians or when fine control over the evolution process is needed, TensorCircuit provides ODE-based evolution methods.
249-
These methods solve the time-dependent Schrödinger equation directly:
250-
the usage can be found at Analog circuit simulation section
257+
These methods solve the time-dependent Schrödinger equation directly by integrating the equation :math:`i\frac{d}{dt}|\psi(t)\rangle = H(t)|\psi(t)\rangle`.
258+
259+
TensorCircuit provides two ODE-based evolution methods depending on whether the Hamiltonian acts on the entire system or just a local subsystem:
260+
261+
1. **Global Evolution** (:py:meth:`tensorcircuit.timeevol.ode_evol_global`): For time-dependent Hamiltonians acting on the entire system. The Hamiltonian should be provided in sparse matrix format for efficiency.
262+
263+
.. code-block:: python
264+
265+
import tensorcircuit as tc
266+
from jax import jit, value_and_grad
267+
268+
# Set JAX backend for ODE support
269+
K = tc.set_backend("jax")
270+
271+
# H(t) = -∑ᵢ Jᵢ(t) ZᵢZᵢ₊₁ - ∑ᵢ hᵢ(t) Xᵢ
272+
273+
# Time-dependent coefficients
274+
def time_dep_J(t):
275+
return 1.0 + 0.5 * tc.backend.sin(2.0 * t)
276+
277+
def time_dep_h(t):
278+
return 0.5 * tc.backend.cos(1.5 * t)
279+
280+
zz_ham = tc.quantum.PauliStringSum2COO(
281+
[[3, 3, 0, 0], [0, 3, 3, 0], [0, 0, 3, 3]], [1, 1, 1]
282+
)
283+
x_ham = tc.quantum.PauliStringSum2COO(
284+
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], [1, 1, 1, 1]
285+
)
286+
287+
# Hamiltonian construction function
288+
def hamiltonian_func(t):
289+
# Create time-dependent ZZ terms
290+
zz_coeff = time_dep_J(t)
291+
292+
# Create time-dependent X terms
293+
x_coeff = time_dep_h(t)
294+
295+
return zz_coeff * zz_ham + x_coeff * x_ham
296+
297+
# Initial state: |↑↓↑↓⟩
298+
c = tc.Circuit(4)
299+
c.x([1, 3])
300+
psi0 = c.state()
301+
302+
# Time points for evolution
303+
times = tc.backend.arange(0, 5, 0.5)
304+
305+
# Perform global ODE evolution
306+
states = tc.timeevol.ode_evol_global(hamiltonian_func, psi0, times)
307+
assert tc.backend.shape_tuple(states) == (10, 16)
308+
309+
zz_ham = tc.quantum.PauliStringSum2COO([[3, 3, 0, 0], [0, 3, 3, 0]], [1, 1])
310+
x_ham = tc.quantum.PauliStringSum2COO([[1, 0, 0, 0], [0, 1, 0, 0]], [1, 1])
311+
312+
# Example with parameterized Hamiltonian and optimization
313+
def parametrized_hamiltonian(t, params):
314+
# params = [J0, J1, h0, h1] - parameters to optimize
315+
J_t = params[0] + params[1] * tc.backend.sin(2.0 * t)
316+
h_t = params[2] + params[3] * tc.backend.cos(1.5 * t)
317+
318+
return J_t * zz_ham + h_t * x_ham
319+
320+
# Observable function: measure ZZ correlation
321+
def zz_correlation(state):
322+
n = int(np.log2(state.shape[0]))
323+
circuit = tc.Circuit(n, inputs=state)
324+
return circuit.expectation_ps(z=[0, 1])
325+
326+
@tc.backend.jit
327+
@tc.backend.value_and_grad
328+
def objective_function(params):
329+
states = tc.timeevol.ode_evol_global(
330+
parametrized_hamiltonian,
331+
psi0,
332+
tc.backend.convert_to_tensor([0, 1.0]),
333+
None,
334+
params,
335+
)
336+
# Measure ZZ correlation at final time
337+
final_state = states[-1]
338+
return tc.backend.real(zz_correlation(final_state))
339+
340+
print(objective_function(tc.backend.ones([4])))
341+
342+
343+
344+
2. **Local Evolution** (:py:meth:`tensorcircuit.timeevol.ode_evol_local`): For time-dependent Hamiltonians acting on a subsystem of qubits. The Hamiltonian should be provided in dense matrix format.
345+
346+
.. code-block:: python
347+
348+
import tensorcircuit as tc
349+
import jax.numpy as jnp
350+
from jax import jit
351+
352+
# Set JAX backend for ODE support
353+
tc.set_backend("jax")
354+
K = tc.backend
355+
356+
# Time-dependent local Hamiltonian on qubits 1 and 2
357+
# H(t) = Ω(t) * (cos(φ(t)) * X + sin(φ(t)) * Y)
358+
def local_hamiltonian(t, Omega, phi):
359+
# Rabi oscillation Hamiltonian
360+
angle = phi * t
361+
coeff = Omega * jnp.cos(2.0 * t) # Amplitude modulation
362+
363+
# Single-qubit Rabi Hamiltonian (2x2 matrix)
364+
hx = coeff * jnp.cos(angle) * tc.gates.x().tensor
365+
hy = coeff * jnp.sin(angle) * tc.gates.y().tensor
366+
return hx + hy
367+
368+
# Initial state: GHZ state |0000⟩ + |1111⟩
369+
c = tc.Circuit(4)
370+
c.h(0)
371+
for i in range(3):
372+
c.cnot(i, i+1)
373+
psi0 = c.state()
374+
375+
times = tc.backend.arange(0.0, 3.0, 0.1)
376+
377+
# Evolve with local Hamiltonian acting on qubit 1
378+
states = tc.timeevol.ode_evol_local(
379+
local_hamiltonian,
380+
psi0,
381+
times,
382+
[1], # Apply to qubit 1
383+
None,
384+
1.0,
385+
2.0 # Omega=1.0, phi=2.0
386+
)
387+
388+
389+
Both ODE-based methods support automatic differentiation and JIT compilation when using the JAX backend, making them suitable for optimization tasks in quantum control and variational quantum algorithms.
390+
The methods integrate the time-dependent Schrödinger equation using JAX's ODE solvers, providing flexible and efficient simulation of quantum dynamics with time-dependent Hamiltonians.
391+
392+
.. note::
251393

394+
1. ODE-based methods currently only support the JAX backend due to the dependency on JAX's ODE solvers.
395+
2. Global evolution requires sparse Hamiltonian matrices for efficiency with large systems.
396+
3. Local evolution requires dense Hamiltonian matrices and is suitable for subsystems with few qubits.
397+
4. Both methods support callback functions to compute observables during evolution without storing all state vectors.
252398

253399
**Comparison of Time Evolution Methods:**
254400

@@ -266,7 +412,7 @@ the usage can be found at Analog circuit simulation section
266412

267413
**Method Selection Guidelines:**
268414

269-
1. **Exact diagonalization Evolution**: Best for small systems where exact results are required. Most efficient for time-independent Hamiltonians.
415+
1. **Exact diagonalization Evolution**: Best for small systems where exact results are required. Most efficient for time-independent Hamiltonians. Support imaginary time evolution.
270416

271417
2. **Krylov Evolution**: Ideal for large systems with time-independent Hamiltonians. Provides a good balance between accuracy and computational efficiency. The subspace dimension controls the trade-off between accuracy and speed.
272418

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
Comparison of different time evolution methods for the Heisenberg model
3+
4+
This example compares four different time evolution methods on an L=8 Heisenberg chain:
5+
1. Exact diagonalization (ed_evol)
6+
2. Krylov subspace method (krylov_evol)
7+
3. ODE-based global evolution (ode_evol_global)
8+
4. ODE-based local evolution (ode_evol_local)
9+
10+
The comparison is done for real-time evolution, and verifies that all methods
11+
produce consistent results.
12+
"""
13+
14+
import tensorcircuit as tc
15+
16+
# Set backend to JAX for ODE-based methods
17+
K = tc.set_backend("jax")
18+
tc.set_dtype("complex128")
19+
20+
21+
def create_heisenberg_hamiltonian(n, sparse=True):
22+
"""Create Heisenberg Hamiltonian for n sites."""
23+
g = tc.templates.graphs.Line1D(n, pbc=False)
24+
# Standard Heisenberg Hamiltonian: H = Σ (XX + YY + ZZ)
25+
h = tc.quantum.heisenberg_hamiltonian(g, hzz=1.0, hxx=1.0, hyy=1.0, sparse=sparse)
26+
return h
27+
28+
29+
def create_initial_state(n):
30+
"""Create initial Neel state |↑↓↑↓↑↓↑↓> for n sites."""
31+
c = tc.Circuit(n)
32+
# Apply X gates to odd sites to create Neel state
33+
c.x([i for i in range(1, n, 2)])
34+
return c.state()
35+
36+
37+
def fidelity(state1, state2):
38+
# Normalize states
39+
state1 = state1 / K.norm(state1)
40+
state2 = state2 / K.norm(state2)
41+
# Calculate inner product
42+
inner_product = K.sum(K.conj(state1) * state2)
43+
# Return fidelity
44+
return K.abs(inner_product) ** 2
45+
46+
47+
def time_evolution_comparison():
48+
"""Compare different time evolution methods for L=8 Heisenberg model."""
49+
n = 8 # Number of sites
50+
print(f"Comparing time evolution methods for L={n} Heisenberg chain")
51+
52+
# Create Hamiltonians - dense for ED and local ODE, sparse for others
53+
h_sparse = create_heisenberg_hamiltonian(n, sparse=True)
54+
h_dense = create_heisenberg_hamiltonian(n, sparse=False)
55+
print(f"Sparse Hamiltonian shape: {getattr(h_sparse, 'shape', 'N/A')}")
56+
print(f"Dense Hamiltonian shape: {h_dense.shape}")
57+
58+
# Create initial state (Neel state)
59+
psi0 = create_initial_state(n)
60+
print(f"Initial state shape: {psi0.shape}")
61+
62+
# Time points for evolution
63+
times = K.convert_to_tensor([0.0, 1.0, 3.0])
64+
# For real-time evolution, we need to multiply by -1j
65+
real_times = 1j * times
66+
67+
print("\n1. Exact diagonalization (ed_evol):")
68+
states_ed = tc.timeevol.ed_evol(h_dense, psi0, real_times)
69+
print(f"Final state shape: {states_ed.shape}")
70+
71+
print("\n2. Krylov subspace method (krylov_evol):")
72+
# Use a subspace dimension of 20
73+
states_krylov = tc.timeevol.krylov_evol(
74+
h_sparse, psi0, times, subspace_dimension=100
75+
)
76+
print(f"Final state shape: {states_krylov.shape}")
77+
78+
print("\n3. ODE-based global evolution (ode_evol_global):")
79+
80+
# Define time-dependent Hamiltonian function (constant in this case)
81+
def h_fun(t, *args):
82+
return h_sparse
83+
84+
# Evolve using ode_evol_global
85+
states_global = tc.timeevol.ode_evol_global(h_fun, psi0, times)
86+
print(f"Final state shape: {states_global.shape}")
87+
88+
print("\n4. ODE-based local evolution (ode_evol_local):")
89+
90+
# For comparison with local evolution, we use the dense Hamiltonian
91+
def h_fun_local(t, *args):
92+
return h_dense
93+
94+
# Index list for all sites
95+
index_list = list(range(n))
96+
97+
# Evolve using ode_evol_local
98+
states_local = tc.timeevol.ode_evol_local(h_fun_local, psi0, times, index_list)
99+
print(f"Final state shape: {states_local.shape}")
100+
101+
# Compare results using fidelity
102+
print("\nComparison of state fidelities:")
103+
print("Time\tED-Krylov\tED-Global\tED-Local")
104+
print("----\t---------\t---------\t--------")
105+
106+
tolerance = 1e-5 # Slightly relaxed tolerance
107+
print(f"\nConsistency check (tolerance = {tolerance}):")
108+
109+
for i, t in enumerate(times):
110+
# Calculate fidelity between states (|<psi1|psi2>|^2)
111+
112+
# Compare ED with Krylov
113+
fid_krylov = fidelity(states_ed[i], states_krylov[i])
114+
diff_krylov = abs(1.0 - fid_krylov)
115+
print(
116+
f"t={t.real}: |1 - F(ED, Krylov)| = {diff_krylov:.2e}",
117+
"✓" if diff_krylov < tolerance else "✗",
118+
)
119+
120+
# Compare ED with Global ODE
121+
fid_global = fidelity(states_ed[i], states_global[i])
122+
diff_global = abs(1.0 - fid_global)
123+
print(
124+
f"t={t.real}: |1 - F(ED, Global)| = {diff_global:.2e}",
125+
"✓" if diff_global < tolerance else "✗",
126+
)
127+
128+
# Compare ED with Local ODE
129+
fid_local = fidelity(states_ed[i], states_local[i])
130+
diff_local = abs(1.0 - fid_local)
131+
print(
132+
f"t={t.real}: |1 - F(ED, Local)| = {diff_local:.2e}",
133+
"✓" if diff_local < tolerance else "✗",
134+
)
135+
136+
return {
137+
"ed": states_ed,
138+
"krylov": states_krylov,
139+
"global_ode": states_global,
140+
"local_ode": states_local,
141+
}
142+
143+
144+
if __name__ == "__main__":
145+
results = time_evolution_comparison()
146+
print("\nTime evolution comparison completed!")

0 commit comments

Comments
 (0)