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

Consistent units #422

Merged
merged 38 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
9a44aae
bypass slow code
IgnacioJPickering Feb 27, 2020
2775daa
delete reference to all_atoms which was unused
IgnacioJPickering Feb 27, 2020
4ad2508
delete whitespace
IgnacioJPickering Feb 27, 2020
0eddbde
make flake8 happy
IgnacioJPickering Feb 27, 2020
537fd6b
rerun
IgnacioJPickering Feb 27, 2020
e9ee023
actual change was off
IgnacioJPickering Mar 9, 2020
02ae152
typo did not turn on bypassing code
IgnacioJPickering Mar 9, 2020
ba65615
Add CODATA and IUPAC unit conversion factors
IgnacioJPickering Mar 10, 2020
6b9678e
Add units to main module
IgnacioJPickering Mar 10, 2020
5ef1176
change neurochem and training-benchmark to use the new hartree2kcalmo…
IgnacioJPickering Mar 10, 2020
26861ce
change training-benchmark to use hartree2kcalmol conversion
IgnacioJPickering Mar 10, 2020
de921df
change comp6 to use the new hartree2kcalmol conversion
IgnacioJPickering Mar 10, 2020
546914a
change training-benchmark without nsys to also use hartree2kcalmol co…
IgnacioJPickering Mar 10, 2020
56fc2a4
change examples to use hartree2kcalmol conversion
IgnacioJPickering Mar 10, 2020
0ef99a3
Rename ase to ase_ani to avoid naming conflicts that come from naming…
IgnacioJPickering Mar 10, 2020
842f21a
fix import
IgnacioJPickering Mar 10, 2020
b208097
Add ASE's hartree_to_ev conversion to compare with codata 2018
IgnacioJPickering Mar 10, 2020
d07d6d5
Standarize our units to be essentially the same as ASE and CODATA 2014
IgnacioJPickering Mar 11, 2020
1a2dd19
Change vibration_analysis to use the consistent constants
IgnacioJPickering Mar 11, 2020
e544ec6
Fix explanation in docstring
IgnacioJPickering Mar 11, 2020
a2ff588
Add millieV conversion factor
IgnacioJPickering Mar 11, 2020
387173e
Add millieV conversion factor to vibrational analysis
IgnacioJPickering Mar 11, 2020
cc26b63
Add clarification
IgnacioJPickering Mar 11, 2020
ae5c23f
Merge branch 'master' of github.com:aiqm/torchani into consistent_units
IgnacioJPickering Mar 11, 2020
b5e6239
flake8
IgnacioJPickering Mar 11, 2020
088a1f4
revert name change
IgnacioJPickering Mar 11, 2020
47a92b7
Fix typo in function name
IgnacioJPickering Mar 11, 2020
2cc9dcc
Add units to __all__'
IgnacioJPickering Mar 11, 2020
6ed8ed7
Improve units docstring and comments in preparation for docs
IgnacioJPickering Mar 12, 2020
b5713c9
delete pad from docs
IgnacioJPickering Mar 12, 2020
bd76cfc
Add units to api.rst
IgnacioJPickering Mar 12, 2020
2768c5d
minor formatting of docstrings and comments
IgnacioJPickering Mar 12, 2020
a4b161d
Add functions into the API
IgnacioJPickering Mar 12, 2020
464dfeb
final formatting changes
IgnacioJPickering Mar 12, 2020
a155673
flake8
IgnacioJPickering Mar 12, 2020
71a305c
try to make mypy happy
IgnacioJPickering Mar 12, 2020
8eca7f7
Checking if mypy prefers this
IgnacioJPickering Mar 12, 2020
544b18d
rerun
IgnacioJPickering Mar 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Utilities
=========

.. automodule:: torchani.utils
.. autofunction:: torchani.utils.pad
.. autofunction:: torchani.utils.pad_atomic_properties
.. autofunction:: torchani.utils.present_species
.. autofunction:: torchani.utils.strip_redundant_padding
Expand Down Expand Up @@ -77,3 +76,17 @@ TorchANI Optimizater

