Skip to content

Commit 040b6d5

Browse files
committed
Add dense Haar random state helper
1 parent adc0b05 commit 040b6d5

9 files changed

Lines changed: 146 additions & 4 deletions

File tree

docs/api/package.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Top-level convenience exports.
44

55
```{eval-rst}
6+
.. autofunction:: pepsy.haar_random_state
67
.. autofunction:: pepsy.tn_fidelity
78
.. autofunction:: pepsy.tn_norm
89
```

docs/development/package_layout.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Use the clearer namespaces for submodule imports:
2828
from pepsy.boundary import BdyMPS, contract_boundary
2929
from pepsy.optimizers import SweepOptimizer, MpsOptimizer
3030
from pepsy.operators import rx, rzz, gate
31-
from pepsy.tensors import ps_to_peps, ps_to_mps
31+
from pepsy.tensors import haar_random_state, ps_to_peps, ps_to_mps
3232
```
3333

3434
When a leaf module is needed, import the new implementation path directly:

src/pepsy/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"build_optimizer": ".tensors",
105105
"contract_hypercompressed_tn": ".tensors",
106106
"expec_mpo": ".tensors",
107+
"haar_random_state": ".tensors",
107108
"hrps_to_mps": ".tensors",
108109
"hrps_to_peps": ".tensors",
109110
"id_to_mpo": ".tensors",
@@ -200,6 +201,7 @@
200201
"FDSolver",
201202
"OneDMap",
202203
"expec_mpo",
204+
"haar_random_state",
203205
"hrps_to_mps",
204206
"hrps_to_peps",
205207
"id_to_mpo",
@@ -297,6 +299,7 @@ def __getattr__(name):
297299
from .tensors import (
298300
OneDMap,
299301
expec_mpo,
302+
haar_random_state,
300303
hrps_to_mps,
301304
hrps_to_peps,
302305
id_to_mpo,

src/pepsy/tensors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
expec_mpo,
1717
get_default_array_backend,
1818
get_default_grad_backend,
19+
haar_random_state,
1920
hrps_to_mps,
2021
hrps_to_peps,
2122
id_to_mpo,
@@ -52,6 +53,7 @@
5253
"expec_mpo",
5354
"get_default_array_backend",
5455
"get_default_grad_backend",
56+
"haar_random_state",
5557
"hrps_to_mps",
5658
"hrps_to_peps",
5759
"id_to_mpo",

src/pepsy/tensors/constructors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .core import (
44
add_cycle,
5+
haar_random_state,
56
hrps_to_mps,
67
hrps_to_peps,
78
id_to_mpo,
@@ -15,6 +16,7 @@
1516

1617
__all__ = [
1718
"add_cycle",
19+
"haar_random_state",
1820
"hrps_to_mps",
1921
"hrps_to_peps",
2022
"id_to_mpo",

src/pepsy/tensors/core.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Shared DMRG backend, optimizer, and fidelity helpers."""
22

33
import math
4+
import warnings
45
from numbers import Integral
56
from string import Formatter
67
from typing import Any
@@ -44,6 +45,7 @@
4445
"ps_to_mps",
4546
"ps_to_pepo",
4647
"ps_to_mpo",
48+
"haar_random_state",
4749
"random_haar_qubit",
4850
"hrps_to_peps",
4951
"hrps_to_mps",
@@ -1847,6 +1849,74 @@ def random_haar_qubit(seed=None, perturb=0.0):
18471849
return float(theta), float(phi)
18481850

18491851

1852+
def haar_random_state(
1853+
L: int,
1854+
dtype: str = "complex128",
1855+
seed=None,
1856+
L_max: int = 20,
1857+
as_tensor: bool = False,
1858+
):
1859+
"""Create a dense Haar-random ``L``-qubit state.
1860+
1861+
This samples a full Hilbert-space state, so the result is generally
1862+
entangled. Unlike :func:`hrps_to_mps`, this is not a product-state tensor
1863+
network: it returns dense amplitudes with ``2**L`` entries.
1864+
1865+
Parameters
1866+
----------
1867+
L : int
1868+
Number of qubits.
1869+
dtype : str, optional
1870+
Complex numpy dtype for the returned amplitudes.
1871+
seed : int | None, optional
1872+
Seed for deterministic samples.
1873+
L_max : int, optional
1874+
Maximum allowed number of qubits. Values above 20 are capped to 20
1875+
with a warning because this helper constructs a dense state.
1876+
as_tensor : bool, optional
1877+
If True, return the amplitudes reshaped as a dense tensor with shape
1878+
``(2,) * L``. Otherwise return a dense vector with shape ``(2**L,)``.
1879+
1880+
Returns
1881+
-------
1882+
numpy.ndarray
1883+
Normalized dense Haar-random state amplitudes.
1884+
"""
1885+
if not isinstance(L, Integral) or L < 0:
1886+
raise ValueError("L must be a non-negative integer.")
1887+
if not isinstance(L_max, Integral) or L_max < 0:
1888+
raise ValueError("L_max must be a non-negative integer.")
1889+
if L_max > 20:
1890+
warnings.warn(
1891+
"haar_random_state constructs dense entangled states and is "
1892+
"intended for L <= 20; capping L_max to 20.",
1893+
UserWarning,
1894+
stacklevel=2,
1895+
)
1896+
L_max = 20
1897+
if L > L_max:
1898+
raise ValueError(
1899+
"haar_random_state constructs a dense entangled state and only "
1900+
f"supports L <= L_max (got L={L}, L_max={L_max})."
1901+
)
1902+
1903+
dtype = np.dtype(dtype)
1904+
if dtype.kind != "c":
1905+
raise TypeError("dtype must be a complex numpy dtype.")
1906+
1907+
real_dtype = np.float32 if dtype == np.dtype("complex64") else np.float64
1908+
rng = np.random.default_rng(seed)
1909+
dim = 2 ** int(L)
1910+
state = rng.normal(size=dim).astype(real_dtype)
1911+
state = state + 1j * rng.normal(size=dim).astype(real_dtype)
1912+
state = state.astype(dtype, copy=False)
1913+
state /= np.linalg.norm(state)
1914+
1915+
if as_tensor:
1916+
return state.reshape((2,) * int(L))
1917+
return state
1918+
1919+
18501920
def hrps_to_peps(
18511921
Lx: int,
18521922
Ly: int,

tests/test_package_layout.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
from pepsy.optimizers import MpsOptimizer, SweepOptimizer
1010
from pepsy.sampling import MpsSampler, PepsBpSampler
1111
from pepsy.solvers import FDSolver
12-
from pepsy.tensors import OneDMap, backend_torch, ps_to_peps, reg_complex_svd_torch
12+
from pepsy.tensors import (
13+
OneDMap,
14+
backend_torch,
15+
haar_random_state,
16+
ps_to_peps,
17+
reg_complex_svd_torch,
18+
)
1319

1420

1521
def test_new_namespace_imports_resolve():
@@ -25,6 +31,7 @@ def test_new_namespace_imports_resolve():
2531
assert FDSolver is not None
2632
assert OneDMap is not None
2733
assert callable(backend_torch)
34+
assert callable(haar_random_state)
2835
assert callable(ps_to_peps)
2936
assert callable(reg_complex_svd_torch)
3037

tests/test_public_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_package_version_available():
2121
"pauli", "x", "y", "z", "s", "sdg", "t", "tdg", "h", "hadamard",
2222
"cnot", "cx", "cy", "cz", "swap", "iswap", "phase", "u1", "u2",
2323
"cphase", "crx", "cry", "crz", "cu1", "cu2", "cu3", "rx", "ry", "rz",
24-
"rxx", "ryy", "rzz", "u3", "su4", "fsim", "fsimg", "ps_to_peps", "expec_mpo",
24+
"rxx", "ryy", "rzz", "u3", "su4", "fsim", "fsimg", "haar_random_state", "ps_to_peps", "expec_mpo",
2525
"id_to_mpo", "id_to_pepo", "ps_to_pepo", "ps_to_mpo", "make_numpy_array_caster", "SweepOptimizer",
2626
"FDSolver", "MpsOptimizer", "MpoOptimizer", "PEPSSampleResult",
2727
"PepsBpSampler", "MpsSampler", "MpsSampleResult", "VecSampler", "gate", "tn_fidelity", "tn_norm",
@@ -64,7 +64,7 @@ def test_internal_symbol_not_exported(name):
6464
"x", "y", "z", "s", "sdg", "t", "tdg", "h", "hadamard",
6565
"cnot", "cx", "cy", "cz", "swap", "iswap", "phase", "u1", "u2",
6666
"cphase", "crx", "cry", "crz", "cu1", "cu2", "cu3", "rx", "ry", "rz",
67-
"rxx", "ryy", "rzz", "u3", "su4", "fsim", "fsimg", "ps_to_peps", "expec_mpo",
67+
"rxx", "ryy", "rzz", "u3", "su4", "fsim", "fsimg", "haar_random_state", "ps_to_peps", "expec_mpo",
6868
"id_to_mpo", "id_to_pepo", "ps_to_pepo", "ps_to_mpo", "SweepOptimizer",
6969
"FDSolver", "MpsOptimizer", "MpoOptimizer", "PEPSSampleResult", "PepsBpSampler",
7070
"tn_fidelity", "tn_norm",

tests/test_tensor_constructors.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Tests for tensor and dense-state constructors."""
2+
3+
import numpy as np
4+
import pytest
5+
6+
from pepsy import haar_random_state
7+
from pepsy.tensors.constructors import haar_random_state as constructors_haar_random_state
8+
9+
10+
def test_haar_random_state_returns_normalized_dense_vector():
11+
"""Dense Haar state should have full Hilbert-space shape and unit norm."""
12+
state = haar_random_state(3, seed=123)
13+
14+
assert state.shape == (8,)
15+
assert state.dtype == np.dtype("complex128")
16+
assert np.isclose(np.linalg.norm(state), 1.0)
17+
18+
19+
def test_haar_random_state_tensor_shape_matches_vector_seed():
20+
"""The tensor form should be a reshape of the seeded dense vector."""
21+
vector = haar_random_state(4, seed=7)
22+
tensor = haar_random_state(4, seed=7, as_tensor=True)
23+
24+
assert tensor.shape == (2, 2, 2, 2)
25+
assert np.allclose(tensor.reshape(-1), vector)
26+
27+
28+
def test_haar_random_state_sample_is_generally_entangled():
29+
"""A full Haar sample should not be restricted to product-state rank."""
30+
state = haar_random_state(3, seed=11)
31+
32+
assert np.linalg.matrix_rank(state.reshape(2, -1), tol=1e-12) > 1
33+
34+
35+
def test_haar_random_state_rejects_large_dense_state():
36+
"""The default guard should keep dense state construction small."""
37+
with pytest.raises(ValueError, match="L <= L_max"):
38+
haar_random_state(21)
39+
40+
41+
def test_haar_random_state_caps_lmax_above_twenty():
42+
"""Raising L_max above the documented dense-state limit should warn."""
43+
with pytest.warns(UserWarning, match="L <= 20"):
44+
state = haar_random_state(2, seed=5, L_max=21)
45+
46+
assert state.shape == (4,)
47+
48+
49+
def test_haar_random_state_rejects_real_dtype():
50+
"""Haar-random qubit amplitudes require a complex dtype."""
51+
with pytest.raises(TypeError, match="complex"):
52+
haar_random_state(2, dtype="float64")
53+
54+
55+
def test_haar_random_state_constructor_facade_resolves():
56+
"""Constructor facade should expose the dense Haar helper."""
57+
assert constructors_haar_random_state is haar_random_state

0 commit comments

Comments
 (0)