httk.atomistic.io.cif.cif_parser ================================ .. py:module:: httk.atomistic.io.cif.cif_parser Classes ------- .. autoapisummary:: httk.atomistic.io.cif.cif_parser.CifMeta httk.atomistic.io.cif.cif_parser.AsuCell Functions --------- .. autoapisummary:: httk.atomistic.io.cif.cif_parser.parse_cif_float httk.atomistic.io.cif.cif_parser.parse_cif_fraction httk.atomistic.io.cif.cif_parser.parse_cif_int httk.atomistic.io.cif.cif_parser.cif_exact_token httk.atomistic.io.cif.cif_parser.parse_asu_cell httk.atomistic.io.cif.cif_parser.parse_structural_modulation httk.atomistic.io.cif.cif_parser.cifblock_to_asu httk.atomistic.io.cif.cif_parser.read_cif_asus httk.atomistic.io.cif.cif_parser.single_asu_from_cif_file Module Contents --------------- .. py:class:: CifMeta Bases: :py:obj:`TypedDict` What a CIF number claims about its own precision, beyond its value. Both are exact rationals, or ``None`` when the token makes no such claim. ``None`` is not the same as claiming exactness: a value written ``1/3`` states an exact number and reports ``None`` for both, while ``?`` states nothing at all and also reports ``None``. ``precision`` is implied by the digits written (``0.3333`` is good to ``1/10000``); ``esd`` is the standard uncertainty a file states explicitly, so ``0.3333(7)`` reports a precision of ``1/10000`` *and* an esd of ``7/10000``. They measure different things and the coarser of the two is the honest claim. :param esd: Standard uncertainty explicitly stated by the CIF token. :param precision: Precision implied by the digits written in the CIF token. .. py:attribute:: esd :type: fractions.Fraction | None .. py:attribute:: precision :type: fractions.Fraction | None .. py:function:: parse_cif_float(token: str, *, meta: Literal[False] = ..., pragmatic: bool = ...) -> float | None parse_cif_float(token: str, *, meta: Literal[True], pragmatic: bool = ...) -> tuple[float | None, CifMeta] Parse a CIF numeric field. If meta=False: return float_value or None. If meta=True: return ``(float_value_or_None, CifMeta)`` — the value plus what the token claims about its own precision. See :class:`CifMeta`; both of its fields are exact rationals or ``None``. :param token: Numeric text to parse, including CIF uncertainty notation when present. :param meta: Include the value's claimed precision and standard uncertainty. :param pragmatic: Salvage selected non-standard numeric spellings instead of rejecting them. :return: The parsed value, optionally paired with its precision metadata. :raises ValueError: If the token is missing or cannot be interpreted as a CIF number. .. py:function:: parse_cif_fraction(token) Parse a CIF numeric token exactly, preserving finite decimals and fractions. :param token: Numeric text whose exact central value should be preserved. :return: The exact value, or ``None`` for an unknown CIF value. :raises ValueError: If the token does not contain a valid exact numeric value. .. py:function:: parse_cif_int(token, *, strict = True, allow_round = False) Convert a CIF numeric token to an integer using its central value. The accepted forms include ``'123(4)'``, ``'3E2'``, and ``'1.0E3'``. - strict=True: require the value to be exactly integral; otherwise raise ValueError. - allow_round=True (only if strict=False): round half-even to the nearest int. :param token: Numeric text to convert. :param strict: Require the central value to be integral. :param allow_round: Permit half-even rounding when ``strict`` is false. :return: The converted integer. :raises ValueError: If the token is missing or its central value cannot be converted under the selected rules. :raises decimal.InvalidOperation: If an unmatched token has malformed decimal syntax. .. py:function:: cif_exact_token(token) The numeric part of a CIF value, as text, with any uncertainty estimate removed. ``"0.3333(5)"`` becomes ``"0.3333"`` and ``"3(1)e-1"`` becomes ``"3e-1"``; ``"?"`` and ``"."`` become ``None``. The point of keeping the text rather than a float is fidelity: a consumer that wants an exact value can read ``0.3333`` as the rational 3333/10000, which is what the file says, whereas ``float("0.3333")`` is a binary approximation whose exact rational value is 6004199023210345/18014398509481984 and says something the file did not. :param token: CIF value text whose uncertainty estimate should be removed. :return: The central numeric text, or ``None`` for an unknown value. .. py:class:: AsuCell Bases: :py:obj:`NamedTuple` Hold the parsed cell, atom sites, exact tokens, and precision claims. :param basis: Conventional Cartesian basis vectors for the unit cell. :param positions: Fractional atom positions. :param positions_exact: Original central coordinate tokens for each atom. :param occupancies: Parsed atom occupancies, when supplied. :param occupancies_exact: Original central occupancy tokens, when supplied. :param occupancy_precisions: Coarsest claimed precision for each occupancy, when supplied. :param coordinate_precision: Coarsest claimed precision across the coordinates. :param basis_precision: Coarsest claimed precision across the cell lengths. :param symbols: Atom-site symbols in input order. :param labels: Atom-site labels in input order. :param equivalent_atoms: One-based identifiers grouping equal atom-site labels. .. py:attribute:: basis :type: list[list[float]] .. py:attribute:: positions :type: list[tuple[float | None, float | None, float | None]] .. py:attribute:: positions_exact :type: list[tuple[str | None, str | None, str | None]] .. py:attribute:: occupancies :type: list[float | None] | None .. py:attribute:: occupancies_exact :type: list[str | None] | None .. py:attribute:: occupancy_precisions :type: list[fractions.Fraction | None] | None .. py:attribute:: coordinate_precision :type: fractions.Fraction | None .. py:attribute:: basis_precision :type: fractions.Fraction | None .. py:attribute:: symbols :type: list[str] .. py:attribute:: labels :type: list[str] .. py:attribute:: equivalent_atoms :type: list[int] .. py:function:: parse_asu_cell(cifblock) Parse a CIF block into its asymmetric-unit cell data. :param cifblock: Normalized CIF data for one data block. :return: The parsed unit cell and atom-site data. :raises ValueError: If required cell or atom-site data is missing or invalid. .. py:function:: parse_structural_modulation(cifblock) Extract structural superspace modulation information from a standard CIF. Returns a tuple ``(structural_q, mod_dim, has_struct_mod, struct_mod_atoms)`` where ``structural_q`` is a list of q-vectors or ``None``, ``mod_dim`` is the modulation dimension (0 if absent), ``has_struct_mod`` is a bool, and ``struct_mod_atoms`` is a sorted list of atom-site labels. :param cifblock: Normalized CIF data for one data block. :return: Structural q-vectors, modulation dimension, presence flag, and affected labels. :raises ValueError: If a structural wave vector contains an unknown component. .. py:function:: cifblock_to_asu(cifblock) Convert one normalized CIF block to a neutral asymmetric-unit payload. The payload keeps ``cell_parameters_exact`` and ``positions_exact`` as the central numeric text written by the file, and keeps ``symops_xyz`` as the raw operation strings. The dual numeric channel gives ``_httk_*_exact`` companion tags precedence over standard numeric tags on read, preserving central text after a lossy display write. :param cifblock: Normalized CIF data for one data block. :return: A neutral mapping containing cell, atom, symmetry, and metadata channels. :raises ValueError: If the block lacks required cell, atom-site, or symmetry data. .. py:function:: read_cif_asus(source, *, autocorrect = False) Read a CIF and return its asymmetric units as a neutral, tagged payload. This is what ``httk.core.load`` returns for a ``.cif`` file: a mapping with ``format`` set to ``"cif"``, ``blocks`` holding one asymmetric-unit mapping per data block that describes a structure, and ``header`` the file's leading comment. The tag lets a consumer dispatch on the file type without knowing which reader produced the payload. When ``autocorrect=True``, the top-level payload also contains ``autocorrect=True`` so downstream adapters can apply compatible repairs. Loading never fails on account of a block that is not a structure. CIF is a general-purpose format and a file may hold bibliographic entries, powder patterns, or an incomplete draft alongside — or instead of — anything crystallographic. Blocks without atom sites are simply not structures and are passed over; blocks that have atom sites but cannot be interpreted are collected in ``unparsed``, each with the reason, so that nothing is dropped silently and the failure surfaces when a structure is actually asked for. The mapping stays neutral — plain lists, strings, exact numeric text channels, and raw symmetry-operation strings, with no domain objects — so this reader layer need not know about the *httk-atomistic* structure model. Turning it into a structure is ``httk.core.load`` (which returns an ``ASUStructure`` when atomistic support is installed). :param source: A filename, open text stream, or iterable of CIF lines. :param autocorrect: Drop malformed auxiliary loops and warn about each repair. :return: A neutral CIF payload containing structural blocks, unparsed block reasons, and the header. :raises ValueError: If the CIF stream contains malformed data that prevents parsing. .. py:function:: single_asu_from_cif_file(source) Return the first structural CIF block from :func:`read_cif_asus`. :param source: A filename, open text stream, or iterable of CIF lines. :return: The first parsed asymmetric-unit mapping. :raises ValueError: If no structural block is available.