diff --git a/.gitignore b/.gitignore index 7a23598..670289c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ build *egg-info env +doc/source/generated diff --git a/LICENSE b/LICENSE index 4c295f6..14a0229 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021-2024, Auburn University +Copyright (c) 2021-2025, Auburn University All rights reserved. diff --git a/README.md b/README.md index ae9c2da..c590439 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ [![PyPI downloads](https://img.shields.io/pypi/dm/lammpsio)](https://pypi.org/project/lammpsio) [![Conda](https://img.shields.io/conda/dn/conda-forge/lammpsio)](https://anaconda.org/conda-forge/lammpsio) -Tools for working with LAMMPS data and dump files. +lammpsio provides a Python interface for reading and writing LAMMPS +data and dump files. It also enables interconversion with HOOMD-blue's +GSD format. This flexible package enables users to work with LAMMPS files +in a variety of ways that improve workflow efficiency and reproducibility. `lammpsio` is a pure Python package that can be installed using `pip`: @@ -13,160 +16,3 @@ Tools for working with LAMMPS data and dump files. or `conda`: conda install -c conda-forge lammpsio - -## Snapshot - -The particle configuration is stored in a `Snapshot`. A `Snapshot` holds the -data for *N* particles, the simulation `Box`, and the timestep. The `Box` follows -the LAMMPS conventions for its shape and bounds. Here is a 3-particle -configuration in an orthorhombic box centered at the origin at step 100: - - box = lammpsio.Box((-2,-3,-4), (2,3,4)) - snapshot = lammpsio.Snapshot(3, box, step=100) - -These constructor arguments are available as attributes: - -- `N`: number of particles (int) -- `box`: bounding box (`Box`) -- `step`: timestep counter (int) -- `num_types`: number of particle types (int). If `num_types is None`, then the number of types is deduced from `typeid`. - -The data contained in a `Snapshot` per particle is: - -- `id`: (*N*,) array atom IDs (dtype: `int`, default: runs from 1 to *N*) -- `position`: (*N*,3) array of coordinates (dtype: `float`, default: `(0,0,0)`) -- `image`: (*N*,3) array of periodic image indexes (dtype: `int`, default: `(0,0,0)`) -- `velocity`: (*N*,3) array of velocities (dtype: `float`, default: `(0,0,0)`) -- `molecule`: (*N*,) array of molecule indexes (dtype: `int`, default: `0`) -- `typeid`: (*N*,) array of type indexes (dtype: `int`, default: `1`) -- `mass`: (*N*,) array of masses (dtype: `float`, default: `1`) -- `charge`: (*N*,) array of charges (dtype: `float`, default: `0`) - -The optional topology data is: - -- `type_label`: Labels of particle typeids. (`LabelMap`, default: `None`) -- `bonds`: Bond data (`Bonds`, default: `None`) -- `angles`: Angle data (`Angles`, default: `None`) -- `dihedrals`: Dihedral data (`Dihedrals`, default: `None`) -- `impropers`: Improper data (`Impropers`, default: `None`) - -All values of indexes will follow the LAMMPS 1-indexed convention, but the -arrays themselves are 0-indexed. - -The `Snapshot` will lazily initialize these per-particle arrays as they are -accessed to save memory. Hence, accessing a per-particle property will allocate -it to default values. If you want to check if an attribute has been set, use the -corresponding `has_` method instead (e.g., `has_position()`): - - snapshot.position = [[0,0,0],[1,-1,1],[1.5,2.5,-3.5]] - snapshot.typeid[2] = 2 - if not snapshot.has_mass(): - snapshot.mass = [2.,2.,10.] - -## Topology - -The topology (bond information) can be stored in `Bonds`, `Angles`, `Dihedrals`, -and `Impropers` objects. All these objects function similarly, differing only in the -number of particles that are included in a connection (2 for a bond, 3 for an angle, -4 for a dihedral or improper). Each connection has an associated `id` and `typeid`. - -```py -bonds = Bonds(N=3, num_types=2) -angles = Angles(N=2, num_types=1) -``` -These constructor arguments are available as attributes: - -- `N`: number of connections (int) -- `num_types`: number of connection types (int). If `num_types is None`, then the number of types is deduced from `typeid`. - -The data contained per connection is: -- `members`: (*N*, *M*) array of particles IDs in each topology (dtype: `int`, default: `1`), -where *M* is the number of particles in a connection. -- `id`: (*N*,) array topology IDs (dtype: `int`, default: runs from 1 to *N*) -- `typeid`: (*N*,) array of type indexes (dtype: `int`, default: `1`) - -A label (type) can be associated with a connection's typeid using a `type_label`. -- `type_label`: Labels of connection typeids. (`LabelMap`, default: `None`) - -All values of indexes will follow the LAMMPS 1-indexed convention, but the -arrays themselves are 0-indexed. Lazy array initialization is used as for the `Snapshot`. - -## Label maps - -A `LabelMap` is effectively a dictionary associating a label (type) with a particle's -or connection's typeid. These labels can be useful for tracking the meaning of -typeids. They are also automatically used when interconverting with -HOOMD GSD files that require such labels. - -The keys of the `LabelMap` are the typeids, and the values are the labels. A -`LabelMap` additionally has the following attributes: - -- `types`: Types in label map. (dtype: `tuple`, default: `()`) -- `typeids`: Typeids in label map. (dtype: `tuple`, default: `()`) - -## Data files - -A LAMMPS data file is represented by a `DataFile`. The file must be explicitly -`read()` to get a `Snapshot`: - - f = lammpsio.DataFile("config.data") - snapshot = f.read() - -The `atom_style` will be read from the comment in the Atoms section -of the file. If it is not present, it must be specified in the `DataFile`. -If `atom_style` is specified and also present in the file, the two must match -or an error will be raised. - -There are many sections that can be stored in a data file, but `lammpsio` does -not currently understand all of them. You can check `DataFile.known_headers`, -`DataFile.unknown_headers`, `DataFile.known_bodies` and `DataFile.unknown_bodies` -for lists of what is currently supported. - -A `Snapshot` can be written using the `create()` method: - - f = lammpsio.DataFile.create("config2.data", snapshot) - -A `DataFile` corresponding to the new file is returned by `create()`. - -## Dump files - -A LAMMPS dump file is represented by a `DumpFile`. The actual file format is -very flexible, but by default embeds a schema that can be read: - - traj = lammpsio.DumpFile(filename="atoms.lammpstrj") - -If the schema does not exist for some reason, it can be manually specified as -a dictionary. Valid keys for the schema match the names and shapes in the -`Snapshot`. The keys requiring only 1 column index are: `id`, `typeid`, -`molecule`, `charge`, and `mass`. The keys requiring 3 column indexes are -`position`, `velocity`, and `image`. - -LAMMPS will dump particles in an unknown order unless you have used the -`dump_modify sort` option. If you want particles to be ordered by `id` in the -`Snapshot`, use `sort_ids=True` (default). - -A `DumpFile` is iterable, so you can use it to go through all the snapshots -of a trajectory: - - for snap in traj: - print(snap.step) - -You can also get the number of snapshots in the `DumpFile`, but this does -require reading the entire file: so use with caution! - - num_frames = len(traj) - -Random access to snapshots is not currently implemented, but it may be added -in future. If you want to randomly access snapshots, you should load the -whole file into a list: - - snaps = [snap for snap in traj] - print(snaps[3].step) - -Keep in the mind that the memory requirements for this can be huge! - -A `DumpFile` can be created from a list of snapshots: - - t = lammpsio.DumpFile.create("atoms.lammpstrj", schema, snaps) - -The object representing the new file is returned and can be used. diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..03a7aae --- /dev/null +++ b/conftest.py @@ -0,0 +1,40 @@ +import numpy + +try: + import sybil + import sybil.parsers.rest + +except ImportError: + sybil = None + +import lammpsio + +try: + import gsd.hoomd + + has_gsd = True +except ModuleNotFoundError: + has_gsd = False + + +def setup_sybil_tests(namespace): + """Sybil setup function.""" + # Common imports. + namespace["numpy"] = numpy + namespace["lammpsio"] = lammpsio + if has_gsd: + namespace["frame"] = gsd.hoomd.Frame() + else: + namespace["frame"] = 0 + + +if sybil is not None: + pytest_collect_file = sybil.Sybil( + parsers=[ + sybil.parsers.rest.PythonCodeBlockParser(), + sybil.parsers.rest.SkipParser(), + ], + pattern="*.py", + setup=setup_sybil_tests, + fixtures=["tmp_path"], + ).pytest() diff --git a/doc/source/_templates/autosummary/class.rst b/doc/source/_templates/autosummary/class.rst new file mode 100644 index 0000000..7b0143d --- /dev/null +++ b/doc/source/_templates/autosummary/class.rst @@ -0,0 +1,29 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + {%- if not item in ['__init__'] %} + ~{{ name }}.{{ item }} + {%- endif -%} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/doc/source/api.rst b/doc/source/api.rst new file mode 100644 index 0000000..ec8cdfc --- /dev/null +++ b/doc/source/api.rst @@ -0,0 +1,15 @@ +API +--- + +Overview +======== + +.. autosummary:: + :nosignatures: + :toctree: generated/ + + lammpsio.Box + lammpsio.DataFile + lammpsio.DumpFile + lammpsio.Snapshot + lammpsio.topology diff --git a/doc/source/api/box.rst b/doc/source/api/box.rst deleted file mode 100644 index e4b0c3e..0000000 --- a/doc/source/api/box.rst +++ /dev/null @@ -1,6 +0,0 @@ -box ---- - -.. automodule:: lammpsio.box - :members: - :show-inheritance: diff --git a/doc/source/api/data.rst b/doc/source/api/data.rst deleted file mode 100644 index 97b526c..0000000 --- a/doc/source/api/data.rst +++ /dev/null @@ -1,6 +0,0 @@ -data ----- - -.. automodule:: lammpsio.data - :members: - :show-inheritance: diff --git a/doc/source/api/dump.rst b/doc/source/api/dump.rst deleted file mode 100644 index dc0874f..0000000 --- a/doc/source/api/dump.rst +++ /dev/null @@ -1,6 +0,0 @@ -dump ----- - -.. automodule:: lammpsio.dump - :members: - :show-inheritance: diff --git a/doc/source/api/snapshot.rst b/doc/source/api/snapshot.rst deleted file mode 100644 index 09dcd73..0000000 --- a/doc/source/api/snapshot.rst +++ /dev/null @@ -1,6 +0,0 @@ -snapshot --------- - -.. automodule:: lammpsio.snapshot - :members: - :show-inheritance: diff --git a/doc/source/api/topology.rst b/doc/source/api/topology.rst deleted file mode 100644 index 7e427db..0000000 --- a/doc/source/api/topology.rst +++ /dev/null @@ -1,6 +0,0 @@ -topology --------- - -.. automodule:: lammpsio.topology - :members: - :show-inheritance: diff --git a/doc/source/conf.py b/doc/source/conf.py index 49d2741..55fb60f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -13,7 +13,7 @@ # -- Project information ----------------------------------------------------- -project = "lammmpsio" +project = "lammpsio" year = datetime.date.today().year copyright = f"2021-{year}, Auburn University" author = "Michael P. Howard" @@ -65,6 +65,8 @@ autosummary_generate = True +autodoc_member_order = "bysource" + autodoc_default_options = {"inherited-members": None, "special-members": False} # -- Options for intersphinx ------------------------------------------------- diff --git a/doc/source/guide/credits.rst b/doc/source/credits.rst similarity index 82% rename from doc/source/guide/credits.rst rename to doc/source/credits.rst index 166d5bb..01ec5b0 100644 --- a/doc/source/guide/credits.rst +++ b/doc/source/credits.rst @@ -5,4 +5,4 @@ Credits * Michael P. Howard * Mayukh Kundu * Philipp Leclercq -* C. Levi Petix +* C\. Levi Petix diff --git a/doc/source/guide/examples/index.rst b/doc/source/guide/examples/index.rst deleted file mode 100644 index b90c0e1..0000000 --- a/doc/source/guide/examples/index.rst +++ /dev/null @@ -1,3 +0,0 @@ -======== -Examples -======== diff --git a/doc/source/guide/release.md b/doc/source/guide/release.md deleted file mode 100644 index 14e66a7..0000000 --- a/doc/source/guide/release.md +++ /dev/null @@ -1,2 +0,0 @@ -```{include} ../../../CHANGELOG.md -``` diff --git a/doc/source/guidelines.rst b/doc/source/guidelines.rst new file mode 100644 index 0000000..12f7e34 --- /dev/null +++ b/doc/source/guidelines.rst @@ -0,0 +1,27 @@ +==================== +Community Guidelines +==================== + +We ask that you please review and adhere to our `Code of Conduct`_. + + +Reporting Issues +================ +If you encounter any bugs or issues while using ``lammpsio``, please report them +on our `GitHub Issues page`_. + +Contributing +============= +We welcome contributions to ``lammpsio`` via GitHub pull requests. We ask that +you please open an issue first to discuss your proposed changes before +submitting a pull request. This helps us to understand the context of your +changes and to ensure that they mesh well with the existing codebase. + +Seeking Help +============ +If you have questions or need help using ``lammpsio``, please feel free to reach +out to us via our `GitHub Discussions page`_. + +.. _Code of Conduct: http://github.com/mphowardlab/lammpsio/blob/main/CODE_OF_CONDUCT.md +.. _GitHub Issues page: http://github.com/mphowardlab/lammpsio/issues +.. _GitHub Discussions page: http://github.com/mphowardlab/lammpsio/discussions diff --git a/doc/source/index.rst b/doc/source/index.rst index db5c178..a5e7514 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,25 +1,30 @@ lammpsio documentation ====================== -Tools for working with LAMMPS data and dump files. +lammpsio provides a Python interface for reading and writing LAMMPS +data and dump files. It also enables interconversion with HOOMD-blue's +GSD format. This flexible package enables users to work with LAMMPS files +in a variety of ways that improve workflow efficiency and reproducibility. .. toctree:: :maxdepth: 1 - :caption: User guide + :caption: Getting started - guide/install - guide/examples/index - guide/release - guide/license - guide/credits + ./install + ./release + ./guidelines .. toctree:: :maxdepth: 1 - :caption: API reference + :caption: Reference - api/box - api/data - api/dump - api/snapshot - api/topology + ./api + ./tutorials + +.. toctree:: + :maxdepth: 1 + :caption: Additional information + + ./credits + ./license diff --git a/doc/source/guide/install.rst b/doc/source/install.rst similarity index 86% rename from doc/source/guide/install.rst rename to doc/source/install.rst index e290b82..3f1e776 100644 --- a/doc/source/guide/install.rst +++ b/doc/source/install.rst @@ -8,6 +8,10 @@ The easiest way to get ``lammpsio`` is from PyPI using ``pip``: pip install lammpsio +or from conda-forge using ``conda`` or ``mamba``: +.. code:: bash + + conda install -c conda-forge lammpsio Building from source ==================== diff --git a/doc/source/guide/license.rst b/doc/source/license.rst similarity index 54% rename from doc/source/guide/license.rst rename to doc/source/license.rst index f6a6f5a..ffd9466 100644 --- a/doc/source/guide/license.rst +++ b/doc/source/license.rst @@ -2,5 +2,5 @@ License ======= -.. literalinclude:: ../../../LICENSE +.. literalinclude:: ../../LICENSE :language: none diff --git a/doc/source/release.md b/doc/source/release.md new file mode 100644 index 0000000..3139cd4 --- /dev/null +++ b/doc/source/release.md @@ -0,0 +1,2 @@ +```{include} ../../CHANGELOG.md +``` diff --git a/doc/source/tutorials.rst b/doc/source/tutorials.rst new file mode 100644 index 0000000..75decd3 --- /dev/null +++ b/doc/source/tutorials.rst @@ -0,0 +1,11 @@ +Tutorials +--------- + +Overview +======== + + +.. toctree:: + :maxdepth: 1 + + ./tutorials/dimer_lattice_tutorial diff --git a/doc/source/tutorials/dimer_lattice_tutorial.ipynb b/doc/source/tutorials/dimer_lattice_tutorial.ipynb new file mode 100644 index 0000000..b93eca6 --- /dev/null +++ b/doc/source/tutorials/dimer_lattice_tutorial.ipynb @@ -0,0 +1,406 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "56c2719c", + "metadata": {}, + "source": [ + "# Packing a dimer on a cubic lattice with `lammpsio`\n", + "In this tutorial, we’ll walk through filling a box with dimers using `lammpsio`. We'll create a simple cubic lattice where each unit cell contains a bonded dimer made up of two particle of type A and B. At the end, we will create a data file ready to be used by `LAMMPS`. \n", + "\n", + "First, we import `numpy` to make setting up the simple cubic lattice and `lammpsio` to handle creating the data file." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "2ab58341", + "metadata": {}, + "outputs": [], + "source": [ + "import lammpsio\n", + "\n", + "import numpy" + ] + }, + { + "cell_type": "markdown", + "id": "3649af64", + "metadata": {}, + "source": [ + "## Creating the simple cubic lattice" + ] + }, + { + "cell_type": "markdown", + "id": "82d4bea0", + "metadata": {}, + "source": [ + "We define the core parameters of our system, including particle diameters, offset between bonded particles, and the number of repetitions of the unit cell. We choose the particles to have a unit diameter $d$ and a bond spacing of 1.5 $d$. We chose a lattice spacing of `2 * diameter + bond_length` so that each dimer are at the equilibrium bond distance. We also choose to place 1000 dimers total (10 in each direction). " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "cd4c3e84", + "metadata": {}, + "outputs": [], + "source": [ + "diameter = 1.0\n", + "bond_length = 1.5\n", + "lattice_spacing = 2 * diameter + bond_length\n", + "num_repeat = [10, 10, 10]" + ] + }, + { + "cell_type": "markdown", + "id": "3b71bc67", + "metadata": {}, + "source": [ + "### Define the unit cell" + ] + }, + { + "cell_type": "markdown", + "id": "a93991ec", + "metadata": {}, + "source": [ + "We'll make our lattice with a simple cubic unit cell. Each unit cell contains two particles: type A at the origin, and type B shifted along the x-axis to give the proper spacing." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "3ddbcaf9", + "metadata": {}, + "outputs": [], + "source": [ + "unit_cell = numpy.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) * lattice_spacing\n", + "unit_cell_coords = numpy.array([\n", + " [0, 0, 0],\n", + " [bond_length, 0, 0]\n", + "])\n", + "unit_cell_typeids = [1, 2]\n", + "unit_cell_mass = [1.0, 1.5]" + ] + }, + { + "cell_type": "markdown", + "id": "8ca906a5", + "metadata": {}, + "source": [ + "### Define `lammpsio.Box`" + ] + }, + { + "cell_type": "markdown", + "id": "f71a29f8", + "metadata": {}, + "source": [ + "\n", + "\n", + "`lammpsio.Box` defines the simulation box using LAMMPS's convention. In LAMMPS, the simulation box is specified by three parameters: `low`, `high`, and `tilt`. The `low` parameter defines the origin (lower corner) of the box, while `high` specifies how far the box extends along each axis. The `tilt` parameter contains tilt factors (xy, xz, yz) that skew the edges to create non-orthorhombic simulation boxes.\n", + "\n", + "These parameters can be transformed into a box matrix with the following form:\n", + "\n", + "$$\n", + "\\begin{bmatrix}\n", + " a_x & b_x & c_x \\\\\n", + " 0 & b_y & c_y \\\\\n", + " 0 & 0 & c_z\n", + "\\end{bmatrix},\n", + "$$\n", + "\n", + "where each column of this matrix represents one of the three edge vectors (**A**, **B**, & **C**) that define the triclinic simulation box, and each of the matrix elements are derived from the `low`, `high`, and `tilt` values. The diagonal elements ($a_x$, $b_y$, $c_z$) represent the box lengths along each axis, while the off-diagonal elements come from the tilt factors. For more details on how to convert between the LAMMPS parameters and box matrix see the [LAMMPS documentation](https://docs.lammps.org/Howto_triclinic.html#transformation-from-general-to-restricted-triclinic-boxes).\n", + "\n", + "For our orthorhombic unit cell, all tilt factors are zero, so `tilt` has the default value of `None`, resulting in a simple rectangular box. We choose `low` to be at `[0, 0, 0]` and `high` is the diagonal values of the box. " + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "b79ec025", + "metadata": {}, + "outputs": [], + "source": [ + "box_matrix = (unit_cell * num_repeat).T\n", + "low = [0, 0, 0]\n", + "high = [box_matrix[0, 0], box_matrix[1, 1], box_matrix[2, 2]]\n", + "box = lammpsio.Box(low=low, high=high, tilt=None)" + ] + }, + { + "cell_type": "markdown", + "id": "16792f2c", + "metadata": {}, + "source": [ + "### Generate particle type IDs and positions" + ] + }, + { + "cell_type": "markdown", + "id": "57a0d852", + "metadata": {}, + "source": [ + "We calculate the total number of particles by multiplying the number of unit cells by the number of particles per cell." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "ef9c74b1", + "metadata": {}, + "outputs": [], + "source": [ + "num_cells = num_repeat[0] * num_repeat[1] * num_repeat[2]\n", + "N = num_cells * len(unit_cell_coords)" + ] + }, + { + "cell_type": "markdown", + "id": "f1371cbc", + "metadata": {}, + "source": [ + "## Create a `lammpsio.Snapshot`" + ] + }, + { + "cell_type": "markdown", + "id": "750e2e0d", + "metadata": {}, + "source": [ + "The `Snapshot` holds the data about the particle's configuration and properties, the `Box`, and the timestep. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "a9358a71", + "metadata": {}, + "outputs": [], + "source": [ + "snap = lammpsio.Snapshot(N=N, box=box)" + ] + }, + { + "cell_type": "markdown", + "id": "48ea8e9b", + "metadata": {}, + "source": [ + "Particle attributes are assigned directly using the Snapshot's attributes. We generate positions for all particles by iterating through each unit cell position in our 3D lattice grid. For each unit cell, we calculate the absolute coordinates by scaling the unit cell position by the lattice spacing and adding the unit cell's internal particle coordinates. The particles are then positioned within the simulation box starting from `snap.box.low`.\n", + "\n", + "Note that lammpsio automatically allocates an array with the right data type and shape for attributes, so we assign positions directly to snap.position rather than using an intermediate array!" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "1dbd74b0", + "metadata": {}, + "outputs": [], + "source": [ + "for i, pos in enumerate(numpy.ndindex(*num_repeat)):\n", + " first = i * len(unit_cell_coords)\n", + " last = first + len(unit_cell_coords)\n", + " absolute_coords = numpy.array(pos) * lattice_spacing + unit_cell_coords\n", + " snap.position[first:last] = absolute_coords + snap.box.low" + ] + }, + { + "cell_type": "markdown", + "id": "5d91b59f", + "metadata": {}, + "source": [ + "We then create an array of type IDs by replicating our unit cell pattern (typeID 1 and 2) across all cells. We do the same thing to give the particles their mass. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "cecf14cb", + "metadata": {}, + "outputs": [], + "source": [ + "snap.typeid = numpy.tile(unit_cell_typeids, num_cells)\n", + "snap.mass = numpy.tile(unit_cell_mass, num_cells)" + ] + }, + { + "cell_type": "markdown", + "id": "46ac8215", + "metadata": {}, + "source": [ + "To specify bonds, we assign the `Snapshot` a `lammpsio.Bonds` object. `lammpsio.Bonds` holds information about the bond typeIDs and which particles are members of the bond.\n", + "\n", + "We know that each cell contains one dimer and thus one bond. Since all of our bonds in this system are the same, we assign them all typeID 1. " + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "3ce97e21", + "metadata": {}, + "outputs": [], + "source": [ + "N_bonds = num_cells\n", + "\n", + "snap.bonds = lammpsio.Bonds(N=N_bonds, num_types=1)\n", + "snap.bonds.typeid = numpy.ones(N_bonds)" + ] + }, + { + "cell_type": "markdown", + "id": "dbe9bda1", + "metadata": {}, + "source": [ + "Each dimer consists of consecutive particle IDs (1-2, 3-4, etc.). We create bonds by connecting these consecutive pairs, resulting in 1000 bonds for our 1000 dimers." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "ae6ecebf", + "metadata": {}, + "outputs": [], + "source": [ + "snap.bonds.members = [[2 * i + 1, 2 * i + 2] for i in range(N_bonds)]" + ] + }, + { + "cell_type": "markdown", + "id": "1112f225", + "metadata": {}, + "source": [ + "### Save to LAMMPS" + ] + }, + { + "cell_type": "markdown", + "id": "09b878bd", + "metadata": {}, + "source": [ + "Finally, we save the configuration to a data file ready to be used in `LAMMPS`. The data file is written in the molecular style since we have bonds. " + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "ada7ffb6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "lammpsio.DataFile.create(filename=\"dimer_lattice.data\", snapshot=snap, atom_style=\"molecular\")" + ] + }, + { + "cell_type": "markdown", + "id": "567bb70a", + "metadata": {}, + "source": [ + "### Save to HOOMD-blue's GSD format" + ] + }, + { + "cell_type": "markdown", + "id": "5fdc51d0", + "metadata": {}, + "source": [ + "HOOMD-blue is molecular dynamics engine commonly used to study soft matter. There may come a time when you want to use HOOMD-blue or share your LAMMPS data file with someone who is more familar with it. Converting the data file to HOOMD-blue's GSD format manually can be tedious, but luckily `lammpsio` allows for quick and seamless conversion between the formats! \n", + "\n", + "Since HOOMD-blue requires alphanumeric types along with TypeIDs we have to add those to our snapshot. The `lammpsio.LabelMap` is effectively a dictionary associating a label (type) with a particle's or connection's typeid. Here we're going to assign the `Snapshot` a type_label mapping particle typeID `1 -> A` and `2 -> B` and bond typeid `1 -> dimer`. `lammpsio` will use the `LabelMap` to set the alphanumeric types in the GSD file. \n", + "\n", + "Note: LAMMPS now also supports alphanumeric type labeling, though `lammpsio` does not currently support for this feature, it is planned as a future addition. " + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "890f5b1f", + "metadata": {}, + "outputs": [], + "source": [ + "snap.type_label = lammpsio.LabelMap({1: \"A\", 2: \"B\"})\n", + "snap.bonds.type_label = lammpsio.LabelMap({1: \"dimer\"})" + ] + }, + { + "cell_type": "markdown", + "id": "64fbc10a", + "metadata": {}, + "source": [ + "Since HOOMD-blue requires the simulation box to be centered about the origin, we need to shift our box coordinates. Currently, our box extends from `[0, 0, 0]` to `[35, 35, 35]`, but HOOMD-blue expects it to be centered at zero, ranging from `[-17.5, -17.5, -17.5]` to `[17.5, 17.5, 17.5]`. We accomplish this by calculating the extent of the box (`high - low`) and then shifting both the high and low boundaries by subtracting half the extent. We then do the same thing to `snap.position`. " + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "caf007f9", + "metadata": {}, + "outputs": [], + "source": [ + "extent = snap.box.high - snap.box.low\n", + "\n", + "snap.box.high -= extent / 2\n", + "snap.box.low -= extent / 2\n", + "\n", + "snap.position -= extent / 2" + ] + }, + { + "cell_type": "markdown", + "id": "01b31b86", + "metadata": {}, + "source": [ + "Using `Snapshot.to_hoomd_gsd()` returns a HOOMD-blue GSD frame object. We can write this out to file and have the same particle and bond data ready to be used in a different simulation engine! You can also use `Snapshot.from_hoomd_gsd` to convert a HOOMD-blue GSD frame into a `Snapshot`! " + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "284768f5", + "metadata": {}, + "outputs": [], + "source": [ + "import gsd.hoomd\n", + "snap_gsd = snap.to_hoomd_gsd()\n", + "\n", + "with gsd.hoomd.open(\"dimer_lattice.gsd\", \"w\") as f:\n", + " f.append(snap_gsd)\n", + " f.flush()\n", + " f.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lammpsio-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/lammpsio/box.py b/src/lammpsio/box.py index 1e4e09a..27d9a40 100644 --- a/src/lammpsio/box.py +++ b/src/lammpsio/box.py @@ -21,6 +21,15 @@ class Box: Tilt factors ``xy``, ``xz``, and ``yz`` for a triclinic box. Default of ``None`` is a strictly orthorhombic box. + Examples + -------- + + Construct a triclinic simulation box: + + .. code-block:: python + + box = lammpsio.Box([-5.0, -10.0, 0.0], [1.0, 10.0, 8.0], [1.0, -2.0, 0.5]) + """ def __init__(self, low, high, tilt=None): @@ -30,7 +39,7 @@ def __init__(self, low, high, tilt=None): @classmethod def cast(cls, value): - """Cast an array to a :class:`Box`. + """Cast an array to a `Box`. If ``value`` has 6 elements, it is unpacked as an orthorhombic box:: @@ -47,9 +56,17 @@ def cast(cls, value): Returns ------- - :class:`Box` + `Box` A simulation box matching the array. + Examples + -------- + Construct an orthorhombic simulation box by casting an array: + + .. code-block:: python + + box = lammpsio.Box.cast([-5.0, -10.0, 0.0, 1.0, 10.0, 8.0]) + """ if isinstance(value, Box): return value @@ -65,7 +82,10 @@ def cast(cls, value): @property def low(self): - """:class:`numpy.ndarray`: Box low.""" + """:class:`numpy.ndarray`: Box low. + + The low of the box is used as the origin of the box. + """ return self._low @low.setter @@ -77,7 +97,10 @@ def low(self, value): @property def high(self): - """:class:`numpy.ndarray`: Box high.""" + """:class:`numpy.ndarray`: Box high. + + The high of the box is used to compute the edge lengths of the box. + """ return self._high @high.setter @@ -89,7 +112,12 @@ def high(self, value): @property def tilt(self): - """:class:`numpy.ndarray`: Box tilt factors.""" + """:class:`numpy.ndarray`: Box tilt factors. + + The tilt factors, ``xy``, ``xz``, and ``yz`` are used to define the + shape of the box. The default of ``None`` is a strictly orthorhombic. + + """ return self._tilt @tilt.setter diff --git a/src/lammpsio/data.py b/src/lammpsio/data.py index c411f13..6a792e1 100644 --- a/src/lammpsio/data.py +++ b/src/lammpsio/data.py @@ -16,6 +16,34 @@ def _readline(file_, require=False): class DataFile: """LAMMPS data file. + A LAMMPS data file is represented by a `DataFile`. + It can be created by the following syntax: + + .. code-block:: python + + box = lammpsio.Box([-5.0, -10.0, 0.0], [1.0, 10.0, 8.0], [1.0, -2.0, 0.5]) + + snap = lammpsio.Snapshot(3, box, 10) + + data = lammpsio.DataFile.create(tmp_path / "atoms.data", + snap, atom_style="atomic") + + The file must be explicitly `read()` to get a `Snapshot`: + + .. code-block:: python + + snap = data.read() + + The `atom_style` will be read from the comment in the Atoms section + of the file. If it is not present, it must be specified in the `DataFile`. + If `atom_style` is specified and also present in the file, the two must match + or an error will be raised. + + There are many sections that can be stored in a data file, but ``lammpsio`` does + not currently understand all of them. You can check `DataFile.known_headers`, + `DataFile.unknown_headers`, `DataFile.known_bodies` and `DataFile.unknown_bodies` + for lists of what is currently supported. + Parameters ---------- filename : str @@ -91,7 +119,7 @@ def create(cls, filename, snapshot, atom_style=None): ---------- filename : str Path to data file. - snapshot : :class:`Snapshot` + snapshot : `Snapshot` Snapshot to write to file. atom_style : str Atom style to use for data file. Defaults to ``None``, which means the @@ -99,7 +127,7 @@ def create(cls, filename, snapshot, atom_style=None): Returns ------- - :class:`DataFile` + `DataFile` The object representing the new data file. Raises @@ -300,7 +328,7 @@ def read(self): Returns ------- - :class:`Snapshot` + `Snapshot` Snapshot from the data file. Raises diff --git a/src/lammpsio/dump.py b/src/lammpsio/dump.py index 9800e90..8c9646d 100644 --- a/src/lammpsio/dump.py +++ b/src/lammpsio/dump.py @@ -35,12 +35,21 @@ class DumpFile: Schema for the contents of the file. Defaults to ``None``, which means to read it from the file. sort_ids : bool - If true, sort the particles by ID in each snapshot. - copy_from : :class:`Snapshot` + If ``True``, sort the particles by ID in each snapshot. + copy_from : `Snapshot` If specified, copy fields that are missing in the dump file but are set in - a reference :class:`Snapshot`. The fields that can be copied are ``typeid``, + a reference `Snapshot`. The fields that can be copied are ``typeid``, ``molecule``, ``charge``, and ``mass``. + Example + ------- + + Read a dump file: + + .. code-block:: python + + f = lammpsio.DumpFile(tmp_path / "atoms.lammpstrj", schema=None) + """ def __init__(self, filename, schema=None, sort_ids=True, copy_from=None): @@ -60,14 +69,29 @@ def create(cls, filename, schema, snapshots): Path to dump file. schema : dict Schema for the contents of the file. - snapshots : :class:`Snapshot` or list + snapshots : `Snapshot` or list One or more snapshots to write to the dump file. Returns ------- - :class:`DumpFile` + `DumpFile` The object representing the new dump file. + Example + ------- + + Create a dump file: + + .. code-block:: python + + box = lammpsio.Box([-5.0, -10.0, 0.0], [1.0, 10.0, 8.0], [1.0, -2.0, 0.5]) + + snap = lammpsio.Snapshot(3, box, 10) + + schema = {"id": 0, "position": (1, 2, 3)} + + f = lammpsio.DumpFile.create(tmp_path /"atoms.lammpstrj", schema, snap) + """ # map out the schema into a dump row # each entry is a tuple: (column, (attribute, index)) diff --git a/src/lammpsio/snapshot.py b/src/lammpsio/snapshot.py index adfe773..3577fa0 100644 --- a/src/lammpsio/snapshot.py +++ b/src/lammpsio/snapshot.py @@ -10,20 +10,34 @@ class Snapshot: """Particle configuration. + The particle configuration is stored in a `Snapshot`. A `Snapshot` holds the + data for *N* particles, the simulation `Box`, and the timestep. The `Box` follows + the LAMMPS conventions for its shape and bounds. + Parameters ---------- N : int Number of particles in configuration. - box : :class:`Box` + box : `Box` Simulation box. step : int Simulation time step counter. Default of ``None`` means time step is not specified. + num_types : int + Number of particle types. If ``num_types is None``, + then the number of types is deduced from `typeid`. - Attributes - ---------- - step : int - Simulation time step counter. + Example + ------- + + Here is a 3-particle configuration in an triclinic box + centered at the origin at step 10: + + .. code-block:: python + + box = lammpsio.Box([-5.0, -10.0, 0.0], [1.0, 10.0, 8.0], [1.0, -2.0, 0.5]) + + snap = lammpsio.Snapshot(3, box, 10, num_types=None) """ @@ -58,11 +72,26 @@ def from_hoomd_gsd(cls, frame): Returns ------- - :class:`Snapshot` + `Snapshot` Snapshot created from HOOMD GSD frame. dict A map from the :attr:`Snapshot.typeid` to the HOOMD type. + Example + ------- + + Create snapshot from a GSD file: + + .. skip: next if(frame == 0, reason="gsd.hoomd not installed") + + .. code-block:: python + + frame.configuration.box = [4, 5, 6, 0.1, 0.2, 0.3] + + frame.particles.N = 3 + + snap_2, type_map = lammpsio.Snapshot.from_hoomd_gsd(frame) + """ # ensures frame is well formed and that we have NumPy arrays frame.validate() @@ -216,6 +245,17 @@ def to_hoomd_gsd(self, type_map=None): :class:`gsd.hoomd.Frame` Converted HOOMD GSD frame. + Example + ------- + + Convert snapshot to a GSD file: + + .. skip: next if(frame == 0, reason="gsd.hoomd not installed") + + .. code-block:: python + + frame = snap_2.to_hoomd_gsd() + """ if _compatibility.gsd_version is None: raise ImportError("GSD package not found") @@ -338,17 +378,42 @@ def to_hoomd_gsd(self, type_map=None): @property def N(self): - """int: Number of particles.""" + """int: Number of particles. + + Example + ------- + .. code-block:: python + + num_particles = snap.N + + """ return self._N @property def box(self): - """:class:`Box`: Simulation box.""" + """`Box`: Simulation box. + + Example + ------- + .. code-block:: python + + snap.box + + """ return self._box @property def id(self): - """:class:`numpy.ndarray`: Particle IDs.""" + """:class:`numpy.ndarray`: Particle IDs. + + Example + ------- + + .. code-block:: python + + snap.id = [2, 0, 1] + + """ if not self.has_id(): self._id = numpy.arange(1, self.N + 1) return self._id @@ -373,14 +438,29 @@ def has_id(self): Returns ------- bool - True if particle IDs have been initialized. + ``True`` if particle IDs have been initialized. + + Example + ------- + .. code-block:: python + + snap.has_id() """ return self._id is not None @property def position(self): - """:class:`numpy.ndarray`: Positions.""" + """:class:`numpy.ndarray`: Positions. + + Example + ------- + + .. code-block:: python + + snap.position = [[0.1, 0.2, 0.3], [-0.4, -0.5, -0.6], [0.7, 0.8, 0.9]] + + """ if not self.has_position(): self._position = numpy.zeros((self.N, 3), dtype=float) return self._position @@ -405,14 +485,30 @@ def has_position(self): Returns ------- bool - True if positions have been initialized. + ``True`` if positions have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_position() """ return self._position is not None @property def image(self): - """:class:`numpy.ndarray`: Images.""" + """:class:`numpy.ndarray`: Images. + + Example + ------- + + .. code-block:: python + + snap.image = [[1, 2, 3], [-4, -5, -6], [7, 8, 9]] + + """ if not self.has_image(): self._image = numpy.zeros((self.N, 3), dtype=int) return self._image @@ -437,14 +533,30 @@ def has_image(self): Returns ------- bool - True if images have been initialized. + ``True`` if images have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_image() """ return self._image is not None @property def velocity(self): - """:class:`numpy.ndarray`: Velocities.""" + """:class:`numpy.ndarray`: Velocities. + + Example + ------- + + .. code-block:: python + + snap.velocity = [[-3, -2, -1], [6, 5, 4], [9, 8, 7]] + + """ if not self.has_velocity(): self._velocity = numpy.zeros((self.N, 3), dtype=float) return self._velocity @@ -469,7 +581,14 @@ def has_velocity(self): Returns ------- bool - True if velocities have been initialized. + ``True`` if velocities have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_velocity() """ return self._velocity is not None @@ -501,7 +620,14 @@ def has_molecule(self): Returns ------- bool - True if molecule tags have been initialized. + ``True`` if molecule tags have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_molecule() """ return self._molecule is not None @@ -526,7 +652,16 @@ def num_types(self, value): @property def typeid(self): - """:class:`numpy.ndarray`: Types.""" + """:class:`numpy.ndarray`: Types. + + Example + ------- + + .. code-block:: python + + snap.typeid = [2, 1, 2] + + """ if not self.has_typeid(): self._typeid = numpy.ones(self.N, dtype=int) return self._typeid @@ -551,14 +686,30 @@ def has_typeid(self): Returns ------- bool - True if types have been initialized. + ``True`` if types have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_typeid() """ return self._typeid is not None @property def charge(self): - """:class:`numpy.ndarray`: Charges.""" + """:class:`numpy.ndarray`: Charges. + + Example + ------- + + .. code-block:: python + + snap.charge = [-1, 0, 1] + + """ if not self.has_charge(): self._charge = numpy.zeros(self.N, dtype=float) return self._charge @@ -583,14 +734,30 @@ def has_charge(self): Returns ------- bool - True if charges have been initialized. + ``True`` if charges have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_charge() """ return self._charge is not None @property def mass(self): - """:class:`numpy.ndarray`: Masses.""" + """:class:`numpy.ndarray`: Masses. + + Example + ------- + + .. code-block:: python + + snap.mass = [3, 2, 3] + + """ if not self.has_mass(): self._mass = numpy.ones(self.N, dtype=float) return self._mass @@ -615,14 +782,33 @@ def has_mass(self): Returns ------- bool - True if masses have been initialized. + ``True`` if masses have been initialized. + + Example + ------- + + .. code-block:: python + + snap.has_mass() """ return self._mass is not None @property def type_label(self): - """LabelMap: Labels for particle typeids.""" + """LabelMap: Labels for particle typeids. + + Example + ------- + + .. code-block ::python + + label_map = lammpsio.topology.LabelMap({1: "typeA", 2: "typeB"}) + + snap.type_label = LabelMap(map=label_map) + + + """ return self._type_label @type_label.setter @@ -636,7 +822,16 @@ def type_label(self, value): @property def bonds(self): - """Bonds: Bond data.""" + """Bonds: Bond data. + + Example + ------- + + .. code-block:: python + + snap.bonds = lammpsio.topology.Bonds(N=6, num_types=2) + + """ return self._bonds @bonds.setter @@ -654,14 +849,32 @@ def has_bonds(self): Returns ------- bool - True if bonds is initialized and there is at least one bond. + ``True`` if bonds is initialized and there is at least one bond. + + + Example + ------- + + .. code-block:: python + + snap.has_bonds() """ return self._bonds is not None and self._bonds.N > 0 @property def angles(self): - """Angles: Angle data.""" + """Angles: Angle data. + + + Example + ------- + + .. code-block:: python + + snap.angles = lammpsio.topology.Angles(N=4, num_types=2) + + """ return self._angles @angles.setter @@ -679,14 +892,30 @@ def has_angles(self): Returns ------- bool - True if angles is initialized and there is at least one angle. + ``True`` if angles is initialized and there is at least one angle. + + Example + ------- + + .. code-block:: python + + snap.has_angles() """ return self._angles is not None and self._angles.N > 0 @property def dihedrals(self): - """Dihedrals: Dihedral data.""" + """Dihedrals: Dihedral data. + + Example + ------- + + .. code-block:: python + + snap.dihedrals = lammpsio.topology.Dihedrals(N=2, num_types=2) + + """ return self._dihedrals @dihedrals.setter @@ -704,14 +933,30 @@ def has_dihedrals(self): Returns ------- bool - True if dihedrals is initialized and there is at least one dihedral. + ``True`` if dihedrals is initialized and there is at least one dihedral. + + Example + ------- + + .. code-block:: python + + snap.has_dihedrals() """ return self._dihedrals is not None and self._dihedrals.N > 0 @property def impropers(self): - """Impropers: Improper data.""" + """Impropers: Improper data. + + Example + ------- + + .. code-block:: python + + snap.impropers = lammpsio.topology.Impropers(N=2, num_types=2) + + """ return self._impropers @impropers.setter @@ -729,7 +974,14 @@ def has_impropers(self): Returns ------- bool - True if impropers is initialized and there is at least one improper. + ``True`` if impropers is initialized and there is at least one improper. + + Example + ------- + + .. code-block:: python + + snap.has_impropers() """ return self._impropers is not None and self._impropers.N > 0 @@ -742,7 +994,16 @@ def reorder(self, order, check_order=True): order : list New order of indexes. check_order : bool - If true, validate the new ``order`` before applying it. + If ``True``, validate the new ``order`` before applying it. + + Example + ------- + + .. code-block:: python + + bond_id = [0, 3, 1, 4, 2, 5] + + snap.bonds.reorder(numpy.sort(bond_id), check_order=True) """ # sanity check the sorting order before applying it @@ -782,12 +1043,12 @@ def _set_type_id(lammps_typeid, gsd_typeid, label_map): List of LAMMPS typeids to be mapped (one-indexed). gsd_typeid : list List of HOOMD GSD typeids to be updated (zero-indexed). - label_map : :class:`LabelMap` + label_map : `LabelMap` LabelMap for connection type mapping LAMMPS typeids to HOOMD GSD types. Returns: ------- - :class:`LabelMap` + `LabelMap` LabelMap mapping LAMMPS typeids to HOOMD GSD types. LabelMap is created mapping typeids to str(typeids) if not provided. """ diff --git a/src/lammpsio/topology.py b/src/lammpsio/topology.py index e237f61..bb328a3 100644 --- a/src/lammpsio/topology.py +++ b/src/lammpsio/topology.py @@ -1,3 +1,12 @@ +"""Topology module. + +The topology (bond information) can be stored in `Bonds`, `Angles`, `Dihedrals`, +and `Impropers` objects. All these objects function similarly, differing only in the +number of particles that are included in a connection (2 for a bond, 3 for an angle, +4 for a dihedral or improper). Each connection has an associated ``id`` and ``typeid``. + +""" + import collections.abc import numpy @@ -62,7 +71,7 @@ def has_id(self): Returns ------- bool - True if connection IDs have been initialized. + ``True`` if connection IDs have been initialized. """ return self._id is not None @@ -94,7 +103,7 @@ def has_typeid(self): Returns ------- bool - True if connection typeids have been initialized. + ``True`` if connection typeids have been initialized. """ return self._typeid is not None @@ -126,7 +135,7 @@ def has_members(self): Returns ------- bool - True if particle members have been initialized. + ``True`` if particle members have been initialized. """ return self._members is not None @@ -172,7 +181,7 @@ def reorder(self, order, check_order=True): order : list New order of indexes. check_order : bool - If true, validate the new ``order`` before applying it. + If ``True``, validate the new ``order`` before applying it. """ # sanity check the sorting order before applying it @@ -201,7 +210,16 @@ class Bonds(Topology): N : int Number of bonds. num_types : int - Number of bond types. + Number of bond types. Default of ``None`` means + the number of types is determined from the unique typeids. + + Example + ------- + Create bonds: + + .. code-block:: python + + bonds = lammpsio.topology.Bonds(N=2, num_types=2) """ @@ -217,7 +235,16 @@ class Angles(Topology): N : int Number of angles. num_types : int - Number of angle types. + Number of Angle types. Default of ``None`` means + the number of types is determined from the unique typeids. + + Example + ------- + Create angles: + + .. code-block:: python + + angles = lammpsio.topology.Angles(N=2, num_types=2) """ @@ -231,9 +258,18 @@ class Dihedrals(Topology): Parameters ---------- N : int - Number of diehdrals. + Number of dihedrals. num_types : int - Number of dihedral types. + Number of Dihedral types. Default of ``None`` means + the number of types is determined from the unique typeids. + + Example + ------- + Create dihedrals: + + .. code-block:: python + + dihedrals = lammpsio.topology.Dihedrals(N=2, num_types=2) """ @@ -249,7 +285,17 @@ class Impropers(Topology): N : int Number of improper dihedrals. num_types : int - Number of improper dihedral types. + Number of improper dihedral types. Default of ``None`` means + the number of types is determined from the unique typeids. + + + Example + ------- + Create dihedrals: + + .. code-block:: python + + impropers = lammpsio.topology.Impropers(N=2, num_types=2) """ @@ -260,11 +306,24 @@ def __init__(self, N, num_types=None): class LabelMap(collections.abc.MutableMapping): """Label map between typeids and types. + A `LabelMap` is effectively a dictionary associating a label (type) with a + particle's or connection's typeid. These labels can be useful for tracking + the meaning of typeids. They are also automatically used when interconverting + with HOOMD GSD files that require such labels. + Parameters ---------- map : dict Map of typeids to types. + Example + ------- + Create `LabelMap`: + + .. code-block:: python + + label = lammpsio.topology.LabelMap({1: "typeA", 2: "typeB"}) + """ def __init__(self, map=None): diff --git a/tests/requirements.txt b/tests/requirements.txt index 69a6b01..72bd04e 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,3 @@ pytest>=8 pytest-lazy-fixtures>=1.1.1 +sybil