The specifications for each package's handling of partial charges is as follows:
mol.partial_charges can be None or a Quantity-wrapped numpy array of floatsGet/SetPartialCharge method, which accepts floats. Partial charges are initialized to 0.0 and can not be None.rdk_atom.SetDoubleProp('PartialCharge', value)<atom.dprop.PartialCharges> tag can be included, with a space-separated list of doubles (and maybe special n/a values to indicate that some atoms dont have charges). The OpenFF partial charge spec has the following equivalents in these representations:
offmol.partial_charges is Nonefloat('nan')PartialCharges set<atom.dprop.PartialCharges> tag is includedoffmol.partial_charges is not None:PartialCharges setPartialCharges set<atom.dprop.PartialCharges> tag is included, with no values of N/AOFFMol roundtrips to OEMols can't preserve whether partial charges were None or all-zero.
The above roundtrip will return all-zero partial_charges from OpenEye, even if the original OFFMol had partial_charges is None.
In #281 and previous work, I've implemented tests for the above roundtrips. All of them can successfully roundtrip None, except OpenEye, which returns all zeroes.
(For SDF roundtrips using OpenEye, I implemented special logic to query for the presence or absence of the <atom.dprop.PartialCharges> tag in the SDF, and so None partial charges can actually survive that roundtrip.)
We can not proceed forever with equating "all zero partial charges" and "partial_charges is None". None and "all-zeroes" have different meanings for OFFTK, and seemingly-reasonable assumptions about their equivalence/meaning have caused us to produce surprising behavior in situations like #381 and barriers in implementation in #488. Further, leaving the distinction ambiguous will cause serious headaches the day someone tries to put truly zero-partial-charge mols like H2, N2, O2, noble gas, or buckyballs through OFFTK.
The best option I can think of quickly is a "nasty hack", such as setting a special SD tag on the OEMol that comes out of offmol.to_openeye that distinguishes whether partial_charges is None or really all-zero.
I would really like to hear if anyone can think of a less hacky solution!
This behavior has always been broken in OFFTK, so it's not critical that we come to a conclusion urgently.
Not ideal, but can you set the OpenEye charges to be NaN as a substitute for None?
Brilliant, @SimonBoothroyd!
from openeye import oechem
oemol = oechem.OEMol()
oechem.OESmilesToMol(oemol, "C")
for atom in oemol.GetAtoms():
print(atom.GetPartialCharge())
atom.SetPartialCharge(float('nan'))
print(atom.GetPartialCharge())
0.0
nan
Most helpful comment
Not ideal, but can you set the OpenEye charges to be
NaNas a substitute forNone?