Merge pull request #4535 from Bibobu/plot_forces_tool

Adding python tool to plot tabulated forces
This commit is contained in:
Axel Kohlmeyer
2025-04-26 00:50:32 -04:00
committed by GitHub
3 changed files with 284 additions and 5 deletions

View File

@ -1250,10 +1250,10 @@ tabulate tool
.. versionadded:: 22Dec2022
The ``tabulate`` folder contains Python scripts scripts to generate tabulated
potential files for LAMMPS. The bulk of the code is in the ``tabulate`` module
in the ``tabulate.py`` file. Some example files demonstrating its use are
included. See the README file for more information.
The ``tabulate`` folder contains Python scripts scripts to generate and
visualize tabulated potential files for LAMMPS. The bulk of the code is in the
``tabulate`` module in the ``tabulate.py`` file. Some example files
demonstrating its use are included. See the README file for more information.
----------
@ -1276,7 +1276,7 @@ Those scripts were written by Steve Plimpton sjplimp at gmail.com
valgrind tool
-------------
The ``valgrind`` folder contains additional suppressions fur LAMMPS when
The ``valgrind`` folder contains additional suppressions for LAMMPS when
using `valgrind's <https://valgrind.org/>`_ ` `memcheck tool
<https://valgrind.org/info/tools.html#memcheck>`_ to search for memory
access violation and memory leaks. These suppressions are automatically

View File

@ -27,6 +27,7 @@ Please see the individual tabulation scripts in this folder for examples:
| wall_harmonic_tabulate.py | creates a table for fix wall/table with a simple repulsive harmonic potential |
| wall_multi_tabulate.py | creates a table for fix wall/table with multiple tables |
| pair_bi_tabulate.py | creates a table from radial distribution file using Boltzmann Inversion |
| plot_forces.py | plots and extracts tabulated forces from table files |
Common command line flags:

