httk.atomistic.wyckoff ====================== .. py:module:: httk.atomistic.wyckoff .. autoapi-nested-parse:: Exact rational Wyckoff-position algebra. A Wyckoff position is a family of symmetry-equivalent sites parameterised by a few free coordinates: SG 15 letter ``e`` is ``0,y,1/4``, so one free parameter ``y`` places four symmetry-equivalent atoms. This module goes both ways between free parameters and coordinates, entirely over the rationals. Two properties of the vendored tables make this much simpler than it looks, both asserted in ``tests/test_symmetry_data.py``: * Each Wyckoff position's ``orbit`` is already the **complete, deduplicated** list of affine maps, of length exactly ``multiplicity``, with centering translations folded in. Generating an orbit is therefore a plain loop with no coincidence testing and no tolerance. * ``hasfreedom`` marks which of ``x``, ``y``, ``z`` are free, and the columns of every orbit matrix for the non-free variables are identically zero, with ``sum(hasfreedom) == rank``. So the free parameters are read straight off, and the ``first_orbit`` strings (``"1/8,y,-y+1/4"``) never need parsing — the same information is already present as an exact affine map. Everything is expressed in the coordinates of whichever setting the record came from. Classes ------- .. autoapisummary:: httk.atomistic.wyckoff.WyckoffBranch httk.atomistic.wyckoff.WyckoffPosition Functions --------- .. autoapisummary:: httk.atomistic.wyckoff.wyckoff_positions Module Contents --------------- .. py:class:: WyckoffBranch(operation: httk.atomistic.affine_operation.AffineOperation, free: collections.abc.Sequence[int]) One member of a Wyckoff position's orbit: an affine map of the free parameters. A position of multiplicity *m* has *m* branches. The representative branch is the first, but a coordinate may lie on any of them, which is why :meth:`WyckoffPosition.parameters_of` tries them all. .. py:property:: operation :type: httk.atomistic.affine_operation.AffineOperation The affine map from a full ``(x, y, z)`` parameter triple to this branch's coordinate. .. py:property:: free :type: tuple[int, Ellipsis] Indices into ``(x, y, z)`` of the free parameters, ascending. .. py:method:: coordinate(parameters: Any) -> httk.core.FracVector The coordinate this branch places at the given free-parameter values. ``parameters`` has one entry per free parameter. Because the non-free columns of the matrix are zero, the values placed at the non-free positions are irrelevant. .. py:method:: parameters_of(coordinate: Any) -> httk.core.FracVector | None The free-parameter values putting this branch on ``coordinate``, or ``None``. ``None`` means the coordinate does not lie on this branch — for *any* lattice translation, not merely the one given. That completeness is what the row-Hermite form buys: with ``U`` unimodular over the integers, ``A t = d (mod Z^3)`` holds iff ``U A t = U d (mod Z^3)``, and the zero rows of ``U A`` turn the lattice-membership question into "are these components integers?" with no search over translations. The returned parameters are reduced into ``[0, 1)``, which is canonical: the pivot block has determinant ``±1`` throughout the vendored tables, so the solution is unique modulo one. The result is verified by re-evaluating the branch, so a table that ever violated that assumption would yield a clean miss rather than a wrong answer. .. py:method:: nearest_parameters(coordinate: Any) -> httk.core.FracVector The free-parameter values putting this branch as close to ``coordinate`` as it goes. Unlike :meth:`parameters_of` this always returns a value: the free directions are solved exactly and any discrepancy is left in the *fixed* directions, where the branch's own coordinates win. It is the projection used when recognizing a measured structure, whose coordinates carry rounding and do not lie exactly on any position. The projection is taken along the branch's own lattice basis rather than being minimised in the cell metric, so for a strongly oblique cell it is a near-optimal rather than provably optimal choice. That is safe because the caller measures the resulting Cartesian displacement and rejects anything beyond its tolerance — the method can cost a match, never grant a wrong one. .. py:class:: WyckoffPosition(record: dict[str, Any]) A Wyckoff position of one space-group setting: a letter and its orbit. .. py:method:: from_record(record: dict[str, Any]) -> Self :classmethod: .. py:property:: letter :type: str The Wyckoff letter, e.g. ``"e"``. Bare, with no multiplicity prefix. .. py:property:: multiplicity :type: int How many sites one set of free-parameter values generates in the unit cell. .. py:property:: site_symmetry :type: str The site-symmetry group in Hermann-Mauguin notation, e.g. ``"2"`` or ``"-1"``. .. py:property:: free :type: tuple[int, Ellipsis] Indices into ``(x, y, z)`` of the free parameters. .. py:property:: free_count :type: int How many degrees of freedom the position has, from 0 for a fixed position to 3. .. py:property:: branches :type: tuple[WyckoffBranch, Ellipsis] The orbit, one branch per equivalent site. Complete and already deduplicated. .. py:property:: representative :type: WyckoffBranch The first orbit member, the one the tables print as ``first_orbit``. .. py:method:: coordinates(parameters: Any) -> httk.core.FracVector Every coordinate of the orbit, as an exact ``(multiplicity, 3)`` block. Not wrapped and not deduplicated: within one setting the tabulated orbit is already distinct, so wrapping is the caller's business (and matters only once a setting transform enters). .. py:method:: parameters_of(coordinate: Any) -> httk.core.FracVector | None The free-parameter values placing *some* branch on ``coordinate``, or ``None``. Tries every branch, not only the representative. That matters: across the vendored tables, 11673 of the 20639 non-representative orbit members lie on a different branch than the representative, so a matcher that only tested ``first_orbit`` would reject a majority of legitimate orbit points. .. py:function:: wyckoff_positions(record: dict[str, Any]) -> tuple[WyckoffPosition, Ellipsis] The Wyckoff positions of a setting record, most specific first. Ordered by ``(free_count, multiplicity, letter)`` so that the first match found when identifying a coordinate is the most specific position it lies on. Ties do not arise: positions are affine subspaces, so a coordinate on two distinct positions of the same dimension also lies on their lower-dimensional intersection, which is covered by an earlier entry.