Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combining SMIRNOFF interchanges with isomorphic molecules with different charges uses only the charges from the latter molecule #1075

Open
Yoshanuikabundi opened this issue Oct 8, 2024 · 7 comments
Labels
bug Something isn't working

Comments

@Yoshanuikabundi
Copy link
Collaborator

Description

I was looking to see if @IAlibay's desired workflow in #1059 could be accomplished by creating two separate interchanges and then combining them to get a single interchange with different charges for isomorphic molecules, and it does not. I think this is surprising - in my head, an interchange associates each atom in the topology with its own parameters, and the fact that parameters are deduplicated is like an optimization. I've already unambiguously identified which atoms should have which charges, and so to lose that information for that optimization is surprising.

Reproduction

Modified version of Irfan's code:

from openff.toolkit import Molecule, Topology, ForceField
from openff.interchange.components._packmol import solvate_topology_nonwater
from openff.interchange import Interchange
from openff.units import unit
import numpy as np

solvent = Molecule.from_smiles('c1ccccc1')
solvent.assign_partial_charges(partial_charge_method='gasteiger')

ligand = Molecule.from_smiles('c1ccccc1')
ligand.generate_conformers()
ligand.assign_partial_charges(partial_charge_method='am1bcc')

print("molecules are isomorphic: ", ligand.is_isomorphic_with(solvent))

off_top = Topology.from_molecules(ligand)
solvated_off_top = solvate_topology_nonwater(topology=off_top, solvent=solvent)
solvent_off_top = Topology.from_molecules([solvated_off_top.molecule(i) for i in range(off_top.n_molecules, solvated_off_top.n_molecules)])

ff = ForceField('openff-2.2.0.offxml', 'opc.offxml')
ligand_interchange = Interchange.from_smirnoff(topology=off_top, force_field=ff, charge_from_molecules=[ligand])
inter_lig_charges = [c.m_as(unit.elementary_charge) for c in ligand_interchange.collections["Electrostatics"].charges.values()] * unit.elementary_charge

solvent_interchange = Interchange.from_smirnoff(topology=off_top, force_field=ff, charge_from_molecules=[solvent])
inter_sol_charges = [c.m_as(unit.elementary_charge) for c in solvent_interchange.collections["Electrostatics"].charges.values()] * unit.elementary_charge

inter = ligand_interchange.combine(solvent_interchange)
inter_charges = [c.m_as(unit.elementary_charge) for c in inter.collections["Electrostatics"].charges.values()] * unit.elementary_charge

print("ligand charges in ligand interchange: ", inter_lig_charges)
print("charges in solvent interchange: ", inter_sol_charges)
print("charges in combined interchange: ", inter_charges)

assert not np.isclose(inter_charges, np.append(inter_lig_charges, inter_sol_charges)).all()

Output

molecules are isomorphic:  True
ligand charges in ligand interchange:  [-0.1301600026587645 -0.1300999956826369 -0.1300999956826369 -0.1300999956826369 -0.1300999956826369 -0.1300999956826369 0.13010999684532484 0.13010999684532484 0.13010999684532484 0.13010999684532484 0.13010999684532484 0.13010999684532484] elementary_charge
charges in solvent interchange:  [-0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489] elementary_charge
charges in combined interchange:  [-0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 -0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489 0.06175815314054489] elementary_charge

Software versions

Interchange v0.4.0beta2

@mattwthompson mattwthompson added the bug Something isn't working label Oct 9, 2024
@mattwthompson
Copy link
Member

I'm still iffy on whether or not this is a valid use case, but I agree that the behavior you're seeing is surprising

@j-wags
Copy link
Member

j-wags commented Oct 9, 2024

I (7/10) think this behavior is valid - Generally, it should be fine to combine interchanges created by two different SMIRNOFF FFs, without either one of them having its assigned parameters overwritten.

@mattwthompson
Copy link
Member

I should expand a little bit on my views -

  • Using different charge models for (approximately) identical molecules: walking a tightrope between funky and invalid
  • Getting different charges on isomorphic molecules from one force field: not valid
  • Combining the results of (intentionally) applying different charge models to topologies which happen to contain an isomorphic molecule(s): quite funky, but not wholly invalid - and the charges on the output should reflect the charges of each input

@Yoshanuikabundi
Copy link
Collaborator Author

Yoshanuikabundi commented Oct 10, 2024

I could imagine someone running a simulation with a deliberately conformation-dependent charge model on two different conformations of the same molecule, on a timescale where those conformations don't interconvert. Funky yes, but pretty reasonable thing to do, definitely valid. So I don't think it's completely obvious that different charges on isomorphic molecules from one force field is invalid? I agree it's currently impossible to write such a force field in SMIRNOFF without plugins.

I could also imagine different charges for the same molecule if those molecules are isolated in different environments for the entire simulation (gas and liquid phases separated by some membrane?). Or maybe alchemic mutation on a subset of copies of a molecule. In Martini 2 you need 10% of your water beads to have different LJ parameters to the rest so that it doesn't freeze at like 20 Celsius, I know coarse grained models are out of scope for now but my point is that people do all sorts of weird things to make their force fields work.

I can also imagine maybe one day this helps someone debug a problem with a different combined system, or make a theoretically indefensible but numerically correct simulation work or something weird like that. As a general rule, in a perfect world I think combined interchanges should have the same parameters for the same atoms as the original interchanges, and that users shouldn't have to think about any edge cases for this conceptually pretty well defined operation. Not making any claims about whether that makes the effort to fix this bug worth it.

@mattwthompson
Copy link
Member

So I don't think it's completely obvious that different charges on isomorphic molecules from one force field is invalid?

This case is invalid if and only if it comes from a single force field, and I think that's the key difference in perspectives here; lots of stuff is possible, but fewer things are really well-defined in force fields

For excessively exotic use cases, the user can also just modify charges after the fact (needs to be documented better sometime #1071)

@Yoshanuikabundi
Copy link
Collaborator Author

When you say "single force field", do you mean, like, a single conceptual force field, or a single SMIRNOFF force field? For example I could imagine having a liquid phase force field and a gas phase force field that are each a single SMIRNOFF force field, but I think conceptually of the combination of the two as a single force field and sometimes use them together in the same simulation.

@mattwthompson
Copy link
Member

I mean a single object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants