vibe-sketcherDocumentation

Source code for vibesketcher.document.crystal_templates

"""A library of standard crystals given by space group and asymmetric unit (#231).

Every template is a space-group setting, conventional-cell parameters and an
asymmetric unit, expanded by :func:`vibesketcher.document.crystal.from_space_group`.
The library is therefore also a test of the space-group machinery: each entry
records the composition and Z it must produce.

The lattice constants are representative room-temperature experimental values.
``reference`` names the source of the structure and its parameters; values a
study depends on should be checked against the primary literature for that
study's conditions (temperature, pressure, composition).
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Any, Literal

from vibesketcher.document.crystal import LatticeParameters, from_space_group
from vibesketcher.document.model import PeriodicStructure

Category = Literal[
    "elements",
    "halides",
    "oxides",
    "semiconductors",
    "perovskites",
    "spinels",
    "layered",
    "minerals",
    "carbides and nitrides",
]

WYCKOFF_1 = "R. W. G. Wyckoff, Crystal Structures, 2nd ed., Vol. 1 (Interscience, New York, 1963)"
WYCKOFF_2 = "R. W. G. Wyckoff, Crystal Structures, 2nd ed., Vol. 2 (Interscience, New York, 1964)"
WYCKOFF_3 = "R. W. G. Wyckoff, Crystal Structures, 2nd ed., Vol. 3 (Interscience, New York, 1965)"

_T = 1 / 3
_TT = 2 / 3

Site = tuple[str, tuple[float, float, float]]


[docs] @dataclass(frozen=True) class Surface: """A commonly studied surface of a template, for one-step slabs (#232).""" miller: tuple[int, int, int] note: str = ""
[docs] @dataclass(frozen=True) class Template: name: str title: str formula: str category: Category space_group: str a: float sites: tuple[Site, ...] z: int reference: str c: float | None = None b: float | None = None beta: float | None = None aliases: tuple[str, ...] = () structure_type: str = "" surfaces: tuple[Surface, ...] = () #: Experimental density in g/cm^3 where checked, for the library test. density: float | None = None extra: dict[str, Any] = field(default_factory=dict)
[docs] def parameters(self) -> LatticeParameters: return LatticeParameters(self.a, b=self.b, c=self.c, beta=self.beta)
_ROCKSALT = "Fm-3m" _TEMPLATES: list[Template] = [] def _add(*templates: Template) -> None: _TEMPLATES.extend(templates) def _rocksalt( name: str, a: str, b: str, lattice: float, category: Category = "halides", title: str = "", aliases: tuple[str, ...] = (), density: float | None = None, surfaces: tuple[Surface, ...] = (Surface((1, 0, 0)),), ) -> Template: return Template( name, title or f"{a}{b} (rocksalt)", f"{a}{b}", category, _ROCKSALT, lattice, ((a, (0, 0, 0)), (b, (0.5, 0.5, 0.5))), 4, WYCKOFF_1, aliases=aliases, structure_type="rocksalt (B1)", surfaces=surfaces, density=density, ) def _metal( name: str, element: str, kind: str, a: float, c: float | None = None, density: float | None = None, ) -> Template: if kind == "fcc": return Template( name, f"{element} (fcc)", element, "elements", "Fm-3m", a, ((element, (0, 0, 0)),), 4, WYCKOFF_1, structure_type="fcc (A1)", surfaces=(Surface((1, 1, 1)), Surface((1, 0, 0)), Surface((1, 1, 0))), density=density, ) if kind == "bcc": return Template( name, f"{element} (bcc)", element, "elements", "Im-3m", a, ((element, (0, 0, 0)),), 2, WYCKOFF_1, structure_type="bcc (A2)", surfaces=(Surface((1, 1, 0)), Surface((1, 0, 0))), density=density, ) return Template( name, f"{element} (hcp)", element, "elements", "P6_3/mmc", a, ((element, (_T, _TT, 0.25)),), 2, WYCKOFF_1, c=c, structure_type="hcp (A3)", surfaces=(Surface((0, 0, 1)),), density=density, ) def _zincblende( name: str, a: str, b: str, lattice: float, category: Category = "semiconductors", aliases: tuple[str, ...] = (), density: float | None = None, ) -> Template: return Template( name, f"{a}{b} (zincblende)", f"{a}{b}", category, "F-43m", lattice, ((a, (0, 0, 0)), (b, (0.25, 0.25, 0.25))), 4, WYCKOFF_1, aliases=aliases, structure_type="zincblende (B3)", surfaces=(Surface((1, 1, 0)), Surface((1, 0, 0)), Surface((1, 1, 1))), density=density, ) def _wurtzite( name: str, a: str, b: str, lattice: float, c: float, u: float, category: Category = "semiconductors", aliases: tuple[str, ...] = (), density: float | None = None, ) -> Template: return Template( name, f"{a}{b} (wurtzite)", f"{a}{b}", category, "P6_3mc", lattice, ((a, (_T, _TT, 0)), (b, (_T, _TT, u))), 2, WYCKOFF_1, c=c, aliases=aliases, structure_type="wurtzite (B4)", surfaces=( Surface((1, 0, 0), "the nonpolar (10-10) surface"), Surface((0, 0, 1), "the polar (0001) surface"), ), density=density, ) def _fluorite( name: str, cation: str, anion: str, lattice: float, category: Category = "oxides", anti: bool = False, aliases: tuple[str, ...] = (), density: float | None = None, ) -> Template: if anti: # anion on 4a, two cations on 8c sites: tuple[Site, ...] = ((anion, (0, 0, 0)), (cation, (0.25, 0.25, 0.25))) formula, kind = f"{cation}2{anion}", "antifluorite" else: sites = ((cation, (0, 0, 0)), (anion, (0.25, 0.25, 0.25))) formula, kind = f"{cation}{anion}2", "fluorite (C1)" return Template( name, f"{formula} ({kind.split()[0]})", formula, category, "Fm-3m", lattice, sites, 4, WYCKOFF_1, aliases=aliases, structure_type=kind, surfaces=(Surface((1, 1, 1)), Surface((1, 1, 0))), density=density, ) def _rutile( name: str, cation: str, anion: str, a: float, c: float, x: float, category: Category = "oxides", aliases: tuple[str, ...] = (), density: float | None = None, ) -> Template: return Template( name, f"{cation}{anion}2 (rutile)", f"{cation}{anion}2", category, "P4_2/mnm", a, ((cation, (0, 0, 0)), (anion, (x, x, 0))), 2, WYCKOFF_1, c=c, aliases=aliases, structure_type="rutile (C4)", surfaces=(Surface((1, 1, 0)), Surface((1, 0, 0)), Surface((1, 0, 1))), density=density, ) # --- elements ------------------------------------------------------------------- _add( _metal("cu", "Cu", "fcc", 3.615, density=8.96), _metal("ag", "Ag", "fcc", 4.086, density=10.50), _metal("au", "Au", "fcc", 4.078, density=19.3), _metal("al", "Al", "fcc", 4.050, density=2.70), _metal("ni", "Ni", "fcc", 3.524, density=8.91), _metal("pd", "Pd", "fcc", 3.891), _metal("pt", "Pt", "fcc", 3.924, density=21.45), _metal("pb", "Pb", "fcc", 4.950), _metal("rh", "Rh", "fcc", 3.803), _metal("ir", "Ir", "fcc", 3.839), _metal("ca", "Ca", "fcc", 5.588), _metal("fe", "Fe", "bcc", 2.866, density=7.87), _metal("cr", "Cr", "bcc", 2.885), _metal("mo", "Mo", "bcc", 3.147, density=10.22), _metal("w", "W", "bcc", 3.165, density=19.3), _metal("v", "V", "bcc", 3.030), _metal("nb", "Nb", "bcc", 3.300), _metal("ta", "Ta", "bcc", 3.303), _metal("li", "Li", "bcc", 3.509), _metal("na", "Na", "bcc", 4.291), _metal("k", "K", "bcc", 5.328), _metal("ba", "Ba", "bcc", 5.025), _metal("mg", "Mg", "hcp", 3.209, 5.211, density=1.74), _metal("ti", "Ti", "hcp", 2.951, 4.686, density=4.51), _metal("zn", "Zn", "hcp", 2.665, 4.947), _metal("co", "Co", "hcp", 2.507, 4.069), _metal("zr", "Zr", "hcp", 3.232, 5.147), _metal("be", "Be", "hcp", 2.286, 3.584), _metal("cd", "Cd", "hcp", 2.979, 5.619), _metal("ru", "Ru", "hcp", 2.706, 4.282), _metal("hf", "Hf", "hcp", 3.195, 5.051), _metal("re", "Re", "hcp", 2.761, 4.456), _metal("os", "Os", "hcp", 2.734, 4.317), _metal("sc", "Sc", "hcp", 3.309, 5.273), _metal("y", "Y", "hcp", 3.647, 5.731), ) for _name, _element, _a, _rho in ( ("diamond", "C", 3.567, 3.515), ("si", "Si", 5.431, 2.329), ("ge", "Ge", 5.658, 5.32), ("alpha-sn", "Sn", 6.489, None), ): _add( Template( _name, f"{_element} (diamond structure)", _element, "elements", "Fd-3m", _a, ((_element, (0.125, 0.125, 0.125)),), 8, WYCKOFF_1, aliases=("c-diamond",) if _element == "C" else ("silicon",) if _element == "Si" else (), structure_type="diamond (A4)", surfaces=(Surface((1, 1, 1)), Surface((1, 0, 0)), Surface((1, 1, 0))), density=_rho, ) ) # --- rocksalt halides, oxides and others ---------------------------------------- _add( _rocksalt( "mgo", "Mg", "O", 4.212, "oxides", "MgO (periclase)", ("periclase", "magnesia"), 3.58 ), _rocksalt("cao", "Ca", "O", 4.811, "oxides", "CaO (lime)", ("lime",)), _rocksalt("sro", "Sr", "O", 5.160, "oxides"), _rocksalt("bao", "Ba", "O", 5.523, "oxides"), _rocksalt("nio", "Ni", "O", 4.177, "oxides", "NiO (bunsenite)"), _rocksalt("mno", "Mn", "O", 4.445, "oxides"), _rocksalt("coo", "Co", "O", 4.260, "oxides"), _rocksalt( "nacl", "Na", "Cl", 5.640, title="NaCl (halite)", aliases=("halite", "rocksalt-nacl"), density=2.165, ), _rocksalt("lif", "Li", "F", 4.027), _rocksalt("licl", "Li", "Cl", 5.130), _rocksalt("naf", "Na", "F", 4.634), _rocksalt("nabr", "Na", "Br", 5.977), _rocksalt("kcl", "K", "Cl", 6.293, title="KCl (sylvite)", aliases=("sylvite",), density=1.98), _rocksalt("kbr", "K", "Br", 6.600), _rocksalt("agcl", "Ag", "Cl", 5.549), _rocksalt("pbs", "Pb", "S", 5.936, "semiconductors", "PbS (galena)", ("galena",)), _rocksalt("tin", "Ti", "N", 4.240, "carbides and nitrides", "TiN (osbornite)"), _rocksalt("tic", "Ti", "C", 4.328, "carbides and nitrides"), ) for _name, _ca, _an, _lattice in ( ("cscl", "Cs", "Cl", 4.123), ("csbr", "Cs", "Br", 4.286), ("csi", "Cs", "I", 4.567), ("nial", "Ni", "Al", 2.887), ): _add( Template( _name, f"{_ca}{_an} (CsCl structure)", f"{_ca}{_an}", "halides" if _an != "Al" else "elements", "Pm-3m", _lattice, ((_ca, (0, 0, 0)), (_an, (0.5, 0.5, 0.5))), 1, WYCKOFF_1, structure_type="CsCl (B2)", surfaces=(Surface((1, 1, 0)), Surface((1, 0, 0))), ) ) # --- fluorite and antifluorite -------------------------------------------------- _add( _fluorite("caf2", "Ca", "F", 5.463, "halides", aliases=("fluorite",), density=3.18), _fluorite("srf2", "Sr", "F", 5.800, "halides"), _fluorite("baf2", "Ba", "F", 6.200, "halides"), _fluorite("ceo2", "Ce", "O", 5.411, aliases=("ceria",), density=7.22), _fluorite("tho2", "Th", "O", 5.597), _fluorite("uo2", "U", "O", 5.470), _fluorite("li2o", "Li", "O", 4.611, anti=True), ) # --- zincblende and wurtzite semiconductors ------------------------------------- _add( _zincblende("gaas", "Ga", "As", 5.653, density=5.32), _zincblende("gap", "Ga", "P", 5.451), _zincblende("inp", "In", "P", 5.869), _zincblende("inas", "In", "As", 6.058), _zincblende("alas", "Al", "As", 5.661), _zincblende("alp", "Al", "P", 5.463), _zincblende("zns", "Zn", "S", 5.409, aliases=("sphalerite", "zns-zincblende"), density=4.09), _zincblende("znse", "Zn", "Se", 5.668), _zincblende("cdte", "Cd", "Te", 6.481), _zincblende( "sic-3c", "Si", "C", 4.360, "carbides and nitrides", aliases=("beta-sic", "3c-sic") ), _zincblende("c-bn", "B", "N", 3.615, "carbides and nitrides", aliases=("cbn", "cubic-bn")), _wurtzite("zno", "Zn", "O", 3.250, 5.207, 0.382, "oxides", ("zincite",)), _wurtzite("gan", "Ga", "N", 3.189, 5.185, 0.377, density=6.15), _wurtzite("aln", "Al", "N", 3.112, 4.982, 0.382, "carbides and nitrides"), _wurtzite("zns-wurtzite", "Zn", "S", 3.821, 6.257, 0.375), _wurtzite("cds", "Cd", "S", 4.136, 6.714, 0.377, aliases=("greenockite",)), ) # --- rutile, anatase and other dioxides ----------------------------------------- _add( _rutile("rutile", "Ti", "O", 4.594, 2.959, 0.3048, aliases=("tio2-rutile",), density=4.25), _rutile("sno2", "Sn", "O", 4.737, 3.186, 0.307, aliases=("cassiterite",), density=6.99), _rutile("stishovite", "Si", "O", 4.180, 2.666, 0.3062), _rutile("ruo2", "Ru", "O", 4.492, 3.107, 0.306), _rutile("mgf2", "Mg", "F", 4.621, 3.052, 0.303, "halides", ("sellaite",)), Template( "anatase", "TiO2 (anatase)", "TiO2", "oxides", "I4_1/amd:1", 3.785, (("Ti", (0, 0, 0)), ("O", (0, 0, 0.2081))), 4, "M. Horn, C. F. Schwerdtfeger, E. P. Meagher, Z. Kristallogr. 136, 273 (1972)", c=9.514, aliases=("tio2-anatase",), structure_type="anatase (C5)", surfaces=(Surface((1, 0, 1)), Surface((0, 0, 1))), density=3.89, ), Template( "alpha-quartz", "SiO2 (alpha-quartz)", "SiO2", "minerals", "P3_221", 4.9134, (("Si", (0.4697, 0, _TT)), ("O", (0.4135, 0.2669, 0.1191 + _TT))), 3, "Y. Le Page, G. Donnay, Acta Cryst. B32, 2456 (1976); origin moved by 2c/3 " "to the ITA setting of P3_221", c=5.4052, aliases=("quartz",), structure_type="alpha-quartz", surfaces=(Surface((0, 0, 1)), Surface((1, 0, 1))), density=2.65, ), Template( "zro2-monoclinic", "ZrO2 (baddeleyite)", "ZrO2", "oxides", "P2_1/c", 5.1507, ( ("Zr", (0.2754, 0.0395, 0.2083)), ("O", (0.0700, 0.3317, 0.3447)), ("O", (0.4496, 0.7569, 0.4792)), ), 4, "C. J. Howard, R. J. Hill, B. E. Reichert, Acta Cryst. B44, 116 (1988)", b=5.2028, c=5.3156, beta=99.194, aliases=("baddeleyite",), structure_type="baddeleyite", surfaces=(Surface((1, 1, 1)), Surface((-1, 1, 1), "the (-111) surface")), ), Template( "cu2o", "Cu2O (cuprite)", "Cu2O", "oxides", "Pn-3m:1", 4.269, (("O", (0, 0, 0)), ("Cu", (0.25, 0.25, 0.25))), 2, WYCKOFF_1, aliases=("cuprite",), structure_type="cuprite (C3)", surfaces=(Surface((1, 1, 1)), Surface((1, 0, 0))), ), ) # --- corundum-type sesquioxides -------------------------------------------------- _add( Template( "al2o3", "Al2O3 (corundum)", "Al2O3", "oxides", "R-3c", 4.7602, (("Al", (0, 0, 0.35216)), ("O", (0.30624, 0, 0.25))), 6, "J. Lewis, D. Schwarzenbach, H. D. Flack, Acta Cryst. A38, 733 (1982)", c=12.9933, aliases=("corundum", "alpha-alumina", "sapphire"), structure_type="corundum (D5_1)", surfaces=( Surface((0, 0, 1), "the (0001) basal plane"), Surface((1, 0, 2), "the r plane (1-102)"), ), density=3.987, ), Template( "cr2o3", "Cr2O3 (eskolaite)", "Cr2O3", "oxides", "R-3c", 4.959, (("Cr", (0, 0, 0.3475)), ("O", (0.3058, 0, 0.25))), 6, WYCKOFF_2, c=13.594, aliases=("eskolaite",), structure_type="corundum (D5_1)", surfaces=(Surface((0, 0, 1)),), ), Template( "fe2o3", "Fe2O3 (hematite)", "Fe2O3", "oxides", "R-3c", 5.035, (("Fe", (0, 0, 0.3553)), ("O", (0.3059, 0, 0.25))), 6, WYCKOFF_2, c=13.747, aliases=("hematite", "alpha-fe2o3"), structure_type="corundum (D5_1)", surfaces=(Surface((0, 0, 1)),), density=5.26, ), ) # --- perovskites and spinels ---------------------------------------------------- for _name, _a_site, _b_site, _lattice, _rho in ( ("srtio3", "Sr", "Ti", 3.905, 5.12), ("ktao3", "K", "Ta", 3.989, None), ("bazro3", "Ba", "Zr", 4.193, None), ): _add( Template( _name, f"{_a_site}{_b_site}O3 (cubic perovskite)", f"{_a_site}{_b_site}O3", "perovskites", "Pm-3m", _lattice, ((_b_site, (0, 0, 0)), (_a_site, (0.5, 0.5, 0.5)), ("O", (0.5, 0, 0))), 1, WYCKOFF_2, structure_type="cubic perovskite (E2_1)", surfaces=(Surface((1, 0, 0)), Surface((1, 1, 0))), density=_rho, ) ) for _name, _formula, _tet, _oct, _lattice, _x, _rho, _aliases in ( ("mgal2o4", "MgAl2O4", "Mg", "Al", 8.083, 0.2624, 3.58, ("spinel",)), ("fe3o4", "Fe3O4", "Fe", "Fe", 8.396, 0.2549, 5.17, ("magnetite",)), ("znal2o4", "ZnAl2O4", "Zn", "Al", 8.086, 0.264, None, ("gahnite",)), ): _add( Template( _name, f"{_formula} (spinel)", _formula, "spinels", "Fd-3m", _lattice, ((_tet, (0.125, 0.125, 0.125)), (_oct, (0.5, 0.5, 0.5)), ("O", (_x, _x, _x))), 8, WYCKOFF_3, aliases=_aliases, structure_type="spinel (H1_1)", surfaces=(Surface((1, 0, 0)), Surface((1, 1, 1))), density=_rho, ) ) # --- layered and carbon --------------------------------------------------------- _add( Template( "graphite", "C (hexagonal graphite)", "C", "layered", "P6_3/mmc", 2.464, (("C", (0, 0, 0.25)), ("C", (_T, _TT, 0.25))), 4, "R. Trucano, R. Chen, Nature 258, 136 (1975)", c=6.711, aliases=("graphite-2h",), structure_type="graphite (A9)", surfaces=(Surface((0, 0, 1), "the basal plane; one layer is graphene"),), density=2.26, ), Template( "h-bn", "BN (hexagonal)", "BN", "layered", "P6_3/mmc", 2.504, (("B", (_T, _TT, 0.25)), ("N", (_T, _TT, 0.75))), 2, WYCKOFF_1, c=6.661, aliases=("hbn-bulk", "hexagonal-bn"), structure_type="h-BN", surfaces=(Surface((0, 0, 1)),), density=2.28, ), Template( "mos2", "MoS2 (2H, molybdenite)", "MoS2", "layered", "P6_3/mmc", 3.160, (("Mo", (_T, _TT, 0.25)), ("S", (_T, _TT, 0.621))), 2, WYCKOFF_1, c=12.295, aliases=("molybdenite", "2h-mos2"), structure_type="2H-MoS2 (C7)", surfaces=(Surface((0, 0, 1)),), ), Template( "ws2", "WS2 (2H, tungstenite)", "WS2", "layered", "P6_3/mmc", 3.153, (("W", (_T, _TT, 0.25)), ("S", (_T, _TT, 0.622))), 2, WYCKOFF_1, c=12.323, aliases=("tungstenite", "2h-ws2"), structure_type="2H-MoS2 (C7)", surfaces=(Surface((0, 0, 1)),), ), Template( "licoo2", "LiCoO2 (layered)", "LiCoO2", "layered", "R-3m", 2.816, (("Co", (0, 0, 0)), ("Li", (0, 0, 0.5)), ("O", (0, 0, 0.2396))), 3, WYCKOFF_2, c=14.05, structure_type="alpha-NaFeO2", surfaces=(Surface((0, 0, 1)), Surface((1, 0, 4))), ), Template( "calcite", "CaCO3 (calcite)", "CaCO3", "minerals", "R-3c", 4.990, (("Ca", (0, 0, 0)), ("C", (0, 0, 0.25)), ("O", (0.257, 0, 0.25))), 6, WYCKOFF_2, c=17.061, aliases=("caco3",), structure_type="calcite (G0_1)", surfaces=(Surface((1, 0, 4), "the cleavage plane (10-14)"),), density=2.71, ), ) # --- lookup ----------------------------------------------------------------------- _BY_NAME = {t.name: t for t in _TEMPLATES} if len(_BY_NAME) != len(_TEMPLATES): # pragma: no cover - guards the data above raise RuntimeError("duplicate crystal template names") def _key(text: str) -> str: return re.sub(r"[\s_]+", "-", text.strip().lower())
[docs] def all_templates() -> list[Template]: return list(_TEMPLATES)
[docs] def find(name: str) -> Template: """A template by name, alias or (when unambiguous) formula.""" key = _key(name) if key in _BY_NAME: return _BY_NAME[key] hits = [t for t in _TEMPLATES if key in {_key(a) for a in t.aliases}] if not hits: hits = [t for t in _TEMPLATES if _key(t.formula) == key] if len(hits) == 1: return hits[0] if hits: names = ", ".join(t.name for t in hits) raise ValueError(f"{name!r} matches several templates ({names}); choose one") raise ValueError(f"unknown crystal template {name!r}; see 'vibe-sketcher solid templates'")
[docs] def describe(template: Template) -> dict[str, Any]: return { "name": template.name, "title": template.title, "formula": template.formula, "category": template.category, "structure_type": template.structure_type, "space_group": template.space_group, "parameters": { k: v for k, v in ( ("a", template.a), ("b", template.b), ("c", template.c), ("beta", template.beta), ) if v is not None }, "asymmetric_unit": [{"element": e, "fractional": list(f)} for e, f in template.sites], "z": template.z, "aliases": list(template.aliases), "surfaces": [{"miller": list(s.miller), "note": s.note} for s in template.surfaces], "reference": template.reference, }
[docs] def build( name: str, *, identifier: str, cell: Literal["primitive", "conventional"] = "primitive", a: float | None = None, c: float | None = None, species_map: dict[str, str] | None = None, ) -> PeriodicStructure: """Expand a template into a crystal, optionally overriding ``a`` and ``c``.""" template = find(name) parameters = template.parameters() if a is not None or c is not None: if a is not None and a <= 0 or c is not None and c <= 0: raise ValueError("lattice lengths must be positive") scale_b = (a / template.a) if a is not None and template.b is not None else 1.0 parameters = LatticeParameters( a if a is not None else template.a, b=template.b * scale_b if template.b is not None else None, c=c if c is not None else template.c, beta=template.beta, ) structure = from_space_group( template.space_group, parameters, list(template.sites), identifier=identifier, cell=cell, species_map=species_map, ) builder = dict(structure.provenance.get("builder", {})) # type: ignore[arg-type] builder["template"] = { "name": template.name, "title": template.title, "formula": template.formula, "reference": template.reference, "overrides": {k: v for k, v in (("a", a), ("c", c)) if v is not None}, "species_map": dict(species_map or {}), } provenance = dict(structure.provenance) provenance["builder"] = builder return structure.model_copy(update={"provenance": provenance})
[docs] def markdown_page() -> str: """The manual page listing every template, generated from the data above.""" def cell(t: Template) -> str: parts = [f"a = {t.a:g}"] parts += [f"{k} = {v:g}" for k, v in (("b", t.b), ("c", t.c), ("β", t.beta)) if v] return ", ".join(parts) lines = [ "# Crystal templates", "", "<!-- Generated by scripts/generate_template_docs.py from", " src/vibesketcher/document/crystal_templates.py; do not edit by hand. -->", "", "Each template is a space group, a conventional cell in angstrom and an", "asymmetric unit. `vibe-sketcher solid crystal --template NAME` expands it", "(see [Crystal templates](solid_state_inputs.md#crystal-templates)). The", "lattice constants are representative room-temperature experimental values.", "Check the values your study depends on against the primary literature.", "", ] references: dict[str, int] = {} for category in dict.fromkeys(t.category for t in _TEMPLATES): lines += [f"## {category.capitalize()}", "", "| Name | Crystal | Space group | Cell (Å, °) | Z | Surfaces | Ref. |", "|---|---|---|---|---|---|---|"] # fmt: skip for t in _TEMPLATES: if t.category != category: continue number = references.setdefault(t.reference, len(references) + 1) surfaces = ", ".join("(" + "".join(str(i) for i in s.miller) + ")" for s in t.surfaces) lines.append( f"| `{t.name}` | {t.title} | `{t.space_group}` | {cell(t)} | {t.z} | {surfaces} | " f"[{number}] |" ) lines.append("") lines += ["## References", ""] lines += [f"{n}. {ref}" for ref, n in references.items()] return "\n".join(lines) + "\n"