.. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_planetary_motion.py: Use ASE to Simulate Planetary Motion ==================================== This tutorial shows how to use `ASE`_ to simulate planetary motion. Although both ASE and NNP are designed for molecular dynamics (MD), implementing a force field to do MD is not at a reasonable abount of work to use as a tutorial. So we simulate something different here. Assume we have two atoms, one hydrogen and one carbon. They are all moving under the gravity centered at the origin. There is no interaction between these two atoms. .. _ASE: https://wiki.fysik.dtu.dk/ase Let's first import all the packages we will use: .. code-block:: default import ase from ase.data import atomic_masses from ase.units import fs from ase.md.verlet import VelocityVerlet import nnp.md as md import pytest import sys import math import matplotlib.pyplot as plt The module `nnp.md` provide tools to run molecular dynamics with a potential defined by PyTorch. To define the potential we need to provide a function that takes the chemical symbols, atom coordinates, unit cell, and PBC flag as an input and returns the molecular energy as an output. The output and input coordinates and cell must be in the a differentiable graph so that PyTorch could automatically compute the force and stress tensor using the `autograd` engine. In this example, we don't care about the periodic boundary condition and unit cell because we are actually not simulating molecular system. From basic physics, we know that the equation for gravitational potential has the equation :math:`E(r)=-\frac{GMm}{r}`. For simplicity, we choose :math:`GM=1` when :math:`E` is in :math:`\mathrm{eV}`, :math:`m` in :math:`\mathrm{amu}`, and :math:`r` in Angstrom. .. code-block:: default def gravity(chemical_symbols, coordinates, _cell, _pbc): assert len(chemical_symbols) == coordinates.shape[0] atomic_numbers = {'H': 0, 'C': 6} mass = coordinates.new_tensor( [atomic_masses[atomic_numbers[s]] for s in chemical_symbols]) inv_r = 1 / coordinates.norm(dim=1) potential_energies = - mass * inv_r return potential_energies.sum() with this potential, we could define an ASE calculator: .. code-block:: default calculator = md.Calculator(gravity) Now let's create two atoms, initially at (1, 0, 0) and (2, 0, 0) .. code-block:: default planets = ase.Atoms('CH', [[1, 0, 0], [2, 0, 0]]) For the purpose of demonstration, we make these two atoms's trajector a perfect circle at the XY plane. To do so, we need to carefully set the initial velocity. It is not hard to get that, the velocity is :math:`v = \sqrt{\frac{GM}{r}}`. .. code-block:: default planets.set_velocities([[0, 1, 0], [0, 1 / math.sqrt(2), 0]]) planets.set_calculator(calculator) Now we can start the dynamics: .. code-block:: default X1 = [] Y1 = [] Z1 = [] X2 = [] Y2 = [] Z2 = [] def dump_positions(): (x1, y1, z1), (x2, y2, z2) = planets.get_positions() X1.append(x1) Y1.append(y1) Z1.append(z1) X2.append(x2) Y2.append(y2) Z2.append(z2) dyn = VelocityVerlet(planets, timestep=0.01 * fs) dyn.attach(dump_positions) dyn.run(20000) .. rst-class:: sphx-glr-script-out Out: .. code-block:: none True Now let's plot the trajectory to see what we get: .. code-block:: default if __name__ == '__main__': plt.clf() plt.plot(X1, Y1, label='C') plt.plot(X2, Y2, label='H') plt.legend() plt.axes().set_aspect('equal') plt.show() .. image:: /examples/images/sphx_glr_planetary_motion_001.png :alt: planetary motion :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out Out: .. code-block:: none /home/runner/work/nnp/nnp/tests/planetary_motion.py:100: MatplotlibDeprecationWarning: Adding an axes using the same arguments as a previous axes currently reuses the earlier instance. In a future version, a new instance will always be created and returned. Meanwhile, this warning can be suppressed, and the future behavior ensured, by passing a unique label to each axes instance. plt.axes().set_aspect('equal') To check the correctness of this dynamics, we use pytest to test if it is on the XY plane and if the trajectory is circle: .. code-block:: default def test_is_on_xy_plan(): for z in Z1 + Z2: assert abs(z) < 1e-4 def test_is_circle(): for x, y in zip(X1, Y1): assert abs(math.sqrt(x * x + y * y) - 1) < 0.1 for x, y in zip(X2, Y2): assert abs(math.sqrt(x * x + y * y) - 2) < 0.1 if __name__ == '__main__': pytest.main([sys.argv[0], '-v']) .. rst-class:: sphx-glr-script-out Out: .. code-block:: none ============================= test session starts ============================== platform linux -- Python 3.7.7, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- /opt/hostedtoolcache/Python/3.7.7/x64/bin/python cachedir: .pytest_cache rootdir: /home/runner/work/nnp/nnp, inifile: setup.cfg collecting ... collected 2 items planetary_motion.py::test_is_on_xy_plan PASSED [ 50%] planetary_motion.py::test_is_circle PASSED [100%] ============================== 2 passed in 24.56s ============================== .. rst-class:: sphx-glr-timing **Total running time of the script:** ( 0 minutes 49.812 seconds) .. _sphx_glr_download_examples_planetary_motion.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: planetary_motion.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: planetary_motion.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_