crystod.slab#

Public surface-slab API of CrystOD (the crystod-slab domain).

Cut surface slabs from a bulk crystal structure, the Python form of crystod-slab. For each Miller index every distinct termination is built with pymatgen’s SlabGenerator and measured: number of atoms, thickness along the surface normal, vacuum, in-plane area, formula, whether the slab is polar (a nonzero dipole per area from the oxidation states, Slab.is_polar) and whether its two faces are equivalent (Slab.is_symmetric). The slabs are written as VASP POSCAR files.

The bulk structure

Miller indices

Slabs

Files

Constants

Usage:

from crystod import slab

structure = slab.load_structure("221_PPOSCAR_SrTiO3")
records = slab.generate_slabs(structure, max_index=1)
for record in records:
    print(record.label, record.n_atoms, record.polar, record.symmetric)
print(slab.format_table(records))
slab.write_slab_poscar(records[0])        # ('POSCAR_slab_SrTiO3_111_t1', 'wrote')
records = slab.generate_slabs(structure, miller_index=(1, 0, 0), symmetrize=True)

Attributes resolve lazily (PEP 562): importing this module is instant and pymatgen loads only on first use. Functions report bad input as ValueError through this namespace (the implementation module crystod.surface_slab raises SystemExit, as the command line wants).

class crystod.slab.CellInfo(formula, space_group, space_group_number, n_atoms, n_conventional, is_conventional, species, lattice, point_group='1', laue_group='-1')[source]#

Bases: object

Symmetry facts of a bulk cell, as crystod-slab prints them.

Variables:
  • formula (str) – Reduced formula, elements in the order of the sites (SrTiO3; YBa2Cu3O6, never pymatgen’s Ba2Y(CuO2)3).

  • space_group (str) – International symbol found by spglib (Pm-3m).

  • space_group_number (int) – Its number (221).

  • n_atoms (int) – Atoms in the cell.

  • n_conventional (int) – Atoms in the standardized conventional cell.

  • is_conventional (bool) – The cell has the lattice parameters and the number of atoms of the conventional standard cell, so its Miller indices are those of that cell.

  • species (tuple[str, ...]) – The elements in the order of their first site.

  • lattice (tuple[float, float, float, float, float, float]) – (a, b, c, alpha, beta, gamma) in Angstrom and degrees.

  • point_group (str) – The crystal point group (m-3m).

  • laue_group (str) – Its Laue group, under which distinct_miller_indices() merges indices (m-3m; -3m for R-3c).

formula: str#
is_conventional: bool#
lattice: tuple[float, float, float, float, float, float]#
laue_group: str = '-1'#
n_atoms: int#
n_conventional: int#
point_group: str = '1'#
space_group: str#
space_group_number: int#
species: tuple[str, ...]#
class crystod.slab.SlabRecord(miller_index, termination, shift, slab, n_atoms, thickness, vacuum, area, formula, polar, symmetric, bulk_formula, species_order, charge=None, symmetrized=False)[source]#

Bases: object

One slab: a termination of one Miller index, with its measurements.

Variables:
  • miller_index (tuple[int, int, int]) – (h, k, l) of the surface (pymatgen’s, divided by any common factor).

  • termination (int) – 1, 2, … in pymatgen’s order within the index.

  • shift (float) – pymatgen’s termination shift, fractional c of the oriented unit cell.

  • slab (object) – The pymatgen.core.surface.Slab.

  • n_atoms (int) – Atoms in the slab cell.

  • thickness (float) – Extent of the atoms along the surface normal (Angstrom).

  • vacuum (float) – Height of the cell along the normal minus the thickness.

  • area (float) – In-plane area of the cell (Angstrom^2).

  • formula (str) – Composition of the slab cell (Sr5Ti5O15), elements in the order of species_order.

  • polar (bool | None) – Slab.is_polar of the slab decorated with the oxidation states; None when there are none.

  • symmetric (bool) – Slab.is_symmetric at the symmetry tolerance.

  • bulk_formula (str) – Reduced formula of the bulk, elements in the order of species_order and never grouped (for the file names).

  • species_order (tuple[str, ...]) – Element order of the bulk structure (for writing).

  • charge (float | None) – Net formal charge of the slab cell (e) from the oxidation states, None without them; nonzero only for a non-stoichiometric (symmetrized) slab.

  • symmetrized (bool) – The slab comes from a symmetrize=True run (its label and file name use s1, s2, ... instead of t1, t2).

area: float#
bulk_formula: str#
charge: float | None = None#
property charged: bool#

