Skip to content

Commit a6aa631

Browse files
committed
Merge branch 'main' into dev/rename-preparse-to-default-name
2 parents 79e3c99 + ac2e41b commit a6aa631

File tree

7 files changed

+860
-8
lines changed

7 files changed

+860
-8
lines changed

qmb/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tyro
88
from . import openfermion as _ # type: ignore[no-redef]
99
from . import fcidump as _ # type: ignore[no-redef]
10+
from . import hubbard as _ # type: ignore[no-redef]
1011
from . import ising as _ # type: ignore[no-redef]
1112
from . import vmc as _ # type: ignore[no-redef]
1213
from . import imag as _ # type: ignore[no-redef]

qmb/_hamiltonian_cpu.cpp

Lines changed: 593 additions & 0 deletions
Large diffs are not rendered by default.

qmb/_hamiltonian_cuda.cu

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ __device__ bool get_bit(std::uint8_t* data, std::uint8_t index) {
4242
return ((*data) >> index) & 1;
4343
}
4444

45-
__device__ bool set_bit(std::uint8_t* data, std::uint8_t index, bool value) {
45+
__device__ void set_bit(std::uint8_t* data, std::uint8_t index, bool value) {
4646
if (value) {
4747
*data |= (1 << index);
4848
} else {

qmb/hamiltonian.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def _load_module(cls, n_qubytes: int = 0, particle_cut: int = 0) -> object:
2727
name=name,
2828
sources=[
2929
f"{folder}/_hamiltonian.cpp",
30+
f"{folder}/_hamiltonian_cpu.cpp",
3031
f"{folder}/_hamiltonian_cuda.cu",
3132
],
3233
is_python_module=n_qubytes == 0,

qmb/hubbard.py

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
"""
2+
This file offers an interface for defining Hubbard models on a two-dimensional lattice.
3+
"""
4+
5+
import typing
6+
import logging
7+
import dataclasses
8+
import torch
9+
import tyro
10+
from .mlp import WaveFunctionElectronUpDown as MlpWaveFunction
11+
from .attention import WaveFunctionElectronUpDown as AttentionWaveFunction
12+
from .hamiltonian import Hamiltonian
13+
from .model_dict import model_dict, ModelProto, NetworkProto, NetworkConfigProto
14+
15+
16+
@dataclasses.dataclass
17+
class ModelConfig:
18+
"""
19+
The configuration for the Hubbard model.
20+
"""
21+
22+
# The width of the hubbard lattice
23+
m: typing.Annotated[int, tyro.conf.Positional]
24+
# The height of the hubbard lattice
25+
n: typing.Annotated[int, tyro.conf.Positional]
26+
27+
# The coefficient of t
28+
t: typing.Annotated[float, tyro.conf.arg(aliases=["-t"])] = 1
29+
# The coefficient of U
30+
u: typing.Annotated[float, tyro.conf.arg(aliases=["-u"])] = 0
31+
32+
# The electron number
33+
electron_number: typing.Annotated[int | None, tyro.conf.arg(aliases=["-e"])] = None
34+
35+
# The ref energy of the model
36+
ref_energy: typing.Annotated[float, tyro.conf.arg(aliases=["-r"])] = 0
37+
38+
def __post_init__(self) -> None:
39+
if self.electron_number is None:
40+
self.electron_number = self.m * self.n
41+
logging.info("Electron number is not specified, set to half-filling (one electron per lattice site) %d", self.electron_number)
42+
43+
if self.m <= 0 or self.n <= 0:
44+
raise ValueError("The dimensions of the Hubbard model must be positive integers.")
45+
46+
if self.electron_number < 0 or self.electron_number > 2 * self.m * self.n:
47+
raise ValueError(f"The electron number {self.electron_number} is out of bounds for a {self.m}x{self.n} lattice. Each site can host up to two electrons (spin up and spin down).")
48+
49+
50+
class Model(ModelProto[ModelConfig]):
51+
"""
52+
This class handles the Hubbard model.
53+
"""
54+
55+
network_dict: dict[str, type[NetworkConfigProto["Model"]]] = {}
56+
57+
config_t = ModelConfig
58+
59+
@classmethod
60+
def preparse(cls, input_args: tuple[str, ...]) -> str:
61+
args = tyro.cli(ModelConfig, args=input_args)
62+
return f"Hubbard_{args.m}x{args.n}_t{args.t}_u{args.u}"
63+
64+
@classmethod
65+
def _prepare_hamiltonian(cls, args: ModelConfig) -> dict[tuple[tuple[int, int], ...], complex]:
66+
67+
def _index(i: int, j: int, o: int) -> int:
68+
return (i + j * args.m) * 2 + o
69+
70+
hamiltonian_dict: dict[tuple[tuple[int, int], ...], complex] = {}
71+
for i in range(args.m):
72+
for j in range(args.n):
73+
# On-site interaction
74+
hamiltonian_dict[(_index(i, j, 0), 1), (_index(i, j, 0), 0), (_index(i, j, 1), 1), (_index(i, j, 1), 0)] = args.u
75+
76+
# Nearest neighbor hopping
77+
if i != 0:
78+
hamiltonian_dict[(_index(i, j, 0), 1), (_index(i - 1, j, 0), 0)] = -args.t
79+
hamiltonian_dict[(_index(i - 1, j, 0), 1), (_index(i, j, 0), 0)] = -args.t
80+
hamiltonian_dict[(_index(i, j, 1), 1), (_index(i - 1, j, 1), 0)] = -args.t
81+
hamiltonian_dict[(_index(i - 1, j, 1), 1), (_index(i, j, 1), 0)] = -args.t
82+
if j != 0:
83+
hamiltonian_dict[(_index(i, j, 0), 1), (_index(i, j - 1, 0), 0)] = -args.t
84+
hamiltonian_dict[(_index(i, j - 1, 0), 1), (_index(i, j, 0), 0)] = -args.t
85+
hamiltonian_dict[(_index(i, j, 1), 1), (_index(i, j - 1, 1), 0)] = -args.t
86+
hamiltonian_dict[(_index(i, j - 1, 1), 1), (_index(i, j, 1), 0)] = -args.t
87+
88+
return hamiltonian_dict
89+
90+
def __init__(self, args: ModelConfig):
91+
logging.info("Input arguments successfully parsed")
92+
93+
assert args.electron_number is not None
94+
self.m: int = args.m
95+
self.n: int = args.n
96+
self.electron_number: int = args.electron_number
97+
logging.info("Constructing Hubbard model with dimensions: width = %d, height = %d", self.m, self.n)
98+
logging.info("The parameters of the model are: t = %.10f, U = %.10f, N = %d", args.t, args.u, args.electron_number)
99+
100+
logging.info("Initializing Hamiltonian for the lattice")
101+
hamiltonian_dict: dict[tuple[tuple[int, int], ...], complex] = self._prepare_hamiltonian(args)
102+
logging.info("Hamiltonian initialization complete")
103+
104+
self.ref_energy: float = args.ref_energy
105+
logging.info("The ref energy is set to %.10f", self.ref_energy)
106+
107+
logging.info("Converting the Hamiltonian to internal Hamiltonian representation")
108+
self.hamiltonian: Hamiltonian = Hamiltonian(hamiltonian_dict, kind="fermi")
109+
logging.info("Internal Hamiltonian representation for model has been successfully created")
110+
111+
def apply_within(self, configs_i: torch.Tensor, psi_i: torch.Tensor, configs_j: torch.Tensor) -> torch.Tensor:
112+
return self.hamiltonian.apply_within(configs_i, psi_i, configs_j)
113+
114+
def find_relative(self, configs_i: torch.Tensor, psi_i: torch.Tensor, count_selected: int, configs_exclude: torch.Tensor | None = None) -> torch.Tensor:
115+
return self.hamiltonian.find_relative(configs_i, psi_i, count_selected, configs_exclude)
116+
117+
def single_relative(self, configs: torch.Tensor) -> torch.Tensor:
118+
return self.hamiltonian.single_relative(configs)
119+
120+
def show_config(self, config: torch.Tensor) -> str:
121+
string = "".join(f"{i:08b}"[::-1] for i in config.cpu().numpy())
122+
return "[" + ".".join("".join(self._show_config_site(string[(i + j * self.m) * 2:(i + j * self.m) * 2 + 2]) for i in range(self.m)) for j in range(self.n)) + "]"
123+
124+
def _show_config_site(self, string: str) -> str:
125+
match string:
126+
case "00":
127+
return " "
128+
case "10":
129+
return "↑"
130+
case "01":
131+
return "↓"
132+
case "11":
133+
return "↕"
134+
case _:
135+
raise ValueError(f"Invalid string: {string}")
136+
137+
138+
model_dict["hubbard"] = Model
139+
140+
141+
@dataclasses.dataclass
142+
class MlpConfig:
143+
"""
144+
The configuration of the MLP network.
145+
"""
146+
147+
# The hidden widths of the network
148+
hidden: typing.Annotated[tuple[int, ...], tyro.conf.arg(aliases=["-w"])] = (512,)
149+
150+
def create(self, model: Model) -> NetworkProto:
151+
"""
152+
Create a MLP network for the model.
153+
"""
154+
logging.info("Hidden layer widths: %a", self.hidden)
155+
156+
network = MlpWaveFunction(
157+
double_sites=model.m * model.n * 2,
158+
physical_dim=2,
159+
is_complex=True,
160+
spin_up=model.electron_number // 2,
161+
spin_down=model.electron_number - model.electron_number // 2,
162+
hidden_size=self.hidden,
163+
ordering=+1,
164+
)
165+
166+
return network
167+
168+
169+
Model.network_dict["mlp"] = MlpConfig
170+
171+
172+
@dataclasses.dataclass
173+
class AttentionConfig:
174+
"""
175+
The configuration of the attention network.
176+
"""
177+
178+
# Embedding dimension
179+
embedding_dim: typing.Annotated[int, tyro.conf.arg(aliases=["-e"])] = 512
180+
# Heads number
181+
heads_num: typing.Annotated[int, tyro.conf.arg(aliases=["-m"])] = 8
182+
# Feedforward dimension
183+
feed_forward_dim: typing.Annotated[int, tyro.conf.arg(aliases=["-f"])] = 2048
184+
# Shared expert number
185+
shared_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-s"])] = 1
186+
# Routed expert number
187+
routed_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-r"])] = 0
188+
# Selected expert number
189+
selected_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-c"])] = 0
190+
# Network depth
191+
depth: typing.Annotated[int, tyro.conf.arg(aliases=["-d"])] = 6
192+
193+
def create(self, model: Model) -> NetworkProto:
194+
"""
195+
Create an attention network for the model.
196+
"""
197+
logging.info(
198+
"Attention network configuration: "
199+
"embedding dimension: %d, "
200+
"number of heads: %d, "
201+
"feed-forward dimension: %d, "
202+
"shared expert number: %d, "
203+
"routed expert number: %d, "
204+
"selected expert number: %d, "
205+
"depth: %d",
206+
self.embedding_dim,
207+
self.heads_num,
208+
self.feed_forward_dim,
209+
self.shared_expert_num,
210+
self.routed_expert_num,
211+
self.selected_expert_num,
212+
self.depth,
213+
)
214+
215+
network = AttentionWaveFunction(
216+
double_sites=model.m * model.n * 2,
217+
physical_dim=2,
218+
is_complex=True,
219+
spin_up=model.electron_number // 2,
220+
spin_down=model.electron_number - model.electron_number // 2,
221+
embedding_dim=self.embedding_dim,
222+
heads_num=self.heads_num,
223+
feed_forward_dim=self.feed_forward_dim,
224+
shared_num=self.shared_expert_num,
225+
routed_num=self.routed_expert_num,
226+
selected_num=self.selected_expert_num,
227+
depth=self.depth,
228+
ordering=+1,
229+
)
230+
231+
return network
232+
233+
234+
Model.network_dict["attention"] = AttentionConfig

qmb/ising.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
This file offers a interface for defining Ising-like models on a two-dimensional lattice.
2+
This file offers an interface for defining Ising-like models on a two-dimensional lattice.
33
"""
44

55
import typing
@@ -194,12 +194,6 @@ def _z(i: int, j: int) -> tuple[tuple[tuple[tuple[int, int], ...], complex], ...
194194

195195
def __init__(self, args: ModelConfig) -> None:
196196
logging.info("Input arguments successfully parsed")
197-
logging.info("Grid dimensions: width = %d, height = %d", args.m, args.n)
198-
logging.info("Element-wise coefficients: X = %.10f, Y = %.10f, Z = %.10f", args.x, args.y, args.z)
199-
logging.info("Horizontal bond coefficients: X = %.10f, Y = %.10f, Z = %.10f", args.xh, args.yh, args.zh)
200-
logging.info("Vertical bond coefficients: X = %.10f, Y = %.10f, Z = %.10f", args.xv, args.yv, args.zv)
201-
logging.info("Diagonal bond coefficients: X = %.10f, Y = %.10f, Z = %.10f", args.xd, args.yd, args.zd)
202-
logging.info("Anti-diagonal bond coefficients: X = %.10f, Y = %.10f, Z = %.10f", args.xa, args.ya, args.za)
203197

204198
self.m: int = args.m
205199
self.n: int = args.n

qmb/model_dict.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,40 @@ class NetworkProto(typing.Protocol):
1616
def __call__(self, x: torch.Tensor) -> torch.Tensor:
1717
"""
1818
Calculate the amplitude for the given configurations.
19+
20+
Parameters
21+
----------
22+
x : torch.Tensor
23+
The configurations to calculate the amplitude for.
24+
The configurations are a two-dimensional uint8 tensor with first dimension equal to some batch size.
25+
The second dimension contains occupation for each qubit which is bitwise encoded.
26+
27+
Returns
28+
-------
29+
torch.Tensor
30+
The amplitudes of the configurations.
31+
The amplitudes are a one-dimensional complex tensor with the only dimension equal to the batch size.
1932
"""
2033

2134
def generate_unique(self, batch_size: int, block_num: int = 1) -> tuple[torch.Tensor, torch.Tensor, None, None]:
2235
"""
2336
Generate a batch of unique configurations.
37+
38+
Parameters
39+
----------
40+
batch_size : int
41+
The number of configurations to generate.
42+
block_num : int, default=1
43+
The number of batch block to generate. It is used to split the batch into smaller parts to avoid memory issues.
44+
45+
Returns
46+
-------
47+
tuple[torch.Tensor, torch.Tensor, None, None]
48+
A tuple containing the generated configurations, their amplitudes, and two None values.
49+
The configurations are a two-dimensional uint8 tensor with first dimension equal to `batch_size`.
50+
The second dimension contains occupation for each qubit which is bitwise encoded.
51+
The amplitudes are a one-dimensional complex tensor with the only dimension equal to `batch_size`.
52+
The last two None values are reserved for future use.
2453
"""
2554

2655
def load_state_dict(self, data: dict[str, torch.Tensor]) -> typing.Any:

0 commit comments

Comments
 (0)