Aleksei Ustimenko
Initial HamiltonZero release
5ccb4fd
Raw
History Blame Contribute Delete
13.8 kB
# Copyright (c) 2026 Simulacra Research Inc.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import equinox as eqx
import jax
import jax.numpy as jnp
import numpy as np
from jaxtyping import Array, Float, Int
def _balanced_mask(mask: Int[Array, "n"]) -> Int[Array, "n"]:
width = int(mask.shape[-1])
n_real = jnp.sum(mask.astype(jnp.int32))
max_power = max(1, (width - 1).bit_length())
powers = 2 ** jnp.arange(max_power + 1, dtype=jnp.int32)
sentinel = jnp.asarray(1 << 30, dtype=jnp.int32)
next_power = jnp.min(jnp.where(powers >= jnp.maximum(n_real, 1), powers, sentinel))
return (jnp.arange(width, dtype=jnp.int32) < next_power).astype(jnp.int32)
_EPS_ABC = jnp.asarray(
[
[[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]],
[[0.0, 0.0, -1.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]],
[[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
],
dtype=jnp.float32,
)
def _real_dtype(dtype):
return jnp.real(jnp.zeros((), dtype)).dtype
def _host_eigvalsh(x):
out_dtype = _real_dtype(x.dtype)
out_shape = jax.ShapeDtypeStruct(x.shape[:-1], out_dtype)
def callback(a):
values = np.linalg.eigvalsh(np.asarray(a))
return values.astype(np.dtype(out_dtype))
return jax.pure_callback(
callback,
out_shape,
x,
vmap_method="sequential",
)
def _compute_J_double_prime_batched(
J_full: Float[Array, "s n n 3 3"],
h: Float[Array, "s n 3"],
mask: Int[Array, "s n"],
) -> tuple[Float[Array, "s n n 10"], Float[Array, "s"]]:
n = J_full.shape[1]
n_systems = J_full.shape[0]
dtype = J_full.dtype
J_filled = (J_full + jnp.conj(jnp.transpose(J_full, (0, 2, 1, 4, 3)))) / 2.0
eps_abc = _EPS_ABC.astype(_real_dtype(dtype))
M_h = jnp.einsum("abc,sic->siab", eps_abc, h.astype(eps_abc.dtype)) * 0.5
M_h = M_h * mask.astype(M_h.dtype)[:, :, None, None]
diagonal = jnp.arange(n)
complex_dtype = jnp.result_type(J_filled.dtype, jnp.complex64)
M_h_diagonal = (
jnp.zeros(J_filled.shape, dtype=complex_dtype)
.at[:, diagonal, diagonal]
.set((2j * M_h).astype(complex_dtype))
)
J_for_norm = J_filled.astype(complex_dtype) + M_h_diagonal
J_matrix = jnp.transpose(J_for_norm, (0, 1, 3, 2, 4)).reshape(
n_systems, 3 * n, 3 * n
)
eigh_epsilon = jnp.asarray(1e-6, dtype=_real_dtype(J_matrix.dtype))
eye_3n = jnp.eye(3 * n, dtype=J_matrix.dtype)
eigenvalues = _host_eigvalsh(J_matrix + eigh_epsilon * eye_3n[None]) - eigh_epsilon
s_norm = jnp.maximum(
jnp.max(jnp.abs(eigenvalues), axis=-1),
jnp.asarray(1e-12, eigenvalues.dtype),
).astype(dtype)
J_normalized = J_filled / s_norm[:, None, None, None, None]
J_normalized = J_normalized.at[:, diagonal, diagonal, :, :].set(0.0)
J_flat = jnp.real(J_normalized).reshape(n_systems, n, n, 9).astype(dtype)
identity_column = jnp.broadcast_to(
jnp.eye(n, dtype=dtype)[None, ..., None],
(n_systems, n, n, 1),
)
return jnp.concatenate([J_flat, identity_column], axis=-1), s_norm
def _compute_J_double_prime(
J_full: Float[Array, "n n 3 3"],
h: Float[Array, "n 3"],
mask: Int[Array, "n"],
) -> tuple[Float[Array, "n n 10"], Float[Array, ""]]:
J_double_prime, s_norm = _compute_J_double_prime_batched(
J_full[None], h[None], mask[None]
)
return J_double_prime[0], s_norm[0]
class SpinContext(eqx.Module):
mask: Int[Array, "n"]
bmask: Int[Array, "n"]
J_double_prime: Float[Array, "n n 10"]
s_norm: Float[Array, ""]
h_prime: Float[Array, "n 3"]
route_quotient_node_key: Int[Array, "n"]
route_quotient_edge_key: Int[Array, "n n"]
needs_fwl2: Array
route_perm: Int[Array, "n"]
def __init__(
self,
J_full: Float[Array, "n n 3 3"],
h: Float[Array, "n 3"],
mask: Int[Array, "n"],
*,
needs_fwl2: Array | bool,
) -> None:
from .route_quotient import route_quotient_keys
self.mask = mask.astype(jnp.int32)
self.bmask = _balanced_mask(self.mask)
self.J_double_prime, self.s_norm = _compute_J_double_prime(J_full, h, self.mask)
self.h_prime = h / jnp.real(self.s_norm).astype(h.dtype)
(
self.route_quotient_node_key,
self.route_quotient_edge_key,
) = route_quotient_keys(J_full, h, self.mask, self.bmask)
self.needs_fwl2 = jnp.asarray(needs_fwl2, dtype=jnp.bool_)
self.route_perm = jnp.arange(self.mask.shape[0], dtype=jnp.int32)
@classmethod
def from_precomputed(
cls,
*,
mask,
bmask,
J_double_prime,
s_norm,
h_prime,
route_quotient_node_key,
route_quotient_edge_key,
needs_fwl2,
route_perm,
) -> "SpinContext":
self = object.__new__(cls)
fields = {
"mask": mask,
"bmask": bmask,
"J_double_prime": J_double_prime,
"s_norm": s_norm,
"h_prime": h_prime,
"route_quotient_node_key": route_quotient_node_key,
"route_quotient_edge_key": route_quotient_edge_key,
"needs_fwl2": needs_fwl2,
"route_perm": route_perm,
}
for name, value in fields.items():
dtype = (
jnp.bool_
if name == "needs_fwl2"
else jnp.int32
if name
in {
"mask",
"bmask",
"route_quotient_node_key",
"route_quotient_edge_key",
"route_perm",
}
else None
)
object.__setattr__(self, name, jnp.asarray(value, dtype=dtype))
return self
@property
def n_sites(self) -> int:
return int(self.mask.shape[0])
class MultiSystemContext(eqx.Module):
mask: Int[Array, "s n"]
bmask: Int[Array, "s n"]
J_double_prime: Float[Array, "s n n 10"]
s_norm: Float[Array, "s"]
h_prime: Float[Array, "s n 3"]
route_quotient_node_key: Int[Array, "s n"]
route_quotient_edge_key: Int[Array, "s n n"]
needs_fwl2: Array
route_perm: Int[Array, "s n"]
def __init__(
self,
J_full: Float[Array, "s n n 3 3"],
h: Float[Array, "s n 3"],
mask: Int[Array, "s n"],
*,
needs_fwl2: Array | bool,
) -> None:
from .route_quotient import route_quotient_keys
self.mask = mask.astype(jnp.int32)
self.bmask = jax.vmap(_balanced_mask)(self.mask)
self.J_double_prime, self.s_norm = _compute_J_double_prime_batched(
J_full, h, self.mask
)
self.h_prime = h / jnp.real(self.s_norm).astype(h.dtype)[:, None, None]
n_systems = self.mask.shape[0]
(
self.route_quotient_node_key,
self.route_quotient_edge_key,
) = jax.jit(jax.vmap(route_quotient_keys))(J_full, h, self.mask, self.bmask)
self.needs_fwl2 = jnp.broadcast_to(
jnp.asarray(needs_fwl2, dtype=jnp.bool_),
(n_systems,),
)
self.route_perm = jnp.broadcast_to(
jnp.arange(self.mask.shape[1], dtype=jnp.int32)[None, :],
self.mask.shape,
)
@classmethod
def from_precomputed(
cls,
*,
mask,
bmask,
J_double_prime,
s_norm,
h_prime,
route_quotient_node_key,
route_quotient_edge_key,
needs_fwl2,
route_perm,
) -> "MultiSystemContext":
self = object.__new__(cls)
fields = {
"mask": mask,
"bmask": bmask,
"J_double_prime": J_double_prime,
"s_norm": s_norm,
"h_prime": h_prime,
"route_quotient_node_key": route_quotient_node_key,
"route_quotient_edge_key": route_quotient_edge_key,
"needs_fwl2": needs_fwl2,
"route_perm": route_perm,
}
for name, value in fields.items():
dtype = (
jnp.bool_
if name == "needs_fwl2"
else jnp.int32
if name
in {
"mask",
"bmask",
"route_quotient_node_key",
"route_quotient_edge_key",
"route_perm",
}
else None
)
object.__setattr__(self, name, jnp.asarray(value, dtype=dtype))
return self
@classmethod
def from_single(cls, context: SpinContext) -> "MultiSystemContext":
return cls.from_precomputed(
mask=context.mask[None],
bmask=context.bmask[None],
J_double_prime=context.J_double_prime[None],
s_norm=context.s_norm[None],
h_prime=context.h_prime[None],
route_quotient_node_key=context.route_quotient_node_key[None],
route_quotient_edge_key=context.route_quotient_edge_key[None],
needs_fwl2=context.needs_fwl2[None],
route_perm=context.route_perm[None],
)
@classmethod
def stack(cls, contexts: list[SpinContext]) -> "MultiSystemContext":
if not contexts:
raise ValueError("MultiSystemContext.stack requires a context")
widths = [int(context.mask.shape[0]) for context in contexts]
n_max = max(widths)
def pad_sites(value, n):
return (
value
if n == n_max
else jnp.pad(value, ((0, n_max - n),) + ((0, 0),) * (value.ndim - 1))
)
def pad_pairs(value, n):
padding = n_max - n
return (
value
if padding == 0
else jnp.pad(
value,
((0, padding), (0, padding)) + ((0, 0),) * (value.ndim - 2),
)
)
mask = jnp.stack(
[
pad_sites(context.mask, width)
for context, width in zip(contexts, widths, strict=True)
]
)
bmask = jax.vmap(_balanced_mask)(mask)
J_double_prime = jnp.stack(
[
pad_pairs(context.J_double_prime, width)
for context, width in zip(contexts, widths, strict=True)
]
)
diagonal = jnp.arange(n_max, dtype=jnp.int32)
J_double_prime = J_double_prime.at[:, diagonal, diagonal, 9].set(1.0)
s_norm = jnp.stack([context.s_norm for context in contexts])
h_prime = jnp.stack(
[
pad_sites(context.h_prime, width)
for context, width in zip(contexts, widths, strict=True)
]
)
edge_shapes = {
tuple(context.route_quotient_edge_key.shape) for context in contexts
}
compact_edges = all(shape == (0, 0) for shape in edge_shapes)
full_edges = all(
shape == (width, width)
for shape, width in zip(
[context.route_quotient_edge_key.shape for context in contexts],
widths,
strict=True,
)
)
if not (compact_edges or full_edges):
raise ValueError("cannot stack mixed quotient edge carriers")
route_edge = (
jnp.zeros((len(contexts), 0, 0), dtype=jnp.int32)
if compact_edges
else jnp.stack(
[
pad_pairs(context.route_quotient_edge_key, width)
for context, width in zip(contexts, widths, strict=True)
]
)
)
def pad_node_key(value, n):
padding = n_max - n
return (
value
if padding == 0
else jnp.pad(value, ((0, padding),), constant_values=-1)
)
def pad_perm(value, n):
if n == n_max:
return value
return jnp.concatenate([value, jnp.arange(n, n_max, dtype=value.dtype)])
return cls.from_precomputed(
mask=mask,
bmask=bmask,
J_double_prime=J_double_prime,
s_norm=s_norm,
h_prime=h_prime,
route_quotient_node_key=jnp.stack(
[
pad_node_key(context.route_quotient_node_key, width)
for context, width in zip(contexts, widths, strict=True)
]
),
route_quotient_edge_key=route_edge,
needs_fwl2=jnp.stack([context.needs_fwl2 for context in contexts]),
route_perm=jnp.stack(
[
pad_perm(context.route_perm, width)
for context, width in zip(contexts, widths, strict=True)
]
),
)
def select(self, system_id: int) -> SpinContext:
return SpinContext.from_precomputed(
mask=self.mask[system_id],
bmask=self.bmask[system_id],
J_double_prime=self.J_double_prime[system_id],
s_norm=self.s_norm[system_id],
h_prime=self.h_prime[system_id],
route_quotient_node_key=self.route_quotient_node_key[system_id],
route_quotient_edge_key=self.route_quotient_edge_key[system_id],
needs_fwl2=self.needs_fwl2[system_id],
route_perm=self.route_perm[system_id],
)
@property
def n_systems(self) -> int:
return int(self.mask.shape[0])
@property
def n_sites(self) -> int:
return int(self.mask.shape[1])
__all__ = [
"MultiSystemContext",
"SpinContext",
]