.. automodule:: torchani.optim
.. autoclass:: torchani.optim.AdamW


Units
=====

.. automodule:: torchani.units
.. autofunction:: torchani.units.hartree2ev
.. autofunction:: torchani.units.hartree2kcalmol
.. autofunction:: torchani.units.hartree2kjoulemol
.. autofunction:: torchani.units.ev2kcalmol
.. autofunction:: torchani.units.ev2kjoulemol
.. autofunction:: torchani.units.mhessian2fconst
.. autofunction:: torchani.units.sqrt_mhessian2invcm
.. autofunction:: torchani.units.sqrt_mhessian2milliev
10 changes: 4 additions & 6 deletions examples/nnp_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import torch.utils.tensorboard
import tqdm

# helper function to convert energy unit from Hartree to kcal/mol
from torchani.units import hartree2kcalmol

# device to run the training
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

Expand Down Expand Up @@ -272,11 +275,6 @@ def init_params(m):
# is better than the best, then save the new best model to a checkpoint


# helper function to convert energy unit from Hartree to kcal/mol
def hartree2kcal(x):
return 627.509 * x


def validate():
# run validation
mse_sum = torch.nn.MSELoss(reduction='sum')
Expand All @@ -291,7 +289,7 @@ def validate():
predicted_energies = torch.cat(predicted_energies)
total_mse += mse_sum(predicted_energies, true_energies).item()
count += predicted_energies.shape[0]
return hartree2kcal(math.sqrt(total_mse / count))
return hartree2kcalmol(math.sqrt(total_mse / count))


###############################################################################
Expand Down
10 changes: 4 additions & 6 deletions examples/nnp_training_force.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import torch.utils.tensorboard
import tqdm

# helper function to convert energy unit from Hartree to kcal/mol
from torchani.units import hartree2kcalmol

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

Rcr = 5.2000e+00
Expand Down Expand Up @@ -217,11 +220,6 @@ def init_params(m):
# is better than the best, then save the new best model to a checkpoint


# helper function to convert energy unit from Hartree to kcal/mol
def hartree2kcal(x):
return 627.509 * x


def validate():
# run validation
mse_sum = torch.nn.MSELoss(reduction='sum')
Expand All @@ -236,7 +234,7 @@ def validate():
predicted_energies = torch.cat(predicted_energies)
total_mse += mse_sum(predicted_energies, true_energies).item()
count += predicted_energies.shape[0]
return hartree2kcal(math.sqrt(total_mse / count))
return hartree2kcalmol(math.sqrt(total_mse / count))


###############################################################################
Expand Down
16 changes: 7 additions & 9 deletions tools/comp6.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
import torch
import torchani
from torchani.data._pyanitools import anidataloader
from torchani.units import hartree2kcalmol
import argparse
import math
import tqdm


HARTREE2KCAL = 627.509

# parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('dir', help='Path to the COMP6 directory')
Expand Down Expand Up @@ -97,13 +96,12 @@ def do_benchmark(model):
rmse_averager_energy.update(ediff ** 2)
rmse_averager_relative_energy.update(relative_ediff ** 2)
rmse_averager_force.update(fdiff ** 2)
mae_energy = mae_averager_energy.compute() * HARTREE2KCAL
rmse_energy = math.sqrt(rmse_averager_energy.compute()) * HARTREE2KCAL
mae_relative_energy = mae_averager_relative_energy.compute() * HARTREE2KCAL
rmse_relative_energy = math.sqrt(rmse_averager_relative_energy.compute()) \
* HARTREE2KCAL
mae_force = mae_averager_force.compute() * HARTREE2KCAL
rmse_force = math.sqrt(rmse_averager_force.compute()) * HARTREE2KCAL
mae_energy = hartree2kcalmol(mae_averager_energy.compute())
rmse_energy = hartree2kcalmol(math.sqrt(rmse_averager_energy.compute()))
mae_relative_energy = hartree2kcalmol(mae_averager_relative_energy.compute())
rmse_relative_energy = hartree2kcalmol(math.sqrt(rmse_averager_relative_energy.compute()))
mae_force = hartree2kcalmol(mae_averager_force.compute())
rmse_force = hartree2kcalmol(math.sqrt(rmse_averager_force.compute()))
print("Energy:", mae_energy, rmse_energy)
print("Relative Energy:", mae_relative_energy, rmse_relative_energy)
print("Forces:", mae_force, rmse_force)
Expand Down
7 changes: 2 additions & 5 deletions tools/training-benchmark-nsys-profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import torchani
import argparse
import pkbar
from torchani.units import hartree2kcalmol


