Skip to content

Commit 57aae3d

Browse files
committed
Add the Hubbard model description.
1 parent 402ba75 commit 57aae3d

File tree

2 files changed

+237
-1
lines changed

2 files changed

+237
-1
lines changed

qmb/hubbard.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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 > 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.")
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+
logging.info("Grid dimensions: width = %d, height = %d", args.m, args.n)
93+
logging.info("t = %.10f, U = %.10f, N = %d", args.t, args.u, args.electron_number)
94+
95+
assert args.electron_number is not None
96+
self.m: int = args.m
97+
self.n: int = args.n
98+
self.electron_number: int = args.electron_number
99+
logging.info("Constructing Hubbard model with dimensions: width = %d, height = %d", self.m, self.n)
100+
logging.info("The parameters of the model are: t = %.10f, U = %.10f, N = %d", args.t, args.u, args.electron_number)
101+
102+
logging.info("Initializing Hamiltonian for the lattice")
103+
hamiltonian_dict: dict[tuple[tuple[int, int], ...], complex] = self._prepare_hamiltonian(args)
104+
logging.info("Hamiltonian initialization complete")
105+
106+
self.ref_energy: float = args.ref_energy
107+
logging.info("The ref energy is set to %.10f", self.ref_energy)
108+
109+
logging.info("Converting the Hamiltonian to internal Hamiltonian representation")
110+
self.hamiltonian: Hamiltonian = Hamiltonian(hamiltonian_dict, kind="fermi")
111+
logging.info("Internal Hamiltonian representation for model has been successfully created")
112+
113+
def apply_within(self, configs_i: torch.Tensor, psi_i: torch.Tensor, configs_j: torch.Tensor) -> torch.Tensor:
114+
return self.hamiltonian.apply_within(configs_i, psi_i, configs_j)
115+
116+
def find_relative(self, configs_i: torch.Tensor, psi_i: torch.Tensor, count_selected: int, configs_exclude: torch.Tensor | None = None) -> torch.Tensor:
117+
return self.hamiltonian.find_relative(configs_i, psi_i, count_selected, configs_exclude)
118+
119+
def single_relative(self, configs: torch.Tensor) -> torch.Tensor:
120+
return self.hamiltonian.single_relative(configs)
121+
122+
def show_config(self, config: torch.Tensor) -> str:
123+
string = "".join(f"{i:08b}"[::-1] for i in config.cpu().numpy())
124+
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)) + "]"
125+
126+
def _show_config_site(self, string: str) -> str:
127+
match string:
128+
case "00":
129+
return " "
130+
case "10":
131+
return "↑"
132+
case "01":
133+
return "↓"
134+
case "11":
135+
return "↕"
136+
case _:
137+
raise ValueError(f"Invalid string: {string}")
138+
139+
140+
model_dict["hubbard"] = Model
141+
142+
143+
@dataclasses.dataclass
144+
class MlpConfig:
145+
"""
146+
The configuration of the MLP network.
147+
"""
148+
149+
# The hidden widths of the network
150+
hidden: typing.Annotated[tuple[int, ...], tyro.conf.arg(aliases=["-w"])] = (512,)
151+
152+
def create(self, model: Model) -> NetworkProto:
153+
"""
154+
Create a MLP network for the model.
155+
"""
156+
logging.info("Hidden layer widths: %a", self.hidden)
157+
158+
network = MlpWaveFunction(
159+
double_sites=model.m * model.n * 2,
160+
physical_dim=2,
161+
is_complex=True,
162+
spin_up=model.electron_number // 2,
163+
spin_down=model.electron_number - model.electron_number // 2,
164+
hidden_size=self.hidden,
165+
ordering=+1,
166+
)
167+
168+
return network
169+
170+
171+
Model.network_dict["mlp"] = MlpConfig
172+
173+
174+
@dataclasses.dataclass
175+
class AttentionConfig:
176+
"""
177+
The configuration of the attention network.
178+
"""
179+
180+
# Embedding dimension
181+
embedding_dim: typing.Annotated[int, tyro.conf.arg(aliases=["-e"])] = 512
182+
# Heads number
183+
heads_num: typing.Annotated[int, tyro.conf.arg(aliases=["-m"])] = 8
184+
# Feedforward dimension
185+
feed_forward_dim: typing.Annotated[int, tyro.conf.arg(aliases=["-f"])] = 2048
186+
# Shared expert number
187+
shared_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-s"])] = 1
188+
# Routed expert number
189+
routed_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-r"])] = 0
190+
# Selected expert number
191+
selected_expert_num: typing.Annotated[int, tyro.conf.arg(aliases=["-c"])] = 0
192+
# Network depth
193+
depth: typing.Annotated[int, tyro.conf.arg(aliases=["-d"])] = 6
194+
195+
def create(self, model: Model) -> NetworkProto:
196+
"""
197+
Create an attention network for the model.
198+
"""
199+
logging.info(
200+
"Attention network configuration: "
201+
"embedding dimension: %d, "
202+
"number of heads: %d, "
203+
"feed-forward dimension: %d, "
204+
"shared expert number: %d, "
205+
"routed expert number: %d, "
206+
"selected expert number: %d, "
207+
"depth: %d",
208+
self.embedding_dim,
209+
self.heads_num,
210+
self.feed_forward_dim,
211+
self.shared_expert_num,
212+
self.routed_expert_num,
213+
self.selected_expert_num,
214+
self.depth,
215+
)
216+
217+
network = AttentionWaveFunction(
218+
double_sites=model.m * model.n * 2,
219+
physical_dim=2,
220+
is_complex=True,
221+
spin_up=model.electron_number // 2,
222+
spin_down=model.electron_number - model.electron_number // 2,
223+
embedding_dim=self.embedding_dim,
224+
heads_num=self.heads_num,
225+
feed_forward_dim=self.feed_forward_dim,
226+
shared_num=self.shared_expert_num,
227+
routed_num=self.routed_expert_num,
228+
selected_num=self.selected_expert_num,
229+
depth=self.depth,
230+
ordering=+1,
231+
)
232+
233+
return network
234+
235+
236+
Model.network_dict["attention"] = AttentionConfig

qmb/ising.py

Lines changed: 1 addition & 1 deletion
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

0 commit comments

Comments
 (0)