OpenFreeEnergy / OpenFreeEnergy/openfe

Add method to assign partial charges based on the FF definition.

Open
#1,301 7 comments 0 reactions 2 assignees View on GitHub

@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)

  1. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.