WARM_UP_BATCHES = 50
Expand Down Expand Up @@ -32,10 +33,6 @@ def wrapper(*args, **kwargs):
return wrapper


def hartree2kcal(x):
return 627.509 * x


def enable_timers(model):
torchani.aev.cutoff_cosine = time_func('cutoff_cosine', torchani.aev.cutoff_cosine)
torchani.aev.radial_terms = time_func('radial_terms', torchani.aev.radial_terms)
Expand Down Expand Up @@ -171,7 +168,7 @@ def enable_timers(model):
num_atoms = torch.cat(num_atoms)
predicted_energies = torch.cat(predicted_energies).to(true_energies.dtype)
loss = (mse(predicted_energies, true_energies) / num_atoms.sqrt()).mean()
rmse = hartree2kcal((mse(predicted_energies, true_energies)).mean()).detach().cpu().numpy()
rmse = hartree2kcalmol((mse(predicted_energies, true_energies)).mean()).detach().cpu().numpy()

if PROFILING_STARTED:
torch.cuda.nvtx.range_push("backward")
Expand Down
7 changes: 2 additions & 5 deletions tools/training-benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import timeit
import argparse
import pkbar
from torchani.units import hartree2kcalmol


def atomic():
Expand Down Expand Up @@ -32,10 +33,6 @@ def wrapper(*args, **kwargs):
return wrapper


def hartree2kcal(x):
return 627.509 * x


if __name__ == "__main__":
# parse command line arguments
parser = argparse.ArgumentParser()
Expand Down Expand Up @@ -166,7 +163,7 @@ def hartree2kcal(x):
num_atoms = torch.cat(num_atoms)
predicted_energies = torch.cat(predicted_energies).to(true_energies.dtype)
loss = (mse(predicted_energies, true_energies) / num_atoms.sqrt()).mean()
rmse = hartree2kcal((mse(predicted_energies, true_energies)).mean()).detach().cpu().numpy()
rmse = hartree2kcalmol((mse(predicted_energies, true_energies)).mean()).detach().cpu().numpy()
loss.backward()
optimizer.step()

Expand Down
3 changes: 2 additions & 1 deletion torchani/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from . import neurochem
from . import models
from . import optim
from . import units
from pkg_resources import get_distribution, DistributionNotFound

try:
Expand All @@ -39,7 +40,7 @@
pass

__all__ = ['AEVComputer', 'EnergyShifter', 'ANIModel', 'Ensemble', 'SpeciesConverter',
'utils', 'neurochem', 'models', 'optim']
'utils', 'neurochem', 'models', 'optim', 'units']

try:
from . import ase # noqa: F401
Expand Down
7 changes: 2 additions & 5 deletions torchani/neurochem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ..aev import AEVComputer
from ..optim import AdamW
from collections import OrderedDict
from torchani.units import hartree2kcalmol


class Constants(collections.abc.Mapping):
Expand Down Expand Up @@ -266,10 +267,6 @@ def load_model_ensemble(species, prefix, count):
return Ensemble(models)


def hartree2kcal(x):
return 627.509 * x


if sys.version_info[0] > 2:

