Repository navigation
Integrate PBC to the config - #112
APerezFadon wants to merge 3 commits into
Conversation
jsspencer
left a comment
There was a problem hiding this comment.
Thanks for this! Looks really useful, but I think we need a bit of cleaning up to avoid a few issues with existing code.
| @@ -0,0 +1,146 @@ | |||
| # Copyright 2022 DeepMind Technologies Limited. | |||
There was a problem hiding this comment.
Can you update the year please?
| @@ -0,0 +1,147 @@ | |||
| # Copyright 2022 DeepMind Technologies Limited. | |||
| """ | ||
| del key # unused | ||
| potential_energy = make_ewald_potential( | ||
| potential_energy = make_potential_energy( |
There was a problem hiding this comment.
The previous name was more descriptive. Is there a reason not to keep it?
There was a problem hiding this comment.
Sure, reverted to the original name
|
|
||
|
|
||
| def make_simple_ee_jastrow(): | ||
| def make_simple_ee_jastrow(lattice: Optional[jnp.ndarray] = None, ndim: int = 3): |
There was a problem hiding this comment.
Nit: jnp.ndarray | None is the modern way
There was a problem hiding this comment.
Done (in some other places too)
| 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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
There are some more differences, but the 2D and 3D Ewalds have been combined into a single function
| 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| norm = make_periodic_r_ee(lattice) | ||
| else: | ||
| norm = lambda x: jnp.linalg.norm(x, axis = -1, keepdims = True) | ||
|
|
There was a problem hiding this comment.
Some care is needed to avoid NaN in the gradient here (though we should not explicitly compute the diagonal terms):
Line 449 in c4312c3
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Do we also need to ensure the initial (random) electron positions are mapped within the simulation cell?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Only if the proposed move in MCMC is accepted.
There was a problem hiding this comment.
Initial positions are now also mapped to the simulation cell
| def mh_update( | ||
| params: networks.ParamTree, | ||
| f: networks.LogFermiNetLike, | ||
| _map: Callable, |
There was a problem hiding this comment.
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?
| x2 = _map(x2) | ||
| lp_2 = 2.0 * f( | ||
| params, x2, data.spins, data.atoms, data.charges | ||
| ) # log prob of proposal |
There was a problem hiding this comment.
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.
…ariables renamed, and safe norm in the Jastrow
| 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 |
There was a problem hiding this comment.
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(...)
| 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))) |
There was a problem hiding this comment.
Note minor inconsistency here: safe_norm returns shape (nelec, nelec) but make_periodic_r_ee returns a function which returns shape (nelec, nelec, 1)
There was a problem hiding this comment.
safe_norm now returns shape (nelec, nelec, 1) for consistency with the previous Jastrow code.
| 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.""" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
The potential now maps positions to the simulation cell and tests pass.
-- 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
|
Merged in 91cf270. Thanks! |
Periodic boundary conditions can now be imposed by setting the
cfg.system.latticeoption 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.