Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
88a3e0f
Update documentation for the relationship between CIF grammar and the…
janbridley Dec 8, 2025
39a9396
Fix string format
janbridley Dec 8, 2025
e283d39
Unix-style wildcards doc
janbridley Dec 8, 2025
aaf5ad5
Remove exclamation points
janbridley Dec 8, 2025
c498b9d
Clean up development guide
janbridley Dec 8, 2025
6db5a2a
Swap to RST link style for doc comment
janbridley Dec 8, 2025
f77c6f8
Update type hints
janbridley Dec 8, 2025
5e82c5c
Add type hints where they cannot be inferred
janbridley Dec 8, 2025
4d0ba4d
Standardize error handling for gemmi tests
janbridley Dec 8, 2025
d032730
Clean up unused TODOs
janbridley Dec 8, 2025
ea0e515
One straggler TODO
janbridley Dec 8, 2025
92ef1f3
Note TODO
janbridley Dec 8, 2025
3550eae
Swap note -> attention admonition
janbridley Dec 8, 2025
8b0e7ff
Swap to caution
janbridley Dec 8, 2025
81ef61a
One more caution
janbridley Dec 8, 2025
f9522b7
Add GSD requirement for testing
janbridley Dec 8, 2025
c66d871
Fix typos
janbridley Dec 8, 2025
2fef24d
Add HOOMD-Blue example and examples toc section
janbridley Dec 8, 2025
e083212
Add LAMMPS example
janbridley Dec 8, 2025
fe34e8d
Fix LAMMPS example
janbridley Dec 8, 2025
13b2823
Remove unused line
janbridley Dec 8, 2025
9398394
Add noisy data and fix headers
janbridley Dec 8, 2025
6357459
Final title
janbridley Dec 8, 2025
a8d80f4
Doctest LAMMPS output
janbridley Dec 8, 2025
239dc7e
Add example on numerical precision
janbridley Dec 9, 2025
81b0771
Update requirements file for py3.14
janbridley Dec 9, 2025
6ec6f29
Update changelog.rst
janbridley Dec 9, 2025
1b5be04
Fix label in CHANGELOG
janbridley Dec 9, 2025
ab33ab2
Pre-compile patterns for unit cell evaluation
janbridley Dec 10, 2025
33bdbfa
Add subheading
janbridley Dec 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/requirements-3.10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ fonttools==4.61.0
# via matplotlib
gemmi==0.7.4
# via -r tests/requirements.in
gsd==4.0.0
# via -r tests/requirements.in
iniconfig==2.3.0
# via pytest
kiwisolver==1.4.9
Expand All @@ -29,6 +31,7 @@ numpy==2.2.6
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
3 changes: 3 additions & 0 deletions .github/requirements-3.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ fonttools==4.61.0
# via matplotlib
gemmi==0.7.4
# via -r tests/requirements.in
gsd==4.2.0
# via -r tests/requirements.in
iniconfig==2.3.0
# via pytest
kiwisolver==1.4.9
Expand All @@ -27,6 +29,7 @@ numpy==2.3.5
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
3 changes: 3 additions & 0 deletions .github/requirements-3.12.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ fonttools==4.61.0
# via matplotlib
gemmi==0.7.4
# via -r tests/requirements.in
gsd==4.2.0
# via -r tests/requirements.in
iniconfig==2.3.0
# via pytest
kiwisolver==1.4.9
Expand All @@ -27,6 +29,7 @@ numpy==2.3.5
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
3 changes: 3 additions & 0 deletions .github/requirements-3.13.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ fonttools==4.61.0
# via matplotlib
gemmi==0.7.4
# via -r tests/requirements.in
gsd==4.2.0
# via -r tests/requirements.in
iniconfig==2.3.0
# via pytest
kiwisolver==1.4.9
Expand All @@ -27,6 +29,7 @@ numpy==2.3.5
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
5 changes: 4 additions & 1 deletion .github/requirements-3.14.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file was autogenerated by uv via the following command:
# uv --allow-python-downloads pip compile --python-version=3.14 pyproject.toml requirements-sympy.in tests/requirements.in
# uv --allow-python-downloads pip compile --python-version=3.14 pyproject.toml requirements-sympy.in tests/requirements.in --output-file=.github/requirements-3.14.txt
ase==3.26.0
# via -r tests/requirements.in
contourpy==1.3.3
Expand All @@ -12,6 +12,8 @@ fonttools==4.60.1
# via matplotlib
gemmi==0.7.3
# via -r tests/requirements.in
gsd==4.2.0
# via -r tests/requirements.in
iniconfig==2.1.0
# via pytest
kiwisolver==1.4.9
Expand All @@ -27,6 +29,7 @@ numpy==2.3.3
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
3 changes: 3 additions & 0 deletions .github/requirements-3.9.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ fonttools==4.60.1
# via matplotlib
gemmi==0.7.4
# via -r tests/requirements.in
gsd==4.0.0
# via -r tests/requirements.in
importlib-resources==6.5.2
# via matplotlib
iniconfig==2.1.0
Expand All @@ -31,6 +33,7 @@ numpy==2.0.2
# parsnip-cif (pyproject.toml)
# ase
# contourpy
# gsd
# matplotlib
# pycifrw
# scipy
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/pypi-test-and-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ jobs:
name: python-package-distributions
path: dist/
- name: Publish to TestPyPI
# TODO: https://github.com/pypa/gh-action-pypi-publish/pull/378
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
repository-url: https://test.pypi.org/legacy/
Expand Down
3 changes: 0 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

