quantax 0.2.1__tar.gz → 0.3.0__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.
Files changed (85) hide show
  1. {quantax-0.2.1/quantax.egg-info → quantax-0.3.0}/PKG-INFO +13 -7
  2. {quantax-0.2.1 → quantax-0.3.0}/README.md +14 -2
  3. {quantax-0.2.1 → quantax-0.3.0}/pyproject.toml +23 -7
  4. {quantax-0.2.1 → quantax-0.3.0}/quantax/__init__.py +7 -0
  5. {quantax-0.2.1 → quantax-0.3.0}/quantax/global_defs.py +14 -13
  6. {quantax-0.2.1 → quantax-0.3.0}/quantax/model/__init__.py +1 -2
  7. {quantax-0.2.1 → quantax-0.3.0}/quantax/model/backflow.py +120 -43
  8. {quantax-0.2.1 → quantax-0.3.0}/quantax/model/conv_nets.py +162 -129
  9. {quantax-0.2.1 → quantax-0.3.0}/quantax/model/fermion_mf.py +259 -135
  10. quantax-0.3.0/quantax/model/jastrow.py +363 -0
  11. quantax-0.3.0/quantax/model/shallow_nets.py +183 -0
  12. quantax-0.3.0/quantax/model/triangular_nets.py +253 -0
  13. {quantax-0.2.1 → quantax-0.3.0}/quantax/nn/__init__.py +2 -1
  14. {quantax-0.2.1 → quantax-0.3.0}/quantax/nn/activation.py +17 -14
  15. {quantax-0.2.1 → quantax-0.3.0}/quantax/nn/conv.py +81 -28
  16. quantax-0.3.0/quantax/nn/embedding.py +96 -0
  17. {quantax-0.2.1 → quantax-0.3.0}/quantax/nn/fermion.py +23 -24
  18. {quantax-0.2.1 → quantax-0.3.0}/quantax/nn/initializers.py +44 -26
  19. quantax-0.3.0/quantax/nn/modules.py +172 -0
  20. quantax-0.3.0/quantax/nn/sign.py +100 -0
  21. {quantax-0.2.1 → quantax-0.3.0}/quantax/operator/__init__.py +2 -1
  22. {quantax-0.2.1 → quantax-0.3.0}/quantax/operator/common_operators.py +48 -41
  23. quantax-0.3.0/quantax/operator/operator.py +819 -0
  24. {quantax-0.2.1 → quantax-0.3.0}/quantax/operator/site_operator.py +105 -8
  25. quantax-0.3.0/quantax/operator/update_mode_filters.py +51 -0
  26. quantax-0.3.0/quantax/optimizer/__init__.py +21 -0
  27. quantax-0.3.0/quantax/optimizer/gradient.py +149 -0
  28. quantax-0.3.0/quantax/optimizer/qngd.py +337 -0
  29. quantax-0.3.0/quantax/optimizer/solver.py +798 -0
  30. quantax-0.3.0/quantax/optimizer/sr.py +234 -0
  31. quantax-0.3.0/quantax/optimizer/supervised.py +124 -0
  32. {quantax-0.2.1 → quantax-0.3.0}/quantax/optimizer/time_evol.py +57 -26
  33. quantax-0.3.0/quantax/optimizer/updater.py +197 -0
  34. {quantax-0.2.1 → quantax-0.3.0}/quantax/sampler/common_samplers.py +47 -49
  35. {quantax-0.2.1 → quantax-0.3.0}/quantax/sampler/metropolis.py +213 -115
  36. {quantax-0.2.1 → quantax-0.3.0}/quantax/sampler/sampler.py +9 -8
  37. {quantax-0.2.1 → quantax-0.3.0}/quantax/sampler/samples.py +15 -12
  38. {quantax-0.2.1 → quantax-0.3.0}/quantax/sites/__init__.py +0 -2
  39. quantax-0.3.0/quantax/sites/common_lattices.py +168 -0
  40. {quantax-0.2.1 → quantax-0.3.0}/quantax/sites/lattice.py +116 -95
  41. {quantax-0.2.1 → quantax-0.3.0}/quantax/sites/sites.py +99 -56
  42. {quantax-0.2.1 → quantax-0.3.0}/quantax/state/__init__.py +0 -1
  43. {quantax-0.2.1 → quantax-0.3.0}/quantax/state/fermion_mf.py +139 -130
  44. {quantax-0.2.1 → quantax-0.3.0}/quantax/state/state.py +129 -37
  45. {quantax-0.2.1 → quantax-0.3.0}/quantax/state/variational.py +260 -195
  46. {quantax-0.2.1 → quantax-0.3.0}/quantax/symmetry/common_symmetries.py +29 -25
  47. {quantax-0.2.1 → quantax-0.3.0}/quantax/symmetry/symmetry.py +64 -22
  48. {quantax-0.2.1 → quantax-0.3.0}/quantax/symmetry/translation.py +13 -6
  49. {quantax-0.2.1 → quantax-0.3.0}/quantax/utils/__init__.py +9 -8
  50. quantax-0.3.0/quantax/utils/array.py +167 -0
  51. {quantax-0.2.1 → quantax-0.3.0}/quantax/utils/basis.py +17 -14
  52. {quantax-0.2.1 → quantax-0.3.0}/quantax/utils/big_array.py +230 -170
  53. {quantax-0.2.1 → quantax-0.3.0}/quantax/utils/data.py +50 -38
  54. quantax-0.3.0/quantax/utils/function.py +267 -0
  55. quantax-0.3.0/quantax/utils/sharding.py +73 -0
  56. {quantax-0.2.1 → quantax-0.3.0}/quantax/utils/tree.py +4 -48
  57. {quantax-0.2.1 → quantax-0.3.0/quantax.egg-info}/PKG-INFO +13 -7
  58. {quantax-0.2.1 → quantax-0.3.0}/quantax.egg-info/SOURCES.txt +5 -5
  59. quantax-0.3.0/quantax.egg-info/requires.txt +21 -0
  60. quantax-0.2.1/docs/source/conf.py +0 -51
  61. quantax-0.2.1/quantax/model/jastrow.py +0 -197
  62. quantax-0.2.1/quantax/model/shallow_nets.py +0 -190
  63. quantax-0.2.1/quantax/model/transformer.py +0 -156
  64. quantax-0.2.1/quantax/model/triangular_nets.py +0 -196
  65. quantax-0.2.1/quantax/nn/modules.py +0 -132
  66. quantax-0.2.1/quantax/nn/sign.py +0 -74
  67. quantax-0.2.1/quantax/operator/operator.py +0 -595
  68. quantax-0.2.1/quantax/optimizer/__init__.py +0 -18
  69. quantax-0.2.1/quantax/optimizer/driver.py +0 -102
  70. quantax-0.2.1/quantax/optimizer/solver.py +0 -302
  71. quantax-0.2.1/quantax/optimizer/sr.py +0 -513
  72. quantax-0.2.1/quantax/optimizer/supervised.py +0 -78
  73. quantax-0.2.1/quantax/sites/cluster.py +0 -74
  74. quantax-0.2.1/quantax/sites/common_lattices.py +0 -138
  75. quantax-0.2.1/quantax/state/lanczos.py +0 -52
  76. quantax-0.2.1/quantax/utils/array.py +0 -152
  77. quantax-0.2.1/quantax/utils/function.py +0 -245
  78. quantax-0.2.1/quantax/utils/sharding.py +0 -22
  79. quantax-0.2.1/quantax.egg-info/requires.txt +0 -14
  80. {quantax-0.2.1 → quantax-0.3.0}/LICENSE +0 -0
  81. {quantax-0.2.1 → quantax-0.3.0}/quantax/sampler/__init__.py +0 -0
  82. {quantax-0.2.1 → quantax-0.3.0}/quantax/symmetry/__init__.py +0 -0
  83. {quantax-0.2.1 → quantax-0.3.0}/quantax.egg-info/dependency_links.txt +0 -0
  84. {quantax-0.2.1 → quantax-0.3.0}/quantax.egg-info/top_level.txt +0 -0
  85. {quantax-0.2.1 → quantax-0.3.0}/setup.cfg +0 -0
