Introduction to atomman: Running LAMMPS and the Log class

Lucas M. Hale, lucas.hale@nist.gov, Materials Science and Engineering Division, NIST.

Disclaimers

1. Introduction

This Notebook outlines the options of the atomman.lammps.Log class and the atomman.lammps.run() function.

Library Imports

[1]:
# Standard libraries
import os
import glob
import time
import datetime

# http://matplotlib.org/
import matplotlib.pyplot as plt
%matplotlib inline

# https://github.com/usnistgov/atomman
import atomman as am
import atomman.lammps as lmp
import atomman.unitconvert as uc

# Show atomman version
print('atomman version =', am.__version__)

# Show date of Notebook execution
print('Notebook executed on', datetime.date.today())
atomman version = 1.4.4
Notebook executed on 2022-03-17

2. Running LAMMPS

Updated version 1.4.0: Parameters updated to provide more running options and remove obsolete Log class-based parameters.

The LAMMPS simulation can be ran from within Python using the run() function. This runs LAMMPS as a subprocess, meaning that it can use any installed LAMMPS and MPI executables.

Parameters

  • lammps_command (str) The LAMMPS inline run command (sans -in script_name).

  • script_name (str, optional) Path of the LAMMPS input script file to use. Either script_name or script must be given.

  • script (str, optional) The LAMMPS input script command lines to use. Either script_name or script must be given.

  • mpi_command (str or None, optional) The MPI inline command to run LAMMPS in parallel. Default value is None (run serially).

  • restart_script_name (str or None, optional) Path to an alternate LAMMPS input script file to use for restart runs. If given, the restart script will be used if the specified logfile already exists. Requires logfile to not be None.

  • restart_script (str, optional) Alternate LAMMPS script command lines to use for restart runs. If given, the restart script will be used if the specified logfile already exists. Requires logfile to not be None.

  • logfile (str or None, optional) Specifies the path to the logfile to write to. Default value is ‘log.lammps’. If set to None, then no logfile will be created.

  • screen (bool, optional) If True (default), then the resulting Log object is built from the LAMMPS screen output. If False, then LAMMPS outputs no screen info and the Log object will be built by reading logfile.

  • suffix (str, optional) Allows for the LAMMPS suffix option to be specified to use any of the accelerated versions of pair styles if available.

Returns

  • (atomman.lammps.Log) An object representation of the log.lammps file.

Create a simple demonstration LAMMPS input script

[2]:
script = """
#Simple demonstration LAMMPS script

units metal
atom_style atomic
boundary p p p

lattice fcc 3.52 origin 0.100000 0.100000 0.100000

region box block 0 15 0 15 0 15
create_box 1 box
create_atoms 1 box

mass 1 58.71

pair_style lj/cut 6
pair_coeff 1 1 0.5408 2.272

velocity all create 100 2987532

thermo 100
thermo_style custom step pe temp press lx ly lz

restart 10000 *.restart

timestep 0.01

fix 1 all npt temp 100 100 1.0 aniso 0.0 0.0 10.0
run 10000

"""

Run in serial

[3]:
# Define lammps command to use (unique to your computer!)
lammps_command = 'lmp_mpi'

# Run and measure execution time
start = time.time()
results = lmp.run(lammps_command, script=script)
end = time.time()
run_time = end-start

print(f"Serial simulation took {run_time} seconds.")
Serial simulation took 95.15768051147461 seconds.

Run in parallel on 4 processors

[4]:
# Define lammps command to use (unique to your computer!)
lammps_command = 'lmp_mpi'

# Define mpi command to use (unique to your computer!)
mpi_command = 'C:/Program Files/MPICH2/bin/mpiexec.exe -localonly 4'

# Run and measure execution time
start = time.time()
results = lmp.run(lammps_command, script=script, mpi_command=mpi_command)
end = time.time()
run_time = end-start

print(f"Parallel simulation took {run_time} seconds.")
Parallel simulation took 28.15028429031372 seconds.

3. Accessing results with Log class

Updated version 1.3.7: now captures performance output. A Simulation class is added to better represent each run/simulation. The flatten method is updated to return a new Simulation rather than overwriting the current data. New ‘all’ style added to flatten that will merge all runs without filtering out duplicate timesteps.

Information from the log.lammps file(s) is automatically parsed by the run() function and returned as an atomman.lammps.Log object. Currently, the Log class stores the following information:

  • lammps_version (str) The LAMMPS version used.

  • lammps_date (datetime.date) The date associated with the LAMMPS version.

  • simulations (list) information for each simulation (run, minimize …) that was executed.

[5]:
print("results.lammps_version ->", results.lammps_version)
print("results.lammps_date ->", results.lammps_date)
results.lammps_version -> 3 Mar 2020
results.lammps_date -> 2020-03-03

Each simulation captures the associated thermo data lines and the computational performance data in separate pandas.DataFrames.