class Trainer:
Expand Down Expand Up @@ -577,7 +574,7 @@ def evaluate(self, dataset):
predicted_energies = torch.cat(predicted_energies)
total_mse += self.mse_sum(predicted_energies, true_energies).item()
count += predicted_energies.shape[0]
return hartree2kcal(math.sqrt(total_mse / count))
return hartree2kcalmol(math.sqrt(total_mse / count))

def run(self):
"""Run the training"""
Expand Down
140 changes: 140 additions & 0 deletions torchani/units.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
r"""Unit conversion factors used in torchani

The torchani networks themselves works internally entirely in Hartrees
(energy), Angstroms (distance) and AMU (mass). In some example code and scripts
we convert to other more commonly used units. Our conversion factors are
consistent with `CODATA 2014 recommendations`_, which is also consistent with
the `units used in ASE`_. (However, take into account that ASE uses
electronvolt as its base energy unit, so the appropriate conversion factors
should always be applied when converting from ASE to torchani) Joule-to-kcal
conversion taken from the `IUPAC Goldbook`_. All the conversion factors we use
are defined in this module, and convenience functions to convert between
different units are provided.


.. _units used in ASE:
https://wiki.fysik.dtu.dk/ase/ase/units.html#units

.. _CODATA 2014 recommendations:
https://arxiv.org/pdf/1507.07956.pdf

.. _IUPAC Goldbook:
https://goldbook.iupac.org/terms/view/C00784

"""
import math


# Comments on ASE:

# ASE uses its own constants, which they take from CODATA 2014 as of
# 03/10/2019. ASE's units are created when ase.units is imported.
# Since we don't want to be dependent on ASE changing their module
# we define here our own units for our own purposes, but right now we define
# them to be consistent with ASE values (i. e. our values are also CODATA 2014)
# the difference between CODATA 2014 and CODATA 2018 is negligible.


# General conversion:

# the codata value for hartree in units of eV can be obtained from
# m_e * e^3 / ( 16 * pi^2 * eps_0^2 hbar^2 )
HARTREE_TO_EV = 27.211386024367243 # equal to ase.units.Hartree
EV_TO_JOULE = 1.6021766208e-19 # equal to ase.units._e (electron charge)
JOULE_TO_KCAL = 1 / 4184. # exact
HARTREE_TO_JOULE = HARTREE_TO_EV * EV_TO_JOULE
AVOGADROS_NUMBER = 6.022140857e+23 # equal to ase.units._Nav
SPEED_OF_LIGHT = 299792458.0 # equal to ase.units._c
AMU_TO_KG = 1.660539040e-27 # equal to ase.units._amu
ANGSTROM_TO_METER = 1e-10
NEWTON_TO_MILLIDYNE = 1e8 # exact relation
HARTREE_TO_KCALMOL = HARTREE_TO_JOULE * JOULE_TO_KCAL * AVOGADROS_NUMBER
HARTREE_TO_KJOULEMOL = HARTREE_TO_JOULE * AVOGADROS_NUMBER / 1000
EV_TO_KCALMOL = EV_TO_JOULE * JOULE_TO_KCAL * AVOGADROS_NUMBER
EV_TO_KJOULEMOL = EV_TO_JOULE * AVOGADROS_NUMBER / 1000

# For vibrational analysis:

INVCM_TO_EV = 0.0001239841973964072 # equal to ase.units.invcm
# To convert from the sqrt of eigenvalues (mass-scaled hessian units of
# sqrt(hartree / (amu * A^2)) into cm^-1
# it is necessary to multiply by the sqrt ( HARTREE_TO_JOULE / AMU_TO_KG ),
# then we convert angstroms to meters and divide by 1/ speed_of_light (to
# convert seconds into meters). Finally divide by 100 to get inverse cm
# The resulting value should be close to 17092
SQRT_MHESSIAN_TO_INVCM = (math.sqrt(HARTREE_TO_JOULE / AMU_TO_KG) / ANGSTROM_TO_METER / SPEED_OF_LIGHT) / 100
# meV is other common unit used to present vibrational transition energies
SQRT_MHESSIAN_TO_MILLIEV = SQRT_MHESSIAN_TO_INVCM * INVCM_TO_EV * 1000
# To convert units form mass-scaled hessian units into mDyne / Angstrom (force
# constant units) factor should be close to 4.36
MHESSIAN_TO_FCONST = HARTREE_TO_JOULE * NEWTON_TO_MILLIDYNE / ANGSTROM_TO_METER


