Aleksei Ustimenko
Initial HamiltonZero release
5ccb4fd
Raw
History Blame Contribute Delete
5.14 kB
# Copyright (c) 2026 Simulacra Research Inc.
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from typing import Any, Callable
import jax
import jax.numpy as jnp
from jaxtyping import Array, Complex, Float
from hamiltonzero.compiled.execute import execute_wavefunction
from hamiltonzero.energy.custom_lap import (
custom_forward_laplacian_with_jac,
use_custom_lap,
)
def _right_su2_chart_jet(
q: Float[Array, "N 4"],
z: Float[Array, "N 3"],
) -> Float[Array, "N 4"]:
q0, q1, q2, q3 = q[:, 0], q[:, 1], q[:, 2], q[:, 3]
zx, zy, zz = z[:, 0], z[:, 1], z[:, 2]
u0 = 1.0 - 0.5 * (zx * zx + zy * zy + zz * zz)
u1, u2, u3 = zz, zy, zx
return jnp.stack(
[
q0 * u0 - q1 * u1 - q2 * u2 - q3 * u3,
q0 * u1 + q1 * u0 + q2 * u3 - q3 * u2,
q0 * u2 - q1 * u3 + q2 * u0 + q3 * u1,
q0 * u3 + q1 * u2 - q2 * u1 + q3 * u0,
],
axis=-1,
)
def _custom_lap_single_finetune(
model: Callable,
q: Float[Array, "N 4"],
t,
energy_frame: Any,
):
if len(energy_frame.one_body_fields) != 1:
raise ValueError("custom-Laplacian energy requires exactly one one-body field")
return _custom_lap_single_from_frame(
model,
None,
q,
t,
J_eff=energy_frame.custom_lap_J_eff,
W_levels=energy_frame.w_levels,
field_xyz=energy_frame.one_body_fields[0],
radial_const=energy_frame.custom_lap_radial_const,
)
def _custom_lap_single_prebuilt(
model: Callable,
q: Float[Array, "N 4"],
t,
frame: Any,
):
if len(frame.one_body_fields) != 1:
raise ValueError("custom-Laplacian energy requires exactly one one-body field")
return _custom_lap_single_from_frame(
model,
None,
q,
t,
J_eff=frame.custom_lap_J_eff,
W_levels=frame.w_levels,
field_xyz=frame.one_body_fields[0],
radial_const=frame.custom_lap_radial_const,
)
def _custom_lap_single_from_frame(
model: Callable,
ctx: Any,
q: Float[Array, "N 4"],
t,
*,
J_eff,
W_levels,
field_xyz,
radial_const,
):
N = q.shape[0]
def f_entry(z):
q_pert = _right_su2_chart_jet(q, z)
re, im = model(q_pert, ctx, t)
return jnp.stack([re, im])
with use_custom_lap():
_value, jac_pair, lap_pair = custom_forward_laplacian_with_jac(
f_entry,
W_levels,
N,
)(jnp.zeros((N, 3), dtype=q.dtype))
tr_total = lap_pair[0] + 1j * lap_pair[1]
g_lie = jac_pair[:, 0] + 1j * jac_pair[:, 1]
quad_total = jnp.einsum("a,ab,b->", g_lie, J_eff.astype(g_lie.dtype), g_lie)
la_xyz = 0.5 * g_lie.reshape(N, 3)
field = (1j * jnp.einsum("ic,ic->", field_xyz.astype(g_lie.dtype), la_xyz)).astype(
g_lie.dtype
)
total = tr_total + quad_total + radial_const.astype(g_lie.dtype) + field
zero = jnp.zeros_like(total)
exchange = total - field
return total, exchange, zero, field
def _vmc_energy_custom_lap_finetune(
model: Callable,
energy_frame: Any,
q: Float[Array, "... N 4"],
t=0.0,
*,
chunk_size: int | None = None,
) -> tuple[
Complex[Array, "..."],
Complex[Array, "..."],
Complex[Array, "..."],
Complex[Array, "..."],
]:
def single(qq, tt):
return _custom_lap_single_finetune(model, qq, tt, energy_frame)
return _run_custom_lap_batch(single, q, t, chunk_size)
def _vmc_energy_custom_lap_prebuilt(
kernel: Any,
tree: Any,
energy_frame: Any,
q: Float[Array, "... N 4"],
*,
chunk_size: int | None = None,
) -> tuple[
Complex[Array, "..."],
Complex[Array, "..."],
Complex[Array, "..."],
Complex[Array, "..."],
]:
def model(q_pert, _ctx, _t):
return execute_wavefunction(kernel, tree, q_pert)
def single(qq, tt):
return _custom_lap_single_prebuilt(model, qq, tt, energy_frame)
return _run_custom_lap_batch(single, q, 0.0, chunk_size)
def _run_custom_lap_batch(single, q, t, chunk_size):
n_sites, n_dims = q.shape[-2], q.shape[-1]
assert n_dims == 4, f"expected quaternion last dim 4, got {n_dims}"
lead = q.shape[:-2]
n_items = 1
for d in lead:
n_items *= d
q_flat = q.reshape(n_items, n_sites, 4)
t_arr = jnp.asarray(t, dtype=q.dtype)
t_bcast = jnp.broadcast_to(t_arr, lead if lead else ())
t_flat = t_bcast.reshape(n_items) if lead else jnp.broadcast_to(t_arr, (n_items,))
with jax.default_matmul_precision("highest"):
if chunk_size is None or chunk_size >= n_items:
total, exchange, casimir, field = jax.vmap(single)(q_flat, t_flat)
else:
total, exchange, casimir, field = jax.lax.map(
lambda x: single(x[0], x[1]),
(q_flat, t_flat),
batch_size=chunk_size,
)
out_shape = lead if lead else ()
return (
total.reshape(out_shape),
exchange.reshape(out_shape),
casimir.reshape(out_shape),
field.reshape(out_shape),
)
__all__ = []