#!/usr/bin/env python
'''
Runs a user defined lammps script or template. LAMMPS must be installed
Author: mkphuthi@github.com
'''
from typing import Dict, Optional, Sequence
import sys
from pathlib import Path
from numpy.random import randint
import subprocess
import logging
from asimtools.utils import (
get_atoms,
)
[docs]
def lammps(
template: str,
image: Optional[Dict] = None,
atom_style: str = 'atomic',
variables: Optional[Dict] = None,
placeholders: Optional[Dict] = None,
lmp_cmd: str = 'lmp',
masses: bool = True,
velocities: bool = False,
seed: Optional[int] = None,
restart_template: Optional[str] = None,
specorder: Sequence[str] = None,
) -> Dict:
"""Runs a lammps script based on a specified template, variables can be
specified as arguments to be defined in the final LAMMPS input file if
placeholders are put in the template
:param template: path to lammps input template file
:type template: str
:param image: Initial image for MD simulation. Image specification,
see :func:`asimtools.utils.get_atoms`, defaults to None
:type image: Dict, optional
:param atom_style: LAMMPS style in which to write image to Lammps data
input e.g. full, atomic etc., defaults to 'atomic'
:type atom_style: str, optional
:param variables: Dictionary of variables to be defined into the lammps
input, defaults to None
:type variables: Dict, optional
:param placeholders: Dictionary of placeholders to be filled into the
lammps input, defaults to None
:type placeholders: Dict, optional
:param lmp_cmd: Command with which to run LAMMPS, defaults to 'lmp'
:type lmp_cmd: str, optional
:param masses: Whether to specify atomic masses in LAMMPS data input,
requires ASE>3.23.0, defaults to True
:param velocities: Whether to specify atomic velocities in LAMMPS data input,
requires ASE>3.23.0, defaults to False
:type velocities: bool, optional
:param seed: Random seed for anywhere necessary in the template. You will
need to put the 'SEED' placeholder anywhere you want a random
seed to be placed, if seed=None, a random one is generated,
defaults to None
:type seed: int, optional
:param restart_template: Optional lammps input template to be used to
generate a restart.lammps file, defaults to None
:type restart_template: str, optional
:param specorder: Optional list of atomic species in the order they
should appear in the LAMMPS data input file, defaults to None
:type specorder: Sequence[str], optional
:return: LAMMPS out file names
:rtype: Dict
"""
if variables is None:
variables = {}
# Make sure the provided asimmodule follows standard for reading
# in arbitrary image provided by asimtools
if image is not None:
atoms = get_atoms(**image)
if masses or velocities:
try:
atoms.write(
'image_input.lmpdat',
format='lammps-data',
atom_style=atom_style,
masses=masses,
velocities=velocities,
specorder=specorder,
)
except ValueError as te:
err_txt = 'Need ASE version >=3.23 to support writing '
err_txt += 'masses to lammps input file. Add mass keyword to '
err_txt += 'lammps template instead'
print(err_txt, file=sys.stderr)
logging.error(err_txt)
raise Exception(err_txt) from te
else:
atoms.write(
'image_input.lmpdat',
format='lammps-data',
atom_style=atom_style,
)
variables['IMAGE_FILE'] = 'image_input.lmpdat'
lmp_txt = ''
for variable, value in variables.items():
lmp_txt += f'variable {variable} equal {value}\n'
lmp_txt += '\n'
template = Path(template).resolve()
with open(template, 'r', encoding='utf-8') as f:
lines = f.readlines()
if placeholders is None:
placeholders = {}
if restart_template is not None:
restart_txt = lmp_txt
with open(restart_template, 'r', encoding='utf-8') as f:
restart_lines = f.readlines()
for rline in restart_lines:
if placeholders is not None:
for placeholder in placeholders:
rline = rline.replace(placeholder, str(placeholders[placeholder]))
restart_txt += rline
with open('restart.lammps', 'w', encoding='utf-8') as f:
f.write(restart_txt)
for line in lines:
if 'SEED' in line and 'SEED' not in placeholders:
if seed is None:
seed = str(randint(0, 100000))
line = line.replace('SEED', str(seed))
if placeholders is not None:
for placeholder in placeholders:
line = line.replace(placeholder, str(placeholders[placeholder]))
lmp_txt += line
if image is not None:
assert 'read_data ' in lmp_txt, \
'Make sure "read_data image_input.lmpdat" command is used \
(with correct atom style) appropriately in lammps input \
file if you specify image keyword'
lmp_inp_file = 'input.lammps'
with open(lmp_inp_file, 'w', encoding='utf-8') as f:
f.write(lmp_txt)
command = lmp_cmd + f' -i {lmp_inp_file}'
command = command.split(' ')
completed_process = subprocess.run(
command, check=False, capture_output=True, text=True,
)
with open('lmp_stdout.txt', 'a+', encoding='utf-8') as f:
f.write(completed_process.stdout)
if completed_process.returncode != 0:
err_txt = f'Failed to run {lmp_inp_file}\n'
err_txt += 'See lmp_stderr.txt for details.'
logging.error(err_txt)
with open('lmp_stderr.txt', 'a+', encoding='utf-8') as f:
f.write(completed_process.stderr)
completed_process.check_returncode()
return {}
results = {'files': {
'log': 'log.lammps',
'thermo': 'log.lammps',
}}
return results