@@ -1,22 +1,28 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantax
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Flexible neural quantum states based on JAX
5
5
  Author: Christopher Roth
6
6
  Author-email: Ao Chen <chenao.phys@gmail.com>
7
- Requires-Python: <=3.13,>=3.10
7
+ Requires-Python: <3.14,>=3.11
8
8
  License-File: LICENSE
9
- Requires-Dist: numpy>=1.26.0
10
- Requires-Dist: scipy>=1.11.1
11
- Requires-Dist: jax>=0.6.1
9
+ Requires-Dist: numpy>=2.0.0
10
+ Requires-Dist: scipy>=1.14.0
11
+ Requires-Dist: jax>=0.8.0
12
12
  Requires-Dist: equinox>=0.11.4
13
13
  Requires-Dist: lrux>=0.1.0
14
14
  Provides-Extra: full
15
15
  Requires-Dist: numpy>=2.0.0; extra == "full"
16
- Requires-Dist: scipy>=1.13.1; extra == "full"
16
+ Requires-Dist: scipy>=1.14.0; extra == "full"
17
17
  Requires-Dist: matplotlib>=3.8.0; extra == "full"
18
18
  Requires-Dist: quspin>=1.0.0; extra == "full"
19
- Requires-Dist: jax>=0.6.1; extra == "full"
19
+ Requires-Dist: jax>=0.8.0; extra == "full"
20
20
  Requires-Dist: equinox>=0.11.4; extra == "full"
