Skip to content

Integrate PBC to the config - #112

Closed
APerezFadon wants to merge 3 commits into
google-deepmind:mainfrom
APerezFadon:main
Closed

APerezFadon wants to merge 3 commits into
google-deepmind:mainfrom
APerezFadon:main

Conversation

@APerezFadon

Copy link
Copy Markdown
Contributor

Periodic boundary conditions can now be imposed by setting the cfg.system.lattice option to the config. If set, evaluate the Coulomb interaction with an Ewald sum (in 2D or 3D), use periodic envelopes, input features, and Jastrow factor. After each MCMC step, also map electrons back to the simulation cell.

@jsspencer jsspencer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! Looks really useful, but I think we need a bit of cleaning up to avoid a few issues with existing code.

Comment thread ferminet/pbc/ewald2d.py Outdated
@@ -0,0 +1,146 @@
# Copyright 2022 DeepMind Technologies Limited.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the year please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread ferminet/pbc/ewald3d.py Outdated
@@ -0,0 +1,147 @@
# Copyright 2022 DeepMind Technologies Limited.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also year here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread ferminet/pbc/hamiltonian.py Outdated
"""
del key # unused
potential_energy = make_ewald_potential(
potential_energy = make_potential_energy(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous name was more descriptive. Is there a reason not to keep it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, reverted to the original name

Comment thread ferminet/jastrows.py Outdated


def make_simple_ee_jastrow():
def make_simple_ee_jastrow(lattice: Optional[jnp.ndarray] = None, ndim: int = 3):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: jnp.ndarray | None is the modern way

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (in some other places too)

Comment thread ferminet/pbc/ewald2d.py Outdated
madelung_const += (
(2 * jnp.pi / volume) *
jnp.sum(jax.scipy.special.erfc(rec_vec_norm / (2 * root_gamma)) / rec_vec_norm) -
2 * (jnp.pi**0.5) / (volume * root_gamma))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a glance: ewald2d.py and ewald3d.py look identical apart from the Madelung constant. Can we deduplicate them and handle the two cases in the same function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some more differences, but the 2D and 3D Ewalds have been combined into a single function

Comment thread ferminet/configs/heg.py Outdated
cfg.system.molecule = [system.Atom('X', (0, 0, 0))]

cfg.system.lattice = _sc_lattice_vecs(rs, sum(cfg.system.electrons), cfg.system.ndim)
cfg.system.make_local_energy_kwargs['heg'] = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little error prone -- I wonder if it's simpler to have a custom make_local_energy_function just for the HEG (similar to previous version) which just hardcodes this flag. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The total charge in a simulation cell should always be zero, otherwise the potential always diverges. Now the ewald potential claculates the background charge of all particles always (including the atoms). Therfore, if there are atoms present and the cell is already charge neutral then nothing happens (the backgrounds cancel). If we are doing the HEG, then the backgrounds do not cancel, and is equivalent to the old code with include_heg_background = True.

Comment thread ferminet/jastrows.py
norm = make_periodic_r_ee(lattice)
else:
norm = lambda x: jnp.linalg.norm(x, axis = -1, keepdims = True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some care is needed to avoid NaN in the gradient here (though we should not explicitly compute the diagonal terms):

def construct_input_features(
and similarly in the periodic norm code.

How about instead we pass either r_ee or the periodic version through to the jastrow? Something like in networks.py:

if jastrow_apply is not None:
  if options.lattice is not None:
    eye = jnp.eye(ee.shape[0])
    s_ee = jnp.einsum('il,jkl->jki', reciprocal_vecs, ee) + eye[..., None]
    jastrow_r_ee = (periodic_norm(lattice_metric, s_ee) * (1.0 - eye))[..., None]
  else:
    jastrow_r_ee = r_ee
  jastrow = jnp.exp(
      jastrow_apply(jastrow_r_ee, params['jastrow'], nspins) / sum(nspins)
  )

A bit more work would be to have make_orbitals re-use the computation of r_ae and r_ee to be periodic versions, but that's outside scope here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The periodic norm was already "safe", the s_ee += jnp.eye(n)[..., None] line makes it so that derivatives of |0| are not computed, which is the source of NaNs. This wasn't done in the Jastrow for open boundary conditions, but now it is. I have not been able to produce any NaNs in my testing.

Comment thread ferminet/train.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need to ensure the initial (random) electron positions are mapped within the simulation cell?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you do a single MCMC step (including burn-in), the walkers are mapped to the simulation cell. As this happens before any energies or other observables are computed, I don't think it is needed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if the proposed move in MCMC is accepted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial positions are now also mapped to the simulation cell

Comment thread ferminet/mcmc.py Outdated
def mh_update(
params: networks.ParamTree,
f: networks.LogFermiNetLike,
_map: Callable,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is a bit of an unclear name and is placed high in the argument list.

How about having it as an optional argument, with default for OBC case:

map_to_cell: Callable = lambda x: x

or similar?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread ferminet/mcmc.py
x2 = _map(x2)
lp_2 = 2.0 * f(
params, x2, data.spins, data.atoms, data.charges
) # log prob of proposal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was preivously only working by accident (f handled the 4D case) but _map flattens x2. We should reshape x2 here to be 4D to match the shape of x1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

…ariables renamed, and safe norm in the Jastrow
Comment thread ferminet/pbc/hamiltonian.py Outdated
from ferminet import networks
from ferminet.pbc.ewald2d import make_ewald_potential as ewald2d
from ferminet.pbc.ewald3d import make_ewald_potential as ewald3d
from ferminet.pbc.ewald import make_ewald_potential

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you match the existing style (import modules rather than directly importing functions and classes into the module namespace) please?

from ferminet.pbc import ewald
...
potential_energy = ewald.make_ewald_potential(...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread ferminet/jastrows.py Outdated
def safe_norm(ee: jnp.ndarray):
n = ee.shape[0]
return (
jnp.linalg.norm(ee + jnp.eye(n)[..., None], axis=-1) * (1.0 - jnp.eye(n)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note minor inconsistency here: safe_norm returns shape (nelec, nelec) but make_periodic_r_ee returns a function which returns shape (nelec, nelec, 1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safe_norm now returns shape (nelec, nelec, 1) for consistency with the previous Jastrow code.

Comment thread ferminet/pbc/ewald.py
atom_atom_potential += 0.5 * jnp.sum(charges**2) * madelung_const

def potential(ae: jnp.ndarray, ee: jnp.ndarray):
"""Accumulates atom-electron, atom-atom, and electron-electron potential."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the tests still pass? The original Ewald code reduced vectors to the unit cell to ensure the potential was periodic and this is tested in pbc/tests/hamiltonian_test.py.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The potential now maps positions to the simulation cell and tests pass.

@jsspencer jsspencer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

copybara-service Bot pushed a commit that referenced this pull request Oct 15, 2026
--
985c1d8 by Andres <andresperezfadon@gmail.com>:

Integrate PBC to the config

--
79269c7 by Andres <andresperezfadon@gmail.com>:

Address PR comments: Ewald potentials combined and refactored, MCMC variables renamed, and safe norm in the Jastrow

--
a5e3e8c by Andres <andresperezfadon@gmail.com>:

Map inital pos, and walkers in the Ewald potential to the simulation cell

COPYBARA_INTEGRATE_REVIEW=#112 from APerezFadon:main a5e3e8c
PiperOrigin-RevId: 996364455
Change-Id: Ie5dc925d828289eea347eeefd5f6d57ad674f4b5
@jsspencer

Copy link
Copy Markdown
Collaborator

Merged in 91cf270. Thanks!

@jsspencer jsspencer closed this Oct 15, 2026
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.

2 participants