diff --git a/.gitignore b/.gitignore index 5c90b0f39c..0f1b01775d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ vgcore.* ehthumbs.db Thumbs.db .clang-format +.lammps_history #cmake /build* diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 0d041b2aa9..6a0b6b7cc9 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -90,6 +90,7 @@ if(BUILD_SHARED_LIBS) # for all pkg libs, mpi_stubs and linalg endif() option(BUILD_TOOLS "Build and install LAMMPS tools (msi2lmp, binary2txt, chain)" OFF) +option(BUILD_LAMMPS_SHELL "Build and install the LAMMPS shell" OFF) include(GNUInstallDirs) file(GLOB ALL_SOURCES ${LAMMPS_SOURCE_DIR}/[^.]*.cpp) @@ -642,6 +643,18 @@ if(BUILD_TOOLS) install(FILES ${LAMMPS_DOC_DIR}/msi2lmp.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) endif() +if(BUILD_LAMMPS_SHELL) + find_package(PkgConfig REQUIRED) + pkg_check_modules(READLINE IMPORTED_TARGET REQUIRED readline) + if(NOT LAMMPS_EXCEPTIONS) + message(WARNING "The LAMMPS shell needs LAMMPS_EXCEPTIONS enabled for full functionality") + endif() + add_executable(lammps-shell ${LAMMPS_TOOLS_DIR}/lammps-shell/lammps-shell.cpp) + target_compile_definitions(lammps-shell PRIVATE -DLAMMPS_LIB_NO_MPI) + target_link_libraries(lammps-shell PRIVATE lammps PkgConfig::READLINE) + install(TARGETS lammps-shell EXPORT LAMMPS_Targets DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + include(Documentation) ############################################################################### diff --git a/doc/src/Build_basics.rst b/doc/src/Build_basics.rst index 674f093aaf..8a9370e5a7 100644 --- a/doc/src/Build_basics.rst +++ b/doc/src/Build_basics.rst @@ -542,7 +542,8 @@ using CMake or Make. .. code-block:: bash - -D BUILD_TOOLS=value # yes or no (default) + -D BUILD_TOOLS=value # yes or no (default) + -D BUILD_LAMMPS_SHELL=value # yes or no (default) The generated binaries will also become part of the LAMMPS installation (see below). @@ -558,6 +559,9 @@ using CMake or Make. make micelle2d # build only micelle2d tool make thermo_extract # build only thermo_extract tool + cd lammps/tools/lammps-shell + make # build LAMMPS shell + ---------- .. _install: diff --git a/doc/src/Howto_cmake.rst b/doc/src/Howto_cmake.rst index 9176600820..30d71edd87 100644 --- a/doc/src/Howto_cmake.rst +++ b/doc/src/Howto_cmake.rst @@ -328,6 +328,8 @@ Some common LAMMPS specific variables - build LAMMPS with OpenMP support (default: ``on`` if compiler supports OpenMP fully, else ``off``) * - ``BUILD_TOOLS`` - compile some additional executables from the ``tools`` folder (default: ``off``) + * - ``BUILD_LAMMPS_SHELL`` + - compile the LAMMPS shell from the ``tools/lammps-shell`` folder (default: ``off``) * - ``BUILD_DOC`` - include building the HTML format documentation for packaging/installing (default: ``off``) * - ``CMAKE_TUNE_FLAGS`` diff --git a/doc/src/Library_config.rst b/doc/src/Library_config.rst index 3389d52173..230c908ad6 100644 --- a/doc/src/Library_config.rst +++ b/doc/src/Library_config.rst @@ -15,6 +15,9 @@ This section documents the following functions: - :cpp:func:`lammps_has_style` - :cpp:func:`lammps_style_count` - :cpp:func:`lammps_style_name` +- :cpp:func:`lammps_has_id` +- :cpp:func:`lammps_id_count` +- :cpp:func:`lammps_id_name` -------------------- @@ -124,3 +127,18 @@ approach. .. doxygenfunction:: lammps_style_name :project: progguide +----------------------- + +.. doxygenfunction:: lammps_has_id + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_id_count + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_id_name + :project: progguide + diff --git a/doc/src/Library_utility.rst b/doc/src/Library_utility.rst index 6cc3337d48..3ca56789c3 100644 --- a/doc/src/Library_utility.rst +++ b/doc/src/Library_utility.rst @@ -9,6 +9,8 @@ functions. They do not directly call the LAMMPS library. - :cpp:func:`lammps_set_fix_external_callback` - :cpp:func:`lammps_fix_external_set_energy_global` - :cpp:func:`lammps_fix_external_set_virial_global` +- :cpp:func:`lammps_is_running` +- :cpp:func:`lammps_force_timeout` - :cpp:func:`lammps_has_error` - :cpp:func:`lammps_get_last_error_message` @@ -39,6 +41,16 @@ functions. They do not directly call the LAMMPS library. ----------------------- +.. doxygenfunction:: lammps_is_running + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_force_timeout + :project: progguide + +----------------------- + .. doxygenfunction:: lammps_has_error :project: progguide diff --git a/doc/src/Tools.rst b/doc/src/Tools.rst index e3e6b344c0..483c77038e 100644 --- a/doc/src/Tools.rst +++ b/doc/src/Tools.rst @@ -92,6 +92,7 @@ Miscellaneous tools * :ref:`emacs ` * :ref:`i-pi ` * :ref:`kate ` + * :ref:`LAMMPS shell ` * :ref:`singularity ` * :ref:`vim ` @@ -397,10 +398,131 @@ The file was provided by Alessandro Luigi Sellerio ---------- +.. _lammps_shell: + +LAMMPS shell +------------ + +Overview +======== + +The LAMMPS Shell, ``lammps-shell`` is a program that functions very +similar to the regular LAMMPS executable but has several modifications +and additions that make it more powerful for interactive sessions, +i.e. where you type LAMMPS commands from the prompt instead of reading +them from a file. + +- It uses the readline and history libraries to provide command line + editing and context aware TAB-expansion (details on that below). + +- When processing an input file with the '-in' or '-i' flag from the + command line, it does not exit at the end of that input file but + stops at a prompt, so that additional commands can be issued + +- Errors will not abort the shell but return to the prompt. + +- It has additional commands aimed at interactive use (details below). + +- Interrupting a calculation with CTRL-C will not terminate the + session but rather enforce a timeout to cleanly stop an ongoing + run (more info on timeouts is in the timer command documentation). + +These enhancements makes the LAMMPS shell an attractive choice for +interactive LAMMPS sessions in graphical user interfaces. + +TAB-expansion +============= + +When writing commands interactively at the shell prompt, you can hit +the TAB key at any time to try and complete the text. This completion +is context aware and will expand any first word only to commands +available in that executable. + +- For style commands it will expand to available styles of the + corresponding category (e.g. pair styles after a + :doc:`pair_style ` command). + +- For :doc:`compute `, :doc:`fix `, or :doc:`dump ` + it will also expand only to already defined groups for the group-ID + keyword. + +- For commands like :doc:`compute_modify `, + :doc:`fix_modify `, or :doc:`dump_modify ` + it will expand to known compute/fix/dump IDs only. + +- When typing references to computes, fixes, or variables with a + "c\_", "f\_", or "v\_" prefix, respectively, then the expansion will + to known compute/fix IDs and variable names. Variable name expansion + is also available for the ${name} variable syntax. + +- In all other cases, expansion will be performed on filenames. + + +Command line editing and history +================================ + +When typing commands, command line editing similar to what BASH +provides is available. Thus it is possible to move around the +currently line and perform various cut and insert and edit operations. +Previous commands can be retrieved by scrolling up (and down) +or searching (e.g. with CTRL-r). + +Also history expansion through using the exclamation mark '!' +can be performed. Examples: '!!' will be replaced with the previous +command, '!-2' will repeat the command before that, '!30' will be +replaced with event number 30 in the command history list, and +'!run' with the last command line that started with "run". Adding +a ":p" to such a history expansion will result that the expansion is +printed and added to the history list, but NOT executed. +On exit the LAMMPS shell will write the history list to a file +".lammps_history" in the current working directory. If such a +file exists when the LAMMPS shell is launched it will be read to +populate the history list. + +This is realized via the readline library and can thus be customized +with an ``.inputrc`` file in the home directory. For application +specific customization, the LAMMPS shell uses the name "lammps-shell". +For more information about using and customizing an application using +readline, please see the available documentation at: +`http://www.gnu.org/s/readline/#Documentation +`_ + +Additional commands +=================== + +The following commands are added to the LAMMPS shell on top of the +regular LAMMPS commands: + +.. parsed-literal:: + + help (or ?) print a brief help message + history display the current command history list + clear_history wipe out the current command history list + \| execute as a shell command and return to the command prompt + exit exit the LAMMPS shell cleanly (unlike the "quit" command) + +Compilation +=========== + +Compilation of the LAMMPS shell can be enabled by setting the CMake +variable ``BUILD_LAMMPS_SHELL`` to "on" or using the makefile in the +``tools/lammps-shell`` folder to compile after building LAMMPS using +the conventional make procedure. The makefile will likely need +customization depending on the features and settings used for +compiling LAMMPS. + +Limitations +=========== + +The LAMMPS shell was not designed for use with MPI parallelization +via ``mpirun`` or ``mpiexec`` or ``srun``. + +---------- + .. _arc: lmp2arc tool ----------------------- +------------ The lmp2arc sub-directory contains a tool for converting LAMMPS output files to the format for Accelrys' Insight MD code (formerly diff --git a/doc/src/fix_wall_gran.rst b/doc/src/fix_wall_gran.rst index 4ebbf35ba1..bc59fd71a7 100644 --- a/doc/src/fix_wall_gran.rst +++ b/doc/src/fix_wall_gran.rst @@ -205,11 +205,11 @@ the following table: +-------+----------------------------------------------------+----------------+ | 4 | Force :math:`f_z` exerted on the wall | force units | +-------+----------------------------------------------------+----------------+ -| 5 | :math:`\Delta x` between wall surface and particle | distance units | +| 5 | :math:`x`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ -| 6 | :math:`\Delta y` between wall surface and particle | distance units | +| 6 | :math:`y`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ -| 7 | :math:`\Delta z` between wall surface and particle | distance units | +| 7 | :math:`z`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ | 8 | Radius :math:`r` of atom | distance units | +-------+----------------------------------------------------+----------------+ diff --git a/doc/src/fix_wall_gran_region.rst b/doc/src/fix_wall_gran_region.rst index 7d62d71a1d..bedf1165c1 100644 --- a/doc/src/fix_wall_gran_region.rst +++ b/doc/src/fix_wall_gran_region.rst @@ -246,11 +246,11 @@ the following table: +-------+----------------------------------------------------+----------------+ | 4 | Force :math:`f_z` exerted on the wall | force units | +-------+----------------------------------------------------+----------------+ -| 5 | :math:`\Delta x` between wall surface and particle | distance units | +| 5 | :math:`x`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ -| 6 | :math:`\Delta y` between wall surface and particle | distance units | +| 6 | :math:`y`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ -| 7 | :math:`\Delta z` between wall surface and particle | distance units | +| 7 | :math:`z`-coordinate of contact point on wall | distance units | +-------+----------------------------------------------------+----------------+ | 8 | Radius :math:`r` of atom | distance units | +-------+----------------------------------------------------+----------------+ diff --git a/src/GRANULAR/fix_wall_gran_region.cpp b/src/GRANULAR/fix_wall_gran_region.cpp index f91bac2c9b..d795d1bc37 100644 --- a/src/GRANULAR/fix_wall_gran_region.cpp +++ b/src/GRANULAR/fix_wall_gran_region.cpp @@ -250,7 +250,7 @@ void FixWallGranRegion::post_force(int /*vflag*/) // store contact info if (peratom_flag) { - array_atom[i][0] = (double)atom->tag[i]; + array_atom[i][0] = 1.0; array_atom[i][4] = x[i][0] - dx; array_atom[i][5] = x[i][1] - dy; array_atom[i][6] = x[i][2] - dz; diff --git a/src/USER-DIFFRACTION/fix_saed_vtk.cpp b/src/USER-DIFFRACTION/fix_saed_vtk.cpp index 00cfcbf91a..b39bc7ab99 100644 --- a/src/USER-DIFFRACTION/fix_saed_vtk.cpp +++ b/src/USER-DIFFRACTION/fix_saed_vtk.cpp @@ -145,8 +145,6 @@ FixSAEDVTK::FixSAEDVTK(LAMMPS *lmp, int narg, char **arg) : memory->create(vector,nrows,"saed/vtk:vector"); memory->create(vector_total,nrows,"saed/vtk:vector_total"); - extlist = nullptr; - vector_flag = 1; size_vector = nrows; @@ -282,7 +280,6 @@ FixSAEDVTK::FixSAEDVTK(LAMMPS *lmp, int narg, char **arg) : FixSAEDVTK::~FixSAEDVTK() { - delete [] extlist; delete [] filename; delete [] ids; memory->destroy(vector); diff --git a/src/USER-MISC/compute_viscosity_cos.cpp b/src/USER-MISC/compute_viscosity_cos.cpp index 4a798eb158..383fa17be2 100644 --- a/src/USER-MISC/compute_viscosity_cos.cpp +++ b/src/USER-MISC/compute_viscosity_cos.cpp @@ -53,8 +53,10 @@ ComputeViscosityCos::ComputeViscosityCos(LAMMPS *lmp, int narg, char **arg) : /* ---------------------------------------------------------------------- */ ComputeViscosityCos::~ComputeViscosityCos() { - if (!copymode) + if (!copymode) { delete[] vector; + delete[] extlist; + } } /* ---------------------------------------------------------------------- */ diff --git a/src/USER-REACTION/fix_bond_react.cpp b/src/USER-REACTION/fix_bond_react.cpp index 814f8a0fcf..28a7ca92e7 100644 --- a/src/USER-REACTION/fix_bond_react.cpp +++ b/src/USER-REACTION/fix_bond_react.cpp @@ -1863,6 +1863,8 @@ int FixBondReact::check_constraints() if (prrhob < rrhandom[(int) constraints[i][2]]->uniform()) return 0; } else if (constraints[i][1] == RMSD) { // call superpose + int iatom; + int iref = -1; // choose first atom as reference int n2superpose = 0; double **xfrozen; // coordinates for the "frozen" target molecule double **xmobile; // coordinates for the "mobile" molecule @@ -1875,20 +1877,28 @@ int FixBondReact::check_constraints() int myincr = 0; for (int j = 0; j < onemol->natoms; j++) { if (onemol->fragmentmask[ifragment][j]) { + iatom = atom->map(glove[j][1]); + if (iref == -1) iref = iatom; + iatom = domain->closest_image(iref,iatom); for (int k = 0; k < 3; k++) { - xfrozen[myincr][k] = x[atom->map(glove[j][1])][k]; + xfrozen[myincr][k] = x[iatom][k]; xmobile[myincr][k] = onemol->x[j][k]; } myincr++; } } } else { + int iatom; + int iref = -1; // choose first atom as reference n2superpose = onemol->natoms; memory->create(xfrozen,n2superpose,3,"bond/react:xfrozen"); memory->create(xmobile,n2superpose,3,"bond/react:xmobile"); for (int j = 0; j < n2superpose; j++) { + iatom = atom->map(glove[j][1]); + if (iref == -1) iref = iatom; + iatom = domain->closest_image(iref,iatom); for (int k = 0; k < 3; k++) { - xfrozen[j][k] = x[atom->map(glove[j][1])][k]; + xfrozen[j][k] = x[iatom][k]; xmobile[j][k] = onemol->x[j][k]; } } diff --git a/src/compute_slice.cpp b/src/compute_slice.cpp index 036a9bb289..7c56f07582 100644 --- a/src/compute_slice.cpp +++ b/src/compute_slice.cpp @@ -224,6 +224,7 @@ ComputeSlice::~ComputeSlice() for (int m = 0; m < nvalues; m++) delete [] ids[m]; delete [] ids; delete [] value2index; + delete [] extlist; memory->destroy(vector); memory->destroy(array); diff --git a/src/dump_custom.cpp b/src/dump_custom.cpp index cf034b0450..b5609b754f 100644 --- a/src/dump_custom.cpp +++ b/src/dump_custom.cpp @@ -234,7 +234,7 @@ DumpCustom::~DumpCustom() for (int i = 0; i < ncustom; i++) delete [] id_custom[i]; memory->sfree(id_custom); - delete [] flag_custom; + memory->sfree(flag_custom); memory->destroy(choose); memory->destroy(dchoose); diff --git a/src/fix_ave_histo.cpp b/src/fix_ave_histo.cpp index 4d1cd5b2dd..5d5abdc9ac 100644 --- a/src/fix_ave_histo.cpp +++ b/src/fix_ave_histo.cpp @@ -760,7 +760,7 @@ void FixAveHisto::end_of_step() } irepeat = 0; - nvalid = ntimestep + nfreq - (nrepeat-1)*nevery; + nvalid = ntimestep + nfreq - static_cast(nrepeat-1)*nevery; modify->addstep_compute(nvalid); // merge histogram stats across procs if necessary @@ -1046,7 +1046,7 @@ bigint FixAveHisto::nextvalid() if (nvalid-nfreq == update->ntimestep && nrepeat == 1) nvalid = update->ntimestep; else - nvalid -= (nrepeat-1)*nevery; + nvalid -= static_cast(nrepeat-1)*nevery; if (nvalid < update->ntimestep) nvalid += nfreq; return nvalid; } diff --git a/src/fix_ave_histo_weight.cpp b/src/fix_ave_histo_weight.cpp index 586c36af8a..08ec3632b4 100644 --- a/src/fix_ave_histo_weight.cpp +++ b/src/fix_ave_histo_weight.cpp @@ -403,7 +403,7 @@ void FixAveHistoWeight::end_of_step() } irepeat = 0; - nvalid = ntimestep + nfreq - (nrepeat-1)*nevery; + nvalid = ntimestep + nfreq - static_cast(nrepeat-1)*nevery; modify->addstep_compute(nvalid); // merge histogram stats across procs if necessary diff --git a/src/fix_ave_time.cpp b/src/fix_ave_time.cpp index b40a7eb009..4a25cca6e5 100644 --- a/src/fix_ave_time.cpp +++ b/src/fix_ave_time.cpp @@ -640,7 +640,7 @@ void FixAveTime::invoke_scalar(bigint ntimestep) } irepeat = 0; - nvalid = ntimestep + nfreq - (nrepeat-1)*nevery; + nvalid = ntimestep + nfreq - static_cast(nrepeat-1)*nevery; modify->addstep_compute(nvalid); // average the final result for the Nfreq timestep @@ -743,7 +743,7 @@ void FixAveTime::invoke_vector(bigint ntimestep) if (!varlen[i] || which[i] != COMPUTE) continue; if (nrepeat > 1 && ave == ONE) { Compute *compute = modify->compute[value2index[i]]; - compute->lock(this,ntimestep,ntimestep+(nrepeat-1)*nevery); + compute->lock(this,ntimestep,ntimestep+static_cast(nrepeat-1)*nevery); } else if ((ave == RUNNING || ave == WINDOW) && !lockforever) { Compute *compute = modify->compute[value2index[i]]; compute->lock(this,update->ntimestep,-1); @@ -838,7 +838,7 @@ void FixAveTime::invoke_vector(bigint ntimestep) } irepeat = 0; - nvalid = ntimestep+nfreq - (nrepeat-1)*nevery; + nvalid = ntimestep+nfreq - static_cast(nrepeat-1)*nevery; modify->addstep_compute(nvalid); // unlock any variable length computes at end of Nfreq epoch @@ -1146,7 +1146,7 @@ bigint FixAveTime::nextvalid() if (nvalid-nfreq == update->ntimestep && nrepeat == 1) nvalid = update->ntimestep; else - nvalid -= (nrepeat-1)*nevery; + nvalid -= static_cast(nrepeat-1)*nevery; if (nvalid < update->ntimestep) nvalid += nfreq; return nvalid; } diff --git a/src/library.cpp b/src/library.cpp index e44cf798f0..7678ebf516 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -22,6 +22,7 @@ #include "comm.h" #include "compute.h" #include "domain.h" +#include "dump.h" #include "error.h" #include "fix.h" #include "fix_external.h" @@ -31,10 +32,13 @@ #include "input.h" #include "memory.h" #include "modify.h" +#include "molecule.h" #include "neigh_list.h" #include "neighbor.h" +#include "region.h" #include "output.h" #include "thermo.h" +#include "timer.h" #include "universe.h" #include "update.h" #include "variable.h" @@ -4013,18 +4017,17 @@ int lammps_style_count(void *handle, const char *category) { /** Look up the name of a style by index in the list of style of a given category in the LAMMPS library. * -\verbatim embed:rst -This function copies the name of the package with the index *idx* into the -provided C-style string buffer. The length of the buffer must be provided -as *buf_size* argument. If the name of the package exceeds the length of the -buffer, it will be truncated accordingly. If the index is out of range, -the function returns 0 and *buffer* is set to an empty string, otherwise 1. -Please see :cpp:func:`lammps_has_style` for a list of valid categories. -\endverbatim + * + * This function copies the name of the *category* style with the index + * *idx* into the provided C-style string buffer. The length of the buffer + * must be provided as *buf_size* argument. If the name of the style + * exceeds the length of the buffer, it will be truncated accordingly. + * If the index is out of range, the function returns 0 and *buffer* is + * set to an empty string, otherwise 1. * * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. * \param category category of styles - * \param idx index of the package in the list of included packages (0 <= idx < style count) + * \param idx index of the style in the list of *category* styles (0 <= idx < style count) * \param buffer string buffer to copy the name of the style to * \param buf_size size of the provided string buffer * \return 1 if successful, otherwise 0 @@ -4046,6 +4049,167 @@ int lammps_style_name(void *handle, const char *category, int idx, /* ---------------------------------------------------------------------- */ +/** Check if a specific ID exists in the current LAMMPS instance + * +\verbatim embed:rst +This function checks if the current LAMMPS instance a *category* ID of +the given *name* exists. Valid categories are: *compute*\ , *dump*\ , +*fix*\ , *group*\ , *molecule*\ , *region*\ , and *variable*\ . +\endverbatim + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + * \param category category of the id + * \param name name of the id + * \return 1 if included, 0 if not. + */ +int lammps_has_id(void *handle, const char *category, const char *name) { + LAMMPS *lmp = (LAMMPS *) handle; + + if (strcmp(category,"compute") == 0) { + int ncompute = lmp->modify->ncompute; + Compute **compute = lmp->modify->compute; + for (int i=0; i < ncompute; ++i) { + if (strcmp(name,compute[i]->id) == 0) return 1; + } + } else if (strcmp(category,"dump") == 0) { + int ndump = lmp->output->ndump; + Dump **dump = lmp->output->dump; + for (int i=0; i < ndump; ++i) { + if (strcmp(name,dump[i]->id) == 0) return 1; + } + } else if (strcmp(category,"fix") == 0) { + int nfix = lmp->modify->nfix; + Fix **fix = lmp->modify->fix; + for (int i=0; i < nfix; ++i) { + if (strcmp(name,fix[i]->id) == 0) return 1; + } + } else if (strcmp(category,"group") == 0) { + int ngroup = lmp->group->ngroup; + char **groups = lmp->group->names; + for (int i=0; i < ngroup; ++i) { + if (strcmp(groups[i],name) == 0) return 1; + } + } else if (strcmp(category,"molecule") == 0) { + int nmolecule = lmp->atom->nmolecule; + Molecule **molecule = lmp->atom->molecules; + for (int i=0; i < nmolecule; ++i) { + if (strcmp(name,molecule[i]->id) == 0) return 1; + } + } else if (strcmp(category,"region") == 0) { + int nregion = lmp->domain->nregion; + Region **region = lmp->domain->regions; + for (int i=0; i < nregion; ++i) { + if (strcmp(name,region[i]->id) == 0) return 1; + } + } else if (strcmp(category,"variable") == 0) { + int nvariable = lmp->input->variable->nvar; + char **varnames = lmp->input->variable->names; + for (int i=0; i < nvariable; ++i) { + if (strcmp(name,varnames[i]) == 0) return 1; + } + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/** Count the number of IDs of a category. + * +\verbatim embed:rst +This function counts how many IDs in the provided *category* +are defined in the current LAMMPS instance. +Please see :cpp:func:`lammps_has_id` for a list of valid +categories. +\endverbatim + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + * \param category category of IDs + * \return number of IDs in category + */ +int lammps_id_count(void *handle, const char *category) { + LAMMPS *lmp = (LAMMPS *) handle; + if (strcmp(category,"compute") == 0) { + return lmp->modify->ncompute; + } else if (strcmp(category,"dump") == 0) { + return lmp->output->ndump; + } else if (strcmp(category,"fix") == 0) { + return lmp->modify->nfix; + } else if (strcmp(category,"group") == 0) { + return lmp->group->ngroup; + } else if (strcmp(category,"molecule") == 0) { + return lmp->atom->nmolecule; + } else if (strcmp(category,"region") == 0) { + return lmp->domain->nregion; + } else if (strcmp(category,"variable") == 0) { + return lmp->input->variable->nvar; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/** Look up the name of an ID by index in the list of IDs of a given category. + * + * This function copies the name of the *category* ID with the index + * *idx* into the provided C-style string buffer. The length of the buffer + * must be provided as *buf_size* argument. If the name of the style + * exceeds the length of the buffer, it will be truncated accordingly. + * If the index is out of range, the function returns 0 and *buffer* is + * set to an empty string, otherwise 1. + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + * \param category category of IDs + * \param idx index of the ID in the list of *category* styles (0 <= idx < count) + * \param buffer string buffer to copy the name of the style to + * \param buf_size size of the provided string buffer + * \return 1 if successful, otherwise 0 + */ +int lammps_id_name(void *handle, const char *category, int idx, + char *buffer, int buf_size) { + LAMMPS *lmp = (LAMMPS *) handle; + + if (strcmp(category,"compute") == 0) { + if ((idx >=0) && (idx < lmp->modify->ncompute)) { + strncpy(buffer, lmp->modify->compute[idx]->id, buf_size); + return 1; + } + } else if (strcmp(category,"dump") == 0) { + if ((idx >=0) && (idx < lmp->output->ndump)) { + strncpy(buffer, lmp->output->dump[idx]->id, buf_size); + return 1; + } + } else if (strcmp(category,"fix") == 0) { + if ((idx >=0) && (idx < lmp->modify->nfix)) { + strncpy(buffer, lmp->modify->fix[idx]->id, buf_size); + return 1; + } + } else if (strcmp(category,"group") == 0) { + if ((idx >=0) && (idx < lmp->group->ngroup)) { + strncpy(buffer, lmp->group->names[idx], buf_size); + return 1; + } + } else if (strcmp(category,"molecule") == 0) { + if ((idx >=0) && (idx < lmp->atom->nmolecule)) { + strncpy(buffer, lmp->atom->molecules[idx]->id, buf_size); + return 1; + } + } else if (strcmp(category,"region") == 0) { + if ((idx >=0) && (idx < lmp->domain->nregion)) { + strncpy(buffer, lmp->domain->regions[idx]->id, buf_size); + return 1; + } + } else if (strcmp(category,"variable") == 0) { + if ((idx >=0) && (idx < lmp->input->variable->nvar)) { + strncpy(buffer, lmp->input->variable->names[idx], buf_size); + return 1; + } + } + buffer[0] = '\0'; + return 0; +} + +/* ---------------------------------------------------------------------- */ + /** This function is used to query whether LAMMPS was compiled with * a real MPI library or in serial. * @@ -4395,6 +4559,33 @@ void lammps_decode_image_flags(imageint image, int *flags) flags[2] = (image >> IMG2BITS) - IMGMAX; } +/** Check if LAMMPS is currently inside a run or minimization + * + * This function can be used from signal handlers or multi-threaded + * applications to determine if the LAMMPS instance is currently active. + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + * \return 0 if idle or >0 if active */ + +int lammps_is_running(void *handle) +{ + LAMMPS * lmp = (LAMMPS *) handle; + return lmp->update->whichflag; +} + +/** Force a timeout to cleanly stop an ongoing run + * + * This function can be used from signal handlers or multi-threaded + * applications to cleanly terminate an ongoing run. + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *`` */ + +void lammps_force_timeout(void *handle) +{ + LAMMPS * lmp = (LAMMPS *) handle; + return lmp->timer->force_timeout(); +} + // ---------------------------------------------------------------------- // Library functions for error handling with exceptions enabled // ---------------------------------------------------------------------- diff --git a/src/library.h b/src/library.h index 2ddad86baa..8089e51ade 100644 --- a/src/library.h +++ b/src/library.h @@ -183,6 +183,10 @@ int lammps_has_style(void *, const char *, const char *); int lammps_style_count(void *, const char *); int lammps_style_name(void *, const char *, int, char *, int); +int lammps_has_id(void *, const char *, const char *); +int lammps_id_count(void *, const char *); +int lammps_id_name(void *, const char *, int, char *, int); + /* ---------------------------------------------------------------------- * Library functions for accessing neighbor lists * ---------------------------------------------------------------------- */ @@ -218,6 +222,9 @@ void lammps_set_fix_external_callback(void *, char *, FixExternalFnPtr, void*); void lammps_fix_external_set_energy_global(void *, char *, double); void lammps_fix_external_set_virial_global(void *, char *, double *); +int lammps_is_running(void *handle); +void lammps_force_timeout(void *handle); + int lammps_has_error(void *handle); int lammps_get_last_error_message(void *handle, char *buffer, int buf_size); diff --git a/src/math_eigen_impl.h b/src/math_eigen_impl.h index 80502b1701..7d6f0385ae 100644 --- a/src/math_eigen_impl.h +++ b/src/math_eigen_impl.h @@ -935,7 +935,7 @@ run(real_t& eigvalue, std::vector& eigvec) const //assert(matrix_size > 0); //assert(0 < this->tridiag_eps_ratio && this->tridiag_eps_ratio < 1); - std::vector> u; // Lanczos vectors + std::vector> u; // Lanczos vectors std::vector> alpha; // Diagonal elements of an approximated tridiagonal matrix std::vector> beta; // Subdiagonal elements of an approximated tridiagonal matrix diff --git a/src/min.cpp b/src/min.cpp index ea07820e53..7474025c1a 100644 --- a/src/min.cpp +++ b/src/min.cpp @@ -244,7 +244,7 @@ void Min::setup(int flag) bigint ndofme = 3 * static_cast(atom->nlocal); for (int m = 0; m < nextra_atom; m++) - ndofme += extra_peratom[m]*atom->nlocal; + ndofme += extra_peratom[m]*static_cast(atom->nlocal); MPI_Allreduce(&ndofme,&ndoftotal,1,MPI_LMP_BIGINT,MPI_SUM,world); ndoftotal += nextra_global; diff --git a/src/update.cpp b/src/update.cpp index 492db2bd7c..41a0910556 100644 --- a/src/update.cpp +++ b/src/update.cpp @@ -51,6 +51,7 @@ Update::Update(LAMMPS *lmp) : Pointers(lmp) multireplica = 0; eflag_global = vflag_global = -1; + eflag_atom = vflag_atom = 0; dt_default = 1; unit_style = nullptr; diff --git a/src/variable.cpp b/src/variable.cpp index 16677ef5ae..6931fd9122 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -2687,23 +2687,23 @@ double Variable::collapse_tree(Tree *tree) } if (tree->type == STAGGER) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); if (tree->first->type != VALUE || tree->second->type != VALUE) return 0.0; tree->type = VALUE; if (ivalue1 <= 0 || ivalue2 <= 0 || ivalue1 <= ivalue2) error->one(FLERR,"Invalid math function in variable formula"); - int lower = update->ntimestep/ivalue1 * ivalue1; - int delta = update->ntimestep - lower; + bigint lower = update->ntimestep/ivalue1 * ivalue1; + bigint delta = update->ntimestep - lower; if (delta < ivalue2) tree->value = lower+ivalue2; else tree->value = lower+ivalue1; return tree->value; } if (tree->type == LOGFREQ) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); - int ivalue3 = static_cast (collapse_tree(tree->extra[0])); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue3 = static_cast (collapse_tree(tree->extra[0])); if (tree->first->type != VALUE || tree->second->type != VALUE || tree->extra[0]->type != VALUE) return 0.0; tree->type = VALUE; @@ -2711,9 +2711,9 @@ double Variable::collapse_tree(Tree *tree) error->one(FLERR,"Invalid math function in variable formula"); if (update->ntimestep < ivalue1) tree->value = ivalue1; else { - int lower = ivalue1; + bigint lower = ivalue1; while (update->ntimestep >= ivalue3*lower) lower *= ivalue3; - int multiple = update->ntimestep/lower; + bigint multiple = update->ntimestep/lower; if (multiple < ivalue2) tree->value = (multiple+1)*lower; else tree->value = lower*ivalue3; } @@ -2721,9 +2721,9 @@ double Variable::collapse_tree(Tree *tree) } if (tree->type == LOGFREQ2) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); - int ivalue3 = static_cast (collapse_tree(tree->extra[0])); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue3 = static_cast (collapse_tree(tree->extra[0])); if (tree->first->type != VALUE || tree->second->type != VALUE || tree->extra[0]->type != VALUE) return 0.0; tree->type = VALUE; @@ -2733,7 +2733,7 @@ double Variable::collapse_tree(Tree *tree) else { tree->value = ivalue1; double delta = ivalue1*(ivalue3-1.0)/ivalue2; - int count = 0; + bigint count = 0; while (update->ntimestep >= tree->value) { tree->value += delta; count++; @@ -2745,9 +2745,9 @@ double Variable::collapse_tree(Tree *tree) } if (tree->type == LOGFREQ3) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); - int ivalue3 = static_cast (collapse_tree(tree->extra[0])); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue3 = static_cast (collapse_tree(tree->extra[0])); if (tree->first->type != VALUE || tree->second->type != VALUE || tree->extra[0]->type != VALUE) return 0.0; tree->type = VALUE; @@ -2760,7 +2760,7 @@ double Variable::collapse_tree(Tree *tree) tree->value = ivalue1; double logsp = ivalue1; double factor = pow(((double)ivalue3)/ivalue1, 1.0/(ivalue2-1)); - int linsp = ivalue1; + bigint linsp = ivalue1; while (update->ntimestep >= (tree->value)) { logsp *= factor; linsp++; @@ -2774,9 +2774,9 @@ double Variable::collapse_tree(Tree *tree) } if (tree->type == STRIDE) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); - int ivalue3 = static_cast (collapse_tree(tree->extra[0])); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue3 = static_cast (collapse_tree(tree->extra[0])); if (tree->first->type != VALUE || tree->second->type != VALUE || tree->extra[0]->type != VALUE) return 0.0; tree->type = VALUE; @@ -2784,7 +2784,7 @@ double Variable::collapse_tree(Tree *tree) error->one(FLERR,"Invalid math function in variable formula"); if (update->ntimestep < ivalue1) tree->value = ivalue1; else if (update->ntimestep < ivalue2) { - int offset = update->ntimestep - ivalue1; + bigint offset = update->ntimestep - ivalue1; tree->value = ivalue1 + (offset/ivalue3)*ivalue3 + ivalue3; if (tree->value > ivalue2) tree->value = (double) MAXBIGINT; } else tree->value = (double) MAXBIGINT; @@ -2792,12 +2792,12 @@ double Variable::collapse_tree(Tree *tree) } if (tree->type == STRIDE2) { - int ivalue1 = static_cast (collapse_tree(tree->first)); - int ivalue2 = static_cast (collapse_tree(tree->second)); - int ivalue3 = static_cast (collapse_tree(tree->extra[0])); - int ivalue4 = static_cast (collapse_tree(tree->extra[1])); - int ivalue5 = static_cast (collapse_tree(tree->extra[2])); - int ivalue6 = static_cast (collapse_tree(tree->extra[3])); + bigint ivalue1 = static_cast (collapse_tree(tree->first)); + bigint ivalue2 = static_cast (collapse_tree(tree->second)); + bigint ivalue3 = static_cast (collapse_tree(tree->extra[0])); + bigint ivalue4 = static_cast (collapse_tree(tree->extra[1])); + bigint ivalue5 = static_cast (collapse_tree(tree->extra[2])); + bigint ivalue6 = static_cast (collapse_tree(tree->extra[3])); if (tree->first->type != VALUE || tree->second->type != VALUE || tree->extra[0]->type != VALUE || tree->extra[1]->type != VALUE || tree->extra[2]->type != VALUE || tree->extra[3]->type != VALUE) @@ -2813,15 +2813,15 @@ double Variable::collapse_tree(Tree *tree) if (update->ntimestep < ivalue1) istep = ivalue1; else if (update->ntimestep < ivalue2) { if (update->ntimestep < ivalue4 || update->ntimestep > ivalue5) { - int offset = update->ntimestep - ivalue1; + bigint offset = update->ntimestep - ivalue1; istep = ivalue1 + (offset/ivalue3)*ivalue3 + ivalue3; if (update->ntimestep < ivalue2 && istep > ivalue4) tree->value = ivalue4; } else { - int offset = update->ntimestep - ivalue4; + bigint offset = update->ntimestep - ivalue4; istep = ivalue4 + (offset/ivalue6)*ivalue6 + ivalue6; if (istep > ivalue5) { - int offset = ivalue5 - ivalue1; + bigint offset = ivalue5 - ivalue1; istep = ivalue1 + (offset/ivalue3)*ivalue3 + ivalue3; if (istep > ivalue2) istep = MAXBIGINT; } diff --git a/src/variable.h b/src/variable.h index 58548bc276..2519bc7ac9 100644 --- a/src/variable.h +++ b/src/variable.h @@ -49,11 +49,13 @@ class Variable : protected Pointers { tagint int_between_brackets(char *&, int); double evaluate_boolean(char *); + public: + int nvar; // # of defined variables + char **names; // name of each variable + private: int me; - int nvar; // # of defined variables int maxvar; // max # of variables following lists can hold - char **names; // name of each variable int *style; // style of each variable int *num; // # of values for each variable int *which; // next available value for each variable diff --git a/tools/README b/tools/README index bed062f918..e98e552b61 100644 --- a/tools/README +++ b/tools/README @@ -29,6 +29,7 @@ fep scripts for free-energy perturbation with USER-FEP pkg i-pi Python wrapper for performing path-integral MD (PIMD) ipp input pre-processor Perl tool for creating input scripts kate add-ons to Kate editor for editing LAMMPS input scripts +lammps-shell LAMMPS executable enhanced for interactive use lmp2arc convert LAMMPS output to Accelrys Insight format lmp2cfg convert LAMMPS output to CFG files for AtomEye viz matlab MatLab scripts for post-processing LAMMPS output diff --git a/tools/lammps-shell/.gitignore b/tools/lammps-shell/.gitignore new file mode 100644 index 0000000000..f6c4ad00fa --- /dev/null +++ b/tools/lammps-shell/.gitignore @@ -0,0 +1 @@ +/lammps-shell diff --git a/tools/lammps-shell/Makefile b/tools/lammps-shell/Makefile new file mode 100644 index 0000000000..434fc471aa --- /dev/null +++ b/tools/lammps-shell/Makefile @@ -0,0 +1,14 @@ +SHELL=/bin/sh + +CXX=g++ +CXXFLAGS=-O -g -Wall -I../../src -DLAMMPS_LIB_NO_MPI +LDFLAGS= -L../../src -llammps -lreadline + +lammps-shell: lammps-shell.o + $(CXX) -o $@ $^ $(LDFLAGS) + +lammps-shell.o: lammps-shell.cpp + $(CXX) -c $(CXXFLAGS) -o $@ $< + +clean: + @rm -f lammps-shell lammps-shell.o core *~ .lammps-history diff --git a/tools/lammps-shell/README b/tools/lammps-shell/README new file mode 100644 index 0000000000..efa19236ce --- /dev/null +++ b/tools/lammps-shell/README @@ -0,0 +1,107 @@ +The LAMMPS Shell. An enhanced LAMMPS executable for interactive sessions. + +Overview +======== + +This is a program that functions very similar to the regular LAMMPS +executable but has several modifications and additions that make it +more powerful for interactive sessions, i.e. where you type LAMMPS +commands from the prompt instead of reading them from a file. + +- It uses the readline and history libraries to provide command line + editing and context aware TAB-expansion (details on that below). + +- When processing an input file with the '-in' or '-i' flag from the + command line, it does not exit at the end of that input file but + stops at a prompt, so that additional commands can be issued + +- Errors will not abort the shell but return to the prompt. + +- It has additional commands aimed at interactive use (details below). + +- Interrupting a calculation with CTRL-C will not terminate the + session but rather enforce a timeout to cleanly stop an ongoing + run (more info on timeouts is in the timer command documentation). + +These enhancements makes the LAMMPS shell an attractive choice for +interactive LAMMPS sessions in graphical user interfaces. + +TAB-expansion +============= + +When writing commands interactively at the shell prompt, you can hit +the TAB key at any time to try and complete the text. This completion +is context aware and will expand any first word only to commands +available in that executable. + +- For style commands it will expand to available styles of the + corresponding category (e.g. pair styles after a pair_style command). + +- For "compute", "fix", or "dump" it will also expand only to already + defined groups for the group-ID keyword. + +- For commands like "compute_modify", "fix_modify", or "dump_modify" + it will expand to known compute/fix/dump IDs only. + +- When typing references to computes, fixes, or variables with a + "c_", "f_", or "v_" prefix, respectively, then the expansion will + to known compute/fix IDs and variable names. Variable name expansion + is also available for the ${name} variable syntax. + +- In all other cases, expansion will be performed on filenames. + +Command line editing and history +================================ + +When typing commands, command line editing similar to what BASH +provides is available. Thus it is possible to move around the +currently line and perform various cut and insert and edit operations. +Previous commands can be retrieved by scrolling up (and down) +or searching (e.g. with CTRL-r). + +Also history expansion through using the exclamation mark '!' +can be performed. Examples: '!!' will be replaced with the previous +command, '!-2' will repeat the command before that, '!30' will be +replaced with event number 30 in the command history list, and +'!run' with the last command line that started with "run". Adding +a ":p" to such a history expansion will result that the expansion is +printed and added to the history list, but NOT executed. +On exit the LAMMPS shell will write the history list to a file +".lammps_history" in the current working directory. If such a +file exists when the LAMMPS shell is launched it will be read to +populate the history list. + +This is realized via the readline library and can thus be customized +with an ".inputrc" file in the home directory. For application specific +customization, the LAMMPS shell uses the name "lammps-shell". +For more information about using and customizing an application using +readline, please see the available documentation at: +http://www.gnu.org/s/readline/#Documentation + +Additional commands +=================== + +The followind commands are added to the LAMMPS shell on top of the +regular LAMMPS commands: + +- help (or ?) print a brief help message +- history display the current command history list +- clear_history wipe out the current command history list +- | execute as a shell command and return to the command prompt +- exit exit the LAMMPS shell cleanly (unlike the "quit" command) + +Compilation +=========== + +Compilation of the LAMMPS shell can be enabled by setting the CMake +variable BUILD_LAMMPS_SHELL to "on" or using the makefile in the +tools/lammps-shell folder to compile after building LAMMPS using +the conventional make procedure. The makefile will likely need +customization depending on the features and settings used for +compiling LAMMPS. + +Limitations +=========== + +The LAMMPS shell was not designed for use with MPI parallelization +via "mpirun" or "mpiexec" or "srun". diff --git a/tools/lammps-shell/lammps-shell.cpp b/tools/lammps-shell/lammps-shell.cpp new file mode 100644 index 0000000000..c1f8182535 --- /dev/null +++ b/tools/lammps-shell/lammps-shell.cpp @@ -0,0 +1,604 @@ +// LAMMPS Shell. An improved interactive LAMMPS session with +// command line editing, history, TAB expansion and shell escapes + +// Copyright (c) 2020 Axel Kohlmeyer + +// This software is distributed under the GNU General Public License. + +#include "library.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) +#include +#else +#if !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#define isatty(x) _isatty(x) +#endif + +#if !defined(_WIN32) +#include +#endif + +#include +#include + +using namespace LAMMPS_NS; + +const int buflen = 512; +char buf[buflen]; +void *lmp = nullptr; +enum { + ATOM_STYLE, + INTEGRATE_STYLE, + MINIMIZE_STYLE, + PAIR_STYLE, + BOND_STYLE, + ANGLE_STYLE, + DIHEDRAL_STYLE, + IMPROPER_STYLE, + KSPACE_STYLE, + FIX_STYLE, + COMPUTE_STYLE, + REGION_STYLE, + DUMP_STYLE +}; +const char *lmp_style[] = {"atom", "integrate", "minimize", "pair", "bond", + "angle", "dihedral", "improper", "kspace", "fix", + "compute", "region", "dump"}; + +enum { COMPUTE_ID, DUMP_ID, FIX_ID, MOLECULE_ID, REGION_ID, VARIABLE_ID }; +const char *lmp_id[] = {"compute", "dump", "fix", "molecule", "region", "variable"}; + +std::vector commands; + +// this list of commands is generated by: +// grep '!strcmp(command,' ../../src/input.cpp | sed -e 's/^.*!strcmp(command,"\(.*\)".*$/"\1",/' + +const char *cmdlist[] = {"clear", + "echo", + "if", + "include", + "jump", + "label", + "log", + "next", + "partition", + "print", + "python", + "quit", + "shell", + "variable", + "angle_coeff", + "angle_style", + "atom_modify", + "atom_style", + "bond_coeff", + "bond_style", + "bond_write", + "boundary", + "box", + "comm_modify", + "comm_style", + "compute", + "compute_modify", + "dielectric", + "dihedral_coeff", + "dihedral_style", + "dimension", + "dump", + "dump_modify", + "fix", + "fix_modify", + "group", + "improper_coeff", + "improper_style", + "kspace_modify", + "kspace_style", + "lattice", + "mass", + "min_modify", + "min_style", + "molecule", + "neigh_modify", + "neighbor", + "newton", + "package", + "pair_coeff", + "pair_modify", + "pair_style", + "pair_write", + "processors", + "region", + "reset_timestep", + "restart", + "run_style", + "special_bonds", + "suffix", + "thermo", + "thermo_modify", + "thermo_style", + "timestep", + "timer", + "uncompute", + "undump", + "unfix", + "units"}; + +static char *dupstring(const std::string &text) +{ + int len = text.size() + 1; + char *copy = (char *)malloc(len); + strcpy(copy, text.c_str()); + return copy; +} + +template char *style_generator(const char *text, int state) +{ + static int idx, num, len; + if (!state) { + idx = 0; + num = lammps_style_count(lmp, lmp_style[STYLE]); + len = strlen(text); + } + + while (idx < num) { + lammps_style_name(lmp, lmp_style[STYLE], idx, buf, buflen); + ++idx; + if ((len == 0) || (strncmp(text, buf, len) == 0)) return dupstring(buf); + } + return nullptr; +} + +template char *id_generator(const char *text, int state) +{ + static int idx, num, len; + if (!state) { + idx = 0; + num = lammps_id_count(lmp, lmp_id[ID]); + len = strlen(text); + } + + while (idx < num) { + lammps_id_name(lmp, lmp_id[ID], idx, buf, buflen); + ++idx; + if ((len == 0) || (strncmp(text, buf, len) == 0)) return dupstring(buf); + } + return nullptr; +} + +template char *ref_generator(const char *text, int state) +{ + char prefix[] = "X_"; + prefix[0] = PREFIX; + + if (strncmp(text, prefix, 2) == 0) { + char *id = id_generator(text + 2, state); + char *ref = nullptr; + if (id) { + ref = (char *)malloc(strlen(id) + 3); + if (ref) { + ref[0] = PREFIX; + ref[1] = '_'; + ref[2] = 0; + strcat(ref, id); + } + free(id); + } + return ref; + } + return nullptr; +} + +extern "C" { + +#if !defined(_WIN32) +static void ctrl_c_handler(int) +#else +static BOOL WINAPI ctrl_c_handler(DWORD event) +#endif +{ +#if defined(_WIN32) + if (event == CTRL_C_EVENT) { +#endif + if (lmp) + if (lammps_is_running(lmp)) lammps_force_timeout(lmp); +#if defined(_WIN32) + return TRUE; + } + return FALSE; +#endif +} + +static char *cmd_generator(const char *text, int state) +{ + static std::size_t idx, len; + if (!state) idx = 0; + len = strlen(text); + + do { + if ((len == 0) || (commands[idx].substr(0, len) == text)) + return dupstring(commands[idx++]); + else + ++idx; + } while (idx < commands.size()); + return nullptr; +} + +static char *compute_id_generator(const char *text, int state) +{ + return id_generator(text, state); +} + +static char *compute_ref_generator(const char *text, int state) +{ + return ref_generator(text, state); +} + +static char *dump_id_generator(const char *text, int state) +{ + return id_generator(text, state); +} + +static char *fix_id_generator(const char *text, int state) +{ + return id_generator(text, state); +} + +static char *fix_ref_generator(const char *text, int state) +{ + return ref_generator(text, state); +} + +static char *variable_ref_generator(const char *text, int state) +{ + return ref_generator(text, state); +} + +static char *variable_expand_generator(const char *text, int state) +{ + if (strncmp(text, "${", 2) == 0) { + char *id = id_generator(text + 2, state); + char *ref = nullptr; + if (id) { + ref = (char *)malloc(strlen(id) + 4); + if (ref) { + ref[0] = '$'; + ref[1] = '{'; + ref[2] = 0; + strcat(ref, id); + strcat(ref, "}"); + } + free(id); + } + return ref; + } + return nullptr; +} + +static char *atom_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *integrate_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *minimize_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *pair_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *bond_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *angle_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *dihedral_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *improper_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *kspace_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *fix_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *compute_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *region_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +static char *dump_generator(const char *text, int state) +{ + return style_generator(text, state); +} + +char *group_generator(const char *text, int state) +{ + static int idx, num, len; + if (!state) { + idx = 0; + num = lammps_id_count(lmp, "group"); + len = strlen(text); + } + + while (idx < num) { + lammps_id_name(lmp, "group", idx, buf, buflen); + ++idx; + if ((len == 0) || (strncmp(text, buf, len) == 0)) return dupstring(buf); + } + return nullptr; +} + +static char **cmd_completion(const char *text, int start, int) +{ + char **matches = nullptr; + + // avoid segfaults + if (strlen(text) == 0) return matches; + + if (start == 0) { + // match command names from the beginning of a line + matches = rl_completion_matches(text, cmd_generator); + } else { + // try to provide context specific matches + // first split the already completed text into words for position specific expansion + auto words = utils::split_words(std::string(rl_line_buffer).substr(0, start)); + + if (strncmp(text, "c_", 2) == 0) { // expand references to computes or fixes + matches = rl_completion_matches(text, compute_ref_generator); + } else if (strncmp(text, "f_", 2) == 0) { + matches = rl_completion_matches(text, fix_ref_generator); + } else if (strncmp(text, "v_", 2) == 0) { + matches = rl_completion_matches(text, variable_ref_generator); + } else if (strncmp(text, "${", 2) == 0) { + matches = rl_completion_matches(text, variable_expand_generator); + } else if (words.size() == 1) { // expand second word + if (words[0] == "atom_style") { + matches = rl_completion_matches(text, atom_generator); + } else if (words[0] == "pair_style") { + matches = rl_completion_matches(text, pair_generator); + } else if (words[0] == "bond_style") { + matches = rl_completion_matches(text, bond_generator); + } else if (words[0] == "angle_style") { + matches = rl_completion_matches(text, angle_generator); + } else if (words[0] == "dihedral_style") { + matches = rl_completion_matches(text, dihedral_generator); + } else if (words[0] == "improper_style") { + matches = rl_completion_matches(text, improper_generator); + } else if (words[0] == "kspace_style") { + matches = rl_completion_matches(text, kspace_generator); + } else if (words[0] == "run_style") { + matches = rl_completion_matches(text, integrate_generator); + } else if (words[0] == "min_style") { + matches = rl_completion_matches(text, minimize_generator); + } else if (words[0] == "compute_modify") { + matches = rl_completion_matches(text, compute_id_generator); + } else if (words[0] == "dump_modify") { + matches = rl_completion_matches(text, dump_id_generator); + } else if (words[0] == "fix_modify") { + matches = rl_completion_matches(text, fix_id_generator); + } + } else if (words.size() == 2) { // expand third word + + // these commands have a group name as 3rd word + if ((words[0] == "fix") || (words[0] == "compute") || (words[0] == "dump")) { + matches = rl_completion_matches(text, group_generator); + } else if (words[0] == "region") { + matches = rl_completion_matches(text, region_generator); + } + } else if (words.size() == 3) { // expand fourth word + + // style name is the fourth word + if (words[0] == "fix") { + matches = rl_completion_matches(text, fix_generator); + } else if (words[0] == "compute") { + matches = rl_completion_matches(text, compute_generator); + } else if (words[0] == "dump") { + matches = rl_completion_matches(text, dump_generator); + } + } + } + + return matches; +} + +} // end of extern "C" + +static void init_commands() +{ + // store internal commands + int ncmds = sizeof(cmdlist) / sizeof(const char *); + for (int i = 0; i < ncmds; ++i) + commands.push_back(cmdlist[i]); + + // store optional commands from command styles + ncmds = lammps_style_count(lmp, "command"); + for (int i = 0; i < ncmds; ++i) { + if (lammps_style_name(lmp, "command", i, buf, buflen)) commands.push_back(buf); + } + + // store LAMMPS shell specific command names + commands.push_back("help"); + commands.push_back("exit"); + commands.push_back("history"); + commands.push_back("clear_history"); + + // set name so there can be specific entries in ~/.inputrc + rl_readline_name = "lammps-shell"; + rl_basic_word_break_characters = " \t\n\"\\'`@><=;|&("; + + // attempt completions only if we are connected to a tty, + // otherwise any tabs in redirected input will cause havoc. + if (isatty(fileno(stdin))) { + rl_attempted_completion_function = cmd_completion; + } else { + rl_bind_key('\t', rl_insert); + } + + // read old history + read_history(".lammps_history"); + +#if !defined(_WIN32) + signal(SIGINT, ctrl_c_handler); +#else + SetConsoleCtrlHandler(ctrl_c_handler, TRUE); +#endif +} + +static int help_cmd() +{ + std::cout << "\nThis is the LAMMPS Shell. An interactive LAMMPS session with command \n" + "line editing, context aware command expansion, and history.\n\n" + "- Hit the TAB key any time to try to expand the current word\n" + "- Issue shell commands by prefixing them with '|' (Example: '|ls -la')\n" + "- Use the '!' character for bash-like history epansion. (Example: '!run)\n\n" + "A history of the session will be written to the a file '.lammps_history'\n" + "in the current working directory and - if present - this file will be\n" + "read at the beginning of the next session of the LAMMPS shell.\n\n"; + return 0; +} + +static int shell_end() +{ + write_history(".lammps_history"); + if (lmp) lammps_close(lmp); + lammps_mpi_finalize(); + lmp = nullptr; + return 0; +} + +static int shell_cmd(const std::string &cmd) +{ + char *expansion; + char *text = dupstring(cmd); + int retval = history_expand(text, &expansion); + + // history expansion error + if (retval < 0) { + free(text); + free(expansion); + std::cout << "History error: " << utils::getsyserror() << "\n"; + return 1; + } + + // use expanded or original text and add to history + if (retval > 0) { + free(text); + text = expansion; + } else + free(expansion); + + add_history(text); + + // only print, don't execute. + if (retval == 2) { + std::cout << text << "\n"; + free(text); + return 0; + } + + // check for commands particular to lammps-shell + auto words = utils::split_words(text); + if (words[0][0] == '|') { + int rv = system(text + 1); + free(text); + return rv; + } else if ((words[0] == "help") || (words[0] == "?")) { + free(text); + return help_cmd(); + } else if (words[0] == "exit") { + free(text); + return shell_end(); + } else if (words[0] == "history") { + free(text); + HIST_ENTRY **list = history_list(); + for (int i = 0; i < history_length; ++i) { + std::cout << i + history_base << ": " << list[i]->line << "\n"; + } + return 0; + } else if (words[0] == "clear_history") { + free(text); + clear_history(); + return 0; + } + + lammps_command(lmp, text); + free(text); + return lammps_has_error(lmp); +} + +int main(int argc, char **argv) +{ + char *line; + std::string trimmed; + + std::cout << "LAMMPS Shell version 1.0\n"; + if (!lammps_config_has_exceptions()) + std::cout << "WARNING: LAMMPS was compiled without exceptions\n" + "WARNING: The shell will terminate on errors.\n"; + + lmp = lammps_open_no_mpi(argc, argv, nullptr); + if (lmp == nullptr) return 1; + + using_history(); + init_commands(); + + // pre-load an input file that was provided on the command line + for (int i = 0; i < argc; ++i) { + if ((strcmp(argv[i], "-in") == 0) || (strcmp(argv[i], "-i") == 0)) { + lammps_file(lmp, argv[i + 1]); + } + } + + while (lmp != nullptr) { + line = readline("LAMMPS Shell> "); + if (!line) break; + trimmed = utils::trim(line); + if (trimmed.size() > 0) { + shell_cmd(trimmed); + } + free(line); + } + + return shell_end(); +} diff --git a/unittest/c-library/test_library_config.cpp b/unittest/c-library/test_library_config.cpp index 0e81683159..55ca1f84ac 100644 --- a/unittest/c-library/test_library_config.cpp +++ b/unittest/c-library/test_library_config.cpp @@ -2,6 +2,7 @@ #include "lammps.h" #include "library.h" +#include "timer.h" #include #include "gmock/gmock.h" @@ -34,7 +35,11 @@ protected: int argc = sizeof(args) / sizeof(char *); ::testing::internal::CaptureStdout(); - lmp = lammps_open_no_mpi(argc, argv, NULL); + lmp = lammps_open_no_mpi(argc, argv, NULL); + lammps_command(lmp, "fix charge all property/atom q ghost yes"); + lammps_command(lmp, "region box block 0 1 0 1 0 1"); + lammps_command(lmp, "create_box 1 box"); + lammps_command(lmp, "group none empty"); std::string output = ::testing::internal::GetCapturedStdout(); if (verbose) std::cout << output; EXPECT_THAT(output, StartsWith("LAMMPS (")); @@ -118,6 +123,76 @@ TEST_F(LibraryConfig, style_name) EXPECT_THAT(buf, StrEq("")); }; +TEST_F(LibraryConfig, has_id) +{ + EXPECT_EQ(lammps_has_id(lmp, "compute", "thermo_temp"), 1); + EXPECT_EQ(lammps_has_id(lmp, "compute", "thermo_press"), 1); + EXPECT_EQ(lammps_has_id(lmp, "compute", "thermo_pe"), 1); + EXPECT_EQ(lammps_has_id(lmp, "dump", "xxx"), 0); + EXPECT_EQ(lammps_has_id(lmp, "fix", "charge"), 1); + EXPECT_EQ(lammps_has_id(lmp, "fix", "xxx"), 0); + EXPECT_EQ(lammps_has_id(lmp, "group", "all"), 1); + EXPECT_EQ(lammps_has_id(lmp, "group", "none"), 1); + EXPECT_EQ(lammps_has_id(lmp, "group", "xxx"), 0); + EXPECT_EQ(lammps_has_id(lmp, "molecule", "xxx"), 0); + EXPECT_EQ(lammps_has_id(lmp, "region", "box"), 1); + EXPECT_EQ(lammps_has_id(lmp, "region", "xxx"), 0); + EXPECT_EQ(lammps_has_id(lmp, "variable", "input_dir"), 1); + EXPECT_EQ(lammps_has_id(lmp, "variable", "xxx"), 0); +}; + +TEST_F(LibraryConfig, id_count) +{ + EXPECT_EQ(lammps_id_count(lmp, "compute"), 3); + EXPECT_EQ(lammps_id_count(lmp, "dump"), 0); + EXPECT_EQ(lammps_id_count(lmp, "fix"), 1); + EXPECT_EQ(lammps_id_count(lmp, "group"), 2); + EXPECT_EQ(lammps_id_count(lmp, "molecule"), 0); + EXPECT_EQ(lammps_id_count(lmp, "region"), 1); + EXPECT_EQ(lammps_id_count(lmp, "variable"), 1); +}; + +TEST_F(LibraryConfig, id_name) +{ + const int bufsize = 128; + char buf[bufsize]; + EXPECT_EQ(lammps_id_name(lmp, "compute", 2, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("thermo_pe")); + EXPECT_EQ(lammps_id_name(lmp, "compute", 10, buf, bufsize), 0); + EXPECT_THAT(buf, StrEq("")); + EXPECT_EQ(lammps_id_name(lmp, "fix", 0, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("charge")); + EXPECT_EQ(lammps_id_name(lmp, "fix", 10, buf, bufsize), 0); + EXPECT_THAT(buf, StrEq("")); + EXPECT_EQ(lammps_id_name(lmp, "group", 0, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("all")); + EXPECT_EQ(lammps_id_name(lmp, "group", 1, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("none")); + EXPECT_EQ(lammps_id_name(lmp, "group", 10, buf, bufsize), 0); + EXPECT_THAT(buf, StrEq("")); + EXPECT_EQ(lammps_id_name(lmp, "region", 0, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("box")); + EXPECT_EQ(lammps_id_name(lmp, "region", 10, buf, bufsize), 0); + EXPECT_THAT(buf, StrEq("")); + EXPECT_EQ(lammps_id_name(lmp, "variable", 0, buf, bufsize), 1); + EXPECT_THAT(buf, StrEq("input_dir")); + EXPECT_EQ(lammps_id_name(lmp, "variable", 10, buf, bufsize), 0); + EXPECT_THAT(buf, StrEq("")); +}; + +TEST_F(LibraryConfig, is_running) +{ + EXPECT_EQ(lammps_is_running(lmp), 0); +} + +TEST_F(LibraryConfig, force_timeout) +{ + LAMMPS_NS::Timer *timer = ((LAMMPS_NS::LAMMPS *)lmp)->timer; + EXPECT_EQ(timer->is_timeout(), false); + lammps_force_timeout(lmp); + EXPECT_EQ(timer->is_timeout(), true); +} + TEST(LAMMPSConfig, exceptions) { EXPECT_EQ(lammps_config_has_exceptions(), LAMMPS_HAS_EXCEPTIONS);