diff --git a/cmake/CMakeSettings.json b/cmake/CMakeSettings.json index ee4b3c46d5..9320341ec4 100644 --- a/cmake/CMakeSettings.json +++ b/cmake/CMakeSettings.json @@ -6,7 +6,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -25,6 +25,54 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x64-Release-MSVC", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${workspaceRoot}\\install\\${name}", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "BUILD_SHARED_LIBS", + "value": "True", + "type": "BOOL" + }, + { + "name": "BUILD_TOOLS", + "value": "True", + "type": "BOOL" + }, + { + "name": "LAMMPS_EXCEPTIONS", + "value": "True", + "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" } ] }, @@ -34,11 +82,16 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "clang_cl_x64" ], "variables": [ + { + "name": "BUILD_SHARED_LIBS", + "value": "True", + "type": "BOOL" + }, { "name": "BUILD_TOOLS", "value": "True", @@ -48,6 +101,16 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" } ] }, @@ -57,7 +120,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on -DCMAKE_CXX_COMPILER=icx -DCMAKE_C_COMPILER=icx -DBUILD_MPI=off", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DCMAKE_CXX_COMPILER=icx -DCMAKE_C_COMPILER=icx", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -76,6 +139,21 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" + }, + { + "name": "BUILD_MPI", + "value": "False", + "type": "BOOL" } ] }, @@ -85,7 +163,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=off -DCMAKE_CXX_COMPILER=icl -DCMAKE_C_COMPILER=icl -DCMAKE_Fortran_COMPILER=ifort -DBUILD_MPI=off", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DCMAKE_CXX_COMPILER=icl -DCMAKE_C_COMPILER=icl -DCMAKE_Fortran_COMPILER=ifort", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -104,8 +182,23 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "False", + "type": "BOOL" + }, + { + "name": "BUILD_MPI", + "value": "False", + "type": "BOOL" } ] } ] -} \ No newline at end of file +} diff --git a/doc/src/Developer_utils.rst b/doc/src/Developer_utils.rst index a9969b7543..7172f81eb7 100644 --- a/doc/src/Developer_utils.rst +++ b/doc/src/Developer_utils.rst @@ -205,6 +205,9 @@ Convenience functions .. doxygenfunction:: logmesg(LAMMPS *lmp, const std::string &mesg) :project: progguide +.. doxygenfunction:: flush_buffers(LAMMPS *lmp) + :project: progguide + .. doxygenfunction:: getsyserror :project: progguide diff --git a/doc/src/Library_utility.rst b/doc/src/Library_utility.rst index 32fac6bcc8..da64e3b8f0 100644 --- a/doc/src/Library_utility.rst +++ b/doc/src/Library_utility.rst @@ -13,6 +13,7 @@ functions. They do not directly call the LAMMPS library. - :cpp:func:`lammps_fix_external_set_virial_peratom` - :cpp:func:`lammps_fix_external_set_vector_length` - :cpp:func:`lammps_fix_external_set_vector` +- :cpp:func:`lammps_flush_buffers` - :cpp:func:`lammps_free` - :cpp:func:`lammps_is_running` - :cpp:func:`lammps_force_timeout` @@ -72,6 +73,11 @@ where such memory buffers were allocated that require the use of ----------------------- +.. doxygenfunction:: lammps_flush_buffers + :project: progguide + +----------------------- + .. doxygenfunction:: lammps_free :project: progguide diff --git a/python/lammps/core.py b/python/lammps/core.py index 62b0f5d8b6..d934ee1baa 100644 --- a/python/lammps/core.py +++ b/python/lammps/core.py @@ -164,6 +164,7 @@ class lammps(object): self.lib.lammps_open.restype = c_void_p self.lib.lammps_open_no_mpi.restype = c_void_p self.lib.lammps_close.argtypes = [c_void_p] + self.lib.lammps_flush_buffers.argtypes = [c_void_p] self.lib.lammps_free.argtypes = [c_void_p] self.lib.lammps_file.argtypes = [c_void_p, c_char_p] @@ -1118,6 +1119,16 @@ class lammps(object): # ------------------------------------------------------------------------- + def flush_buffers(self): + """Flush output buffers + + This is a wrapper around the :cpp:func:`lammps_flush_buffers` + function of the C-library interface. + """ + self.lib.lammps_flush_buffers(self.lmp) + + # ------------------------------------------------------------------------- + def set_variable(self,name,value): """Set a new value for a LAMMPS string style variable diff --git a/python/lammps/numpy_wrapper.py b/python/lammps/numpy_wrapper.py index 20ec85d001..3619728081 100644 --- a/python/lammps/numpy_wrapper.py +++ b/python/lammps/numpy_wrapper.py @@ -92,7 +92,7 @@ class numpy_wrapper: if dim == LAMMPS_AUTODETECT: if dtype in (LAMMPS_INT_2D, LAMMPS_DOUBLE_2D, LAMMPS_INT64_2D): # TODO add other fields - if name in ("x", "v", "f", "x0", "omega", "angmom", "torque", "vforce", "vest"): + if name in ("x", "v", "f", "x0","omega", "angmom", "torque", "csforce", "vforce", "vest"): dim = 3 elif name == "smd_data_9": dim = 9 diff --git a/python/lammps/pylammps.py b/python/lammps/pylammps.py index abd4d6da98..cdb6620c27 100644 --- a/python/lammps/pylammps.py +++ b/python/lammps/pylammps.py @@ -20,47 +20,47 @@ from __future__ import print_function +import io import os import re -import select +import sys +import tempfile from collections import namedtuple from .core import lammps +# ------------------------------------------------------------------------- class OutputCapture(object): """ Utility class to capture LAMMPS library output """ - def __init__(self): - self.stdout_pipe_read, self.stdout_pipe_write = os.pipe() self.stdout_fd = 1 + self.captured_output = "" def __enter__(self): - self.stdout = os.dup(self.stdout_fd) - os.dup2(self.stdout_pipe_write, self.stdout_fd) + 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, self.stdout_fd) - os.close(self.stdout) - os.close(self.stdout_pipe_read) - os.close(self.stdout_pipe_write) - - # check if we have more to read from the pipe - def more_data(self, pipe): - r, _, _ = select.select([pipe], [], [], 0) - return bool(r) - - # read the whole pipe - def read_pipe(self, pipe): - out = "" - while self.more_data(pipe): - out += os.read(pipe, 1024).decode() - return out + os.dup2(self.stdout_orig, self.stdout_fd) + os.close(self.stdout_orig) + self.tmpfile.close() @property def output(self): - return self.read_pipe(self.stdout_pipe_read) + 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 # ------------------------------------------------------------------------- @@ -109,9 +109,9 @@ class AtomList(object): """ if index not in self._loaded: if self.dimensions == 2: - atom = Atom2D(self._pylmp, index + 1) + atom = Atom2D(self._pylmp, index) else: - atom = Atom(self._pylmp, index + 1) + atom = Atom(self._pylmp, index) self._loaded[index] = atom return self._loaded[index] @@ -134,6 +134,12 @@ class Atom(object): 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): """ @@ -141,7 +147,7 @@ class Atom(object): :type: int """ - return int(self._pylmp.eval("id[%d]" % self.index)) + return self.get("id", self.index) @property def type(self): @@ -150,7 +156,7 @@ class Atom(object): :type: int """ - return int(self._pylmp.eval("type[%d]" % self.index)) + return self.get("type", self.index) @property def mol(self): @@ -159,7 +165,7 @@ class Atom(object): :type: int """ - return self._pylmp.eval("mol[%d]" % self.index) + return self.get("mol", self.index) @property def mass(self): @@ -168,52 +174,114 @@ class Atom(object): :type: float """ - return self._pylmp.eval("mass[%d]" % self.index) + return self.get("mass", self.index) + + @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: tuple (float, float, float) + :type: numpy.array (float, float, float) """ - return (self._pylmp.eval("x[%d]" % self.index), - self._pylmp.eval("y[%d]" % self.index), - self._pylmp.eval("z[%d]" % self.index)) + return self.get("x", self.index) @position.setter def position(self, value): - """ - :getter: Return velocity of atom - :setter: Set velocity of atom - :type: tuple (float, float, float) - """ - self._pylmp.set("atom", self.index, "x", value[0]) - self._pylmp.set("atom", self.index, "y", value[1]) - self._pylmp.set("atom", self.index, "z", value[2]) + current = self.position + current[:] = value @property def velocity(self): - return (self._pylmp.eval("vx[%d]" % self.index), - self._pylmp.eval("vy[%d]" % self.index), - self._pylmp.eval("vz[%d]" % self.index)) + """ + :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): - self._pylmp.set("atom", self.index, "vx", value[0]) - self._pylmp.set("atom", self.index, "vy", value[1]) - self._pylmp.set("atom", self.index, "vz", value[2]) + current = self.velocity + current[:] = value @property def force(self): """ Return the total force acting on the atom - :type: tuple (float, float, float) + :type: numpy.array (float, float, float) """ - return (self._pylmp.eval("fx[%d]" % self.index), - self._pylmp.eval("fy[%d]" % self.index), - self._pylmp.eval("fz[%d]" % self.index)) + 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): @@ -222,7 +290,7 @@ class Atom(object): :type: float """ - return self._pylmp.eval("q[%d]" % self.index) + return self.get("q", self.index) # ------------------------------------------------------------------------- @@ -244,39 +312,42 @@ class Atom2D(Atom): :getter: Return position of atom :setter: Set position of atom - :type: tuple (float, float) + :type: numpy.array (float, float) """ - return (self._pylmp.eval("x[%d]" % self.index), - self._pylmp.eval("y[%d]" % self.index)) + return super(Atom2D, self).position[0:2] @position.setter def position(self, value): - self._pylmp.set("atom", self.index, "x", value[0]) - self._pylmp.set("atom", self.index, "y", value[1]) + 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: tuple (float, float) + :type: numpy.array (float, float) """ - return (self._pylmp.eval("vx[%d]" % self.index), - self._pylmp.eval("vy[%d]" % self.index)) + return super(Atom2D, self).velocity[0:2] @velocity.setter def velocity(self, value): - self._pylmp.set("atom", self.index, "vx", value[0]) - self._pylmp.set("atom", self.index, "vy", value[1]) + current = self.velocity + current[:] = value @property def force(self): """Access to force of an atom - - :type: tuple (float, float) + :getter: Return force of atom + :setter: Set force of atom + :type: numpy.array (float, float) """ - return (self._pylmp.eval("fx[%d]" % self.index), - self._pylmp.eval("fy[%d]" % self.index)) + return super(Atom2D, self).force[0:2] + + @force.setter + def force(self, value): + current = self.force + current[:] = value # ------------------------------------------------------------------------- @@ -541,7 +612,8 @@ class PyLammps(object): :getter: Returns an object with properties storing the current system state :type: namedtuple """ - output = self.info("system") + 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()) @@ -553,7 +625,8 @@ class PyLammps(object): :getter: Returns an object with properties storing the current communication state :type: namedtuple """ - output = self.info("communication") + 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()) @@ -565,7 +638,8 @@ class PyLammps(object): :getter: Returns a list of computes that are currently active in this LAMMPS instance :type: list """ - output = self.info("computes") + output = self.lmp_info("computes") + output = output[output.index("Compute information:")+1:] return self._parse_element_list(output) @property @@ -576,7 +650,8 @@ class PyLammps(object): :getter: Returns a list of dumps that are currently active in this LAMMPS instance :type: list """ - output = self.info("dumps") + output = self.lmp_info("dumps") + output = output[output.index("Dump information:")+1:] return self._parse_element_list(output) @property @@ -587,7 +662,8 @@ class PyLammps(object): :getter: Returns a list of fixes that are currently active in this LAMMPS instance :type: list """ - output = self.info("fixes") + output = self.lmp_info("fixes") + output = output[output.index("Fix information:")+1:] return self._parse_element_list(output) @property @@ -598,7 +674,8 @@ class PyLammps(object): :getter: Returns a list of atom groups that are currently active in this LAMMPS instance :type: list """ - output = self.info("groups") + output = self.lmp_info("groups") + output = output[output.index("Group information:")+1:] return self._parse_groups(output) @property @@ -609,11 +686,12 @@ class PyLammps(object): :getter: Returns a dictionary of all variables that are defined in this LAMMPS instance :type: dict """ - output = self.info("variables") - vars = {} + output = self.lmp_info("variables") + output = output[output.index("Variable information:")+1:] + variables = {} for v in self._parse_element_list(output): - vars[v['name']] = Variable(self, v['name'], v['style'], v['def']) - return vars + variables[v['name']] = Variable(self, v['name'], v['style'], v['def']) + return variables def eval(self, expr): """ @@ -638,10 +716,9 @@ class PyLammps(object): return [x.strip() for x in value.split('=')] def _parse_info_system(self, output): - lines = output[5:-2] system = {} - for line in lines: + for line in output: if line.startswith("Units"): system['units'] = self._get_pair(line)[1] elif line.startswith("Atom style"): @@ -699,10 +776,9 @@ class PyLammps(object): return system def _parse_info_communication(self, output): - lines = output[5:-3] comm = {} - for line in lines: + for line in output: if line.startswith("MPI library"): comm['mpi_version'] = line.split(':')[1].strip() elif line.startswith("Comm style"): @@ -720,10 +796,10 @@ class PyLammps(object): return comm def _parse_element_list(self, output): - lines = output[5:-3] elements = [] - for line in lines: + 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:]]: @@ -732,11 +808,10 @@ class PyLammps(object): return elements def _parse_groups(self, output): - lines = output[5:-3] groups = [] group_pattern = re.compile(r"(?P.+) \((?P.+)\)") - for line in lines: + for line in output: m = group_pattern.match(line.split(':')[1].strip()) group = {'name': m.group('name'), 'type': m.group('type')} groups.append(group) @@ -747,7 +822,7 @@ class PyLammps(object): return self.__getattr__("print")(s) def __dir__(self): - return ['angle_coeff', 'angle_style', 'atom_modify', 'atom_style', 'atom_style', + 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', @@ -757,7 +832,16 @@ class PyLammps(object): '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'] + '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): """ @@ -773,10 +857,12 @@ class PyLammps(object): """ 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() diff --git a/src/error.cpp b/src/error.cpp index e2162cf661..912093c865 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -213,8 +213,7 @@ void Error::one(const std::string &file, int line, const std::string &str) throw LAMMPSAbortException(mesg, world); #else - if (screen) fflush(screen); - if (logfile) fflush(logfile); + utils::flush_buffers(lmp); KokkosLMP::finalize(); MPI_Abort(world,1); exit(1); // to trick "smart" compilers into believing this does not return diff --git a/src/library.cpp b/src/library.cpp index a27da0d478..8c6ee5e774 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -5439,6 +5439,21 @@ void lammps_fix_external_set_vector(void *handle, const char *id, int idx, doubl /* ---------------------------------------------------------------------- */ +/** Flush output buffers + +\verbatim embed:rst +This function can be used to force output to be written to screen and logfiles +to simplify capturing output from LAMMPS library calls. +\endverbatim + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + */ +void lammps_flush_buffers(void *handle) { + utils::flush_buffers((LAMMPS *) handle); +} + +/* ---------------------------------------------------------------------- */ + /** Free memory buffer allocated by LAMMPS. * \verbatim embed:rst diff --git a/src/library.h b/src/library.h index 1605267818..94fd7f7380 100644 --- a/src/library.h +++ b/src/library.h @@ -246,6 +246,8 @@ void lammps_fix_external_set_virial_peratom(void *handle, const char *id, double void lammps_fix_external_set_vector_length(void *handle, const char *id, int len); void lammps_fix_external_set_vector(void *handle, const char *id, int idx, double val); +void lammps_flush_buffers(void *ptr); + void lammps_free(void *ptr); int lammps_is_running(void *handle); diff --git a/src/thermo.cpp b/src/thermo.cpp index e39d7d7c57..27d74c58b6 100644 --- a/src/thermo.cpp +++ b/src/thermo.cpp @@ -375,8 +375,7 @@ void Thermo::compute(int flag) if (me == 0) { utils::logmesg(lmp,line); - if (screen && flushflag) fflush(screen); - if (logfile && flushflag) fflush(logfile); + if (flushflag) utils::flush_buffers(lmp); } // set to 1, so that subsequent invocations of CPU time will be non-zero diff --git a/src/utils.cpp b/src/utils.cpp index ca2a0c4f5b..c60908a2f2 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -24,6 +24,7 @@ #include "text_file_reader.h" #include "tokenizer.h" #include "update.h" +#include "universe.h" #include #include @@ -138,6 +139,14 @@ void utils::fmtargs_logmesg(LAMMPS *lmp, fmt::string_view format, fmt::format_ar } } +void utils::flush_buffers(LAMMPS *lmp) +{ + if (lmp->screen) fflush(lmp->screen); + if (lmp->logfile) fflush(lmp->logfile); + if (lmp->universe->uscreen) fflush(lmp->universe->uscreen); + if (lmp->universe->ulogfile) fflush(lmp->universe->ulogfile); +} + /* define this here, so we won't have to include the headers everywhere and utils.h will more likely be included anyway. */ diff --git a/src/utils.h b/src/utils.h index 47a4ace5f9..425fbfe0c1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -74,6 +74,14 @@ namespace utils { void logmesg(LAMMPS *lmp, const std::string &mesg); + /*! Flush output buffers + * + * This function calls fflush on screen and logfile FILE pointers + * if available + */ + + void flush_buffers(LAMMPS *lmp); + /*! Return a string representing the current system error status * * This is a wrapper around calling strerror(errno). diff --git a/tools/swig/lammps.i b/tools/swig/lammps.i index 4d0d52f779..fb4322af34 100644 --- a/tools/swig/lammps.i +++ b/tools/swig/lammps.i @@ -156,6 +156,8 @@ extern int lammps_is_running(void *handle); extern void lammps_force_timeout(void *handle); extern int lammps_has_error(void *handle); extern int lammps_get_last_error_message(void *handle, char *buffer, int buf_size); + +extern void lammps_flush_buffers(void *ptr); %} enum _LMP_DATATYPE_CONST { @@ -287,4 +289,6 @@ extern void lammps_force_timeout(void *handle); extern int lammps_has_error(void *handle); extern int lammps_get_last_error_message(void *handle, char *buffer, int buf_size); -/* last revised on 21 July 2021 */ +extern void lammps_flush_buffers(void *ptr); + +/* last revised on 4 February 2022 */ diff --git a/unittest/python/CMakeLists.txt b/unittest/python/CMakeLists.txt index 7f9b5e71b2..3fc0f6ba58 100644 --- a/unittest/python/CMakeLists.txt +++ b/unittest/python/CMakeLists.txt @@ -98,14 +98,10 @@ if(Python_EXECUTABLE) WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set_tests_properties(PythonCapabilities PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - message(STATUS "Skipping Tests for PyLammps Module: not yet ported to Windows") -else() add_test(NAME PythonPyLammps COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-pylammps.py -v WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set_tests_properties(PythonPyLammps PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") -endif() add_test(NAME PythonFormats COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-formats.py -v diff --git a/unittest/python/python-pylammps.py b/unittest/python/python-pylammps.py index 6294972ab4..2b92f82248 100644 --- a/unittest/python/python-pylammps.py +++ b/unittest/python/python-pylammps.py @@ -1,6 +1,13 @@ -import sys,os,unittest +import os,unittest from lammps import PyLammps +try: + import numpy + NUMPY_INSTALLED = True +except ImportError: + NUMPY_INSTALLED = False + +@unittest.skipIf(not NUMPY_INSTALLED, "numpy is not available") class PythonPyLammps(unittest.TestCase): def setUp(self): machine = None @@ -49,8 +56,8 @@ class PythonPyLammps(unittest.TestCase): self.assertEqual(self.pylmp.lmp.create_atoms(2, id=None, type=types, x=x), 2) self.assertEqual(self.pylmp.system.natoms, 2) self.assertEqual(len(self.pylmp.atoms), 2) - self.assertEqual(self.pylmp.atoms[0].position, tuple(x[0:3])) - self.assertEqual(self.pylmp.atoms[1].position, tuple(x[3:6])) + numpy.testing.assert_array_equal(self.pylmp.atoms[0].position, tuple(x[0:3])) + numpy.testing.assert_array_equal(self.pylmp.atoms[1].position, tuple(x[3:6])) self.assertEqual(self.pylmp.last_run, None)