def sqrt_mhessian2invcm(x):
r"""Converts sqrt(mass-scaled hessian units) into cm^-1

Converts form units of sqrt(Hartree / (amu * Angstrom^2))
which are sqrt(units of the mass-scaled hessian matrix)
into units of inverse centimeters.

Take into account that to convert the actual eigenvalues of the hessian
into wavenumbers it is necessary to multiply by an extra factor of 1 / (2 *
pi)"""
return x * SQRT_MHESSIAN_TO_INVCM


def sqrt_mhessian2milliev(x):
r"""Converts sqrt(mass-scaled hessian units) into meV

Converts form units of sqrt(Hartree / (amu * Angstrom^2))
which are sqrt(units of the mass-scaled hessian matrix)
into units of milli-electronvolts.

Take into account that to convert the actual eigenvalues of the hessian
into wavenumbers it is necessary to multiply by an extra factor of 1 / (2 *
pi)"""
return x * SQRT_MHESSIAN_TO_MILLIEV


def mhessian2fconst(x):
r"""Converts mass-scaled hessian units into mDyne/Angstrom

Converts from units of mass-scaled hessian (Hartree / (amu * Angstrom^2)
into force constant units (mDyne/Angstom), where 1 N = 1 * 10^8 mDyne"""
return x * MHESSIAN_TO_FCONST


def hartree2ev(x):
r"""Hartree to eV conversion factor from 2014 CODATA"""
return x * HARTREE_TO_EV


def ev2kjoulemol(x):
r"""Electronvolt to kJ/mol conversion factor from CODATA 2014"""
return x * EV_TO_KJOULEMOL


def ev2kcalmol(x):
r"""Electronvolt to kcal/mol conversion factor from CODATA 2014"""
return x * EV_TO_KCALMOL


def hartree2kjoulemol(x):
r"""Hartree to kJ/mol conversion factor from CODATA 2014"""
return x * HARTREE_TO_KJOULEMOL


def hartree2kcalmol(x):
r"""Hartree to kJ/mol conversion factor from CODATA 2014"""
return x * HARTREE_TO_KCALMOL


# Add actual values to docstrings on import
hartree2ev.__doc__ = str(hartree2ev.__doc__) + f'\n\n1 Hartree = {hartree2ev(1)} eV'
hartree2kcalmol.__doc__ = str(hartree2kcalmol.__doc__) + f'\n\n1 Hartree = {hartree2kcalmol(1)} kcal/mol'
hartree2kjoulemol.__doc__ = str(hartree2kjoulemol) + f'\n\n1 Hartree = {hartree2kjoulemol(1)} kJ/mol'
ev2kjoulemol.__doc__ = str(ev2kjoulemol.__doc__) + f'\n\n1 eV = {ev2kjoulemol(1)} kJ/mol'
ev2kcalmol.__doc__ = str(ev2kcalmol.__doc__) + f'\n\n1 eV = {ev2kcalmol(1)} kcal/mol'
mhessian2fconst.__doc__ = str(mhessian2fconst.__doc__) + f'\n\n1 Hartree / (AMU * Angstrom^2) = {ev2kcalmol(1)} mDyne/Angstrom'
sqrt_mhessian2milliev.__doc__ = str(sqrt_mhessian2milliev.__doc__) + f'\n\n1 sqrt(Hartree / (AMU * Angstrom^2)) = {sqrt_mhessian2milliev(1)} meV'
sqrt_mhessian2invcm.__doc__ = str(sqrt_mhessian2invcm.__doc__) + f'\n\n1 sqrt(Hartree / (AMU * Angstrom^2)) = {sqrt_mhessian2invcm(1)} cm^-1'
Loading