quantum-simulation-lab 1.2.2__tar.gz → 2.0.1__tar.gz
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.
- quantum_simulation_lab-2.0.1/CHANGELOG.md +125 -0
- quantum_simulation_lab-2.0.1/CONVENTIONS.md +440 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/PKG-INFO +91 -11
- quantum_simulation_lab-2.0.1/README.md +205 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/ROADMAP.md +19 -5
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/pyproject.toml +1 -1
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/algorithms/tebd.py +503 -27
- quantum_simulation_lab-2.0.1/tensor_network_library/requirements.txt +11 -0
- quantum_simulation_lab-2.0.1/tests/algorithms/test_ground_state_search.py +246 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/algorithms/test_tebd.py +79 -8
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/examples/test_tebd_physical_problem.py +2 -2
- quantum_simulation_lab-1.2.2/README.md +0 -125
- quantum_simulation_lab-1.2.2/tensor_network_library/requirements.txt +0 -3
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.gitattributes +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.github/workflows/publish.yml +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.github/workflows/tests.yml +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.github/workflows/update-loc.yml +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.gitignore +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/.vscode/settings.json +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/DIARY.md +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/LICENSE +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/dmrg_hamiltonians.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/results/H2_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/results/H2_energy_convergence.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/results/H2_itensors_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/results/H2_itensors_energy.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/run_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/run_dmrg_itensors.jl +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_x_field/test_run_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/results/H1_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/results/H1_energy_convergence.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/results/H1_itensors_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/results/H1_itensors_energy.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/run_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/run_dmrg_itensors.jl +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/random_z_field/test_run_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/results/H3_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/results/H3_energy_convergence.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/results/H3_itensors_convergence.csv +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/results/H3_itensors_energy.png +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/run_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/examples/zz_plus_z/run_dmrg_itensors.jl +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/algorithms/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/algorithms/dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/canonical.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/env.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/gate_application.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/gates.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/index.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/mpo.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/mps.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/policy.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/tensor.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/core/utils.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/hamiltonian/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/hamiltonian/models.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/hamiltonian/operators.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/states/__init__.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/states/entangled_states.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tensor_network_library/states/qubit_states.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/algorithms/test_dmrg.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_canonical.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_env.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_gate_application.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_gates.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_index.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_mpo.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_mps.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_policy.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_tensor.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/core/test_utils.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/examples/test_dmrg_hamiltonians.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/hamiltonian/test_hamiltonians.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/hamiltonian/test_operators.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/states/test_entnagled_states.py +0 -0
- {quantum_simulation_lab-1.2.2 → quantum_simulation_lab-2.0.1}/tests/states/test_qubit_states.py +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `quantum-simulation-lab` are documented here.
|
|
4
|
+
This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
_Changes that are merged to `main` but not yet tagged._
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [2.0.0] — 2026-06-06
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **`ground_state_search`** (`tebd.py`): high-level imaginary-time TEBD ground-state search.
|
|
18
|
+
Runs a first-order Trotter loop of `exp(-Δτ H)` steps, renormalises after every step, and
|
|
19
|
+
stops when `|E_n − E_{n−1}| < energy_tol`. Returns a `GroundStateResult` with the
|
|
20
|
+
converged MPS, full energy history, norm history, convergence flag, and step count.
|
|
21
|
+
- **`GroundStateResult`** dataclass: structured return type for `ground_state_search`.
|
|
22
|
+
- **`_left_canonicalize_inplace`** (internal helper): left-to-right QR sweep bringing any
|
|
23
|
+
MPS into left-canonical form in O(L χ² d). Used before every energy measurement in
|
|
24
|
+
`ground_state_search` to ensure Im(⟨H⟩) ≈ 0 regardless of the TEBD gauge.
|
|
25
|
+
- **`CONVENTIONS.md`** — tensor and index ordering documentation.
|
|
26
|
+
- **Second-order (Strang) Trotter splitting** (`finite_tebd_strang`).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **Spurious imaginary energy warning in `ground_state_search`**: after a TEBD sweep the MPS
|
|
30
|
+
is in a mixed gauge (S absorbed into the right tensor of the last updated bond). Calling
|
|
31
|
+
`measure_bond_energies` on this state produces non-identity transfer matrices, causing
|
|
32
|
+
Im(⟨H⟩) up to ~0.35 early in the evolution and firing the `> 1e-10` guard ~40 times per
|
|
33
|
+
test run. Fix: `ground_state_search` now QR-sweeps a *copy* of the MPS to left-canonical
|
|
34
|
+
form before every energy measurement; Im(⟨H⟩) drops to machine precision (~1e-14).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- `finite_tebd_imaginary` now accepts an optional `measure_fn: (MPS) -> float` callback for
|
|
38
|
+
in-loop energy tracking, populating `TEBDResult.energy_history`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## [1.2.2] — 2026-04-23
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- CI `publish.yml` heredoc scoping bug: `NEW_VERSION` shell variable was out of scope across `run:` steps; replaced with Actions expression syntax `${{ steps.version.outputs.version }}` substituted by the runner before shell execution.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## [1.2.1] — 2026-04-23
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- `ComplexWarning` in `utils.py` and `test_dmrg_hamiltonians.py` caused by implicit cast from complex to float in `np.vdot` calls; cast is now explicit.
|
|
53
|
+
- Flaky `test_strang_more_accurate_than_first_order` test: tightened dt and tolerance so second-order convergence is reliably distinguishable from first-order.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## [1.2.0] — 2026-04-23
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **Entangled-state helpers** (`tensor_network_library/states/entangled_states.py`):
|
|
61
|
+
- All four Bell states as statevectors and as MPS
|
|
62
|
+
- GHZ states for arbitrary `L` as statevectors and MPS
|
|
63
|
+
- W states for arbitrary `L` as statevectors and MPS
|
|
64
|
+
- Public re-exports via `tensor_network_library/__init__.py`
|
|
65
|
+
- **Two-site gate application** (`apply_two_site_gate`): in-place SVD-based gate on adjacent MPS sites with optional `TruncationPolicy`.
|
|
66
|
+
- **Gate builders**:
|
|
67
|
+
- `two_site_gate_from_hamiltonian(H, dt)` — real-time gate via exact diagonalisation of a 4×4 local Hamiltonian.
|
|
68
|
+
- `two_site_gate_imaginary(H, dt)` — imaginary-time gate (non-unitary) for ground-state preparation.
|
|
69
|
+
- **Finite TEBD** (`finite_tebd`): first-order Trotter time-stepper sweeping even/odd bond layers.
|
|
70
|
+
- **Imaginary-time TEBD** (`finite_tebd_imaginary`): Euclidean time evolution converging to the ground state; validated against DMRG energies for TFIM and Heisenberg.
|
|
71
|
+
- **`measure_local`**: single-site expectation values via efficient transfer-matrix sweep without forming the full statevector.
|
|
72
|
+
- **`TEBDConfig`**: dataclass for step count, truncation policy, and normalisation flag.
|
|
73
|
+
- **Transverse Heisenberg MPO builder** (`transverse_heisenberg_mpo`).
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- `MPS` constructors unified: `from_statevector`, `from_qubit_labels`, and product-state paths now all pass through a single canonicalisation routine.
|
|
77
|
+
- `TruncationPolicy` gains a `strict` flag; truncation errors are now returned from `apply_two_site_gate` for downstream inspection.
|
|
78
|
+
|
|
79
|
+
### Tests
|
|
80
|
+
- 47 new tests covering entangled states, gate builders, TEBD convergence, and imaginary-time evolution.
|
|
81
|
+
- Total test count: **362**.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## [1.0.4] — 2026-03-08
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
- Edge-case in right-to-left DMRG sweep: gauge was not restored after last site update, causing incorrect energies on subsequent sweeps for open boundary conditions with `L=4`.
|
|
89
|
+
- `MPO.to_dense()` memory layout was transposing physical indices for `L > 8`; now consistent with statevector qubit ordering.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## [1.0.3] — 2026-03-08
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
- `MPO.apply(mps)` — applies an MPO to an MPS and returns a new MPS, used as the basis for the DMRG effective Hamiltonian contraction.
|
|
97
|
+
- ZZ+Z random field Hamiltonian builder.
|
|
98
|
+
- Random X-field Hamiltonian builder.
|
|
99
|
+
|
|
100
|
+
### Fixed
|
|
101
|
+
- `Environment.update_left` / `update_right` were not normalising the boundary tensors, leading to numerical drift over many sweeps.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## [1.0.2] — 2026-03-08
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
- `heisenberg_mpo` and `xx_mpo` Hamiltonian builders for XXZ and isotropic XX models.
|
|
109
|
+
- Finite 2-site DMRG converges on Heisenberg and random-field models; energies match iTensor to `1e-8`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## [1.0.0] — 2026-03-04
|
|
114
|
+
|
|
115
|
+
### Added
|
|
116
|
+
- Initial release.
|
|
117
|
+
- `Tensor`, `Index` — numpy-backed tensors with named indices.
|
|
118
|
+
- `MPS` — product-state, statevector, and qubit-label constructors; left/right canonicalisation; SVD truncation via `TruncationPolicy`.
|
|
119
|
+
- `MPO` — identity construction and dense conversion.
|
|
120
|
+
- `Environment` — qubit chain and spin-1/2 bosonic chain; incremental left/right environment updates.
|
|
121
|
+
- `FiniteChain` geometry, `QubitSite` site type.
|
|
122
|
+
- `tfim_mpo` — transverse-field Ising model MPO.
|
|
123
|
+
- Finite 2-site DMRG (`finite_dmrg`) with `DMRGConfig`; converges on TFIM; energies match iTensor.
|
|
124
|
+
- GitHub Actions CI: pytest, LOC auto-badge, PyPI trusted publishing.
|
|
125
|
+
- 315 tests at initial release.
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# Conventions
|
|
2
|
+
|
|
3
|
+
> **Scope:** This document records every tensor-ordering, index-naming, sign, and algorithmic convention used in `quantum-simulation-lab`. It is the single source of truth. When code and this document disagree, fix the code.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Site and chain labelling](#1-site-and-chain-labelling)
|
|
10
|
+
2. [Tensor axis ordering](#2-tensor-axis-ordering)
|
|
11
|
+
3. [Index naming](#3-index-naming)
|
|
12
|
+
4. [Bond dimensions](#4-bond-dimensions)
|
|
13
|
+
5. [Canonicalisation gauge](#5-canonicalisation-gauge)
|
|
14
|
+
6. [SVD and singular-value absorption](#6-svd-and-singular-value-absorption)
|
|
15
|
+
7. [Gate ordering](#7-gate-ordering)
|
|
16
|
+
8. [MPO structure](#8-mpo-structure)
|
|
17
|
+
9. [Hamiltonian sign conventions](#9-hamiltonian-sign-conventions)
|
|
18
|
+
10. [Statevector qubit ordering](#10-statevector-qubit-ordering)
|
|
19
|
+
11. [DMRG conventions](#11-dmrg-conventions)
|
|
20
|
+
12. [TEBD conventions](#12-tebd-conventions)
|
|
21
|
+
13. [Imaginary-time evolution](#13-imaginary-time-evolution)
|
|
22
|
+
14. [Measurement conventions](#14-measurement-conventions)
|
|
23
|
+
15. [Dtype and precision](#15-dtype-and-precision)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Site and chain labelling
|
|
28
|
+
|
|
29
|
+
- Sites are labelled $0, 1, \ldots, L-1$ (zero-indexed, left to right).
|
|
30
|
+
- **Open boundary conditions (OBC)** are the default for all finite algorithms. Periodic boundaries are not yet implemented.
|
|
31
|
+
- Bond $b$ connects site $b-1$ to site $b$. Bond $0$ is the left virtual vacuum (dimension 1); bond $L$ is the right virtual vacuum (dimension 1).
|
|
32
|
+
- A chain of length $L$ has $L$ site tensors and $L+1$ bonds (including both boundary bonds of dimension 1).
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
bond_0 site_0 bond_1 site_1 bond_2 ... bond_{L-1} site_{L-1} bond_L
|
|
36
|
+
[1] ── [A_0] ── [χ_1] ── [A_1] ── [χ_2] ── ... ── [χ_{L-1}] ── [A_{L-1}] ── [1]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Tensor axis ordering
|
|
42
|
+
|
|
43
|
+
### MPS site tensor
|
|
44
|
+
|
|
45
|
+
Every site tensor $A_i$ is a rank-3 array with axes in this fixed order:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
A_i.data.shape == (χ_left, d_i, χ_right)
|
|
49
|
+
axis 0 axis 1 axis 2
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Axis | Role | Symbol |
|
|
53
|
+
|------|------|--------|
|
|
54
|
+
| 0 | Left virtual bond | $\chi_\text{left}$ |
|
|
55
|
+
| 1 | Physical (local Hilbert space) | $d_i$ |
|
|
56
|
+
| 2 | Right virtual bond | $\chi_\text{right}$ |
|
|
57
|
+
|
|
58
|
+
This matches the `_create_empty_tensors` ordering in `mps.py`:
|
|
59
|
+
```python
|
|
60
|
+
inds = [self.bonds[i], self.indices[i], self.bonds[i + 1]]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### MPO site tensor
|
|
64
|
+
|
|
65
|
+
Every MPO site tensor $W_i$ is rank-4:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
W_i.shape == (χ_left, d_i, d_i, χ_right)
|
|
69
|
+
axis 0 axis 1 axis 2 axis 3
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Axis | Role |
|
|
73
|
+
|------|------|
|
|
74
|
+
| 0 | Left MPO virtual bond |
|
|
75
|
+
| 1 | Physical ket (output) index |
|
|
76
|
+
| 2 | Physical bra (input) index |
|
|
77
|
+
| 3 | Right MPO virtual bond |
|
|
78
|
+
|
|
79
|
+
The ket index (axis 1) always comes before the bra index (axis 2).
|
|
80
|
+
|
|
81
|
+
### Two-site (theta) tensor
|
|
82
|
+
|
|
83
|
+
When two adjacent MPS tensors are contracted together during a gate application or DMRG update:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
theta[a, i, j, b] == sum_c A_i[a, i, c] * A_j[c, j, b]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Shape: `(χ_left, d_i, d_j, χ_right)` — left bond, left physical, right physical, right bond.
|
|
90
|
+
|
|
91
|
+
This is the ordering used in `gate_application.py`:
|
|
92
|
+
```python
|
|
93
|
+
theta = np.einsum("aic,cjb->aijb", A_i, A_j)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Gate tensor (rank-4)
|
|
97
|
+
|
|
98
|
+
A two-site gate acting on sites $(i, i+1)$ is stored as:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
U[i', j', i, j]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
where primed indices are **output (ket)** and unprimed are **input (bra)**. As a matrix it is flattened as `U.reshape(d*d, d*d)` with row index $(i', j')$ and column index $(i, j)$ — standard operator convention.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# gate_application.py applies it as:
|
|
108
|
+
theta_prime = np.einsum("mnij,aijb->amnb", U, theta)
|
|
109
|
+
# U[m=i', n=j', i, j] * theta[a, i, j, b]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 3. Index naming
|
|
115
|
+
|
|
116
|
+
`Index` objects carry a human-readable `name` and a frozen set of `tags`. The naming scheme:
|
|
117
|
+
|
|
118
|
+
| Index type | Name pattern | Tags |
|
|
119
|
+
|-----------|--------------|------|
|
|
120
|
+
| Physical index at site $i$ | `{mps_name}_phys_{i}` | `{"phys", "i={i}"}` |
|
|
121
|
+
| Bond between sites $i$ and $i+1$ | `{mps_name}_bond_{i+1}` | `{"bond", "b={i+1}"}` |
|
|
122
|
+
| Left boundary bond | `{mps_name}_bond_0` | `{"bond", "b=0"}` |
|
|
123
|
+
| Right boundary bond | `{mps_name}_bond_{L}` | `{"bond", "b={L}"}` |
|
|
124
|
+
|
|
125
|
+
Bond index $b$ sits between site $b-1$ and site $b$, so `bond_1` is shared between `tensors[0]` (as its `axis 2`) and `tensors[1]` (as its `axis 0`).
|
|
126
|
+
|
|
127
|
+
After a gate application, the shared bond between site $i$ and $i+1$ is recreated with the same name and tags as the original `bonds[i+1]` but with the updated dimension `chi_new`.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 4. Bond dimensions
|
|
132
|
+
|
|
133
|
+
- `mps.bonds` is a list of `Index` objects of length $L+1$. `bonds[b].dim` gives $\chi_b$.
|
|
134
|
+
- `mps._bond_dims` mirrors this as a plain `List[int]` for fast arithmetic.
|
|
135
|
+
- Boundary bonds: `bonds[0].dim == bonds[L].dim == 1` always.
|
|
136
|
+
- **Default bond policy** (`bond_policy="default"`): $\chi_b = \min\!\left(\prod_{k<b} d_k,\; \prod_{k \geq b} d_k\right)$, optionally capped by `TruncationPolicy.max_bond_dim`.
|
|
137
|
+
- **Uniform bond policy** (`bond_policy="uniform"`): all interior bonds set to `truncation.max_bond_dim`; boundary bonds remain 1.
|
|
138
|
+
|
|
139
|
+
### TruncationPolicy
|
|
140
|
+
|
|
141
|
+
`TruncationPolicy` (in `core/policy.py`) controls SVD truncation in both `from_statevector` and `apply_two_site_gate`:
|
|
142
|
+
|
|
143
|
+
- `max_bond_dim`: hard cap on $\chi$ after SVD.
|
|
144
|
+
- `svd_cutoff`: singular values $\sigma < \text{cutoff}$ are discarded (default `1e-12`).
|
|
145
|
+
- `strict`: if `True`, raises on truncation error above threshold (default `False`).
|
|
146
|
+
|
|
147
|
+
At least one singular value is always kept, even if all fall below the cutoff.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 5. Canonicalisation gauge
|
|
152
|
+
|
|
153
|
+
### Left-canonical tensor
|
|
154
|
+
|
|
155
|
+
$A_i$ is **left-canonical** if:
|
|
156
|
+
|
|
157
|
+
$$\sum_{a,\sigma} (A_i)^*_{a\sigma c}\,(A_i)_{a\sigma c'} = \delta_{cc'}$$
|
|
158
|
+
|
|
159
|
+
i.e. `A_i.reshape(χ_left * d, χ_right)` has orthonormal columns.
|
|
160
|
+
|
|
161
|
+
### Right-canonical tensor
|
|
162
|
+
|
|
163
|
+
$B_i$ is **right-canonical** if:
|
|
164
|
+
|
|
165
|
+
$$\sum_{\sigma,c} (B_i)^*_{a\sigma c}\,(B_i)_{a'\sigma c} = \delta_{aa'}$$
|
|
166
|
+
|
|
167
|
+
i.e. `B_i.reshape(χ_left, d * χ_right)` has orthonormal rows.
|
|
168
|
+
|
|
169
|
+
### Mixed-canonical (site-canonical) form
|
|
170
|
+
|
|
171
|
+
An MPS is in **mixed-canonical form centred at site $k$** when:
|
|
172
|
+
- Sites $0, \ldots, k-1$ are left-canonical.
|
|
173
|
+
- Sites $k+1, \ldots, L-1$ are right-canonical.
|
|
174
|
+
- Site $k$ is unconstrained (carries all norm).
|
|
175
|
+
|
|
176
|
+
This is the standard gauge used by the DMRG local update: the effective Hamiltonian at site $k$ is orthogonally projected by the left/right environments.
|
|
177
|
+
|
|
178
|
+
### Canonicalisation in `canonical.py`
|
|
179
|
+
|
|
180
|
+
`left_canonicalize(mps, site)` and `right_canonicalize(mps, site)` sweep QR decompositions across the chain. Singular values from QR are absorbed **rightward** during left-sweep, **leftward** during right-sweep. The norm is concentrated at the orthogonality centre.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6. SVD and singular-value absorption
|
|
185
|
+
|
|
186
|
+
When splitting a two-site tensor $\Theta$ via SVD:
|
|
187
|
+
|
|
188
|
+
$$\Theta = U\, S\, V^\dagger$$
|
|
189
|
+
|
|
190
|
+
the singular values $S$ must be absorbed into one of the two new tensors. The `absorb` parameter controls this:
|
|
191
|
+
|
|
192
|
+
| `absorb` value | Left tensor $A'$ | Right tensor $B'$ | Use case |
|
|
193
|
+
|---------------|-----------------|------------------|----------|
|
|
194
|
+
| `"right"` (default) | $U$ | $S V^\dagger$ | Left-sweep; keeps $A'$ left-canonical |
|
|
195
|
+
| `"left"` | $U S$ | $V^\dagger$ | Right-sweep; keeps $B'$ right-canonical |
|
|
196
|
+
| `"both"` | $U \sqrt{S}$ | $\sqrt{S} V^\dagger$ | Symmetric split; neither tensor is canonical |
|
|
197
|
+
|
|
198
|
+
The default in `apply_two_site_gate` is `absorb="right"` — appropriate for a left-to-right TEBD sweep where $A'$ should be left-isometric before the next bond update.
|
|
199
|
+
|
|
200
|
+
In `from_statevector`, the default is `absorb="right"` as well, producing a right-canonical MPS after the full left-to-right SVD sweep (with the last site holding the norm).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 7. Gate ordering
|
|
205
|
+
|
|
206
|
+
### Two-site gate matrix convention
|
|
207
|
+
|
|
208
|
+
A two-site gate $U$ on sites $(i, i+1)$ with local dimension $d$ is stored as a $(d^2 \times d^2)$ matrix or equivalently a $(d, d, d, d)$ rank-4 tensor. The index ordering is:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
U[i', j', i, j] — output-left, output-right, input-left, input-right
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
As a matrix: rows index the **output** basis $(i' d + j')$, columns index the **input** basis $(i\,d + j)$.
|
|
215
|
+
|
|
216
|
+
**Example — CNOT with $d=2$, control = left site:**
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
U_CNOT[i', j', i, j]:
|
|
220
|
+
i'=i (control unchanged)
|
|
221
|
+
j'=j XOR i (target flipped if control=1)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Real-time gate from Hamiltonian
|
|
225
|
+
|
|
226
|
+
`two_site_gate_from_hamiltonian(H, dt)` computes:
|
|
227
|
+
|
|
228
|
+
$$U = e^{-i\,dt\,H}$$
|
|
229
|
+
|
|
230
|
+
where $H$ is a $(d^2 \times d^2)$ Hermitian matrix for a local two-site interaction. The result is reshaped to `(d, d, d, d)` with the `(i', j', i, j)` ordering above.
|
|
231
|
+
|
|
232
|
+
### Imaginary-time gate
|
|
233
|
+
|
|
234
|
+
`two_site_gate_imaginary(H, dt)` computes:
|
|
235
|
+
|
|
236
|
+
$$G = e^{-\tau\,H}, \qquad \tau = dt > 0$$
|
|
237
|
+
|
|
238
|
+
This is **not unitary**. After applying $G$ the MPS must be renormalised (handled automatically by `finite_tebd_imaginary` via `TEBDConfig(normalize=True)`).
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 8. MPO structure
|
|
243
|
+
|
|
244
|
+
### Virtual bond ordering (MPO row/column)
|
|
245
|
+
|
|
246
|
+
The MPO virtual bond for a standard two-body Hamiltonian is arranged as:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
W_i = [ I 0 0 ]
|
|
250
|
+
[ O_L 0 0 ]
|
|
251
|
+
[ h O_R I ]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
where the first row/column corresponds to the **left boundary** (passes the identity through from the left) and the last row/column corresponds to the **right boundary** (accumulates the completed term). This is the standard "zipper" or "finite-state machine" MPO construction.
|
|
255
|
+
|
|
256
|
+
For the TFIM and Heisenberg MPOs in `hamiltonian/`, the MPO bond dimension is:
|
|
257
|
+
- TFIM: $\chi_W = 3$
|
|
258
|
+
- Heisenberg / XXZ: $\chi_W = 5$
|
|
259
|
+
|
|
260
|
+
### `MPO.apply(mps)`
|
|
261
|
+
|
|
262
|
+
Contracts the MPO with the MPS to produce a new MPS. The result has physical indices in the same ordering as the input MPS. Bond dimensions of the output MPS grow as $\chi_\text{out} = \chi_\text{MPS} \times \chi_W$ before any compression.
|
|
263
|
+
|
|
264
|
+
### `MPO.to_dense()`
|
|
265
|
+
|
|
266
|
+
Converts the MPO to a dense $(d^L \times d^L)$ matrix. The row index is the **ket** (output) multi-index and the column index is the **bra** (input) multi-index, both in **little-endian qubit order** (site 0 is the least significant bit). This matches `MPS.to_dense()` so that `mpo.to_dense() @ mps.to_dense()` gives the correct action.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 9. Hamiltonian sign conventions
|
|
271
|
+
|
|
272
|
+
All Hamiltonians are written so that the **ground state energy is negative** for ferromagnetic/antiferromagnetic ordering in their standard parameter regime.
|
|
273
|
+
|
|
274
|
+
### TFIM
|
|
275
|
+
|
|
276
|
+
$$H_\text{TFIM} = -J \sum_i Z_i Z_{i+1} - h \sum_i X_i$$
|
|
277
|
+
|
|
278
|
+
- $J > 0$: ferromagnetic coupling.
|
|
279
|
+
- $h > 0$: transverse field.
|
|
280
|
+
- Critical point at $h/J = 1$ for the infinite chain.
|
|
281
|
+
|
|
282
|
+
### Heisenberg (XXX)
|
|
283
|
+
|
|
284
|
+
$$H_\text{Heis} = J \sum_i \left(X_i X_{i+1} + Y_i Y_{i+1} + Z_i Z_{i+1}\right)$$
|
|
285
|
+
|
|
286
|
+
- $J > 0$: antiferromagnetic. Ground state energy per bond $\approx -0.4431$ for the infinite chain.
|
|
287
|
+
- $J < 0$: ferromagnetic.
|
|
288
|
+
|
|
289
|
+
### XX model
|
|
290
|
+
|
|
291
|
+
$$H_\text{XX} = J \sum_i \left(X_i X_{i+1} + Y_i Y_{i+1}\right)$$
|
|
292
|
+
|
|
293
|
+
### Pauli matrix convention
|
|
294
|
+
|
|
295
|
+
We use the standard Pauli matrices (not $\hbar/2$ normalised):
|
|
296
|
+
|
|
297
|
+
$$X = \begin{pmatrix}0&1\\1&0\end{pmatrix}, \quad Y = \begin{pmatrix}0&-i\\i&0\end{pmatrix}, \quad Z = \begin{pmatrix}1&0\\0&-1\end{pmatrix}$$
|
|
298
|
+
|
|
299
|
+
The computational basis is $|0\rangle = (1,0)^T$ (spin-up, $+Z$ eigenstate) and $|1\rangle = (0,1)^T$ (spin-down, $-Z$ eigenstate).
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 10. Statevector qubit ordering
|
|
304
|
+
|
|
305
|
+
`MPS.to_dense()` returns a $d^L$-dimensional statevector. The multi-index is **big-endian**: site 0 is the **most significant** position.
|
|
306
|
+
|
|
307
|
+
For $L=3$, $d=2$:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
index k = σ_0 * 4 + σ_1 * 2 + σ_2 * 1
|
|
311
|
+
psi[k] = <σ_0 σ_1 σ_2 | ψ>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
So `psi[0]` = $\langle 000|\psi\rangle$, `psi[1]` = $\langle 001|\psi\rangle$, `psi[4]` = $\langle 100|\psi\rangle$, etc.
|
|
315
|
+
|
|
316
|
+
`MPO.to_dense()` follows the same convention so that matrix-vector products are consistent.
|
|
317
|
+
|
|
318
|
+
> **Watch out:** some quantum computing libraries (e.g. Qiskit) use little-endian (site 0 = least significant). When comparing with those libraries, reverse the qubit order.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 11. DMRG conventions
|
|
323
|
+
|
|
324
|
+
### Two-site update
|
|
325
|
+
|
|
326
|
+
The finite DMRG implemented here uses the **two-site update** at each step:
|
|
327
|
+
1. Form the effective Hamiltonian $H_\text{eff}$ for sites $(i, i+1)$ from left/right environments and the MPO.
|
|
328
|
+
2. Diagonalise $H_\text{eff}$ (via `scipy.sparse.linalg.eigsh` with `which="SA"`) to get the ground-state two-site tensor $\Theta$.
|
|
329
|
+
3. SVD-split $\Theta$ into $A_i$ and $A_{i+1}$ with optional truncation.
|
|
330
|
+
4. Absorb singular values to the **right** on the left-to-right sweep, and to the **left** on the right-to-left sweep, maintaining mixed-canonical form.
|
|
331
|
+
|
|
332
|
+
### Sweep direction
|
|
333
|
+
|
|
334
|
+
A full DMRG sweep is: left-to-right (sites $0 \to L-2$) then right-to-left (sites $L-2 \to 0$). One full sweep = one DMRG iteration. Convergence is checked by comparing the energy after each full sweep.
|
|
335
|
+
|
|
336
|
+
### Environment update
|
|
337
|
+
|
|
338
|
+
Left environment $L_i$ and right environment $R_i$ are updated incrementally:
|
|
339
|
+
|
|
340
|
+
$$L_{i+1}[a', a] = \sum_{\alpha', \alpha, \sigma} A^*_i[\alpha', \sigma, a']\; W_i[\alpha', \sigma', \sigma, \alpha]\; A_i[\alpha, \sigma, a]\; L_i[\alpha', \alpha]$$
|
|
341
|
+
|
|
342
|
+
(and analogously for right environments). This is the standard MPS-MPO-MPS contraction. Environments are stored as rank-3 tensors with shape `(χ_mps, χ_mpo, χ_mps)`.
|
|
343
|
+
|
|
344
|
+
### DMRGConfig
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
@dataclass
|
|
348
|
+
class DMRGConfig:
|
|
349
|
+
n_sweeps: int # number of full sweeps
|
|
350
|
+
max_bond_dim: int # maximum bond dimension χ
|
|
351
|
+
svd_cutoff: float # singular-value cutoff (default 1e-10)
|
|
352
|
+
tol: float # energy convergence tolerance (default 1e-8)
|
|
353
|
+
verbose: bool # print energy per sweep (default False)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 12. TEBD conventions
|
|
359
|
+
|
|
360
|
+
### Trotter decomposition
|
|
361
|
+
|
|
362
|
+
**First-order (Lie–Trotter):**
|
|
363
|
+
|
|
364
|
+
$$e^{-i\,dt\,H} \approx \prod_{\langle i,i+1\rangle \in \text{even}} e^{-i\,dt\,h_{i,i+1}} \cdot \prod_{\langle i,i+1\rangle \in \text{odd}} e^{-i\,dt\,h_{i,i+1}} + O(dt^2)$$
|
|
365
|
+
|
|
366
|
+
Even bonds: $(0,1), (2,3), \ldots$ — applied first.
|
|
367
|
+
Odd bonds: $(1,2), (3,4), \ldots$ — applied second.
|
|
368
|
+
|
|
369
|
+
**Second-order (Strang / Leapfrog):**
|
|
370
|
+
|
|
371
|
+
$$e^{-i\,dt\,H} \approx \prod_\text{even} e^{-i\,\frac{dt}{2}\,h} \cdot \prod_\text{odd} e^{-i\,dt\,h} \cdot \prod_\text{even} e^{-i\,\frac{dt}{2}\,h} + O(dt^3)$$
|
|
372
|
+
|
|
373
|
+
`finite_tebd_strang` implements this: half-step even gates → full odd gates → half-step even gates per time step.
|
|
374
|
+
|
|
375
|
+
### Gate layer ordering
|
|
376
|
+
|
|
377
|
+
Within each layer, gates are applied **left to right** (bond 0 first, then bond 2, then bond 4, … for even; bond 1, then bond 3, … for odd). This is purely a sweep convention and has no physical significance for commuting gates within a layer.
|
|
378
|
+
|
|
379
|
+
### TEBDConfig
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
@dataclass
|
|
383
|
+
class TEBDConfig:
|
|
384
|
+
n_steps: int # number of Trotter steps
|
|
385
|
+
truncation: TruncationPolicy | None # SVD truncation per gate application
|
|
386
|
+
normalize: bool # renormalize MPS after each full step
|
|
387
|
+
verbose: bool # print norm/energy per step
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 13. Imaginary-time evolution
|
|
393
|
+
|
|
394
|
+
Imaginary-time TEBD (`finite_tebd_imaginary`) evolves under:
|
|
395
|
+
|
|
396
|
+
$$|\psi(\tau)\rangle = e^{-\tau H}|\psi(0)\rangle \Big/ \|e^{-\tau H}|\psi(0)\rangle\|$$
|
|
397
|
+
|
|
398
|
+
with $\tau = n\_steps \times dt$. The gates are **non-unitary** ($G = e^{-dt\,h_{ij}}$, real positive spectrum for $h_{ij}$ positive semi-definite). After each full Trotter step the MPS is renormalised so the norm does not underflow.
|
|
399
|
+
|
|
400
|
+
**Ground-state preparation recipe:**
|
|
401
|
+
|
|
402
|
+
1. Start from a random or product MPS that is not orthogonal to the ground state.
|
|
403
|
+
2. Choose $dt$ small enough that the first-order Trotter error is acceptable (typically $dt \in [0.01, 0.1]$).
|
|
404
|
+
3. Run for enough steps that $\langle E \rangle$ converges to within tolerance.
|
|
405
|
+
4. Cross-check final energy against DMRG.
|
|
406
|
+
|
|
407
|
+
The imaginary-time gates are built by `two_site_gate_imaginary(H_local, dt)` which computes $e^{-dt H_\text{local}}$ via `scipy.linalg.expm` (exact for the small $d^2 \times d^2$ matrix).
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## 14. Measurement conventions
|
|
412
|
+
|
|
413
|
+
### `measure_local(mps, operators)`
|
|
414
|
+
|
|
415
|
+
Computes single-site expectation values $\langle \psi | O_i | \psi \rangle$ for each site $i$ via a left–right transfer-matrix sweep:
|
|
416
|
+
|
|
417
|
+
1. Build left environments $\{L_i\}$ by contracting MPS tensors left to right.
|
|
418
|
+
2. For each site $i$, insert $O_i$ and contract with the matching right environment $R_i$.
|
|
419
|
+
3. Normalise by $\langle\psi|\psi\rangle$ (computed from the same sweep).
|
|
420
|
+
4. Return `float(Re(...))` — imaginary parts are discarded after a warning if $|\text{Im}| > 10^{-10}$.
|
|
421
|
+
|
|
422
|
+
`operators` is a `dict[int, np.ndarray]` mapping site index to a $(d \times d)$ operator matrix. Sites not in the dict are skipped.
|
|
423
|
+
|
|
424
|
+
### Return type
|
|
425
|
+
|
|
426
|
+
`measure_local` returns `dict[int, float]` — one real number per queried site.
|
|
427
|
+
|
|
428
|
+
### Operator convention
|
|
429
|
+
|
|
430
|
+
Operator matrices follow the same basis as the Pauli matrices in §9: row = ket (output), column = bra (input), with $|0\rangle = (1,0)^T$.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 15. Dtype and precision
|
|
435
|
+
|
|
436
|
+
- Default dtype throughout the library: `np.complex128`.
|
|
437
|
+
- All internal contractions are performed in `complex128` even when the state is known to be real, to avoid silent cast errors.
|
|
438
|
+
- When computing norms or energies (real scalars), the result is explicitly cast via `float(np.real(...))` — never `float(complex_value)` directly, which raises `ComplexWarning`.
|
|
439
|
+
- Singular values are always real and non-negative; they are stored as `float64` arrays.
|
|
440
|
+
- `TruncationPolicy.svd_cutoff` defaults to `1e-12` — one order of magnitude below `complex128` machine epsilon ($\approx 2.2 \times 10^{-16}$) relative to the largest singular value.
|