The slab carries a net formal charge (see charge).

formula: str#
property label: str#

"(1 1 1) t1" ("(1 0 0) s1" for a symmetrized slab).

miller_index: tuple[int, int, int]#
n_atoms: int#
polar: bool | None#
shift: float#
slab: object#
species_order: tuple[str, ...]#
symmetric: bool#
symmetrized: bool = False#
termination: int#
thickness: float#
vacuum: float#
class crystod.slab.SlabSet(records=(), *, miller_indices=(), oxidation_states=None, oxidation_source=None, notes=())[source]#

Bases: list

The SlabRecord list of generate_slabs(), with its context.

Variables:
  • miller_indices – The indices enumerated, in order.

  • oxidation_states – The states the polarity was computed with, or None.

  • oxidation_source – "guessed", "given" or None.

  • notes – What the command prints as NOTE lines (an index without a symmetric termination, an index given twice, …).

crystod.slab.conventional_cell(structure, tolerance=0.1, species_order=None)[source]#

The standardized conventional cell of structure (--conventional).

pymatgen’s SpacegroupAnalyzer.get_conventional_standard_structure at tolerance; the sites are sorted (stably) by element in species_order, by default the element order of structure.

Raises:

SystemExit – No conventional cell at this tolerance (ValueError through crystod.slab).

crystod.slab.describe_cell(structure, tolerance=0.1)[source]#

Space group of structure and how it compares with the conventional cell.

Return type:

CellInfo

crystod.slab.distinct_miller_indices(structure, max_index, tolerance=0.1)[source]#

The Miller indices with |h|, |k|, |l| <= max_index that are distinct under the Laue group of the crystal.

Two indices are the same surface when a rotation of the space group of structure (found by spglib at tolerance), possibly followed by the inversion that turns a slab over, maps one onto the other. This is done in the axes of the cell as given, so it holds for every setting (a primitive rhombohedral cell, a monoclinic cell of pseudo-orthorhombic metric, …). The indices are reduced by their common factor, and the first index of each family is kept in pymatgen’s order (get_symmetrically_distinct_miller_indices: low indices first), with whose list this one agrees for a crystal that is not trigonal and whose lattice has no more symmetry than the crystal. pymatgen merges indices under the symmetry of the lattice (and, for a trigonal crystal, of its hexagonal setting), which on a primitive rhombohedral cell lists one surface twice and misses another, and on a lattice of higher symmetry than the crystal misses surfaces.

Example

>>> from crystod import slab
>>> from crystod.examples import example_path
>>> s = slab.load_structure(example_path("221_PPOSCAR_SrTiO3"))
>>> slab.distinct_miller_indices(s, 1)
[(1, 1, 1), (1, 1, 0), (1, 0, 0)]
Return type:

list[tuple[int, int, int]]

crystod.slab.format_charge(value)[source]#

"+4", "-1", "0", "+2.67": a charge in units of e.

Return type:

str

crystod.slab.format_miller_index(index)[source]#

"(1 0 -1)": an index in parentheses, the components separated by spaces.

Return type:

str

crystod.slab.format_oxidation_states(states)[source]#

"Sr+2 Ti+4 O-2" ("none" for no states).

Return type:

str

crystod.slab.format_table(records, max_atoms=None, no_polar=False, no_files=False, width=100)[source]#

The slab table crystod-slab prints, one header line and one line per slab.

The columns are # h k l shift atoms thickness vacuum area formula [charge] polar symmetric status (see table_legend(); the charge column only when a slab is charged); the status is slab_status(). The column widths follow the content; when the table would be wider than width columns the units leave the header, then the columns move closer together, then the formulas leave the table for lines of their own (#1 Sr5Ti5O15) below it.

Returns:

The table as one string (lines joined by newlines, no trailing one; crystod.search.format_table() returns a list of lines instead).

Return type:

str

crystod.slab.generate_slabs(structure, miller_index=None, *, max_index=None, slab_size=10.0, vacuum_size=15.0, symmetrize=False, oxidation_states=None, tolerance=0.1, center=True, primitive=True, lll_reduce=False, in_unit_planes=False, max_normal_search=None, orthogonal_c=False)[source]#

Every distinct termination of the given surfaces, measured.

This is crystod-slab without the files. For each index, SlabGenerator(structure, hkl, slab_size, vacuum_size, ...) .get_slabs(symmetrize=symmetrize, tol=0.1, ftol=0.1); the slabs are numbered t1, t2, ... per index in pymatgen’s order (s1, s2, ... with symmetrize).