.. _header:

..
TODO: set up Readthedocs, PyPI, and conda-forge

|ReadTheDocs|
|PyPI|
|conda-forge|
Expand Down
15 changes: 15 additions & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ Changelog
The format is based on `Keep a Changelog <http://keepachangelog.com/en/1.1.0/>`__.
This project adheres to `Semantic Versioning <http://semver.org/spec/v2.0.0.html>`__.

v0.X.X - 20XX-XX-XX
-------------------

Added
~~~~~
- Tutorial on loading CIF files in HOOMD-Blue
- Tutorial on loading CIF files in LAMMPS
- Tutorial on reconstructing CIF files with limited numerical precision
- Documentation for the ``CifFile.PATTERNS`` dict and its relation to the formal CIF
grammar

Changed
~~~~~~~
- ``CifFile.__repr__`` now includes a copy-pasteable section for reproducibility

v0.4.1 - 2025-10-08
-------------------

Expand Down
24 changes: 16 additions & 8 deletions doc/source/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ Development Guide
=================


All contributions to **parsnip** are welcome!
Developers are invited to contribute to the framework by pull request to the package repository on `GitHub`_, and all users are welcome to provide contributions in the form of **user feedback** and **bug reports**.
We recommend discussing new features in form of a proposal on the issue tracker for the appropriate project prior to development.
All contributions to **parsnip** are welcome! Developers are invited to contribute to
the framework by pull request to the package repository on `GitHub`_, and all users are
welcome to provide contributions in the form of **user feedback** and **bug reports**.
We recommend discussing new features in form of a proposal on the issue tracker for the
appropriate project prior to development.

.. _github: https://github.com/glotzerlab/parsnip

Expand All @@ -16,10 +18,12 @@ General Guidelines

All code contributed to **parsnip** must adhere to the following guidelines:

* Hard dependencies (those that end users must install to use **parsnip**) are *strongly* discouraged, and should be avoided where possible. Additional dependencies required by developers (those used to run tests or build docs) are allowed where necessary.
* Hard dependencies (those that end users must install to use **parsnip**) are *strongly* discouraged, and should be avoided where possible. Additional dependencies required by developers (those used to run tests or build docs) are allowed if necessary.
* All code should adhere to the source code conventions and satisfy the documentation and testing requirements discussed below.

As portability is a primary feature of **parsnip**, tests are run run on Python versions 3.7 and later. However, first class support should only be expected for versions covered by `NEP 29`_.
As portability is a primary feature of **parsnip**, tests are run run on Python versions
3.9 and later. However, first class support should only be expected for versions covered
by `NEP 29`_.

.. _NEP 29: https://numpy.org/neps/nep-0029-deprecation_policy.html

Expand All @@ -43,9 +47,10 @@ API documentation should be written as part of the docstrings of the package in
Docstrings are automatically validated using `pydocstyle <http://www.pydocstyle.org/>`_ whenever the ruff prek hooks are run.
The `official documentation <https://parsnip.readthedocs.io/>`_ is generated from the docstrings using `Sphinx <http://www.sphinx-doc.org/en/stable/index.html>`_.

In addition to API documentation, inline comments are strongly encouraged.
Code should be written as transparently as possible, so the primary goal of documentation should be explaining the algorithms or mathematical concepts underlying the code.
Multiline comments for regex strings may sometimes be necessary.
In addition to API documentation, inline comments are strongly encouraged. Code should
be written as transparently as possible, so the primary goal of documentation should
be explaining the algorithms or mathematical concepts underlying the code.


Building Documentation
^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -65,4 +70,7 @@ All code should include a set of tests which validate correct behavior.
All tests should be placed in the ``tests`` folder at the root of the project.
In general, most parts of parsnip primarily require `unit tests <https://en.wikipedia.org/wiki/Unit_testing>`_, but where appropriate `integration tests <https://en.wikipedia.org/wiki/Integration_testing>`_ are also welcome. Core functions should be tested against the sample CIF files included in ``tests/sample_data``.
Tests in **parsnip** use the `pytest <https://docs.pytest.org/>`__ testing framework.
Doctests are automatically integrated with ``pytest`` via
`pytest-doctestplus <https://github.com/scientific-python/pytest-doctestplus>`_.

