Files
lammps/tools/tabulate/plot_forces.py
2025-04-26 00:56:47 -04:00

291 lines
8.6 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------
# LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
# https://www.lammps.org/ Sandia National Laboratories
# LAMMPS Development team: developers@lammps.org
#
# Copyright (2003) Sandia Corporation. Under the terms of Contract
# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
# certain rights in this software. This software is distributed under
# the GNU General Public License.
#
# See the README file in the top-level LAMMPS directory.
# -------------------------------------------------------------------------
# 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.")