Skip to content

Commit 9413b51

Browse files
committed
added mpo-sim, re-organize APIs
1 parent 5e64e87 commit 9413b51

14 files changed

Lines changed: 1586 additions & 243 deletions

‎src/pepsy/__init__.py‎

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
optimize_global,
2323
optimize_sweep,
2424
optimize_energy,
25+
optimize_mpo,
2526
)
2627
from .boundary_metrics import (
2728
BoundaryContractResult,
@@ -36,6 +37,10 @@
3637
get_default_array_backend,
3738
get_default_grad_backend,
3839
ps_to_peps,
40+
ps_to_mps,
41+
random_haar_qubit,
42+
hrps_to_peps,
43+
hrps_to_mps,
3944
register_torch_linalg,
4045
reset_default_backends,
4146
set_default_array_backend,
@@ -45,10 +50,10 @@
4550
from .linalg_registrations import reg_complex_svd_jax, reg_complex_svd_torch
4651
from .fit import FIT
4752
from .gate import (
48-
apply_2d_gate,
49-
apply_2d_gates,
53+
apply_gate_2d,
54+
apply_gates_2d,
5055
gates_to_pepo,
51-
gate_1d,
56+
apply_gate_1d,
5257
gen_long_range_swap_path,
5358
pauli,
5459
x,
@@ -92,6 +97,7 @@
9297
from .optimize_sweep import SweepOptimizer
9398
from .optimize_energy import EnergyOptimizer
9499
from .optimize_mps import MpsOptimizer
100+
from .optimize_mpo import MpoOptimizer
95101

96102
__all__ = [
97103
"__version__",
@@ -117,10 +123,10 @@
117123
"EnergyOptimizer",
118124
"tns_align",
119125
"gen_long_range_swap_path",
120-
"apply_2d_gate",
121-
"apply_2d_gates",
126+
"apply_gate_2d",
127+
"apply_gates_2d",
122128
"gates_to_pepo",
123-
"gate_1d",
129+
"apply_gate_1d",
124130
"pauli",
125131
"x",
126132
"y",
@@ -157,9 +163,14 @@
157163
"su4",
158164
"ham_tn",
159165
"ps_to_peps",
166+
"ps_to_mps",
167+
"random_haar_qubit",
168+
"hrps_to_peps",
169+
"hrps_to_mps",
160170
"optimize_global",
161171
"optimize_sweep",
162172
"optimize_energy",
173+
"optimize_mpo",
163174
"gradient_solver",
164175
"gate",
165176
"ham",
@@ -169,6 +180,7 @@
169180
"core",
170181
"fit",
171182
"MpsOptimizer",
183+
"MpoOptimizer",
172184
]
173185

174186

@@ -185,6 +197,7 @@ def __getattr__(name):
185197
"optimize_global",
186198
"optimize_sweep",
187199
"optimize_energy",
200+
"optimize_mpo",
188201
"core",
189202
"fit",
190203
):
@@ -225,10 +238,10 @@ def __getattr__(name):
225238

226239
if name in (
227240
"gen_long_range_swap_path",
228-
"apply_2d_gate",
229-
"apply_2d_gates",
241+
"apply_gate_2d",
242+
"apply_gates_2d",
230243
"gates_to_pepo",
231-
"gate_1d",
244+
"apply_gate_1d",
232245
"pauli",
233246
"x",
234247
"y",
@@ -265,10 +278,10 @@ def __getattr__(name):
265278
"su4",
266279
):
267280
from .gate import ( # pylint: disable=import-outside-toplevel
268-
apply_2d_gate,
269-
apply_2d_gates,
281+
apply_gate_2d,
282+
apply_gates_2d,
270283
gates_to_pepo,
271-
gate_1d,
284+
apply_gate_1d,
272285
gen_long_range_swap_path,
273286
pauli,
274287
x,
@@ -308,10 +321,10 @@ def __getattr__(name):
308321

309322
return {
310323
"gen_long_range_swap_path": gen_long_range_swap_path,
311-
"apply_2d_gate": apply_2d_gate,
312-
"apply_2d_gates": apply_2d_gates,
324+
"apply_gate_2d": apply_gate_2d,
325+
"apply_gates_2d": apply_gates_2d,
313326
"gates_to_pepo": gates_to_pepo,
314-
"gate_1d": gate_1d,
327+
"apply_gate_1d": apply_gate_1d,
315328
"pauli": pauli,
316329
"x": x,
317330
"y": y,
@@ -353,15 +366,23 @@ def __getattr__(name):
353366

354367
return ham_tn
355368

356-
if name in ("tns_align", "ps_to_peps"):
369+
if name in ("tns_align", "ps_to_peps", "ps_to_mps", "random_haar_qubit", "hrps_to_peps", "hrps_to_mps"):
357370
from .core import ( # pylint: disable=import-outside-toplevel
358371
ps_to_peps,
372+
ps_to_mps,
373+
random_haar_qubit,
374+
hrps_to_peps,
375+
hrps_to_mps,
359376
tns_align,
360377
)
361378

362379
return {
363380
"tns_align": tns_align,
364381
"ps_to_peps": ps_to_peps,
382+
"ps_to_mps": ps_to_mps,
383+
"random_haar_qubit": random_haar_qubit,
384+
"hrps_to_peps": hrps_to_peps,
385+
"hrps_to_mps": hrps_to_mps,
365386
}[name]
366387

367388
if name in ("BdyMPS", "make_numpy_array_caster"):
@@ -432,4 +453,9 @@ def __getattr__(name):
432453

433454
return MpsOptimizer
434455

456+
if name == "MpoOptimizer":
457+
from .optimize_mpo import MpoOptimizer # pylint: disable=import-outside-toplevel
458+
459+
return MpoOptimizer
460+
435461
raise AttributeError(f"module 'pepsy' has no attribute {name!r}")

‎src/pepsy/_backend_utils.py‎

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""Shared backend inference and array-conversion helpers."""
2+
3+
from __future__ import annotations
4+
5+
import numpy as np
6+
import autoray as ar
7+
8+
_NUMPY_DTYPE_MAP = {
9+
"complex128": np.complex128,
10+
"complex64": np.complex64,
11+
"float64": np.float64,
12+
"float32": np.float32,
13+
"float16": np.float16,
14+
"int64": np.int64,
15+
"int32": np.int32,
16+
}
17+
18+
19+
def resolve_backend_sample_data(obj):
20+
"""Return representative array data from an array- or gate-like object."""
21+
if obj is None:
22+
return None
23+
24+
if hasattr(obj, "shape") and hasattr(obj, "dtype"):
25+
return obj
26+
27+
data = getattr(obj, "data", None)
28+
if hasattr(data, "shape") and hasattr(data, "dtype"):
29+
return data
30+
31+
return None
32+
33+
34+
def resolve_backend_sample_data_from_tn(tn):
35+
"""Return representative array data from a tensor network-like object."""
36+
if tn is None:
37+
return None
38+
39+
tensor_map = getattr(tn, "tensor_map", None)
40+
if tensor_map:
41+
first_tensor = next(iter(tensor_map.values()))
42+
data = getattr(first_tensor, "data", None)
43+
if hasattr(data, "shape") and hasattr(data, "dtype"):
44+
return data
45+
46+
try:
47+
first_tensor = next(iter(tn))
48+
except (TypeError, StopIteration):
49+
return None
50+
51+
data = getattr(first_tensor, "data", None)
52+
if hasattr(data, "shape") and hasattr(data, "dtype"):
53+
return data
54+
55+
return None
56+
57+
58+
def infer_backend_and_dtype(sample_data):
59+
"""Infer backend name and dtype name from sample tensor data."""
60+
if sample_data is None:
61+
raise ValueError("Cannot infer backend: sample_data is None.")
62+
63+
dtype_name = ar.get_dtype_name(sample_data)
64+
backend = ar.infer_backend(sample_data)
65+
return backend, dtype_name
66+
67+
68+
def _build_to_numpy(sample_data, dtype_name, *, cast_complex_to_real=False):
69+
del sample_data
70+
if dtype_name not in _NUMPY_DTYPE_MAP:
71+
raise ValueError(f"Unsupported dtype '{dtype_name}' for numpy backend.")
72+
dtype = _NUMPY_DTYPE_MAP[dtype_name]
73+
74+
def _to_numpy(x, dtype=dtype, cast_complex_to_real=cast_complex_to_real):
75+
arr = np.asarray(x)
76+
if cast_complex_to_real and np.issubdtype(dtype, np.floating) and np.iscomplexobj(arr):
77+
arr = arr.real
78+
return np.asarray(arr, dtype=dtype)
79+
80+
return _to_numpy
81+
82+
83+
def _build_to_torch(sample_data, dtype_name, *, cast_complex_to_real=False):
84+
import torch # pylint: disable=import-outside-toplevel
85+
86+
dtype_map = {
87+
"complex128": torch.complex128,
88+
"complex64": torch.complex64,
89+
"float64": torch.float64,
90+
"float32": torch.float32,
91+
"float16": torch.float16,
92+
"int64": torch.int64,
93+
"int32": torch.int32,
94+
}
95+
if dtype_name not in dtype_map:
96+
raise ValueError(f"Unsupported dtype '{dtype_name}' for torch backend.")
97+
98+
dtype = getattr(sample_data, "dtype", None) or dtype_map[dtype_name]
99+
device = getattr(sample_data, "device", None)
100+
101+
def _to_torch(
102+
x,
103+
dtype=dtype,
104+
device=device,
105+
cast_complex_to_real=cast_complex_to_real,
106+
):
107+
if isinstance(x, torch.Tensor):
108+
arr = x
109+
if device is not None:
110+
arr = arr.to(device=device)
111+
else:
112+
if isinstance(x, np.ndarray) and (not x.flags.writeable):
113+
x = np.array(x, copy=True)
114+
kwargs = {"device": device} if device is not None else {}
115+
arr = torch.as_tensor(x, **kwargs)
116+
117+
if cast_complex_to_real and dtype.is_floating_point and torch.is_complex(arr):
118+
arr = arr.real
119+
120+
kwargs = {"dtype": dtype}
121+
if device is not None:
122+
kwargs["device"] = device
123+
return torch.as_tensor(arr, **kwargs)
124+
125+
return _to_torch
126+
127+
128+
def _build_to_cupy(sample_data, dtype_name, *, cast_complex_to_real=False):
129+
import cupy as cp # pylint: disable=import-outside-toplevel
130+
131+
dtype_map = {
132+
"complex128": cp.complex128,
133+
"complex64": cp.complex64,
134+
"float64": cp.float64,
135+
"float32": cp.float32,
136+
"float16": cp.float16,
137+
"int64": cp.int64,
138+
"int32": cp.int32,
139+
}
140+
if dtype_name not in dtype_map:
141+
raise ValueError(f"Unsupported dtype '{dtype_name}' for cupy backend.")
142+
143+
dtype = getattr(sample_data, "dtype", None) or dtype_map[dtype_name]
144+
device = getattr(sample_data, "device", None)
145+
146+
def _to_cupy(
147+
x,
148+
dtype=dtype,
149+
device=device,
150+
cast_complex_to_real=cast_complex_to_real,
151+
):
152+
if device is None:
153+
arr = cp.asarray(x)
154+
else:
155+
with device:
156+
arr = cp.asarray(x)
157+
158+
if cast_complex_to_real and cp.issubdtype(dtype, cp.floating) and cp.iscomplexobj(arr):
159+
arr = arr.real
160+
161+
return arr.astype(dtype, copy=False)
162+
163+
return _to_cupy
164+
165+
166+
def dispatch_backend_converter(
167+
*,
168+
backend,
169+
dtype_name,
170+
sample_data,
171+
cast_complex_to_real=False,
172+
):
173+
"""Return backend converter callable for the specified backend and dtype."""
174+
if sample_data is None:
175+
raise ValueError("Cannot infer backend: tensor network has no tensors.")
176+
177+
if backend == "numpy":
178+
return _build_to_numpy(
179+
sample_data,
180+
dtype_name,
181+
cast_complex_to_real=cast_complex_to_real,
182+
)
183+
if backend == "torch":
184+
return _build_to_torch(
185+
sample_data,
186+
dtype_name,
187+
cast_complex_to_real=cast_complex_to_real,
188+
)
189+
if backend == "cupy":
190+
return _build_to_cupy(
191+
sample_data,
192+
dtype_name,
193+
cast_complex_to_real=cast_complex_to_real,
194+
)
195+
196+
raise ValueError(f"Unsupported backend: {backend}")
197+
198+
199+
def infer_backend_converter_from_sample(
200+
sample_data,
201+
*,
202+
cast_complex_to_real=False,
203+
):
204+
"""Infer and return converter callable from representative sample data."""
205+
if sample_data is None:
206+
return None
207+
208+
backend, dtype_name = infer_backend_and_dtype(sample_data)
209+
return dispatch_backend_converter(
210+
backend=backend,
211+
dtype_name=dtype_name,
212+
sample_data=sample_data,
213+
cast_complex_to_real=cast_complex_to_real,
214+
)

0 commit comments

Comments
 (0)