diff --git a/doc/src/pg_python.rst b/doc/src/pg_python.rst index c4de7c4788..31bd3fa2e3 100644 --- a/doc/src/pg_python.rst +++ b/doc/src/pg_python.rst @@ -73,12 +73,13 @@ Creating or deleting a LAMMPS object ************************************ With the Python interface the creation of a :cpp:class:`LAMMPS -` instance is included in the constructor for the -:py:func:`lammps ` class. Internally it will call either -:cpp:func:`lammps_open` or :cpp:func:`lammps_open_no_mpi` from the C +` instance is included in the constructors for the +:py:meth:`lammps `, :py:meth:`PyLammps `, +and :py:meth:`PyLammps ` classes. +Internally it will call either :cpp:func:`lammps_open` or :cpp:func:`lammps_open_no_mpi` from the C library API to create the class instance. -All arguments are optional. The *name* argument is to allow loading a +All arguments are optional. The *name* argument allows loading a LAMMPS shared library that is named ``liblammps_machine.so`` instead of the default name of ``liblammps.so``. In most cases the latter will be installed or used. The *ptr* argument is for use of the @@ -89,22 +90,111 @@ to the Python class and used instead of creating a new instance. The *comm* argument may be used in combination with the `mpi4py `_ module to pass an MPI communicator to LAMMPS and thus it is possible to run the Python module like the library interface on a subset of the -MPI ranks after splitting the communicator. Here is a simple example: +MPI ranks after splitting the communicator. -.. code-block:: python - from lammps import lammps +Here are simple examples using all three Python interfaces: - # NOTE: argv[0] is set by the Python module - args = ["-log", "none"] - # create LAMMPS instance - lmp = lammps(cmdargs=args) - # get and print numerical version code - print("LAMMPS Version: ", lmp.version()) - # explicitly close and delete LAMMPS instance (optional) - lmp.close() +.. tabs:: -Same as with the :ref:`C library API ` this will use the + .. tab:: lammps API + + .. code-block:: python + + from lammps import lammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + # create LAMMPS instance + lmp = lammps(cmdargs=args) + # get and print numerical version code + print("LAMMPS Version: ", lmp.version()) + # explicitly close and delete LAMMPS instance (optional) + lmp.close() + + .. tab:: PyLammps API + + The :py:class:`PyLammps` class is a wrapper around the + :py:class:`lammps` class and all of its lower level functions. + By default, it will create a new instance of :py:class:`lammps` passing + along all arguments to the constructor of :py:class:`lammps`. + + .. code-block:: python + + from lammps import PyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + # create LAMMPS instance + L = PyLammps(cmdargs=args) + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + # explicitly close and delete LAMMPS instance (optional) + L.close() + + :py:class:`PyLammps` objects can also be created on top of an existing :py:class:`lammps` object: + + .. code-block:: Python + + from lammps import lammps, PyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + # create LAMMPS instance + lmp = lammps(cmdargs=args) + # create PyLammps instance using previously created LAMMPS instance + L = PyLammps(ptr=lmp) + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + # explicitly close and delete LAMMPS instance (optional) + L.close() + + This is useful if you have to create the :py:class:`lammps ` + instance is a specific way, but want to take advantage of the + :py:class:`PyLammps ` interface. + + .. tab:: IPyLammps API + + The :py:class:`IPyLammps` class is an extension of the + :py:class:`PyLammps` class. It has the same construction behavior. By + default, it will create a new instance of :py:class:`lammps` passing + along all arguments to the constructor of :py:class:`lammps`. + + .. code-block:: python + + from lammps import IPyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + # create LAMMPS instance + L = IPyLammps(cmdargs=args) + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + # explicitly close and delete LAMMPS instance (optional) + L.close() + + You can also initialize IPyLammps on top of an existing :py:class:`lammps` or :py:class:`PyLammps` object: + + .. code-block:: Python + + from lammps import lammps, IPyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + # create LAMMPS instance + lmp = lammps(cmdargs=args) + # create IPyLammps instance using previously created LAMMPS instance + L = IPyLammps(ptr=lmp) + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + # explicitly close and delete LAMMPS instance (optional) + L.close() + + This is useful if you have to create the :py:class:`lammps ` + instance is a specific way, but want to take advantage of the + :py:class:`IPyLammps ` interface. + +In all of the above cases, same as with the :ref:`C library API `, this will use the ``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was compiled with. The :py:func:`lmp.close() ` call is optional since the LAMMPS class instance will also be deleted @@ -114,39 +204,107 @@ destructor. Executing LAMMPS commands ************************* -Once an instance of the :py:class:`lammps ` class is -created, there are multiple ways to "feed" it commands. In a way that is -not very different from running a LAMMPS input script, except that -Python has many more facilities for structured programming than the -LAMMPS input script syntax. Furthermore it is possible to "compute" -what the next LAMMPS command should be. Same as in the equivalent `C -library functions `, commands can be read from a file, a -single string, a list of strings and a block of commands in a single -multi-line string. They are processed under the same boundary conditions -as the C library counterparts. The example below demonstrates the use -of :py:func:`lammps.file`, :py:func:`lammps.command`, -:py:func:`lammps.commands_list`, and :py:func:`lammps.commands_string`: +Once an instance of the :py:class:`lammps`, :py:class:`PyLammps`, or +:py:class:`IPyLammps` class is created, there are multiple ways to "feed" it +commands. In a way that is not very different from running a LAMMPS input +script, except that Python has many more facilities for structured +programming than the LAMMPS input script syntax. Furthermore it is possible +to "compute" what the next LAMMPS command should be. -.. code-block:: python +.. tabs:: - from lammps import lammps + .. tab:: lammps API - lmp = lammps() - # read commands from file 'in.melt' - lmp.file('in.melt') - # issue a single command - lmp.command('variable zpos index 1.0') - # create 10 groups with 10 atoms each - cmds = ["group g{} id {}:{}".format(i,10*i+1,10*(i+1)) for i in range(10)] - lmp.commands_list(cmds) - # run commands from a multi-line string - block = """ - clear - region box block 0 2 0 2 0 2 - create_box 1 box - create_atoms 1 single 1.0 1.0 ${zpos} - """ - lmp.commands_string(block) + Same as in the equivalent + `C library functions `, commands can be read from a file, a + single string, a list of strings and a block of commands in a single + multi-line string. They are processed under the same boundary conditions + as the C library counterparts. The example below demonstrates the use + of :py:func:`lammps.file`, :py:func:`lammps.command`, + :py:func:`lammps.commands_list`, and :py:func:`lammps.commands_string`: + + .. code-block:: python + + from lammps import lammps + lmp = lammps() + # read commands from file 'in.melt' + lmp.file('in.melt') + # issue a single command + lmp.command('variable zpos index 1.0') + # create 10 groups with 10 atoms each + cmds = ["group g{} id {}:{}".format(i,10*i+1,10*(i+1)) for i in range(10)] + lmp.commands_list(cmds) + # run commands from a multi-line string + block = """ + clear + region box block 0 2 0 2 0 2 + create_box 1 box + create_atoms 1 single 1.0 1.0 ${zpos} + """ + lmp.commands_string(block) + + .. tab:: PyLammps/IPyLammps API + + Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS + commands by calling equivalent member functions. + + For instance, the following LAMMPS command: + + .. code-block:: LAMMPS + + region box block 0 10 0 5 -0.5 0.5 + + In the original interface this command can be executed with the following + Python code if *L* was a lammps instance: + + .. code-block:: Python + + L.command("region box block 0 10 0 5 -0.5 0.5") + + With the PyLammps interface, any command can be split up into arbitrary parts + separated by white-space, passed as individual arguments to a :code:`region` method. + + .. code-block:: Python + + L.region("box block", 0, 10, 0, 5, -0.5, 0.5) + + Note that each parameter is set as Python literal floating-point number. In the + PyLammps interface, each command takes an arbitrary parameter list and transparently + merges it to a single command string, separating individual parameters by white-space. + + The benefit of this approach is avoiding redundant command calls and easier + parameterization. In the original interface parameterization needed to be done + manually by creating formatted strings. + + .. code-block:: Python + + L.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) + + In contrast, methods of PyLammps accept parameters directly and will convert + them automatically to a final command string. + + .. code-block:: Python + + L.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) + + Using these facilities, the example shown for the lammps API can be rewritten as follows: + + .. code-block:: python + + from lammps import PyLammps + L = PyLammps() + # read commands from file 'in.melt' + L.file('in.melt') + # issue a single command + L.variable('zpos', 'index', 1.0) + # create 10 groups with 10 atoms each + for i in range(10): + L.group(f"g{i}", "id", f"{10*i+1}:{10*(i+1)}") + + L.clear() + L.region("box block", 0, 2, 0, 2, 0, 2) + L.create_box(1, "box") + L.create_atoms(1, "single", 1.0, 1.0, "${zpos}") ---------- @@ -176,239 +334,17 @@ simpler, more "Pythonic" interface to common LAMMPS functionality. LAMMPS data structures are exposed through objects and properties. This makes Python scripts shorter and more concise. -Creating a new instance of PyLammps ------------------------------------ - -To create a PyLammps object you need to first import the class from the lammps -module. By using the default constructor, a new :py:class:`lammps ` instance is created. - -.. code-block:: Python - - from lammps import PyLammps - L = PyLammps() - -You can also initialize PyLammps on top of this existing :py:class:`lammps ` object: - -.. code-block:: Python - - from lammps import lammps, PyLammps - lmp = lammps() - L = PyLammps(ptr=lmp) - -This is useful if you have create the :py:class:`lammps ` -instance is a specific way, but want to take advantage of the -:py:class:`PyLammps ` interface. - -Commands --------- - -Sending a LAMMPS command with the existing library interfaces is done using -the command method of the lammps object instance. - -For instance, let's take the following LAMMPS command: - -.. code-block:: LAMMPS - - region box block 0 10 0 5 -0.5 0.5 - -In the original interface this command can be executed with the following -Python code if *L* was a lammps instance: - -.. code-block:: Python - - L.command("region box block 0 10 0 5 -0.5 0.5") - -With the PyLammps interface, any command can be split up into arbitrary parts -separated by white-space, passed as individual arguments to a region method. - -.. code-block:: Python - - L.region("box block", 0, 10, 0, 5, -0.5, 0.5) - -Note that each parameter is set as Python literal floating-point number. In the -PyLammps interface, each command takes an arbitrary parameter list and transparently -merges it to a single command string, separating individual parameters by white-space. - -The benefit of this approach is avoiding redundant command calls and easier -parameterization. In the original interface parameterization needed to be done -manually by creating formatted strings. - -.. code-block:: Python - - L.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) - -In contrast, methods of PyLammps accept parameters directly and will convert -them automatically to a final command string. - -.. code-block:: Python - - L.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) - -System state ------------- - -In addition to dispatching commands directly through the PyLammps object, it -also provides several properties which allow you to query the system state. - -:py:attr:`lammps.PyLammps.system` - Is a dictionary describing the system such as the bounding box or number of atoms - -L.system.xlo, L.system.xhi - bounding box limits along x-axis - -L.system.ylo, L.system.yhi - bounding box limits along y-axis - -L.system.zlo, L.system.zhi - bounding box limits along z-axis - -L.communication - configuration of communication subsystem, such as the number of threads or processors - -L.communication.nthreads - number of threads used by each LAMMPS process - -L.communication.nprocs - number of MPI processes used by LAMMPS - -L.fixes - List of fixes in the current system - -L.computes - List of active computes in the current system - -L.dump - List of active dumps in the current system - -L.groups - List of groups present in the current system - -Working with LAMMPS variables ------------------------------ - -LAMMPS variables can be both defined and accessed via the PyLammps interface. - -To define a variable you can use the :doc:`variable ` command: - -.. code-block:: Python - - L.variable("a index 2") - -A dictionary of all variables is returned by L.variables - -you can access an individual variable by retrieving a variable object from the -L.variables dictionary by name - -.. code-block:: Python - - a = L.variables['a'] - -The variable value can then be easily read and written by accessing the value -property of this object. - -.. code-block:: Python - - print(a.value) - a.value = 4 - -Retrieving the value of an arbitrary LAMMPS expressions -------------------------------------------------------- - -LAMMPS expressions can be immediately evaluated by using the eval method. The -passed string parameter can be any expression containing global thermo values, -variables, compute or fix data. - -.. code-block:: Python - - result = L.eval("ke") # kinetic energy - result = L.eval("pe") # potential energy - - result = L.eval("v_t/2.0") - -Accessing atom data -------------------- - -All atoms in the current simulation can be accessed by using the L.atoms list. -Each element of this list is an object which exposes its properties (id, type, -position, velocity, force, etc.). - -.. code-block:: Python - - # access first atom - L.atoms[0].id - L.atoms[0].type - - # access second atom - L.atoms[1].position - L.atoms[1].velocity - L.atoms[1].force - -Some properties can also be used to set: - -.. code-block:: Python - - # set position in 2D simulation - L.atoms[0].position = (1.0, 0.0) - - # set position in 3D simulation - L.atoms[0].position = (1.0, 0.0, 1.) - -Evaluating thermo data ----------------------- - -Each simulation run usually produces thermo output based on system state, -computes, fixes or variables. The trajectories of these values can be queried -after a run via the L.runs list. This list contains a growing list of run data. -The first element is the output of the first run, the second element that of -the second run. - -.. code-block:: Python - - L.run(1000) - L.runs[0] # data of first 1000 time steps - - L.run(1000) - L.runs[1] # data of second 1000 time steps - -Each run contains a dictionary of all trajectories. Each trajectory is -accessible through its thermo name: - -.. code-block:: Python - - L.runs[0].thermo.Step # list of time steps in first run - L.runs[0].thermo.Ke # list of kinetic energy values in first run - -Together with matplotlib plotting data out of LAMMPS becomes simple: - -.. code-block:: Python - - import matplotlib.plot as plt - steps = L.runs[0].thermo.Step - ke = L.runs[0].thermo.Ke - plt.plot(steps, ke) - -Error handling with PyLammps ----------------------------- - -Compiling the shared library with C++ exception support provides a better error -handling experience. Without exceptions the LAMMPS code will terminate the -current Python process with an error message. C++ exceptions allow capturing -them on the C++ side and rethrowing them on the Python side. This way you -can handle LAMMPS errors through the Python exception handling mechanism. - -.. warning:: - - Capturing a LAMMPS exception in Python can still mean that the - current LAMMPS process is in an illegal state and must be terminated. It is - advised to save your data and terminate the Python instance as quickly as - possible. - .. autoclass:: lammps.PyLammps :members: .. autoclass:: lammps.AtomList :members: +.. autoclass:: lammps.Atom + :members: + +.. autoclass:: lammps.Atom2D + :members: ---------- @@ -463,3 +399,19 @@ and the :py:class:`NeighList ` class: :members: :no-undoc-members: + +LAMMPS error handling in Python +******************************* + +Compiling the shared library with C++ exception support provides a better error +handling experience. Without exceptions the LAMMPS code will terminate the +current Python process with an error message. C++ exceptions allow capturing +them on the C++ side and rethrowing them on the Python side. This way +LAMMPS errors can be handled through the Python exception handling mechanism. + +.. warning:: + + Capturing a LAMMPS exception in Python can still mean that the + current LAMMPS process is in an illegal state and must be terminated. It is + advised to save your data and terminate the Python instance as quickly as + possible. diff --git a/python/lammps.py b/python/lammps.py index 5d879691ae..4b70abf3d5 100644 --- a/python/lammps.py +++ b/python/lammps.py @@ -1992,6 +1992,10 @@ class PyLammps(object): 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 @@ -2091,32 +2095,68 @@ class PyLammps(object): @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.info("communication") 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.info("computes") 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.info("dumps") 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.info("fixes") 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 + """ output = self.info("groups") return self._parse_groups(output) @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 + """ output = self.info("variables") vars = {} for v in self._parse_element_list(output): @@ -2302,7 +2342,21 @@ class PyLammps(object): class IPyLammps(PyLammps): """ - IPython wrapper for LAMMPS which adds embedded graphics capabilities + IPython wrapper for LAMMPS which adds embedded graphics capabilities to PyLammmps 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 `_). ``None`` means use ``MPI_COMM_WORLD`` implicitly. + :type comm: MPI_Comm """ def __init__(self,name="",cmdargs=None,ptr=None,comm=None): @@ -2310,6 +2364,34 @@ class IPyLammps(PyLammps): 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 ` 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: @@ -2345,5 +2427,15 @@ class IPyLammps(PyLammps): return Image(filename) def video(self, filename): + """ + Load video from file + + Can be used to visualize videos from :doc:`dump_movie`. + + :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("")