| |
| |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass, field |
| from pathlib import Path |
| from typing import Any, NamedTuple |
|
|
| import jax |
| import jax.numpy as jnp |
| import numpy as np |
|
|
| from hamiltonzero.config import EnergyConfig, EvalMCMCConfig, ModelConfig |
| from hamiltonzero.evaluation.runtime import DefaultEvalBackend |
| from hamiltonzero.evaluation.runner import _compose_walker_route |
| from hamiltonzero.hamiltonian import SpinHamiltonian |
| from hamiltonzero.observables import local_spin |
|
|
|
|
| class EnergySamples(NamedTuple): |
| total: Any |
| exchange: Any |
| casimir: Any |
| field: Any |
|
|
|
|
| class CompiledOrder(NamedTuple): |
| leaf_to_input: Any |
| input_to_leaf: Any |
|
|
|
|
| @dataclass(slots=True) |
| class _InferenceRuntime: |
| backend: DefaultEvalBackend |
| wavefunction: Any = None |
| context: Any = None |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class PreparedInference: |
| wavefunction: Any = field(repr=False) |
| context: Any = field(repr=False) |
| energy_frame: Any = field(repr=False) |
| route: Any |
| route_log_probability: float |
| _initial_context: Any = field(repr=False) |
| _walker_route: Any = field(repr=False) |
| _runtime: _InferenceRuntime = field(repr=False) |
|
|
|
|
| def prepare( |
| system: SpinHamiltonian, |
| checkpoint: str | Path, |
| model_key, |
| *, |
| model: ModelConfig = ModelConfig(), |
| route_temperature: float = 4.0, |
| eps: float = 0.1, |
| ) -> tuple[PreparedInference, CompiledOrder]: |
| if not isinstance(system, SpinHamiltonian): |
| raise TypeError("system must be a SpinHamiltonian") |
| temperature = float(route_temperature) |
| if not np.isfinite(temperature) or temperature <= 0.0: |
| raise ValueError("route_temperature must be finite and positive") |
| backend = DefaultEvalBackend() |
| initial_context = backend.build_system(system, EnergyConfig(eps=float(eps))) |
| foundation = backend.load_model( |
| Path(checkpoint), |
| model, |
| model_key, |
| initial_context, |
| contextualizer_attention=None, |
| ) |
| if backend.embedded_route(foundation) is not None: |
| raise ValueError("prepare requires a foundation router checkpoint") |
| canonical = backend.canonicalize_context(initial_context) |
| candidates = backend.beam_candidates( |
| foundation, |
| canonical.context, |
| beam_width=8, |
| top_k=1, |
| temperature=temperature, |
| ) |
| permutations = jnp.asarray(candidates.permutations, dtype=jnp.int32) |
| if permutations.ndim != 3 or permutations.shape[:2] != (1, 1): |
| raise ValueError("router must return one route with shape [1, 1, N]") |
| route = permutations[:, 0] |
| walker_route = _compose_walker_route(canonical.old_inverse, route) |
| routed_context = backend.route_context( |
| canonical.context, |
| route, |
| compact_custom_lap=False, |
| ) |
| wavefunction = backend.compile_single(foundation, routed_context) |
| compact_context = backend.route_context( |
| canonical.context, |
| route, |
| compact_custom_lap=True, |
| ) |
| backend.block_until_ready(wavefunction) |
| route = route[0] |
| order = CompiledOrder( |
| leaf_to_input=route, |
| input_to_leaf=jnp.argsort(route).astype(jnp.int32), |
| ) |
| prepared = PreparedInference( |
| wavefunction=wavefunction, |
| context=compact_context, |
| energy_frame=compact_context.energy_frame, |
| route=order.leaf_to_input, |
| route_log_probability=float(np.asarray(candidates.log_probabilities)[0, 0]), |
| _initial_context=initial_context, |
| _walker_route=walker_route, |
| _runtime=_InferenceRuntime(backend), |
| ) |
| return prepared, order |
|
|
|
|
| def _mcmc_config( |
| *, |
| batch_size: int, |
| replicas: int, |
| steps: int, |
| burn_in: int, |
| walker_chunk_size: int, |
| burn_in_replica_steps: int = 2, |
| initial_sigma: float = 0.3, |
| initial_haar_sites: int = 1, |
| ) -> EvalMCMCConfig: |
| if int(batch_size) < 1: |
| raise ValueError("batch_size must be positive") |
| if int(replicas) < 2: |
| raise ValueError("replicas must be at least two") |
| if int(steps) < 1: |
| raise ValueError("steps must be positive") |
| if int(burn_in) < 0: |
| raise ValueError("burn_in must be non-negative") |
| if int(walker_chunk_size) < 1: |
| raise ValueError("walker_chunk_size must be positive") |
| if int(burn_in_replica_steps) < 1: |
| raise ValueError("replica_steps must be positive") |
| if not np.isfinite(float(initial_sigma)) or float(initial_sigma) <= 0.0: |
| raise ValueError("initial_sigma must be finite and positive") |
| if int(initial_haar_sites) < 1: |
| raise ValueError("initial_haar_sites must be positive") |
| return EvalMCMCConfig( |
| batch_size=int(batch_size), |
| replicas=int(replicas), |
| steps=int(steps), |
| burn_in=int(burn_in), |
| burn_in_replica_steps=int(burn_in_replica_steps), |
| walker_chunk_size=int(walker_chunk_size), |
| initial_sigma=float(initial_sigma), |
| initial_haar_sites=int(initial_haar_sites), |
| ) |
|
|
|
|
| def burn_in( |
| prepared: PreparedInference, |
| key, |
| *, |
| batch_size: int = 256, |
| replicas: int = 8, |
| burn_in: int = 1024, |
| replica_steps: int = 2, |
| walker_chunk_size: int = 16, |
| initial_sigma: float = 0.3, |
| initial_haar_sites: int = 1, |
| ): |
| config = _mcmc_config( |
| batch_size=batch_size, |
| replicas=replicas, |
| steps=1, |
| burn_in=burn_in, |
| walker_chunk_size=walker_chunk_size, |
| burn_in_replica_steps=replica_steps, |
| initial_sigma=initial_sigma, |
| initial_haar_sites=initial_haar_sites, |
| ) |
| runtime = prepared._runtime |
| backend = runtime.backend |
| state = backend.initialize_mcmc( |
| key, |
| prepared.wavefunction, |
| prepared._initial_context, |
| config, |
| ) |
| state = backend.route_mcmc(state, prepared._walker_route) |
| runtime.wavefunction, runtime.context, state = backend.prepare_singular( |
| prepared.wavefunction, |
| prepared.context, |
| state, |
| ) |
| for _ in range(config.burn_in): |
| state = backend.step_mcmc( |
| state, |
| runtime.wavefunction, |
| runtime.context, |
| replica_steps=config.burn_in_replica_steps, |
| walker_chunk_size=config.walker_chunk_size, |
| ) |
| backend.block_until_ready(backend.cold_walkers(state)) |
| state = backend.adapt_mcmc(state, config) |
| q_cold = backend.cold_walkers(state) |
| backend.block_until_ready(q_cold) |
| return state, q_cold |
|
|
|
|
| def step( |
| prepared: PreparedInference, |
| state, |
| *, |
| steps: int = 24, |
| walker_chunk_size: int = 16, |
| ): |
| config = _mcmc_config( |
| batch_size=int(state.q.shape[0]), |
| replicas=int(state.q.shape[1]), |
| steps=steps, |
| burn_in=0, |
| walker_chunk_size=walker_chunk_size, |
| ) |
| runtime = prepared._runtime |
| backend = runtime.backend |
| if runtime.wavefunction is None or runtime.context is None: |
| raise RuntimeError("burn_in must be called before step") |
| state = backend.step_mcmc( |
| state, |
| runtime.wavefunction, |
| runtime.context, |
| replica_steps=config.steps, |
| walker_chunk_size=config.walker_chunk_size, |
| ) |
| q_cold = backend.cold_walkers(state) |
| backend.block_until_ready(q_cold) |
| state = backend.adapt_mcmc(state, config) |
| return state, q_cold |
|
|
|
|
| def energy( |
| prepared: PreparedInference, |
| q, |
| *, |
| chunk_size: int = 512, |
| ): |
| if int(chunk_size) < 1: |
| raise ValueError("chunk_size must be positive") |
| runtime = prepared._runtime |
| if runtime.wavefunction is None or runtime.context is None: |
| raise RuntimeError("burn_in must be called before energy") |
| values = runtime.backend.custom_lap_energy( |
| runtime.wavefunction, |
| runtime.context, |
| q, |
| EnergyConfig(chunk_size=int(chunk_size)), |
| ) |
| runtime.backend.block_until_ready(values) |
| return EnergySamples(*(value[0] for value in values)) |
|
|
|
|
| def spin( |
| prepared: PreparedInference, |
| q, |
| *, |
| chunk_size: int | None = 512, |
| ): |
| runtime = prepared._runtime |
| if runtime.wavefunction is None or runtime.context is None: |
| raise RuntimeError("burn_in must be called before spin") |
| values = local_spin( |
| runtime.wavefunction, |
| runtime.context, |
| q, |
| chunk_size=chunk_size, |
| ) |
| runtime.backend.block_until_ready(values) |
| return values |
|
|
|
|
| __all__ = [ |
| "EnergySamples", |
| "CompiledOrder", |
| "PreparedInference", |
| "burn_in", |
| "energy", |
| "prepare", |
| "spin", |
| "step", |
| ] |
|
|