To run the tests, simply execute ``pytest`` at the root of the repository.
110 changes: 110 additions & 0 deletions doc/source/example_noisy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
Reconstrucing Noisy Unit Cells
==============================

Diffraction experiments and other experimental techniques for quantifying structure
typically offer limited precision in the measurements that can be made. As a result,
the Wyckoff position data recorded in some CIF files -- particularly older ones -- may
make reproduction of the original structure challenging. In this example, we explore how
**parsnip**'s `build_unit_cell` method can be tuned to accurately reproduce structures
with complicated geometries, using alpha-Selenium as an example.

.. testsetup::

>>> import os
>>> import numpy as np
>>> if "doc/source" not in os.getcwd(): os.chdir("doc/source")


.. literalinclude:: hP3.cif

Note that the basis positions for alpha-Selenium are provided to five decimal
places of accuracy, while the symmetry operations are provided in a rational form.

.. doctest::

>>> from parsnip import CifFile
>>> cif = CifFile("hP3.cif")
>>> # Let's make sure we reconstruct the unit cell's three atoms
>>> correct_uc = cif.build_unit_cell()
>>> correct_uc
array([[0.2254 , 0. , 0.33333 ],
[0. , 0.2254 , 0.66666333],
[0.7746 , 0.7746 , 0.99999667]])
>>> site_multiplicity = int(cif["_atom_site_symmetry_multiplicity"].squeeze())
>>> assert len(correct_uc) == site_multiplicity

**parsnip**'s default settings are able to correctly reproduce the unit cell -- but
the mismatch between numerical data and the symmetry operation strings can cause issues.
If we truncate the Wyckoff position data, even by one decimal place, the reconstructed
crystal contains duplicate atoms:

.. literalinclude:: hP3-four-decimal-places.cif
:diff: hP3.cif

Rebuilding our crystal results in an error:

.. doctest::

>>> lower_precision_cif = CifFile("hP3-four-decimal-places.cif")
>>> uc = lower_precision_cif.build_unit_cell()
>>> uc # doctest: +SKIP
array([[0.2254 , 0. , 0.3333 ], # A
[0. , 0.2254 , 0.66663333], # B
[0.7746 , 0.7746 , 0.99996667], # C
[0.2254 , 0. , 0.33336667], # A
[0. , 0.2254 , 0.6667 ]]) # B
>>> uc.shape == correct_uc.shape # Our unit cell has duplicate atoms!
False

By default, **parsnip** uses four decimal places of accuracy to reconstruct crystals.
This yields the best overall accuracy (tested with several thousand CIFs), but is not
always the best choice in general. A good rule of thumb is to use one fewer decimal
places than the CIF file contains. This ensures positions are rounded sufficiently to
detect duplicate atoms, and avoids issues in complex structures where Wyckoff positions
may be very close to one another. Making this change results in the correct structure
once again.


.. doctest::

>>> cif = CifFile("hP3-four-decimal-places.cif")
>>> four_decimal_places = cif.build_unit_cell(n_decimal_places=3)
>>> four_decimal_places
array([[0.2254 , 0. , 0.3333 ],
[0. , 0.2254 , 0.66663333],
[0.7746 , 0.7746 , 0.99996667]])
>>> assert four_decimal_places.shape == correct_uc.shape

.. important::

Rounding of Wyckoff positions is an intermediate step in the unit cell
reconstruction, and does not negatively impact the accuracy of the returned data.
The unit cell is always returned in the full precision of the input CIF:

.. doctest::

>>> cif = CifFile("hP3-four-decimal-places.cif")
>>> one_decimal_place = cif.build_unit_cell(n_decimal_places=1)
>>> np.testing.assert_array_equal(one_decimal_place, four_decimal_places)


Symbolic Parsing
^^^^^^^^^^^^^^^^

In some cases, particularly in structures with many atoms, careful tuning of numerical
precision is not enough to accurately reproduce a crystal. **parsnip** includes a
specialized parser that uses rational arithmetic to correctly compare fractions that
only differ by a few units in last place. To enable this, install the `sympy`_ library
and set ``parse_mode="sympy"`` when building the unit cell.

.. doctest::

>>> cif = CifFile("hP3.cif")
>>> symbolic = cif.build_unit_cell(n_decimal_places=4, parse_mode="sympy")
>>> symbolic
array([[0.2254 , 0. , 0.33333 ],
[0. , 0.2254 , 0.6666633],
[0.7746 , 0.7746 , 0.99999667]])
>>> assert symbolic.shape == correct_uc.shape

.. _sympy: https://www.sympy.org/en/index.html
Loading
Loading