Parameters:
  • structure – The bulk Structure (see load_structure()); Miller indices refer to its axes.

  • miller_index – One index ((1, 0, 0), "1 0 0", a four-index (1, 0, -1, 0)) or a list of them. Give this or max_index.

  • max_index (int | None) – Every index with |h|, |k|, |l| <= max_index distinct under the Laue group of the crystal (distinct_miller_indices()).

  • slab_size (float) – Minimum slab height in Angstrom (in d_hkl lattice planes with in_unit_planes): whole oriented cells are stacked until they reach it, and the atoms span that height less the gap at the cut.

  • vacuum_size (float) – Minimum vacuum height, likewise in whole cells.

  • symmetrize (bool) – Symmetric slabs: atoms are removed from one face of every asymmetric slab until the faces are equivalent at tolerance (non-stoichiometric, possibly charged: SlabRecord.charge).

  • oxidation_states (dict[str, float] | None) – {"Sr": 2, "Ti": 4, "O": -2} for the dipole; by default pymatgen’s guess, and no polarity (polar = None) when there is none. Given states must balance the bulk.

  • tolerance (float) – Symmetry tolerance in Angstrom of the Laue group of max_index, of Slab.is_symmetric and of the symmetrization.

  • center (bool) – Center the slab in the cell (pymatgen center_slab).

  • primitive (bool) – Reduce the in-plane cell to a primitive one when pymatgen can (it keeps the oriented cell’s a, b otherwise).

  • lll_reduce (bool) – Reduce the in-plane cell to its shortest a, b and make c as perpendicular as whole in-plane vectors allow (a and b stay in the surface).

  • in_unit_planes (bool) – slab_size and vacuum_size count d_hkl lattice planes of the cell (one can hold several atomic layers).

  • max_normal_search (int | None) – pymatgen max_normal_search (a c vector as perpendicular to the surface as possible).

  • orthogonal_c (bool) – Make c perpendicular to the surface (Slab.get_orthogonal_c_slab) before the measurements.

Returns:

The SlabSet (a list of SlabRecord).

Raises:

SystemExit – Both or neither of miller_index and max_index, a bad index, non-positive sizes or tolerance, oxidation states that do not fit the structure, or a slab pymatgen cannot build (ValueError through crystod.slab).

Return type:

SlabSet

Example

>>> from crystod import slab
>>> from crystod.examples import example_path
>>> s = slab.load_structure(example_path("221_PPOSCAR_SrTiO3"))
>>> records = slab.generate_slabs(s, max_index=1)
>>> [(r.miller_index, r.n_atoms, r.polar) for r in records]
[((1, 1, 1), 25, True), ((1, 1, 0), 20, True), ((1, 0, 0), 15, False)]
crystod.slab.guess_oxidation_states(structure)[source]#

pymatgen’s most likely oxidation states of the bulk composition.

None when pymatgen has no charge-balanced guess, or only the all-zero one of a metal: the dipole, hence the polarity, is then unknown.

Return type:

dict[str, float] | None

crystod.slab.load_structure(path, conventional=False, tolerance=0.1)[source]#

Read the bulk structure (POSCAR; CIF and other pymatgen formats work too).

The structure is read by pymatgen in VASP’s reading of a POSCAR (crystod.vasp_io.poscar_structure()), stripped of site properties and oxidation states, and checked to be ordered.

Parameters:
  • path – The structure file.

  • conventional (bool) – Return the standardized conventional cell (SpacegroupAnalyzer.get_conventional_standard_structure) instead of the cell as given; Miller indices then refer to its axes. The sites keep the element order of the file.

  • tolerance (float) – Symmetry tolerance in Angstrom of the standardization.

Returns:

A pymatgen.core.Structure.

Raises:

SystemExit – The file is missing or unreadable, or a site is partially occupied or shared by several species (ValueError through crystod.slab).

crystod.slab.parse_miller_index(index)[source]#

A Miller index (h, k, l) from three integers or a Miller-Bravais index.

Parameters:

index – "1 0 0", "1,0,0", ["1", "0", "0"], (1, 0, 0), or four integers (h k i l) of hexagonal axes, of which the third must be i = -(h + k) and is dropped: ["1", "0", "-1", "0"] -> (1, 0, 0).

Returns:

(h, k, l) as given (not divided by a common factor).

Raises:

SystemExit – Not three or four integers, a four-index input with i != -(h + k), or the zero vector (ValueError through crystod.slab).

Return type:

tuple[int, int, int]

Example

>>> from crystod import slab
>>> slab.parse_miller_index(["1", "0", "-1", "0"])
(1, 0, 0)
crystod.slab.parse_oxidation_states(tokens)[source]#

{"Sr": 2.0, "O": -2.0} from ["Sr=2", "O=-2"] (or "Sr=2 O=-2").

A state may be a fraction, Fe=8/3.

Raises:

SystemExit – A token that is not EL=Q with a number Q, an unknown element, or an element given twice (ValueError through crystod.slab).

Return type:

dict[str, float]

crystod.slab.read_index_list(path)[source]#

The Miller indices of a list file, one per line.

A line holds 1 0 0, 1,0,0 or a four-index 1 0 -1 0; # starts a comment, blank lines are skipped.

Raises:

SystemExit – The file is missing or unreadable, a line is not a Miller index (the message names the line number), or the file holds no index (ValueError through crystod.slab).

Return type:

list[tuple[int, int, int]]

crystod.slab.resolve_oxidation_states(structure, oxidation_states=None)[source]#

The states used for the dipole and where they came from.

Returns:

the given states (checked against the structure: one per element, balancing the bulk) and "given", pymatgen’s guess and "guessed", or (None, None) when there is no guess. Given states that are all 0 come back as (None, "given"): no ions, so the polarity is unknown.

Return type:

(states, source)

Raises:

SystemExit – Given states that miss an element, name one the structure does not have, or leave the bulk charged (ValueError through crystod.slab).

crystod.slab.slab_filename(record)[source]#

POSCAR_slab_{bulk formula}_{h}{k}{l}_t{n}: POSCAR_slab_SrTiO3_111_t1.

A negative index is written as -1 (POSCAR_slab_NaCl_10-1_t1); when a component is 10 or more the components are separated by _ (POSCAR_slab_SrTiO3_1_1_10_t1); a symmetrized slab has s{n} instead of t{n} (POSCAR_slab_SrTiO3_100_s1). Any character other than a letter, a digit, ., _ or - becomes _.

Return type:

str

crystod.slab.slab_poscar_text(record, species_order=None)[source]#

The POSCAR of a slab: direct coordinates, one block per element.

The sites are sorted (stably) by element in species_order, by default the element order of the bulk structure; the comment line reads SrTiO3 (1 1 1) slab, termination t1, Sr5Ti5O15, shift 0.250, crystod-slab (symmetrized s1 for a symmetrized slab).

Return type:

str

crystod.slab.slab_status(record, max_atoms=None, no_polar=False, no_files=False)[source]#

What crystod-slab does with the slab, in the words of its table.

"skipped: 25 > 16 atoms" (more atoms than max_atoms), then "skipped: polar" (no_polar and a polar slab; a slab of unknown polarity is kept), otherwise "written", or "not written (--no-files)" with no_files.

Return type:

str

crystod.slab.table_legend(records)[source]#

TABLE_LEGEND, plus CHARGE_LEGEND when the table of records has a charge column (a charged, symmetrized slab).

Return type:

str

crystod.slab.write_slab_poscar(record, directory='.', species_order=None, *, overwrite=False)[source]#

Write the POSCAR of a slab (slab_poscar_text()) into directory.

The directory is created if needed. As crystod.search.write_poscar() does, an identical file of the same name (slab_filename()) is kept, and one with different content is replaced only with overwrite.

Returns:

(path, status) with status "wrote", "kept" (an identical file was already there) or "replaced".

Raises:

SystemExit – A different file is in the way and overwrite is False, the directory cannot be created or the file cannot be written (ValueError through crystod.slab).

Return type:

tuple[str, str]

crystod.slab.SLAB_CITATION_LINES#

The papers to cite for crystod-slab (that of the author of the original slabkit.py) and for pymatgen’s slab construction.

crystod.slab.TABLE_LEGEND#

What the columns of format_table() mean (printed under the table).

crystod.slab.CHARGE_LEGEND#

Appended to TABLE_LEGEND when the table has a charge column.

crystod.slab.DEFAULT_SLAB_SIZE = 10.0#

Minimum slab height in Angstrom (pymatgen min_slab_size).

crystod.slab.DEFAULT_VACUUM_SIZE = 15.0#

Minimum vacuum height in Angstrom (pymatgen min_vacuum_size).

crystod.slab.DEFAULT_TOLERANCE = 0.1#

Symmetry tolerance in Angstrom of the space group, the Laue group of the Miller indices and the symmetric-slab test.