278
tools/tabulate/plot_forces.py Executable file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Germain Clavier (Unicaen), germain.clavier at unicaen.fr
"""
Plot LAMMPS tabulated forces.
"""
import argparse
import numpy as np
import os
import logging
import sys
from matplotlib import pyplot as plt
logger = logging.getLogger(__name__)
units = {
"lj": {
"Distance": "Reduced units",
"Energy": "Reduced units",
"Force": "Reduced units",
"kb": 1,
},
"real": {
"Distance": "[A]",
"Energy": "[kcal/mol]",
"Force": "[kcal/mol/A]",
"kb": 0.001985875,
},
"metal": {
"Distance": "[A]",
"Energy": "[eV]",
"Force": "[eV/A]",
"kb": 8.6173332e-5,
},
"si": {"Distance": "[m]", "Energy": "[J]", "Force": "[N]", "kb": 1.380649e-23},
"cgs": {
"Distance": "[cm]",
"Energy": "[ergs]",
"Force": "[dynes]",
"kb": 1.3806504e-16,
},
"electron": {
"Distance": "[Bohr]",
"Energy": "[Hartrees]",
"Force": "[Hartree/Bohr]",
"kb": 3.16681534e-6,
},
"micro": {
"Distance": "[um]",
"Energy": "[pg·um^2/us^2]",
"Force": "[pg·um/us^2]",
"kb": 1.3806504e-8,
},
"nano": {
"Distance": "[nm]",
"Energy": "[ag·nm^2/ns^2]",
"Force": "[ag·nm/ns^2]",
"kb": 0.013806504,
},
}
def compute_energy(tp):
r = tp[0]
fo = tp[2]
e = np.zeros(r.shape)
for i, (ri, fi) in enumerate(zip(r, fo)):
if i == 0:
continue
dr = ri - r[i - 1]
e[i] = e[i - 1] - dr * fo[i - 1]
e -= e[-1]
return e
def main():
parser = argparse.ArgumentParser(
description="""
Plots LAMMPS tabulated forces. This script takes a table
file as an input and plots all the tabulated forces inside with their
corresponding energy. The forces label is the token used to name the
force in the file. It can be used to output all the forces in separate
files and/or recompute the energy from forces through finite difference
(assuming e(rc)=0). This script requires the matplotlib and numpy
Python libraries. Bitmap format is not supported.
"""
)
parser.add_argument(
"-u",
"--units",
dest="units",
default="real",
help="Units of the file (LAMMPS units system)",
)
parser.add_argument(
"-f",
"--file",
dest="infile",
default="",
help="File to read",
)
parser.add_argument(
"-x",
dest="xrange",
default="",
help="xrange separated by : (for negative values use the '=' sign: -x=-3:10)",
)
parser.add_argument(
"-y",
dest="yrange",
default="",
help="yrange separated by :",
)
parser.add_argument(
"-t",
dest="temp",
default=None,
type=float,
help="temperature for KbT plot [default none]",
)
parser.add_argument(
"-d",
"--diff-num",
dest="recompute",
action="store_true",
help="Recompute the energies from forces and distances through finite differences",
)
parser.add_argument(
"-e",
dest="extract",
action="store_true",
help="Extract the forces in separate files",
)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO)
##########
# Manage arguments
udistance = units[args.units]["Distance"]
uenergy = units[args.units]["Energy"]
uforce = units[args.units]["Force"]
kb = units[args.units]["kb"]
rlabel = " ".join(["Rij", udistance])
elabel = " ".join(["E", uenergy])
flabel = " ".join(["F", uforce])
etitle = "Energy"
ftitle = "Force"
font = "DejaVu Sans"
fontsize = 30
infile = args.infile
if not os.path.isfile(infile):
logger.error("Input file not found")
sys.exit(1)
toplot = []
with open(infile, "r") as f:
lines = iter(f.readlines())
while True:
try:
r = []
force = []
ener = []
tok = []
while not tok:
tok = next(lines).partition("#")[0].rstrip()
logger.info("Found {} token".format(tok))
infos = next(lines).split()
npoints = int(infos[1])
next(lines)
if "bitmap" in infos:
logger.info("Unsupported bitmap format for token {:s}".format(tok))
for _ in range(npoints):
continue
else:
for i in range(npoints):
line = next(lines).split()
r.append(float(line[1]))
ener.append(float(line[2]))
force.append(float(line[3]))
r = np.array(r)
ener = np.array(ener)
force = np.array(force)
toplot.append([r, ener, force, tok])
tok = []
next(lines)
except StopIteration:
break
if args.recompute:
etitle = "Estimated energy"
for tp in toplot:
tp[1] = compute_energy(tp)
fig, axes = plt.subplots(1, 2)
for tp in toplot:
axes[0].plot(tp[0], tp[1], label=tp[3], linewidth=3)
axes[1].plot(tp[0], tp[2], label=tp[3], linewidth=3)
hmin, hmax = axes[1].get_xlim()
axes[1].hlines(0, hmin, hmax, color="black", linewidth=3, linestyles="dashdot")
if args.temp:
if args.temp > 0:
hmin, hmax = axes[0].get_xlim()
axes[0].hlines(
kb * args.temp,
hmin,
hmax,
color="orange",
label=r"$k_BT$",
linewidth=3,
linestyles="dashdot",
)
axes[0].text(hmax / 2.0, kb * args.temp, "KbT", fontsize=0.7 * fontsize)
logger.info("KbT value= {:e} {:s}".format(kb * args.temp, uenergy))
else:
logger.info("Invalid temperature value: {:e}".format(args.temp))
if args.xrange:
xmin, xmax = list(map(float, args.xrange.split(":")))
axes[0].set_xlim(xmin, xmax)
axes[1].set_xlim(xmin, xmax)
if args.yrange:
ymin, ymax = list(map(float, args.yrange.split(":")))
axes[0].set_ylim(ymin, ymax)
axes[1].set_ylim(ymin, ymax)
# Setting axes 0
axes[0].set_title(etitle, fontsize=fontsize)
axes[0].set_xlabel(
rlabel, fontname=font, fontsize=fontsize
) # xlabel name, size 30pts
axes[0].set_ylabel(
elabel, fontname=font, fontsize=fontsize
) # ylabel name, size 30pts
axes[0].tick_params(
axis="both", which="major", labelsize=fontsize
) # Biggers ticks, bigger tick labels!
# Setting axes 1
axes[1].set_title(ftitle, fontsize=fontsize)
axes[1].legend(frameon=False, fontsize=fontsize) # Fat font, no frame
axes[1].set_xlabel(
rlabel, fontname=font, fontsize=fontsize
)
axes[1].set_ylabel(
flabel, fontname=font, fontsize=fontsize
)
axes[1].tick_params(
axis="both", which="major", labelsize=fontsize
)
figManager = plt.get_current_fig_manager()
figManager.window.showMaximized()
plt.show()
if args.extract:
for tp in toplot:
outfile = "".join([tp[3], ".plot"])
logger.info("Writing file {}".format(outfile))
with open(outfile, "w") as f:
f.write("# {} force extracted from {}\n".format(tp[3], infile))
f.write("# {:^20} {:^20} {:^20}\n".format("r", "energy", "force"))
for a, b, c in zip(tp[0], tp[1], tp[2]):
f.write("{:>18.16e} {:>18.16e} {:>18.16e}\n".format(a, b, c))
return
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
raise SystemExit("User interruption.")