21
+ Requires-Dist: lineax>=0.1.0; extra == "full"
21
22
  Requires-Dist: lrux>=0.1.0; extra == "full"
23
+ Requires-Dist: jaxmg>=0.0.6; extra == "full"
24
+ Provides-Extra: docs
25
+ Requires-Dist: sphinx; extra == "docs"
26
+ Requires-Dist: myst-nb; extra == "docs"
27
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
22
28
  Dynamic: license-file
@@ -1,7 +1,7 @@
1
1
  <h1 align='center'>Quantax</h1>
2
2
 
3
3
  <p align="center">
4
- <strong>Flexible and scalable neural quantum states built on JAX</strong>
4
+ <strong>Flexible and scalable neural quantum states in JAX</strong>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -22,7 +22,7 @@ Quantax is a research-oriented Python package for quantum many-body physics, wit
22
22
 
23
23
  ## ⚙️Installation
24
24
 
25
- Requires Python 3.10+, JAX 0.6.1+
25
+ Requires Python 3.11+, JAX 0.8.0+
26
26
 
27
27
  First, ensure that a correct JAX version is installed. For details, check [JAX Installation](https://docs.jax.dev/en/latest/installation.html).
28
28
 
@@ -75,3 +75,15 @@ energy_data.plot(baseline=E)
75
75
  plt.show()
76
76
  ```
77
77
  ![quick_start](images/quick_start.svg)
78
+
79
+ ## 📚Citation
80
+
81
+ ```bibtex
82
+ @software{quantax,
83
+ author = {Chen, Ao and Roth, Christopher},
84
+ title = {Quantax: Flexible neural quantum states in JAX},
85
+ year = {2024},
86
+ url = {https://github.com/ChenAo-Phys/quantax},
87
+ version = {0.3.0}
88
+ }
89
+ ```
@@ -4,17 +4,17 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantax"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  description = "Flexible neural quantum states based on JAX"
9
9
  authors = [
10
10
  { name = "Ao Chen", email = "chenao.phys@gmail.com" },
11
11
  { name = "Christopher Roth" }
12
12
  ]
13
- requires-python = ">=3.10,<=3.13"
13
+ requires-python = ">=3.11,<3.14"
14
14
  dependencies = [
15
- "numpy>=1.26.0",
16
- "scipy>=1.11.1",
17
- "jax>=0.6.1",
15
+ "numpy>=2.0.0",
16
+ "scipy>=1.14.0",
17
+ "jax>=0.8.0",
18
18
  "equinox>=0.11.4",
19
19
  "lrux>=0.1.0",
20
20
  ]
@@ -22,14 +22,30 @@ dependencies = [
22
22
  [project.optional-dependencies]
23
23
  full = [
24
24
  "numpy>=2.0.0",
25
- "scipy>=1.13.1",
25
+ "scipy>=1.14.0",
26
26
  "matplotlib>=3.8.0",
27
27
  "quspin>=1.0.0",
28
- "jax>=0.6.1",
28
+ "jax>=0.8.0",
29
29
  "equinox>=0.11.4",
30
+ "lineax>=0.1.0",
30
31
  "lrux>=0.1.0",
32
+ "jaxmg>=0.0.6",
33
+ ]
34
+ docs = [
35
+ "sphinx",
36
+ "myst-nb",
37
+ "pydata-sphinx-theme",
31
38
  ]
32
39
 
33
40
  [tool.setuptools.packages.find]
34
41
  where = ["."]
35
42
  include = ["quantax*"]
43
+
44
+ [tool.black]
45
+ target-version = ["py311", "py312", "py313"]
46
+
47
+ [tool.pyright]
48
+ include = ["quantax"]
49
+ pythonVersion = "3.12"
50
+ typeCheckingMode = "standard"
51
+
@@ -1,3 +1,10 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+
3
+ try:
4
+ __version__ = version("quantax")
5
+ except PackageNotFoundError:
6
+ __version__ = "unknown"
7
+
1
8
  from .global_defs import (
2
9
  set_random_seed,
3
10
  set_default_dtype,
@@ -1,31 +1,32 @@
1
- from typing import Optional, Tuple
2
1
  from functools import partial
3
2
  from enum import Enum
4
3
  import jax
5
4
  import jax.numpy as jnp
6
5
  import jax.random as jr
6
+ from jax.typing import DTypeLike
7
7
 
8
+ DTYPE = jnp.dtype(jnp.float32)
8
9
 
9
- jax.config.update("jax_enable_x64", True)
10
- jax.config.update("jax_default_matmul_precision", "float32")
11
10
 
12
-
13
- DTYPE = jnp.float64
14
-
15
-
16
- def set_default_dtype(dtype: jnp.dtype) -> None:
11
+ def set_default_dtype(dtype: DTypeLike) -> None:
17
12
  """
18
13
  Set the default data type in Quantax.
19
- Recommended to be ``jnp.float64`` or ``jnp.complex128``. Default to ``jnp.float64``.
14
+ Recommended to be ``jnp.float32`` or ``jnp.complex64``. Default to ``jnp.float32``.
20
15
 
21
16
  .. note::
22
17
  This doesn't alter the computation inside ``quantax.model``.
23
18
  """
19
+ dtype = jnp.dtype(dtype)
20
+
24
21
  if not (
25
22
  jnp.issubdtype(dtype, jnp.floating)
26
23
  or jnp.issubdtype(dtype, jnp.complexfloating)
27
24
  ):
28
25
  raise ValueError("'dtype' should be float or complex types")
26
+
27
+ if dtype == jnp.float64 or dtype == jnp.complex128:
28
+ jax.config.update("jax_enable_x64", True)
29
+
29
30
  global DTYPE
30
31
  DTYPE = dtype
31
32
 
@@ -58,13 +59,13 @@ def set_random_seed(seed: int) -> None:
58
59
  global KEY
59
60
  KEY = jr.key(seed)
60
61
 
61
- from .utils import to_replicate_array
62
+ from .utils import local_to_replicated
62
63
 
63
- KEY = to_replicate_array(KEY)
64
+ KEY = local_to_replicated(KEY)
64
65
 
65
66
 
66
67
  @partial(jax.jit, static_argnums=1)
67
- def _gen_keys(key, num: Optional[int] = None) -> Tuple[jax.Array, jax.Array]:
68
+ def _gen_keys(key, num: int | None = None) -> tuple[jax.Array, jax.Array]:
68
69
  nkeys = 2 if num is None else num + 1
69
70
  new_keys = jr.split(key, nkeys)
70
71
  key = new_keys[0]
@@ -72,7 +73,7 @@ def _gen_keys(key, num: Optional[int] = None) -> Tuple[jax.Array, jax.Array]:
72
73
  return key, new_keys
73
74
 
74
75
 
75
- def get_subkeys(num: Optional[int] = None) -> jax.Array:
76
+ def get_subkeys(num: int | None = None) -> jax.Array:
76
77
  """
77
78
  Get jax keys stored in Quantax. The keys are replicated across all devices.
78
79
 
@@ -11,6 +11,5 @@ from .fermion_mf import (
11
11
  MultiPf,
12
12
  PartialPair,
13
13
  )
14
- from .transformer import Transformer, Embedding, MHSA, FFN
15
- from .jastrow import NeuralJastrow
14
+ from .jastrow import Jastrow, GeneralJastrow
16
15
  from .backflow import DetBackflow, PfBackflow
@@ -1,8 +1,8 @@
1
- from typing import Optional, Tuple, Union
1
+ from typing import Any, Callable, overload, Literal
2
2
  import jax
3
3
  import jax.numpy as jnp
4
4
  import jax.random as jr
5
- import equinox as eqx
5
+ from jax.typing import DTypeLike
6
6
  import lrux
7
7
  from .fermion_mf import GeneralDet, MF_Internal, _init_spinless_orbs
8
8
  from ..global_defs import get_sites, get_subkeys
@@ -18,23 +18,23 @@ from ..utils import LogArray
18
18
 
19
19
 
20
20
  class DetBackflow(RefModel):
21
- net: eqx.Module
22
- U0: jax.Array
23
- W: jax.Array
24
- dtype: jnp.dtype
25
-
26
21
  r"""
27
22
  Determinant backflow model.
28
23
  :math:`\psi(n) = \mathrm{det}(n \star (U_0 + U_1(n)))`,
29
24
  where :math:`\star` denotes the operation slicing the rows of the matrix.
30
25
  """
31
26
 
27
+ net: Callable[[jax.Array], jax.Array]
28
+ U0: jax.Array
29
+ W: jax.Array
30
+ dtype: DTypeLike
31
+
32
32
  def __init__(
33
33
  self,
34
- net: eqx.Module,
34
+ net: Callable[[jax.Array], jax.Array],
35
35
  d: int,
36
- U0: Optional[jax.Array] = None,
37
- dtype: jnp.dtype = jnp.float64,
36
+ U0: jax.Array | None = None,
37
+ dtype: DTypeLike = jnp.float64,
38
38
  ):
39
39
  r"""
40
40
  Initialize the determinant backflow model.
@@ -71,8 +71,8 @@ class DetBackflow(RefModel):
71
71
  d //= 2
72
72
  self.W = lecun_normal(get_subkeys(), (sites.Ntotal, d), dtype=dtype) / 10
73
73
 
74
- def __call__(self, s: jax.Array) -> jax.Array:
75
- x = self.net(s)
74
+ def __call__(self, s: jax.Array) -> LogArray:
75
+ x = jnp.asarray(self.net(s))
76
76
 
77
77
  idx = fermion_idx(s)
78
78
  x = x.reshape(-1, get_sites().Nfmodes).astype(self.dtype)
@@ -89,43 +89,78 @@ class DetBackflow(RefModel):
89
89
  backflow correction is larger than the total number of particles.
90
90
  """
91
91
  Ntotal = get_sites().Ntotal
92
+ if Ntotal is None:
93
+ raise ValueError
92
94
  rank = self.W.shape[1]
93
95
  return rank < Ntotal
94
96
 
95
- def init_internal(self, s) -> Optional[MF_Internal]:
97
+ def init_internal(self, s: jax.Array) -> tuple[LogArray, MF_Internal | None]:
96
98
  """
97
- Initialize internal values for given input configurations.
99
+ Return wavefunction and internal values for given input configurations.
98
100
  See `~quantax.nn.RefModel` for details.
99
101
  """
100
102
  if not self.use_ref:
101
- return None
103
+ return self(s), None
102
104
 
103
105
  idx = fermion_idx(s)
104
106
  orbs = self.U0[idx, :]
105
107
  inv = jnp.linalg.inv(orbs)
106
108
  sign, logabs = jnp.linalg.slogdet(orbs)
107
109
  psi = LogArray(sign, logabs) * fermion_inverse_sign(s)
108
- return MF_Internal(idx, inv, psi)
110
+ return psi, MF_Internal(idx, inv, psi)
111
+
112
+ @property
113
+ def required_update_modes(self) -> tuple[str, ...]:
114
+ """
115
+ The required update modes for accelerated ref_forward pass.
116
+ """
117
+ return ("nflips",)
118
+
119
+ @overload
120
+ def ref_forward(
121
+ self,
122
+ s: jax.Array,
123
+ s_old: jax.Array,
124
+ update_mode: dict[str, Any],
125
+ internal: MF_Internal | None,
126
+ return_update: Literal[False] = False,
127
+ ) -> LogArray: ...
128
+
129
+ @overload
130
+ def ref_forward(
131
+ self,
132
+ s: jax.Array,
133
+ s_old: jax.Array,
134
+ update_mode: dict[str, Any],
135
+ internal: MF_Internal | None,
136
+ return_update: Literal[True],
137
+ ) -> tuple[LogArray, MF_Internal | None]: ...
109
138
 
110
139
  def ref_forward(
111
140
  self,
112
141
  s: jax.Array,
113
142
  s_old: jax.Array,
114
- nflips: int,
115
- internal: Optional[MF_Internal],
143
+ update_mode: dict[str, Any],
144
+ internal: MF_Internal | None,
116
145
  return_update: bool = False,
117
- ) -> Union[LogArray, Tuple[LogArray, MF_Internal]]:
146
+ ) -> LogArray | tuple[LogArray, MF_Internal | None]:
118
147
  """
119
148
  Accelerated forward pass through local updates and internal quantities.
120
149
  See `~quantax.nn.RefModel` for details.
121
150
  """
122
- if not self.use_ref:
151
+ if (not self.use_ref) or (internal is None):
123
152
  psi = self(s)
124
153
  if return_update:
125
154
  return psi, internal
126
155
  else:
127
156
  return psi
128
157
 
158
+ if not isinstance(internal.idx, jax.Array):
159
+ raise ValueError
160
+ if not isinstance(internal.inv, jax.Array):
161
+ raise ValueError
162
+
163
+ nflips = update_mode["nflips"]
129
164
  nhops = nflips // 2
130
165
  idx_annihilate, idx_create = changed_inds(s, s_old, nhops)
131
166
  idx = internal.idx
@@ -135,7 +170,7 @@ class DetBackflow(RefModel):
135
170
  new_idx = idx.at[row_update_idx].set(idx_create)
136
171
  row_update = self.U0[idx_create] - self.U0[idx_annihilate]
137
172
 
138
- x = self.net(s)
173
+ x = jnp.asarray(self.net(s))
139
174
  x = x.reshape(-1, get_sites().Nfmodes).astype(self.dtype)
140
175
  x = x.T[new_idx]
141
176
 
@@ -156,25 +191,25 @@ class DetBackflow(RefModel):
156
191
 
157
192
 
158
193
  class PfBackflow(RefModel):
159
- net: eqx.Module
160
- U0: jax.Array
161
- J0: jax.Array
162
- W: jax.Array
163
- dtype: jnp.dtype
164
-
165
194
  r"""
166
195
  Pfaffian backflow model.
167
196
  :math:`\psi(n) = \mathrm{pf}(n \star (U_0 + U_1(n)) J_0 (U_0 + U_1(n))^T)`,
168
197
  where :math:`\star` denotes the operation slicing the rows and columns of the matrix.
169
198
  """
170
199
 
200
+ net: Callable[[jax.Array], jax.Array]
201
+ U0: jax.Array
202
+ J0: jax.Array
203
+ W: jax.Array
204
+ dtype: DTypeLike
205
+
171
206
  def __init__(
172
207
  self,
173
- net: eqx.Module,
208
+ net: Callable[[jax.Array], jax.Array],
174
209
  d: int,
175
- U0: Optional[jax.Array] = None,
176
- J0: Optional[jax.Array] = None,
177
- dtype: jnp.dtype = jnp.float64,
210
+ U0: jax.Array | None = None,
211
+ J0: jax.Array | None = None,
212
+ dtype: DTypeLike = jnp.float64,
178
213
  ):
179
214
  r"""
180
215
  Initialize the Pfaffian backflow model.
@@ -206,6 +241,9 @@ class PfBackflow(RefModel):
206
241
  U1 = U0.conj() if jnp.issubdtype(dtype, jnp.complexfloating) else U0
207
242
  O = jnp.zeros_like(U0)
208
243
  U0 = jnp.block([[U0, O], [O, U1]])
244
+ # Break the degeneracy of the clean Fermi sea, otherwise the spinful
245
+ # block structure makes the mean-field Pfaffian U0 J0 U0^T singular.
246
+ U0 += jr.normal(get_subkeys(), U0.shape, U0.dtype) * jnp.std(U0) * 0.1
209
247
  elif U0.shape != (M, M):
210
248
  raise ValueError(f"U0 must have shape {(M, M)}, got {U0.shape}")
211
249
  U0 /= jnp.std(U0)
@@ -213,7 +251,7 @@ class PfBackflow(RefModel):
213
251
 
214
252
  if J0 is None:
215
253
  if sites.is_spinful:
216
- J0 = lrux.skew_eye(M // 2, dtype)
254
+ J0 = lrux.skew_eye(M // 2, dtype) # type: ignore
217
255
  else:
218
256
  J0 = jr.normal(get_subkeys(), (M, M), dtype=dtype)
219
257
  J0 = (J0 - J0.T) / 2
@@ -227,6 +265,10 @@ class PfBackflow(RefModel):
227
265
 
228
266
  @property
229
267
  def J0_full(self) -> jax.Array:
268
+ """
269
+ Returns the full antisymmetric mean-field pairing matrix J0, reconstructed
270
+ from its stored upper-triangular entries.
271
+ """
230
272
  M = self.U0.shape[0]
231
273
  J_full = jnp.zeros((M, M), dtype=self.dtype)
232
274
  triu_indices = jnp.triu_indices(M, k=1)
@@ -234,8 +276,8 @@ class PfBackflow(RefModel):
234
276
  J_full = J_full - J_full.T
235
277
  return J_full
236
278
 
237
- def __call__(self, s: jax.Array) -> jax.Array:
238
- x = self.net(s)
279
+ def __call__(self, s: jax.Array) -> LogArray:
280
+ x = jnp.asarray(self.net(s))
239
281
 
240
282
  idx = fermion_idx(s)
241
283
  x = x.reshape(-1, get_sites().Nfmodes).astype(self.dtype)
@@ -253,16 +295,18 @@ class PfBackflow(RefModel):
253
295
  backflow correction is larger than the total number of particles.
254
296
  """
255
297
  Ntotal = get_sites().Ntotal
298
+ if Ntotal is None:
299
+ raise ValueError
256
300
  rank = self.W.shape[1] * 2
257
301
  return rank < Ntotal
258
302
 
259
- def init_internal(self, s) -> Optional[MF_Internal]:
303
+ def init_internal(self, s: jax.Array) -> tuple[LogArray, MF_Internal | None]:
260
304
  """
261
- Initialize internal values for given input configurations.
305
+ Return wavefunction and internal values for given input configurations.
262
306
  See `~quantax.nn.RefModel` for details.
263
307
  """
264
308
  if not self.use_ref:
265
- return None
309
+ return self(s), None
266
310
 
267
311
  idx = fermion_idx(s)
268
312
  U = self.U0[idx, :]
@@ -270,27 +314,60 @@ class PfBackflow(RefModel):
270
314
  inv = jnp.linalg.inv(F)
271
315
  sign, logabs = lrux.slogpf(F)
272
316
  psi = LogArray(sign, logabs) * fermion_inverse_sign(s)
273
- return MF_Internal(idx, inv, psi)
317
+ return psi, MF_Internal(idx, inv, psi)
318
+
319
+ @property
320
+ def required_update_modes(self) -> tuple[str, ...]:
321
+ """
322
+ The required update modes for accelerated ref_forward pass.
323
+ """
324
+ return ("nflips",)
274
325
 
326
+ @overload
275
327
  def ref_forward(
276
328
  self,
277
329
  s: jax.Array,
278
330
  s_old: jax.Array,
279
- nflips: int,
280
- internal: Optional[MF_Internal],
331
+ update_mode: dict[str, Any],
332
+ internal: MF_Internal | None,
333
+ return_update: Literal[False] = False,
334
+ ) -> LogArray: ...
335
+
336
+ @overload
337
+ def ref_forward(
338
+ self,
339
+ s: jax.Array,
340
+ s_old: jax.Array,
341
+ update_mode: dict[str, Any],
342
+ internal: MF_Internal | None,
343
+ return_update: Literal[True],
344
+ ) -> tuple[LogArray, MF_Internal | None]: ...
345
+
346
+ def ref_forward(
347
+ self,
348
+ s: jax.Array,
349
+ s_old: jax.Array,
350
+ update_mode: dict[str, Any],
351
+ internal: MF_Internal | None,
281
352
  return_update: bool = False,
282
- ) -> Union[LogArray, Tuple[LogArray, MF_Internal]]:
353
+ ) -> LogArray | tuple[LogArray, MF_Internal | None]:
283
354
  """
284
355
  Accelerated forward pass through local updates and internal quantities.
285
356
  See `~quantax.nn.RefModel` for details.
286
357
  """
287
- if not self.use_ref:
358
+ if (not self.use_ref) or (internal is None):
288
359
  psi = self(s)
289
360
  if return_update:
290
361
  return psi, internal
291
362
  else:
292
363
  return psi
293
364
 
365
+ if not isinstance(internal.idx, jax.Array):
366
+ raise ValueError
367
+ if not isinstance(internal.inv, jax.Array):
368
+ raise ValueError
369
+
370
+ nflips = update_mode["nflips"]
294
371
  nhops = nflips // 2
295
372
  idx_annihilate, idx_create = changed_inds(s, s_old, nhops)
296
373
  idx = internal.idx
@@ -305,7 +382,7 @@ class PfBackflow(RefModel):
305
382
  U_mean = (U0[new_idx, :] + U0[idx, :]) / 2
306
383
  x = jnp.einsum("im,mn,jn->ij", U_diff, J0, U_mean).T
307
384
 
308
- U1 = self.net(s)
385
+ U1 = jnp.asarray(self.net(s))
309
386
  U1 = U1.reshape(-1, get_sites().Nfmodes).astype(self.dtype)
310
387
  U1 = U1.T[new_idx, :]
311
388
  W = self.W