Periodicity¶
Not everything worth describing is a crystal. A slab is periodic in two directions, a
nanowire in one, a molecule in none. A Cell records which of its
three directions actually repeat, so httk can represent all of them and reject operations
that only make sense for a crystal.
from httk.atomistic import Cell, Structure
slab = Structure(
Cell([[3, 0, 0], [0, 3, 0], [0, 0, 1]], periodicity=(True, True, False)),
sites, species, species_at_sites,
)
slab.periodicity # (True, True, False)
slab.nperiodic_dimensions # 2
The default is (True, True, True), representing a fully periodic crystal.
The basis is a frame, not a box¶
This is the idea the rest follows from. A basis row flagged periodic is a lattice vector, in the usual sense. A row flagged non-periodic is not a lattice vector — it is only a coordinate frame, saying what a fractional coordinate means along that direction.
Three consequences, and they are the whole model:
Coordinates there are unbounded. Freely below
0or above1, and never wrapped. An atom atz = 1.05is a real position, one frame-vector out; it is not another way of writingz = 0.05.There is no vacuum and no padding. The frame vector is not a wall and the atoms are not inside a box. Nothing is being separated from its periodic image, because there is no periodic image.
A unit frame vector makes the coordinate a length. Set the row to a unit vector and the fractional coordinate along it simply is a displacement in the basis’s units.
That last point is what makes a molecule fall out for free. Give it the identity basis and nothing periodic, and its “fractional” coordinates are literally its ångström coordinates:
water = Structure(
Cell([[1, 0, 0], [0, 1, 0], [0, 0, 1]], periodicity=(False, False, False)),
sites=[[0, 0, 0], [0.7576, 0.5865, 0], [-0.7576, 0.5865, 0]], # angstrom
species=[oxygen, hydrogen],
species_at_sites=["O", "H", "H"],
)
water.cartesian_sites().to_floats() == water.sites.reduced_coords.to_floats() # True
Note the negative coordinate surviving. In a crystal it would have been folded to the far side of the cell; here there is no far side.
All three basis vectors are still required
A cell is always a full, non-degenerate 3×3 basis, whatever its periodicity. A frame vector
must be a real vector — a unit one, not a zero one — or fractional coordinates along it mean
nothing and the basis cannot be inverted. This is also exactly what OPTIMADE mandates: three
lattice vectors regardless of dimension_types, with the ones in non-periodic directions
carrying “no significance beyond fulfilling these requirements”.
What changes¶
Wrapping¶
Only the periodic directions are wrapped into [0, 1). That matters most in
same_crystal(), where folding a non-periodic direction would call the
top of a slab the same place as the bottom, and in the minimum-separation cap behind
structure_tolerance(), where it would report two atoms 9 Å apart as
close neighbours and derive a tolerance far tighter than the data justifies.
Identity¶
Periodicity takes part in ==, on Cell and on Structure, and in same_crystal. It is not
provenance the way a recorded precision is — it says which rows are lattice vectors at all, so
a slab is never the same cell as the bulk that happens to share its basis.
The comparison stays exact in the frame as written. Two descriptions of the same slab that
differ in their non-periodic frame vector compare unequal, because reconciling them would
need an origin convention that same_crystal deliberately does not have.
Volume¶
volume raises for anything but a full 3D crystal. The
determinant of a reduced-periodicity basis mixes real lattice vectors with frame vectors, so
it changes when a frame vector is rescaled even though nothing about the material did — and
any density or packing fraction derived from it would inherit that.
periodic_measure is the quantity that is always defined: the
size of the repeating unit, whatever its dimension.
periodicity |
|
|---|---|
3D |
the cell volume (identical to |
2D |
the area of the periodic plane |
1D |
the length of the periodic direction |
0D |
|
It is exact in the crystallographic case, through the same machinery as
lengths: the area of a hexagonal slab of a = 3 comes back as
(9/2)·√3 in the surd field, not as a float near it.
What is refused¶
Space groups describe three-dimensionally periodic lattices, and every part of the symmetry
machinery here is built on that — 3×3 rational operations, coordinates reduced modulo Z³,
Wyckoff membership as a congruence in all three directions, and tables holding the 230 space
groups. A slab or a wire has symmetry too, but it is a layer group or a rod group,
which is different tabulated data and different mathematics. httk does not have either.
So these refuse a reduced-periodicity structure outright, rather than answering with machinery whose assumptions it violates:
recognize_asu()andASUStructureView
>>> recognize_asu(slab)
ValueError: recognize_asu requires a fully 3D-periodic structure, but this one is periodic
in 2 of 3 directions ((True, True, False)). ...
Rejecting these operations prevents silent failures: an orbit generated across a non-periodic direction could delete atoms, and a distance folded across one could make an unsymmetric structure pass as symmetric.
Everything else keeps working. lengths, angles, metric() and cartesian_sites() are
honest geometry of whatever the three vectors are; only their crystallographic reading
changes.
Marking a structure as a slab¶
Neither CIF nor POSCAR can tell you a structure is not a crystal. VASP expresses a slab purely by convention — a lot of empty space along c — and nothing in the file says so. The periodicity has to come from you:
from httk.atomistic import Cell, Structure, load_structure
relaxed = load_structure("relaxed.POSCAR")
slab = Structure(
Cell(
relaxed.cell.unscaled_basis,
relaxed.cell.scale,
relaxed.cell.precision,
(True, True, False),
),
relaxed.sites,
relaxed.species,
relaxed.species_at_sites,
)
Rebuilding the Cell through unscaled_basis/scale rather than basis keeps the exact
factoring, and passing precision through keeps the stated data precision — see
Data precision.
Note what this does not do: it does not remove the empty space. A relaxed VASP slab keeps whatever c it was computed with, and that is correct — the vector is still a perfectly good frame, and the atoms sit where they sit. Marking it non-periodic only stops httk from treating that direction as repeating.
Serving it¶
nperiodic_dimensions and dimension_types are standard OPTIMADE properties, served from the
structure’s own periodicity. dimension_types is ordered by lattice vector, exactly as
Cell.periodicity is — entry i refers to basis row i, not to the Cartesian x, y, z.
nperiodic_dimensions carries query-support all mandatory, so it filters:
/v1/structures?filter=nperiodic_dimensions<=2
which is OPTIMADE’s own documented example.
The space-group properties come out null for anything that is not a 3D crystal, which is
what OPTIMADE requires. That is not enforced separately — it falls out, because symmetry is
only ever served for an ASUStructure and one of those cannot hold a reduced-periodicity
cell.
site_coordinate_span stays unit_cell while anything is periodic, since translating the
given sites by the lattice vectors does reconstruct the whole system. With nothing periodic
there is no periodic system to reconstruct, and the molecular_* values do not fit either —
each additionally requires bonded atoms to be placed at a bond distance, which httk does not
know — so a molecule is served as other.
See also¶
Structures — the structure classes themselves
Asymmetric units — the symmetry machinery this is refused by
Data precision — the other thing a
Cellrecords about its provenance