Skip to content

Commit 5925504

Browse files
authored
Add option to evaluate unit cells symbolically (#45)
<!-- Provide a general summary of your changes in the Title above --> ## Description Unit cells can now be build symbolically, using `sympy`'s Rational numbers to determine the correct atomic coordinates. This reduces rounding errors for large and complicated unit cells and allows for more accurate deduplication of atoms. ## Motivation and Context As in #42 and #44, floating point errors can accumulate to result in imperfect deduplication of atoms for complex structures. The new implementation has a few changes: - Fractional coordinates are rounded before PBC wrapping, reducing the number of duplicate points near the edges of a cell - Symmetry operation expressions can be evaluated symbolically by setting the `parse_mode` flag of `build_unit_cell` to `sympy`. The previous implementation is accessible by setting that value to `python_float` - New `sympy` evaluation mode is enabled by default only if sympy is installed on the system. ### Notes on the method Symbolically evaluating every site is slow relative to the performance of the pure python implementation - however, the total runtime per crystal is on the order of tens of milliseconds and should be fast enough for most users. Symbolic evaluation of sites is NOT guaranteed to accurately reconstruct every crystal! While `parsnip` works for 1102/1103 of the files in my test suite, some tuning of `n_decimal_places` may be required. For really degenerate systems (orthorhombic, monoclinic, or triclinic crystals with >100 atoms and sites represented with 3 or fewer decimal places), even symbolic evaluation may not be able to properly deduplicate a crystal. ## Types of Changes <!-- Please select all items that apply, either now or after creating the pull request: --> - [ ] Documentation update - [x] Bug fix - [x] New feature - [ ] Breaking change<sup>1</sup> <sup>1</sup>The change breaks (or has the potential to break) existing functionality and should be merged into the `breaking` branch ## Checklist: <!-- Please select all items that apply either now or after creating the pull request. --> - [x] I am familiar with the [Development Guidelines](https://github.com/glotzerlab/parsnip/blob/main/doc/source/development.rst) - [x] The changes introduced by this pull request are covered by existing or newly introduced tests. - [ ] I have updated the [changelog](https://github.com/glotzerlab/parsnip/blob/main/ChangeLog.rst) and added my name to the [credits](https://github.com/glotzerlab/parsnip/blob/main/doc/source/credits.rst).
1 parent a0f4dcf commit 5925504

19 files changed

+310
-84
lines changed

.github/requirements-3.10.txt

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.10 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.10 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.24.0
44
# via -r tests/requirements.in
55
contourpy==1.3.1
@@ -8,19 +8,21 @@ cycler==0.12.1
88
# via matplotlib
99
exceptiongroup==1.2.2
1010
# via pytest
11-
fonttools==4.56.0
11+
fonttools==4.57.0
1212
# via matplotlib
13-
gemmi==0.7.0
13+
gemmi==0.7.1
1414
# via -r tests/requirements.in
15-
iniconfig==2.0.0
15+
iniconfig==2.1.0
1616
# via pytest
1717
kiwisolver==1.4.8
1818
# via matplotlib
1919
matplotlib==3.10.1
2020
# via ase
2121
more-itertools==10.6.0
2222
# via parsnip-cif (pyproject.toml)
23-
numpy==2.2.3
23+
mpmath==1.3.0
24+
# via sympy
25+
numpy==2.2.4
2426
# via
2527
# parsnip-cif (pyproject.toml)
2628
# ase
@@ -39,9 +41,11 @@ pluggy==1.5.0
3941
# via pytest
4042
ply==3.11
4143
# via pycifrw
42-
pycifrw==4.4.6
44+
prettytable==3.16.0
45+
# via pycifrw
46+
pycifrw==5.0.1
4347
# via -r tests/requirements.in
44-
pyparsing==3.2.1
48+
pyparsing==3.2.3
4549
# via matplotlib
4650
pytest==8.3.5
4751
# via
@@ -55,5 +59,11 @@ scipy==1.15.2
5559
# via ase
5660
six==1.17.0
5761
# via python-dateutil
62+
sympy==1.13.3
63+
# via
64+
# -r requirements-sympy.txt
65+
# -r tests/requirements.in
5866
tomli==2.2.1
5967
# via pytest
68+
wcwidth==0.2.13
69+
# via prettytable

.github/requirements-3.11.txt

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.11 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.11 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.24.0
44
# via -r tests/requirements.in
55
contourpy==1.3.1
66
# via matplotlib
77
cycler==0.12.1
88
# via matplotlib
9-
fonttools==4.56.0
9+
fonttools==4.57.0
1010
# via matplotlib
11-
gemmi==0.7.0
11+
gemmi==0.7.1
1212
# via -r tests/requirements.in
13-
iniconfig==2.0.0
13+
iniconfig==2.1.0
1414
# via pytest
1515
kiwisolver==1.4.8
1616
# via matplotlib
1717
matplotlib==3.10.1
1818
# via ase
1919
more-itertools==10.6.0
2020
# via parsnip-cif (pyproject.toml)
21-
numpy==2.2.3
21+
mpmath==1.3.0
22+
# via sympy
23+
numpy==2.2.4
2224
# via
2325
# parsnip-cif (pyproject.toml)
2426
# ase
@@ -37,9 +39,11 @@ pluggy==1.5.0
3739
# via pytest
3840
ply==3.11
3941
# via pycifrw
40-
pycifrw==4.4.6
42+
prettytable==3.16.0
43+
# via pycifrw
44+
pycifrw==5.0.1
4145
# via -r tests/requirements.in
42-
pyparsing==3.2.1
46+
pyparsing==3.2.3
4347
# via matplotlib
4448
pytest==8.3.5
4549
# via
@@ -53,3 +57,9 @@ scipy==1.15.2
5357
# via ase
5458
six==1.17.0
5559
# via python-dateutil
60+
sympy==1.13.3
61+
# via
62+
# -r requirements-sympy.txt
63+
# -r tests/requirements.in
64+
wcwidth==0.2.13
65+
# via prettytable

.github/requirements-3.12.txt

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.12 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.12 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.24.0
44
# via -r tests/requirements.in
55
contourpy==1.3.1
66
# via matplotlib
77
cycler==0.12.1
88
# via matplotlib
9-
fonttools==4.56.0
9+
fonttools==4.57.0
1010
# via matplotlib
11-
gemmi==0.7.0
11+
gemmi==0.7.1
1212
# via -r tests/requirements.in
13-
iniconfig==2.0.0
13+
iniconfig==2.1.0
1414
# via pytest
1515
kiwisolver==1.4.8
1616
# via matplotlib
1717
matplotlib==3.10.1
1818
# via ase
1919
more-itertools==10.6.0
2020
# via parsnip-cif (pyproject.toml)
21-
numpy==2.2.3
21+
mpmath==1.3.0
22+
# via sympy
23+
numpy==2.2.4
2224
# via
2325
# parsnip-cif (pyproject.toml)
2426
# ase
@@ -37,9 +39,11 @@ pluggy==1.5.0
3739
# via pytest
3840
ply==3.11
3941
# via pycifrw
40-
pycifrw==4.4.6
42+
prettytable==3.16.0
43+
# via pycifrw
44+
pycifrw==5.0.1
4145
# via -r tests/requirements.in
42-
pyparsing==3.2.1
46+
pyparsing==3.2.3
4347
# via matplotlib
4448
pytest==8.3.5
4549
# via
@@ -53,3 +57,9 @@ scipy==1.15.2
5357
# via ase
5458
six==1.17.0
5559
# via python-dateutil
60+
sympy==1.13.3
61+
# via
62+
# -r requirements-sympy.txt
63+
# -r tests/requirements.in
64+
wcwidth==0.2.13
65+
# via prettytable

.github/requirements-3.13.txt

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.13 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.13 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.24.0
44
# via -r tests/requirements.in
55
contourpy==1.3.1
66
# via matplotlib
77
cycler==0.12.1
88
# via matplotlib
9-
fonttools==4.56.0
9+
fonttools==4.57.0
1010
# via matplotlib
11-
gemmi==0.7.0
11+
gemmi==0.7.1
1212
# via -r tests/requirements.in
13-
iniconfig==2.0.0
13+
iniconfig==2.1.0
1414
# via pytest
1515
kiwisolver==1.4.8
1616
# via matplotlib
1717
matplotlib==3.10.1
1818
# via ase
1919
more-itertools==10.6.0
2020
# via parsnip-cif (pyproject.toml)
21-
numpy==2.2.3
21+
mpmath==1.3.0
22+
# via sympy
23+
numpy==2.2.4
2224
# via
2325
# parsnip-cif (pyproject.toml)
2426
# ase
@@ -37,9 +39,11 @@ pluggy==1.5.0
3739
# via pytest
3840
ply==3.11
3941
# via pycifrw
40-
pycifrw==4.4.6
42+
prettytable==3.16.0
43+
# via pycifrw
44+
pycifrw==5.0.1
4145
# via -r tests/requirements.in
42-
pyparsing==3.2.1
46+
pyparsing==3.2.3
4347
# via matplotlib
4448
pytest==8.3.5
4549
# via
@@ -53,3 +57,9 @@ scipy==1.15.2
5357
# via ase
5458
six==1.17.0
5559
# via python-dateutil
60+
sympy==1.13.3
61+
# via
62+
# -r requirements-sympy.txt
63+
# -r tests/requirements.in
64+
wcwidth==0.2.13
65+
# via prettytable

.github/requirements-3.8.txt

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.8 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.8 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.23.0
44
# via -r tests/requirements.in
55
contourpy==1.1.1
@@ -8,23 +8,25 @@ cycler==0.12.1
88
# via matplotlib
99
exceptiongroup==1.2.2
1010
# via pytest
11-
fonttools==4.55.3
11+
fonttools==4.57.0
1212
# via matplotlib
13-
gemmi==0.7.0
13+
gemmi==0.7.1
1414
# via -r tests/requirements.in
1515
importlib-resources==6.4.5
1616
# via matplotlib
17-
iniconfig==2.0.0
17+
iniconfig==2.1.0
1818
# via pytest
1919
kiwisolver==1.4.7
2020
# via matplotlib
2121
matplotlib==3.7.5
2222
# via ase
2323
more-itertools==10.5.0
24-
# via parsnip (pyproject.toml)
24+
# via parsnip-cif (pyproject.toml)
25+
mpmath==1.3.0
26+
# via sympy
2527
numpy==1.24.4
2628
# via
27-
# parsnip (pyproject.toml)
29+
# parsnip-cif (pyproject.toml)
2830
# ase
2931
# contourpy
3032
# matplotlib
@@ -41,11 +43,13 @@ pluggy==1.5.0
4143
# via pytest
4244
ply==3.11
4345
# via pycifrw
44-
pycifrw==4.4.6
46+
prettytable==3.11.0
47+
# via pycifrw
48+
pycifrw==5.0.1
4549
# via -r tests/requirements.in
4650
pyparsing==3.1.4
4751
# via matplotlib
48-
pytest==8.3.4
52+
pytest==8.3.5
4953
# via
5054
# -r tests/requirements.in
5155
# pytest-doctestplus
@@ -57,7 +61,13 @@ scipy==1.10.1
5761
# via ase
5862
six==1.17.0
5963
# via python-dateutil
64+
sympy==1.13.3
65+
# via
66+
# -r requirements-sympy.txt
67+
# -r tests/requirements.in
6068
tomli==2.2.1
6169
# via pytest
70+
wcwidth==0.2.13
71+
# via prettytable
6272
zipp==3.20.2
6373
# via importlib-resources

.github/requirements-3.9.txt

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# This file was autogenerated by uv via the following command:
2-
# uv pip compile --python-version=3.9 pyproject.toml tests/requirements.in
2+
# uv --allow-python-downloads pip compile --python-version=3.9 pyproject.toml requirements-sympy.txt tests/requirements.in
33
ase==3.24.0
44
# via -r tests/requirements.in
55
contourpy==1.3.0
@@ -8,20 +8,22 @@ cycler==0.12.1
88
# via matplotlib
99
exceptiongroup==1.2.2
1010
# via pytest
11-
fonttools==4.56.0
11+
fonttools==4.57.0
1212
# via matplotlib
13-
gemmi==0.7.0
13+
gemmi==0.7.1
1414
# via -r tests/requirements.in
1515
importlib-resources==6.5.2
1616
# via matplotlib
17-
iniconfig==2.0.0
17+
iniconfig==2.1.0
1818
# via pytest
1919
kiwisolver==1.4.7
2020
# via matplotlib
2121
matplotlib==3.9.4
2222
# via ase
2323
more-itertools==10.6.0
2424
# via parsnip-cif (pyproject.toml)
25+
mpmath==1.3.0
26+
# via sympy
2527
numpy==2.0.2
2628
# via
2729
# parsnip-cif (pyproject.toml)
@@ -41,9 +43,11 @@ pluggy==1.5.0
4143
# via pytest
4244
ply==3.11
4345
# via pycifrw
44-
pycifrw==4.4.6
46+
prettytable==3.16.0
47+
# via pycifrw
48+
pycifrw==5.0.1
4549
# via -r tests/requirements.in
46-
pyparsing==3.2.1
50+
pyparsing==3.2.3
4751
# via matplotlib
4852
pytest==8.3.5
4953
# via
@@ -57,7 +61,13 @@ scipy==1.13.1
5761
# via ase
5862
six==1.17.0
5963
# via python-dateutil
64+
sympy==1.13.3
65+
# via
66+
# -r requirements-sympy.txt
67+
# -r tests/requirements.in
6068
tomli==2.2.1
6169
# via pytest
70+
wcwidth==0.2.13
71+
# via prettytable
6272
zipp==3.21.0
6373
# via importlib-resources
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
22

3-
for version in 3.{9..13}; do
4-
uv pip compile --python-version="$version" pyproject.toml tests/requirements.in > ".github/requirements-$version.txt"
3+
for version in 3.{8..13}; do
4+
uv pip compile --python-version="$version" pyproject.toml requirements-sympy.txt tests/requirements.in > ".github/requirements-$version.txt"
55
done

README.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ additional dependencies are required to run the tests and build the docs.
7575
.. code:: bash
7676
7777
pip install . # Install with no additional dependencies
78-
pip install .[tests] # Install with dependencies required to run tests
78+
pip install .[sympy] # Install with sympy for symbolic unit cell math
79+
pip install .[tests] # Install with dependencies required to run tests (including sympy)
7980
pip install .[tests,doc] # Install with dependencies required to run tests and make docs
8081
8182
Dependencies

changelog.rst

+8
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,19 @@ v0.X.X - 20XX-XX-XX
1010
Added
1111
~~~~~
1212
- Additional testpath flag to conftest
13+
- Symbolic parsing mode for ``build_unit_cell``
14+
15+
Changed
16+
~~~~~~~
17+
- ``build_unit_cell`` now uses sympy by default if it is intalled - otherwise, it falls
18+
back to the previous variant
1319

1420
Fixed
1521
~~~~~
1622
- Accessing data pairs with ``get_from_pairs`` or ``__getitem__`` now allows for case-insensitive searches
1723
- Quote-delimited strings containing the delimiting character are now parsed properly
24+
- ``build_unit_cell`` now rounds coordinates before wrapping into the box, fixing edge cases
25+
where boundary atoms were not properly deduplicated
1826

1927
v0.2.1 - 2025-03-12
2028
-------------------

0 commit comments

Comments
 (0)