Data precision¶
A data file states its numbers to a definite number of digits, and that is a claim.
0.3333 says a coordinate is known to about 1e-4 of a cell edge; 0.33 says only
1e-2. httk records that claim and uses it, so a matching tolerance or an spglib
symprec follows the data instead of being a constant somebody guessed.
from httk.atomistic import load_asu_structure, structure_tolerance
asu = load_asu_structure("measured.cif")
asu.coordinate_precision # Fraction(1, 10000) — from the file's own digits
asu.cell.precision # Fraction(3, 10000) — widened by a stated esd
structure_tolerance(asu) # a matching tolerance derived from both
What is recorded¶
Two values, on the objects that own the numbers they describe.
Where |
Units |
|
|---|---|---|
coordinate precision |
fractional (dimensionless) |
|
basis precision |
the cell’s length units |
Both are exact rationals — 1e-4 is Fraction(1, 10000) — so nothing picks up binary
noise on the way to becoming a tolerance. None means unknown, which is a real answer
and not the same as claiming exactness. A non-positive value is rejected: zero would assert
an exactness no measurement has.
Structure reads both through, and adds the value a tolerance actually wants:
structure.coordinate_precision # fractional
structure.basis_precision # a length
structure.cartesian_precision() # the coordinate precision as a length
cartesian_precision() multiplies by the longest cell edge — the largest displacement
a fractional uncertainty can produce — and takes the cell’s own precision as a floor, since
a cell known only to 1e-3 cannot place an atom better than that however many digits the
coordinates carry. The same 1e-4 coordinates therefore mean 5e-4 in a 5 Å cell and
3e-3 in a 30 Å one, which is the entire reason a fractional precision has to be converted
before it is used as a distance.
Precision is not part of identity
It takes no part in ==, on Cell, Sites, Structure, or in
same_crystal(). The same atoms read from a more carefully written
file are still the same structure. (Cell.__eq__ already ignored the scale/unscaled
factoring for the same reason.)
Where it comes from¶
Any source that writes its numbers to a definite number of digits infers a precision from them.
CIF states both a number of digits and, often, an explicit standard uncertainty. Where
both are present the coarser wins, that being the weaker and therefore safer claim — so
5.6402(3) is precise to 3e-4, not to the 1e-4 its four decimals alone would suggest.
POSCAR has no uncertainty concept, so the digits are all there is. Two conversions happen when the structure is built, because they need the assembled cell:
Cartesian coordinates are a length, so they are divided by the shortest cell edge to become fractional.
The cell vectors are multiplied by the scaling factor, so their precision scales with them. The scaling factor’s own digits are deliberately not charged as uncertainty: it defines units rather than being measured apart from the rows it scales, and charging it would declare a 5.64 Å cell precise to only half an ångström because the file wrote
1.0.
Across a set of values the coarsest wins: a structure is only as precisely stated as
its least precisely stated number, so one sloppy 0.5 in a table of six-decimal
coordinates really does mean the table is good to 1e-1.
A value written as an exact rational — 1/3 — states a value rather than a measurement and
contributes nothing at all, rather than dragging the table down to the width of its last
digit.
The underlying primitive is in httk-core and is usable directly:
from httk.core import combined_precision, decimal_precision
decimal_precision("0.3333") # Fraction(1, 10000)
decimal_precision("1.2e-3") # Fraction(1, 10000)
decimal_precision("1/3") # None — exact, not measured
combined_precision(["0.123456", "0.5"]) # Fraction(1, 10) — the coarsest
What it is used for¶
structure_tolerance() turns a recorded precision into a matching
tolerance, and it is the default for every tolerant operation in the package:
recognize_asu(), ASUStructureView,
asu_structure_from_cif(), and spglib’s symprec. Passing an
explicit tolerance= still overrides it.
The tolerance is the Cartesian precision times two — two independently rounded values can differ by twice their precision, so anything tighter would reject data that is in fact consistent — capped so that it can never reach half the closest approach between two sites, which is what would let genuinely distinct atoms merge.
As a calibration, coordinates written to four decimals in a 5 Å cell derive exactly 1e-3,
matching the fallback used when precision is unknown. Recorded precision lets other data
derive an appropriate value.
Why it matters¶
Consider a CIF written to three decimals whose site is meant to sit on Wyckoff 4e of
space group 15 (0, y, 1/4), rounded to 0.001 0.333 0.251 — 0.005 Å off in x and
0.007 Å off in z:
tolerance |
outcome |
|---|---|
fixed |
misses the special position, lands on the general position |
derived |
recognised as |
Using the recorded precision avoids silently producing a structure with twice the correct number of atoms in this case.
Minimum separation is a cap, not a precision
The minimum separation bounds a derived tolerance from above. Treating it as part of the recorded precision would be incorrect: two accidentally close atoms would make a structure look far more precisely stated than it is.
Serving it¶
Two provider-specific OPTIMADE properties, described by published definitions from schemas.httk.org rather than paraphrased locally:
Property |
Definition |
Unit |
|---|---|---|
|
|
dimensionless |
|
|
ångström |
Both serve null for a structure that states no precision. OPTIMADE standardises nothing
here — the only standard_uncertainty in its structures definition is CODATA metadata
inside a unit definition — so these carry the _httk_ prefix OPTIMADE requires of a
database-specific property name, while the definitions keep their own $id.
Storing precision needs nothing special: float and Fraction are already first-class
scalar kinds in httk-data.
See also¶
Asymmetric units — where the derived tolerance is used
Data precision: letting the file choose the tolerance — a runnable walkthrough