Skip to content

les_alpha: latent polarizability head + induced-dipole LES term - #16

Open
alacour wants to merge 1 commit into
mainfrom
feature/les-alpha
Open

alacour wants to merge 1 commit into
mainfrom
feature/les-alpha

Conversation

@alacour

@alacour alacour commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an optional latent polarizability head to the LES edge read-out, feeding upstream les's non-self-consistent induced-dipole term. This is the Allegro-LES construction from the polarizable-multipole LES paper (Kim, King, Park et al. 2026, arXiv 2605.05746), transplanted onto ECENet's edge frames.

  • les_alpha='iso': one scalar per edge, scatter-summed to a per-atom α_i.
  • les_alpha='aniso': two scalars per edge, a_e·I + b_e·(r̂r̂ᵀ − I/3) — an isotropic part plus a traceless bond-axial part. Symmetric and equivariant by construction; a lone bond gives the uniaxial α_∥/α_⊥ of a diatomic.
  • Packed l0 = [q | u | α]; ecenet.les.unpack_l0 is the one place that knows the layout, model.les_flags carries the flag. α slots are zero-init (not a saddle: E_lr is linear in α); les_charge_scale doesn't touch α.

Energy. The periodic path passes latent_alphas straight to upstream's Ewald. The vectorized isolated path forms the field of the fixed multipoles from the already-built masked f_qu/f_uu kernels and adds −½ E_i·α_i·E_i.

Read-outs. LESLongRange.born_charges() is now the single Z* implementation behind the calculator and eval_spice_bec, going through upstream's forward when α is present so the induced dipoles are part of the polarization. total_polarizability() sums α_i per structure (paper Eq. 29 — exactly dP/dE_ext for this model). The calculator stashes results['les_alphas'] and gains compute_polarizability(atoms). All four trainers and predict_charges take/handle the flag.

Test plan

  • tests/test_les.py: packed layout, α = 0 at init, 'iso' invariant / 'aniso' RαRᵀ-equivariant and symmetric, lone bond uniaxial with r̂ as principal axis, les_charge_scale leaves α alone, batched variants consistent, bad configs rejected
  • tests/test_les.py: vectorized isolated path == upstream loop for iso/aniso × dipoles on/off (energies and gradients, worst 2e-15), FD forces 5e-11, periodic path exact, α = 0 reduces to the no-α energy, born_charges α = 0 == direct BEC call
  • tests/test_xyz_trainer.py: xyz-trained 'aniso' checkpoint through load_calculator — joint-graph forces, stress FD 2e-13 through the periodic α path, α stashed, compute_polarizability == Σα_i == dP/dE_ext via upstream e_ext (autograd), BEC finite with a non-zero induced part, α-less checkpoint refused
  • tests/test_trainer_les.py: rMD17 smoke with les_alpha='iso'
  • All other test scripts and ruff clean

Notes

  • Physical α needs the ε_e unscaling of the paper (Eqs. 23–24): ε_e = 1 for isolated molecules, ε_∞/(1 + χ^les) for bulk. Left to the caller.
  • No positivity constraint on α (the paper found it unnecessary); trivially addable if it turns out to matter.
  • Validation against reference polarizabilities (e.g. a Psi4 subset of SPICE) is the open follow-up — SPICE carries no α labels, so the head trains from energies/forces only, as in the paper.

🤖 Generated with Claude Code

https://claude.ai/code/session_019oREKbosSjju8EEuuPhHea

The edge head can now emit a per-atom latent polarizability alongside the
charge and bond dipole (Allegro-LES's construction from the polarizable
multipole LES paper, arXiv 2605.05746), feeding upstream les's
non-self-consistent induced-dipole energy -1/2 E_i.alpha_i.E_i.

  les_alpha='iso'   one scalar a_e per edge, scatter-summed -> alpha_i
  les_alpha='aniso' two scalars per edge, a_e.I + b_e.(r r^T - I/3):
                    isotropic + traceless bond-axial parts, symmetric and
                    equivariant by construction (a lone bond is uniaxial)

l0 is packed [q | u | alpha] (1 or 9 extra columns); ecenet.les.unpack_l0
is the one place that knows the layout and model.les_flags carries the
flag. Alpha slots are zero-init (not a saddle: E_lr is linear in alpha),
les_charge_scale leaves alpha alone.

ecenet/les.py: the periodic path passes latent_alphas through to upstream;
the vectorized isolated path forms the field of the fixed multipoles from
the masked f_qu/f_uu kernels and adds the induced term (verified equal to
upstream's loop in energies and gradients, and against finite differences).
New born_charges() is the single induced-aware Z* implementation behind the
calculator and eval_spice_bec; total_polarizability() sums alpha_i per
structure (paper Eq. 29 -- exactly dP/dE_ext for this model, verified).

Calculator: results['les_alphas'] stashed on every force call;
compute_polarizability(atoms) returns the (3,3) latent tensor (physical for
isolated molecules; bulk needs the eps_e unscaling of the paper's Eqs. 23-24).
Trainers, predict_charges, eval_spice_bec take/handle the flag; README notes.

Tests: model-side layout/equivariance/uniaxial-bond/scale; wrapper parity
with upstream (iso/aniso x dipoles on/off), FD forces, periodic path,
born_charges; end-to-end xyz-trained 'aniso' checkpoint through
load_calculator (joint-graph forces, stress FD, sum(alpha) == dP/dE_ext,
BEC with induced dipoles); rMD17 trainer smoke with 'iso'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oREKbosSjju8EEuuPhHea
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant