OpenFreeEnergy / OpenFreeEnergy/openfe
Add method to assign partial charges based on the FF definition.
@jthorton is already working on this.
Since Jul 27, 2026.
- Dominant language
- Python
- Stars
- 331
- Forks
- 56
- Avg merge
- 3d 9h
- Merged PRs (30d)
- 13
Description
Background
OpenFE takes a "partial charges first" approach to things. That is to say that we encourage users to assign partial charges before a Protocol is created.
For now, our approach has been to have users assign partial charges using the OFF toolkit via AmberTools/OEChem/NAGL and then store those partial charges back into SDF files (e.g. https://docs.openfree.energy/en/latest/tutorials/charge_molecules_cli_tutorial.html) or passing through to a SmallMoleculeComponent.
Motivation
A first example here is a case where we must start having to deal with OFFXML ChargeIncrements, we might not be able to just plainly add partial charges based on a given partial charge generation model. This is partly because Interchange currently will let user charges override charge increments.
As a second example, openff 2.3 will be built around a specific NAGL model (AshGC).
We should therefore provide users the ability to assign partial charges to their own molecules based on what their target force field would be.
What would we do?
The proposal is a method that takes a list of offxmls (files or strings), and then assigns partial charges back to a molecule based on its Electrostatics collection. The output here would be some openff molecule that we can then serialize back to an SDF file (if necessary) for user in future simulations.
The limitation(s?)
(todo: add more limitations as we see them)
- Virtual sites only exist at parameter assignment time (i.e. in the OpenMM System or Gromacs Topology), and not in the Molecule (or any serialization of it). Interchange does apply virtual sites charge increments to user supplied charges, so the stored charges must account for any charge overage that will be subsequently transfered to a virtual site.
Potential solutions
Here is an ugly solution I've been using:
def get_vsite_increments(inter):
increments = []
for top_key, pot_key in inter.collections['Electrostatics'].key_map.items():
potential = inter.collections['Electrostatics'].potentials[pot_key]
for param_key, param_value in potential.parameters.items():
if param_key == "charge_increments":
for i, increment in enumerate(param_value):
increments.append((top_key.orientation_atom_indices[i], increment))
return increments
def assign_ff_charges(molecule):
ff = ForceField("virtual_sites_files/vsites-v3_n2r-vdw-dimers_hmix-only-v2-torsions.offxml")
inter = ff.create_interchange(molecule.to_topology())
charges = [
c for c in
inter.collections['Electrostatics']._get_charges(include_virtual_sites=False).values()
]
for increment in get_vsite_increments(inter):
charges[increment[0]] -= increment[1]
molecule.partial_charges = [c.m for c in charges] * unit.elementary_charge
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.