[6]:
results.simulations[0].thermo.keys()
[6]:
Index(['Step', 'PotEng', 'Temp', 'Press', 'Lx', 'Ly', 'Lz'], dtype='object')
[7]:
results.simulations[0].performance
[7]:
min time avg time max time %varavg %total
Section
Pair 22.412000 22.766000 23.024000 5.6 88.35
Neigh 0.005201 0.005262 0.005323 0.1 0.02
Comm 1.098800 1.365600 1.727300 23.3 5.30
Output 0.016530 0.016752 0.017356 0.3 0.07
Modify 1.415600 1.422000 1.430000 0.4 5.52
Other 0.000000 0.191300 0.000000 0.0 0.74

This makes it very easy to plot the simulation thermo data

[8]:
for simulation in results.simulations:
    plt.plot(simulation.thermo.Step, simulation.thermo.PotEng)
plt.show()
../_images/tutorial_2.2._Running_LAMMPS_and_the_Log_class_17_0.png

4. Restarting and flattening

There is also some built-in functionality for conveniently handling simulations containing multiple runs and/or multiple sequential simulation executions of the same overall run (i.e. restarts).

4.1. Running with restart script included

Create a new demonstration LAMMPS input script that restarts the previous simulation and runs for an additional 10000 steps.

[9]:
restart_script = """
#Simple demonstration LAMMPS restart script

read_restart *.restart

mass 1 58.71

pair_style lj/cut 6
pair_coeff 1 1 0.5408 2.272

thermo 100
thermo_style custom step pe temp press lx ly lz

restart 10000 *.restart

fix 1 all npt temp 100 100 1.0 aniso 0.0 0.0 10.0
run 10000

"""

Now, if we call run() with either restart_script_name or restart_script set, a number of cool things happen:

  • If logfile (default log.lammps) does not exist in the working directory then the regular script is passed to LAMMPS.

  • If logfile does exist then it is moved from NAME.EXT to NAME-{i}.EXT, where {i} is the smallest integer not already there, and the restart script is passed to LAMMPS.

  • Upon completion, the data in NAME.EXT logfile and all NAME-{i}.EXT logfiles are automatically read in by the Log object.

[10]:
for i in range(3):
    results = lmp.run(lammps_command, script=script, mpi_command=mpi_command, restart_script=restart_script)

Show all log files in the run directory

[11]:
for logfile in glob.iglob('*.lammps'):
    print(logfile)
log-1.lammps
log-2.lammps
log-3.lammps
log.lammps

Show that results now has two simulations, one for steps 0 to 10000 and one for steps 10000 to 20000 (the restart)

[12]:
for simulation in results.simulations:
    plt.plot(simulation.thermo.Step, simulation.thermo.PotEng)
plt.show()
../_images/tutorial_2.2._Running_LAMMPS_and_the_Log_class_26_0.png

4.2. Flattening data from multiple simulations together

Changed version 1.3.7 flatten now returns a new Simulation rather than overwriting the current content.

The Log.flatten() method creates a new Simulation object that combines the thermo data from all of the simulations. flatten takes a single parameter that specifies how thermo lines with duplicate timesteps are treated:

  • ‘last’ (default) will use values for each timestep from the last runs where they appear. This is useful for MD restart runs where thermo data from eariler runs may be incomplete if externally stopped.

  • ‘first’ will use values for each timestep from the first runs where they appear. This is useful for quasistatic simulations where minimizations are performed under different conditions and only the final relaxed state of each condition is important.

  • ‘all’ will use all thermo lines from all simulations including ones with the same timesteps. This is useful for “run 0” simulations of different conditions, or if the timestep is reset.

[13]:
allsims = results.flatten('last')
plt.plot(allsims.thermo.Step, allsims.thermo.PotEng)
plt.show()
../_images/tutorial_2.2._Running_LAMMPS_and_the_Log_class_28_0.png

Note that only thermo data is included in the merged simulation and not the performance data.

[14]:
repr(allsims.performance)
[14]:
'None'

5. Error messages

LAMMPSError added and error handling improved version 1.3.2

To assist in running LAMMPS simulations, a LAMMPSError error type is defined which does its best to extract the error message that LAMMPS issues as a Python error.

[15]:
# Create a demo script with an error: missing a value in the fix npt line
bad_script = """
#Simple demonstration LAMMPS script

units metal
atom_style atomic
boundary p p p

lattice fcc 3.52 origin 0.100000 0.100000 0.100000

region box block 0 15 0 15 0 15
create_box 1 box
create_atoms 1 box

mass 1 58.71

pair_style lj/cut 6
pair_coeff 1 1 0.5408 2.272

velocity all create 100 2987532

thermo 100
thermo_style custom step pe temp press lx ly lz

restart 10000 *.restart

timestep 0.01

fix 1 all npt temp 100 100 1.0 aniso 0.0 0.0
run 10000

"""
[16]:
try:
    results = lmp.run(lammps_command, script=bad_script, mpi_command=mpi_command)
except Exception as ex:
    print(type(ex).__name__, 'raised with message')
    print(ex.args[0])
LammpsError raised with message
Illegal fix nvt/npt/nph command (../fix_nh.cpp:153)
Last command: fix 1 all npt temp 100 100 1.0 aniso 0.0 0.0

File Cleanup

[17]:
os.remove('log.lammps')
for restart in glob.iglob('*.restart'):
    os.remove(restart)
for restart in glob.iglob('log-*.lammps'):
    os.remove(restart)