1023 lines
31 KiB
Python
1023 lines
31 KiB
Python
# ----------------------------------------------------------------------
|
|
# 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.
|
|
# -------------------------------------------------------------------------
|
|
|
|
################################################################################
|
|
# Alternative Python Wrapper
|
|
# Written by Richard Berger <richard.berger@outlook.com>
|
|
################################################################################
|
|
|
|
# for python2/3 compatibility
|
|
|
|
from __future__ import print_function
|
|
|
|
import io
|
|
import os
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
from collections import namedtuple
|
|
|
|
from lammps.core import lammps
|
|
from lammps.constants import LMP_VAR_EQUAL, LMP_VAR_ATOM, LMP_VAR_VECTOR, LMP_VAR_STRING
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class OutputCapture(object):
|
|
""" Utility class to capture LAMMPS library output """
|
|
def __init__(self):
|
|
self.stdout_fd = 1
|
|
self.captured_output = ""
|
|
|
|
def __enter__(self):
|
|
self.tmpfile = tempfile.TemporaryFile(mode='w+b')
|
|
|
|
sys.stdout.flush()
|
|
|
|
# make copy of original stdout
|
|
self.stdout_orig = os.dup(self.stdout_fd)
|
|
|
|
# replace stdout and redirect to temp file
|
|
os.dup2(self.tmpfile.fileno(), self.stdout_fd)
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
os.dup2(self.stdout_orig, self.stdout_fd)
|
|
os.close(self.stdout_orig)
|
|
self.tmpfile.close()
|
|
|
|
@property
|
|
def output(self):
|
|
sys.stdout.flush()
|
|
self.tmpfile.flush()
|
|
self.tmpfile.seek(0, io.SEEK_SET)
|
|
self.captured_output = self.tmpfile.read().decode('utf-8')
|
|
return self.captured_output
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class Variable(object):
|
|
def __init__(self, pylammps_instance, name):
|
|
self._pylmp = pylammps_instance
|
|
self.name = name
|
|
|
|
@property
|
|
def style(self):
|
|
vartype = self._pylmp.lmp.lib.lammps_extract_variable_datatype(self._pylmp.lmp.lmp, self.name.encode())
|
|
if vartype == LMP_VAR_EQUAL:
|
|
return "equal"
|
|
elif vartype == LMP_VAR_ATOM:
|
|
return "atom"
|
|
elif vartype == LMP_VAR_VECTOR:
|
|
return "vector"
|
|
elif vartype == LMP_VAR_STRING:
|
|
return "string"
|
|
return None
|
|
|
|
@property
|
|
def value(self):
|
|
return self._pylmp.lmp.extract_variable(self.name)
|
|
|
|
@value.setter
|
|
def value(self, newvalue):
|
|
style = self.style
|
|
if style == "equal" or style == "string":
|
|
self._pylmp.variable("{} {} {}".format(self.name, style, newvalue))
|
|
else:
|
|
raise Exception("Setter not implemented for {} style variables.".format(style))
|
|
|
|
def __str__(self):
|
|
value = self.value
|
|
if isinstance(value, str):
|
|
value = "\"{}\"".format(value)
|
|
return "Variable(name=\"{}\", value={})".format(self.name, value)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class AtomList(object):
|
|
"""
|
|
A dynamic list of atoms that returns either an :py:class:`Atom` or
|
|
:py:class:`Atom2D` instance for each atom. Instances are only allocated
|
|
when accessed.
|
|
|
|
:ivar natoms: total number of atoms
|
|
:ivar dimensions: number of dimensions in system
|
|
"""
|
|
def __init__(self, pylammps_instance):
|
|
self._pylmp = pylammps_instance
|
|
self.natoms = self._pylmp.system.natoms
|
|
self.dimensions = self._pylmp.system.dimensions
|
|
self._loaded = {}
|
|
|
|
def __getitem__(self, index):
|
|
"""
|
|
Return Atom with given local index
|
|
|
|
:param index: Local index of atom
|
|
:type index: int
|
|
:rtype: Atom or Atom2D
|
|
"""
|
|
if index not in self._loaded:
|
|
if self.dimensions == 2:
|
|
atom = Atom2D(self._pylmp, index)
|
|
else:
|
|
atom = Atom(self._pylmp, index)
|
|
self._loaded[index] = atom
|
|
return self._loaded[index]
|
|
|
|
def __len__(self):
|
|
return self.natoms
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class Atom(object):
|
|
"""
|
|
A wrapper class then represents a single atom inside of LAMMPS
|
|
|
|
It provides access to properties of the atom and allows you to change some of them.
|
|
"""
|
|
def __init__(self, pylammps_instance, index):
|
|
self._pylmp = pylammps_instance
|
|
self.index = index
|
|
|
|
def __dir__(self):
|
|
return [k for k in super().__dir__() if not k.startswith('_')]
|
|
|
|
def get(self, name, index):
|
|
prop = self._pylmp.lmp.numpy.extract_atom(name)
|
|
if prop is not None:
|
|
return prop[index]
|
|
return None
|
|
|
|
@property
|
|
def id(self):
|
|
"""
|
|
Return the atom ID
|
|
|
|
:type: int
|
|
"""
|
|
return self.get("id", self.index)
|
|
|
|
@property
|
|
def type(self):
|
|
"""
|
|
Return the atom type
|
|
|
|
:type: int
|
|
"""
|
|
return self.get("type", self.index)
|
|
|
|
@property
|
|
def mol(self):
|
|
"""
|
|
Return the atom molecule index
|
|
|
|
:type: int
|
|
"""
|
|
return self.get("mol", self.index)
|
|
|
|
@property
|
|
def mass(self):
|
|
"""
|
|
Return the atom mass as a per-atom property.
|
|
This returns either the per-type mass or the per-atom
|
|
mass (AKA 'rmass') depending on what is available with
|
|
preference for the per-atom mass.
|
|
|
|
.. versionchanged:: 17Apr2024
|
|
|
|
Support both per-type and per-atom masses. With
|
|
per-type return "mass[type[i]]" else return "rmass[i]".
|
|
Per-atom mass is preferred if available.
|
|
|
|
:type: float
|
|
"""
|
|
if self._pylmp.lmp.extract_setting('rmass_flag'):
|
|
return self.get("rmass", self.index)
|
|
else:
|
|
return self.get("mass", self.type)
|
|
|
|
@property
|
|
def radius(self):
|
|
"""
|
|
Return the particle radius
|
|
|
|
:type: float
|
|
"""
|
|
return self.get("radius", self.index)
|
|
|
|
@property
|
|
def position(self):
|
|
"""
|
|
:getter: Return position of atom
|
|
:setter: Set position of atom
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("x", self.index)
|
|
|
|
@position.setter
|
|
def position(self, value):
|
|
current = self.position
|
|
current[:] = value
|
|
|
|
@property
|
|
def velocity(self):
|
|
"""
|
|
:getter: Return velocity of atom
|
|
:setter: Set velocity of atom
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("v", self.index)
|
|
|
|
@velocity.setter
|
|
def velocity(self, value):
|
|
current = self.velocity
|
|
current[:] = value
|
|
|
|
@property
|
|
def force(self):
|
|
"""
|
|
Return the total force acting on the atom
|
|
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("f", self.index)
|
|
|
|
@force.setter
|
|
def force(self, value):
|
|
current = self.force
|
|
current[:] = value
|
|
|
|
@property
|
|
def torque(self):
|
|
"""
|
|
Return the total torque acting on the atom
|
|
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("torque", self.index)
|
|
|
|
@force.setter
|
|
def torque(self, value):
|
|
current = self.torque
|
|
current[:] = value
|
|
|
|
@property
|
|
def omega(self):
|
|
"""
|
|
Return the rotational velocity of the particle
|
|
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("torque", self.index)
|
|
|
|
@omega.setter
|
|
def omega(self, value):
|
|
current = self.torque
|
|
current[:] = value
|
|
|
|
@property
|
|
def torque(self):
|
|
"""
|
|
Return the total torque acting on the particle
|
|
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("torque", self.index)
|
|
|
|
@torque.setter
|
|
def torque(self, value):
|
|
current = self.torque
|
|
current[:] = value
|
|
|
|
@property
|
|
def angular_momentum(self):
|
|
"""
|
|
Return the angular momentum of the particle
|
|
|
|
:type: numpy.array (float, float, float)
|
|
"""
|
|
return self.get("angmom", self.index)
|
|
|
|
@angular_momentum.setter
|
|
def angular_momentum(self, value):
|
|
current = self.angular_momentum
|
|
current[:] = value
|
|
|
|
@property
|
|
def charge(self):
|
|
"""
|
|
Return the atom charge
|
|
|
|
:type: float
|
|
"""
|
|
return self.get("q", self.index)
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class Atom2D(Atom):
|
|
"""
|
|
A wrapper class then represents a single 2D atom inside of LAMMPS
|
|
|
|
Inherits all properties from the :py:class:`Atom` class, but returns 2D versions
|
|
of position, velocity, and force.
|
|
|
|
It provides access to properties of the atom and allows you to change some of them.
|
|
"""
|
|
def __init__(self, pylammps_instance, index):
|
|
super(Atom2D, self).__init__(pylammps_instance, index)
|
|
|
|
@property
|
|
def position(self):
|
|
"""Access to coordinates of an atom
|
|
|
|
:getter: Return position of atom
|
|
:setter: Set position of atom
|
|
:type: numpy.array (float, float)
|
|
"""
|
|
return super(Atom2D, self).position[0:2]
|
|
|
|
@position.setter
|
|
def position(self, value):
|
|
current = self.position
|
|
current[:] = value
|
|
|
|
@property
|
|
def velocity(self):
|
|
"""Access to velocity of an atom
|
|
|
|
:getter: Return velocity of atom
|
|
:setter: Set velocity of atom
|
|
:type: numpy.array (float, float)
|
|
"""
|
|
return super(Atom2D, self).velocity[0:2]
|
|
|
|
@velocity.setter
|
|
def velocity(self, value):
|
|
current = self.velocity
|
|
current[:] = value
|
|
|
|
@property
|
|
def force(self):
|
|
"""Access to force of an atom
|
|
|
|
:getter: Return force of atom
|
|
:setter: Set force of atom
|
|
:type: numpy.array (float, float)
|
|
"""
|
|
return super(Atom2D, self).force[0:2]
|
|
|
|
@force.setter
|
|
def force(self, value):
|
|
current = self.force
|
|
current[:] = value
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
class variable_set:
|
|
def __init__(self, name, variable_dict):
|
|
self._name = name
|
|
array_pattern = re.compile(r"(?P<arr>.+)\[(?P<index>[0-9]+)\]")
|
|
|
|
for key, value in variable_dict.items():
|
|
m = array_pattern.match(key)
|
|
if m:
|
|
g = m.groupdict()
|
|
varname = g['arr']
|
|
idx = int(g['index'])
|
|
if varname not in self.__dict__:
|
|
self.__dict__[varname] = {}
|
|
self.__dict__[varname][idx] = value
|
|
else:
|
|
self.__dict__[key] = value
|
|
|
|
def __str__(self):
|
|
return "{}({})".format(self._name, ','.join(["{}={}".format(k, self.__dict__[k]) for k in self.__dict__.keys() if not k.startswith('_')]))
|
|
|
|
def __dir__(self):
|
|
return [k for k in self.__dict__.keys() if not k.startswith('_')]
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
# -------------------------------------------------------------------------
|
|
# -------------------------------------------------------------------------
|
|
|
|
class PyLammps(object):
|
|
"""
|
|
This is a Python wrapper class around the lower-level
|
|
:py:class:`lammps` class, exposing a more Python-like,
|
|
object-oriented interface for prototyping systems inside of IPython and
|
|
Jupyter notebooks.
|
|
|
|
It either creates its own instance of :py:class:`lammps` or can be
|
|
initialized with an existing instance. The arguments are the same of the
|
|
lower-level interface. The original interface can still be accessed via
|
|
:py:attr:`PyLammps.lmp`.
|
|
|
|
.. deprecated:: TBA
|
|
|
|
:param name: "machine" name of the shared LAMMPS library ("mpi" loads ``liblammps_mpi.so``, "" loads ``liblammps.so``)
|
|
:type name: string
|
|
:param cmdargs: list of command line arguments to be passed to the :cpp:func:`lammps_open` function. The executable name is automatically added.
|
|
:type cmdargs: list
|
|
:param ptr: pointer to a LAMMPS C++ class instance when called from an embedded Python interpreter. None means load symbols from shared library.
|
|
:type ptr: pointer
|
|
:param comm: MPI communicator (as provided by `mpi4py <mpi4py_docs_>`_). ``None`` means use ``MPI_COMM_WORLD`` implicitly.
|
|
:type comm: MPI_Comm
|
|
:param verbose: print all LAMMPS output to stdout
|
|
:type verbose: bool
|
|
|
|
:ivar lmp: instance of original LAMMPS Python interface
|
|
:vartype lmp: :py:class:`lammps`
|
|
|
|
:ivar runs: list of completed runs, each storing the thermo output
|
|
:vartype run: list
|
|
"""
|
|
|
|
def __init__(self, name="", cmdargs=None, ptr=None, comm=None, verbose=False):
|
|
self.has_echo = False
|
|
self.verbose = verbose
|
|
|
|
if cmdargs:
|
|
if '-echo' in cmdargs:
|
|
idx = cmdargs.index('-echo')
|
|
# ensures that echo line is ignored during output capture
|
|
self.has_echo = idx+1 < len(cmdargs) and cmdargs[idx+1] in ('screen', 'both')
|
|
|
|
if ptr:
|
|
if isinstance(ptr,PyLammps):
|
|
self.lmp = ptr.lmp
|
|
elif isinstance(ptr,lammps):
|
|
self.lmp = ptr
|
|
else:
|
|
self.lmp = lammps(name=name,cmdargs=cmdargs,ptr=ptr,comm=comm)
|
|
else:
|
|
self.lmp = lammps(name=name,cmdargs=cmdargs,ptr=None,comm=comm)
|
|
self.comm_nprocs = self.lmp.extract_setting("world_size")
|
|
self.comm_me = self.lmp.extract_setting("world_rank")
|
|
if self.comm_me == 0:
|
|
print("\nWARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING")
|
|
print("WARNING:")
|
|
print("WARNING: The PyLammps class is obsolete and will be removed from LAMMPS soon.")
|
|
print("WARNING: Please use the lammps Python class instead.")
|
|
print("WARNING:")
|
|
print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING\n")
|
|
print("LAMMPS output is captured by PyLammps wrapper")
|
|
if self.comm_nprocs > 1:
|
|
print("WARNING: Using PyLammps with multiple MPI ranks is experimental. Not all functionality is supported.")
|
|
self._cmd_history = []
|
|
self._enable_cmd_history = False
|
|
self.runs = []
|
|
|
|
if not self.lmp.has_package("PYTHON"):
|
|
if self.comm_me == 0:
|
|
print("WARNING: run thermo data not captured since PYTHON LAMMPS package is not enabled")
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, ex_type, ex_value, ex_traceback):
|
|
self.close()
|
|
|
|
def __del__(self):
|
|
if self.lmp: self.lmp.close()
|
|
self.lmp = None
|
|
|
|
def close(self):
|
|
"""Explicitly delete a LAMMPS instance
|
|
|
|
This is a wrapper around the :py:meth:`lammps.close` of the Python interface.
|
|
"""
|
|
if self.lmp: self.lmp.close()
|
|
self.lmp = None
|
|
|
|
def version(self):
|
|
"""Return a numerical representation of the LAMMPS version in use.
|
|
|
|
This is a wrapper around the :py:meth:`lammps.version` function of the Python interface.
|
|
|
|
:return: version number
|
|
:rtype: int
|
|
"""
|
|
return self.lmp.version()
|
|
|
|
def file(self, file):
|
|
"""Read LAMMPS commands from a file.
|
|
|
|
This is a wrapper around the :py:meth:`lammps.file` function of the Python interface.
|
|
|
|
:param path: Name of the file/path with LAMMPS commands
|
|
:type path: string
|
|
"""
|
|
self.lmp.file(file)
|
|
|
|
@property
|
|
def enable_cmd_history(self):
|
|
"""
|
|
:getter: Return whether command history is saved
|
|
:setter: Set if command history should be saved
|
|
:type: bool
|
|
"""
|
|
return self._enable_cmd_history
|
|
|
|
@enable_cmd_history.setter
|
|
def enable_cmd_history(self, value):
|
|
"""
|
|
:getter: Return whether command history is saved
|
|
:setter: Set if command history should be saved
|
|
:type: bool
|
|
"""
|
|
self._enable_cmd_history = (value == True)
|
|
|
|
def write_script(self, filepath):
|
|
"""
|
|
Write LAMMPS script file containing all commands executed up until now
|
|
|
|
:param filepath: path to script file that should be written
|
|
:type filepath: string
|
|
"""
|
|
with open(filepath, "w") as f:
|
|
for cmd in self._cmd_history:
|
|
print(cmd, file=f)
|
|
|
|
def clear_cmd_history(self):
|
|
"""
|
|
Clear LAMMPS command history up to this point
|
|
"""
|
|
self._cmd_history = []
|
|
|
|
|
|
def append_cmd_history(self, cmd):
|
|
"""
|
|
Commands will be added to the command history but not executed.
|
|
|
|
Add `commands` only to the command history, but do not execute them, so that you can
|
|
conveniently create LAMMPS input files, using
|
|
:py:meth:`PyLammps.write_script()`.
|
|
"""
|
|
self._cmd_history.append(cmd)
|
|
|
|
|
|
def command(self, cmd):
|
|
"""
|
|
Execute LAMMPS command
|
|
|
|
If :py:attr:`PyLammps.enable_cmd_history` is set to ``True``, commands executed
|
|
will be recorded. The entire command history can be written to a file using
|
|
:py:meth:`PyLammps.write_script()`. To clear the command history, use
|
|
:py:meth:`PyLammps.clear_cmd_history()`.
|
|
|
|
:param cmd: command string that should be executed
|
|
:type: cmd: string
|
|
"""
|
|
self.lmp.command(cmd)
|
|
|
|
if self.enable_cmd_history:
|
|
self._cmd_history.append(cmd)
|
|
|
|
def _append_run_thermo(self, thermo):
|
|
for k, v in thermo.items():
|
|
if k in self._current_run:
|
|
self._current_run[k].append(v)
|
|
else:
|
|
self._current_run[k] = [v]
|
|
|
|
def run(self, *args, **kwargs):
|
|
"""
|
|
Execute LAMMPS run command with given arguments
|
|
|
|
Thermo data of the run is recorded and saved as new entry in
|
|
:py:attr:`PyLammps.runs`. The latest run can be retrieved by
|
|
:py:attr:`PyLammps.last_run`.
|
|
|
|
Note, for recording of all thermo steps during a run, the PYTHON package
|
|
needs to be enabled in LAMMPS. Otherwise, it will only capture the final
|
|
timestep.
|
|
"""
|
|
self._current_run = {}
|
|
self._last_thermo_step = -1
|
|
def end_of_step_callback(lmp):
|
|
if self.lmp.last_thermo_step == self._last_thermo_step: return
|
|
thermo = self.lmp.last_thermo()
|
|
self._append_run_thermo(thermo)
|
|
self._last_thermo_step = thermo['Step']
|
|
|
|
import __main__
|
|
__main__._PyLammps_end_of_step_callback = end_of_step_callback
|
|
capture_thermo = self.lmp.has_package("PYTHON")
|
|
|
|
if capture_thermo:
|
|
self.fix("__pylammps_internal_run_callback", "all", "python/invoke", "1", "end_of_step", "_PyLammps_end_of_step_callback")
|
|
|
|
output = self.__getattr__('run')(*args, **kwargs)
|
|
|
|
if capture_thermo:
|
|
self.unfix("__pylammps_internal_run_callback")
|
|
self._append_run_thermo(self.lmp.last_thermo())
|
|
|
|
thermo_data = variable_set('ThermoData', self._current_run)
|
|
r = {'thermo' : thermo_data }
|
|
self.runs.append(namedtuple('Run', list(r.keys()))(*list(r.values())))
|
|
return output
|
|
|
|
@property
|
|
def last_run(self):
|
|
"""
|
|
Return data produced of last completed run command
|
|
|
|
:getter: Returns an object containing information about the last run command
|
|
:type: dict
|
|
"""
|
|
if len(self.runs) > 0:
|
|
return self.runs[-1]
|
|
return None
|
|
|
|
@property
|
|
def atoms(self):
|
|
"""
|
|
All atoms of this LAMMPS instance
|
|
|
|
:getter: Returns a list of atoms currently in the system
|
|
:type: AtomList
|
|
"""
|
|
return AtomList(self)
|
|
|
|
@property
|
|
def system(self):
|
|
"""
|
|
The system state of this LAMMPS instance
|
|
|
|
:getter: Returns an object with properties storing the current system state
|
|
:type: namedtuple
|
|
"""
|
|
output = self.lmp_info("system")
|
|
output = output[output.index("System information:")+1:]
|
|
d = self._parse_info_system(output)
|
|
return namedtuple('System', d.keys())(*d.values())
|
|
|
|
@property
|
|
def communication(self):
|
|
"""
|
|
The communication state of this LAMMPS instance
|
|
|
|
:getter: Returns an object with properties storing the current communication state
|
|
:type: namedtuple
|
|
"""
|
|
output = self.lmp_info("communication")
|
|
output = output[output.index("Communication information:")+1:]
|
|
d = self._parse_info_communication(output)
|
|
return namedtuple('Communication', d.keys())(*d.values())
|
|
|
|
@property
|
|
def computes(self):
|
|
"""
|
|
The list of active computes of this LAMMPS instance
|
|
|
|
:getter: Returns a list of computes that are currently active in this LAMMPS instance
|
|
:type: list
|
|
"""
|
|
output = self.lmp_info("computes")
|
|
output = output[output.index("Compute information:")+1:]
|
|
return self._parse_element_list(output)
|
|
|
|
@property
|
|
def dumps(self):
|
|
"""
|
|
The list of active dumps of this LAMMPS instance
|
|
|
|
:getter: Returns a list of dumps that are currently active in this LAMMPS instance
|
|
:type: list
|
|
"""
|
|
output = self.lmp_info("dumps")
|
|
output = output[output.index("Dump information:")+1:]
|
|
return self._parse_element_list(output)
|
|
|
|
@property
|
|
def fixes(self):
|
|
"""
|
|
The list of active fixes of this LAMMPS instance
|
|
|
|
:getter: Returns a list of fixes that are currently active in this LAMMPS instance
|
|
:type: list
|
|
"""
|
|
output = self.lmp_info("fixes")
|
|
output = output[output.index("Fix information:")+1:]
|
|
return self._parse_element_list(output)
|
|
|
|
@property
|
|
def groups(self):
|
|
"""
|
|
The list of active atom groups of this LAMMPS instance
|
|
|
|
:getter: Returns a list of atom groups that are currently active in this LAMMPS instance
|
|
:type: list
|
|
"""
|
|
return self.lmp.available_ids("group")
|
|
|
|
@property
|
|
def variables(self):
|
|
"""
|
|
Returns a dictionary of all variables defined in the current LAMMPS instance
|
|
|
|
:getter: Returns a dictionary of all variables that are defined in this LAMMPS instance
|
|
:type: dict
|
|
"""
|
|
variables = {}
|
|
for name in self.lmp.available_ids("variable"):
|
|
variables[name] = Variable(self, name)
|
|
return variables
|
|
|
|
def eval(self, expr):
|
|
"""
|
|
Evaluate LAMMPS input file expression.
|
|
|
|
This is equivalent to using immediate variable expressions in the format "$(...)"
|
|
in the LAMMPS input and will return the result of that expression.
|
|
|
|
.. warning::
|
|
|
|
This function is only supported on MPI rank 0. Calling it from a different
|
|
MPI rank will raise an exception.
|
|
|
|
:param expr: the expression string that should be evaluated inside of LAMMPS
|
|
:type expr: string
|
|
|
|
:return: the value of the evaluated expression
|
|
:rtype: float if numeric, string otherwise
|
|
"""
|
|
if self.comm_me > 0:
|
|
raise Exception("PyLammps.eval() may only be used on MPI rank 0")
|
|
|
|
value = self.lmp_print('"$(%s)"' % expr).strip()
|
|
try:
|
|
return float(value)
|
|
except ValueError:
|
|
return value
|
|
|
|
def _split_values(self, line):
|
|
return [x.strip() for x in line.split(',')]
|
|
|
|
def _get_pair(self, value):
|
|
return [x.strip() for x in value.split('=')]
|
|
|
|
def _parse_info_system(self, output):
|
|
system = {}
|
|
system['dimensions'] = self.lmp.extract_setting("dimension")
|
|
system['xlo'] = self.lmp.extract_global("boxxlo")
|
|
system['ylo'] = self.lmp.extract_global("boxylo")
|
|
system['zlo'] = self.lmp.extract_global("boxzlo")
|
|
system['xhi'] = self.lmp.extract_global("boxxhi")
|
|
system['yhi'] = self.lmp.extract_global("boxyhi")
|
|
system['zhi'] = self.lmp.extract_global("boxzhi")
|
|
xprd = system["xhi"] - system["xlo"]
|
|
yprd = system["yhi"] - system["ylo"]
|
|
zprd = system["zhi"] - system["zlo"]
|
|
if self.lmp.extract_setting("triclinic") == 1:
|
|
system['triclinic_box'] = (xprd, yprd, zprd)
|
|
else:
|
|
system['orthogonal_box'] = (xprd, yprd, zprd)
|
|
system['nangles'] = self.lmp.extract_global("nbonds")
|
|
system['nangletypes'] = self.lmp.extract_setting("nbondtypes")
|
|
system['angle_style'] = self.lmp.extract_global("angle_style")
|
|
system['nbonds'] = self.lmp.extract_global("nbonds")
|
|
system['nbondtypes'] = self.lmp.extract_setting("nbondtypes")
|
|
system['bond_style'] = self.lmp.extract_global("bond_style")
|
|
system['ndihedrals'] = self.lmp.extract_global("ndihedrals")
|
|
system['ndihedraltypes'] = self.lmp.extract_setting("ndihedraltypes")
|
|
system['dihedral_style'] = self.lmp.extract_global("dihedral_style")
|
|
system['nimpropers'] = self.lmp.extract_global("nimpropers")
|
|
system['nimpropertypes'] = self.lmp.extract_setting("nimpropertypes")
|
|
system['improper_style'] = self.lmp.extract_global("improper_style")
|
|
system['kspace_style'] = self.lmp.extract_global("kspace_style")
|
|
system['natoms'] = self.lmp.extract_global("natoms")
|
|
system['ntypes'] = self.lmp.extract_global("ntypes")
|
|
system['pair_style'] = self.lmp.extract_global("pair_style")
|
|
system['atom_style'] = self.lmp.extract_global("atom_style")
|
|
system['units'] = self.lmp.extract_global("units")
|
|
|
|
for line in output:
|
|
if line.startswith("Atom map"):
|
|
system['atom_map'] = self._get_pair(line)[1]
|
|
elif line.startswith("Boundaries"):
|
|
system['boundaries'] = self._get_pair(line)[1]
|
|
elif line.startswith("Molecule type"):
|
|
system['molecule_type'] = self._get_pair(line)[1]
|
|
|
|
return system
|
|
|
|
def _parse_info_communication(self, output):
|
|
comm = {}
|
|
comm['nprocs'] = self.lmp.extract_setting("world_size")
|
|
comm['nthreads'] = self.lmp.extract_setting("nthreads")
|
|
comm['proc_grid'] = comm['procgrid'] = self.lmp.extract_global("procgrid")
|
|
idx = self.lmp.extract_setting("comm_style")
|
|
comm['comm_style'] = ('brick', 'tiled')[idx]
|
|
idx = self.lmp.extract_setting("comm_style")
|
|
comm['comm_layout'] = ('uniform', 'nonuniform', 'irregular')[idx]
|
|
comm['ghost_velocity'] = self.lmp.extract_setting("ghost_velocity") == 1
|
|
|
|
for line in output:
|
|
if line.startswith("MPI library"):
|
|
comm['mpi_version'] = line.split(':')[1].strip()
|
|
return comm
|
|
|
|
def _parse_element_list(self, output):
|
|
elements = []
|
|
|
|
for line in output:
|
|
if not line or (":" not in line): continue
|
|
element_info = self._split_values(line.split(':')[1].strip())
|
|
element = {'name': element_info[0]}
|
|
for key, value in [self._get_pair(x) for x in element_info[1:]]:
|
|
element[key] = value
|
|
elements.append(element)
|
|
return elements
|
|
|
|
def lmp_print(self, s):
|
|
""" needed for Python2 compatibility, since print is a reserved keyword """
|
|
return self.__getattr__("print")(s)
|
|
|
|
def __dir__(self):
|
|
return sorted(set(['angle_coeff', 'angle_style', 'atom_modify', 'atom_style', 'atom_style',
|
|
'bond_coeff', 'bond_style', 'boundary', 'change_box', 'communicate', 'compute',
|
|
'create_atoms', 'create_box', 'delete_atoms', 'delete_bonds', 'dielectric',
|
|
'dihedral_coeff', 'dihedral_style', 'dimension', 'dump', 'fix', 'fix_modify',
|
|
'group', 'improper_coeff', 'improper_style', 'include', 'kspace_modify',
|
|
'kspace_style', 'lattice', 'mass', 'minimize', 'min_style', 'neighbor',
|
|
'neigh_modify', 'newton', 'nthreads', 'pair_coeff', 'pair_modify',
|
|
'pair_style', 'processors', 'read', 'read_data', 'read_restart', 'region',
|
|
'replicate', 'reset_timestep', 'restart', 'run', 'run_style', 'thermo',
|
|
'thermo_modify', 'thermo_style', 'timestep', 'undump', 'unfix', 'units',
|
|
'variable', 'velocity', 'write_restart'] + self.lmp.available_styles("command")))
|
|
|
|
def lmp_info(self, s):
|
|
# skip anything before and after Info-Info-Info
|
|
# also skip timestamp line
|
|
output = self.__getattr__("info")(s)
|
|
indices = [index for index, line in enumerate(output) if line.startswith("Info-Info-Info-Info")]
|
|
start = indices[0]
|
|
end = indices[1]
|
|
return [line for line in output[start+2:end] if line]
|
|
|
|
def __getattr__(self, name):
|
|
"""
|
|
This method is where the Python 'magic' happens. If a method is not
|
|
defined by the class PyLammps, it assumes it is a LAMMPS command. It takes
|
|
all the arguments, concatinates them to a single string, and executes it using
|
|
:py:meth:`lammps.PyLammps.command()`.
|
|
|
|
:param verbose: Print output of command
|
|
:type verbose: bool
|
|
:return: line or list of lines of output, None if no output
|
|
:rtype: list or string
|
|
"""
|
|
def handler(*args, **kwargs):
|
|
cmd_args = [name] + [str(x) for x in args]
|
|
self.lmp.flush_buffers()
|
|
|
|
with OutputCapture() as capture:
|
|
cmd = ' '.join(cmd_args)
|
|
self.command(cmd)
|
|
self.lmp.flush_buffers()
|
|
output = capture.output
|
|
|
|
comm = self.lmp.get_mpi_comm()
|
|
if comm:
|
|
output = self.lmp.comm.bcast(output, root=0)
|
|
|
|
if self.verbose or ('verbose' in kwargs and kwargs['verbose']):
|
|
print(output, end = '')
|
|
|
|
lines = output.splitlines()
|
|
|
|
if self.has_echo:
|
|
lines = lines[1:]
|
|
|
|
if len(lines) > 1:
|
|
return lines
|
|
elif len(lines) == 1:
|
|
return lines[0]
|
|
return None
|
|
|
|
return handler
|
|
|
|
|
|
class IPyLammps(PyLammps):
|
|
"""
|
|
IPython wrapper for LAMMPS which adds embedded graphics capabilities to PyLammps interface
|
|
|
|
It either creates its own instance of :py:class:`lammps` or can be
|
|
initialized with an existing instance. The arguments are the same of the
|
|
lower-level interface. The original interface can still be accessed via
|
|
:py:attr:`PyLammps.lmp`.
|
|
|
|
:param name: "machine" name of the shared LAMMPS library ("mpi" loads ``liblammps_mpi.so``, "" loads ``liblammps.so``)
|
|
:type name: string
|
|
:param cmdargs: list of command line arguments to be passed to the :cpp:func:`lammps_open` function. The executable name is automatically added.
|
|
:type cmdargs: list
|
|
:param ptr: pointer to a LAMMPS C++ class instance when called from an embedded Python interpreter. None means load symbols from shared library.
|
|
:type ptr: pointer
|
|
:param comm: MPI communicator (as provided by `mpi4py <mpi4py_docs_>`_). ``None`` means use ``MPI_COMM_WORLD`` implicitly.
|
|
:type comm: MPI_Comm
|
|
"""
|
|
|
|
def __init__(self,name="",cmdargs=None,ptr=None,comm=None):
|
|
super(IPyLammps, self).__init__(name=name,cmdargs=cmdargs,ptr=ptr,comm=comm)
|
|
|
|
def image(self, filename="snapshot.png", group="all", color="type", diameter="type",
|
|
size=None, view=None, center=None, up=None, zoom=1.0, background_color="white"):
|
|
""" Generate image using write_dump command and display it
|
|
|
|
See :doc:`dump image <dump_image>` for more information.
|
|
|
|
:param filename: Name of the image file that should be generated. The extension determines whether it is PNG or JPEG
|
|
:type filename: string
|
|
:param group: the group of atoms write_image should use
|
|
:type group: string
|
|
:param color: name of property used to determine color
|
|
:type color: string
|
|
:param diameter: name of property used to determine atom diameter
|
|
:type diameter: string
|
|
:param size: dimensions of image
|
|
:type size: tuple (width, height)
|
|
:param view: view parameters
|
|
:type view: tuple (theta, phi)
|
|
:param center: center parameters
|
|
:type center: tuple (flag, center_x, center_y, center_z)
|
|
:param up: vector pointing to up direction
|
|
:type up: tuple (up_x, up_y, up_z)
|
|
:param zoom: zoom factor
|
|
:type zoom: float
|
|
:param background_color: background color of scene
|
|
:type background_color: string
|
|
|
|
:return: Image instance used to display image in notebook
|
|
:rtype: :py:class:`IPython.core.display.Image`
|
|
"""
|
|
cmd_args = [group, "image", filename, color, diameter]
|
|
|
|
if size is not None:
|
|
width = size[0]
|
|
height = size[1]
|
|
cmd_args += ["size", width, height]
|
|
|
|
if view is not None:
|
|
theta = view[0]
|
|
phi = view[1]
|
|
cmd_args += ["view", theta, phi]
|
|
|
|
if center is not None:
|
|
flag = center[0]
|
|
Cx = center[1]
|
|
Cy = center[2]
|
|
Cz = center[3]
|
|
cmd_args += ["center", flag, Cx, Cy, Cz]
|
|
|
|
if up is not None:
|
|
Ux = up[0]
|
|
Uy = up[1]
|
|
Uz = up[2]
|
|
cmd_args += ["up", Ux, Uy, Uz]
|
|
|
|
if zoom is not None:
|
|
cmd_args += ["zoom", zoom]
|
|
|
|
cmd_args.append("modify backcolor " + background_color)
|
|
|
|
self.write_dump(*cmd_args)
|
|
from IPython.core.display import Image
|
|
return Image(filename)
|
|
|
|
def video(self, filename):
|
|
"""
|
|
Load video from file
|
|
|
|
Can be used to visualize videos from :doc:`dump movie <dump_image>`.
|
|
|
|
:param filename: Path to video file
|
|
:type filename: string
|
|
:return: HTML Video Tag used by notebook to embed a video
|
|
:rtype: :py:class:`IPython.display.HTML`
|
|
"""
|
|
from IPython.display import HTML
|
|
return HTML("<video controls><source src=\"" + filename + "\"></video>")
|
|
|
|
# Local Variables:
|
|
# fill-column: 100
|
|
# python-indent-offset: 2
|
|
# End:
|