Crystal lattices

A crystal is a lattice plus a basis: an infinite, periodic arrangement of points where each point carries one or more atoms. Every periodic calculation in vibe-qc — PeriodicSystem, run_rhf_periodic_scf, monkhorst_pack, band-structure plots — starts from one of the 14 Bravais lattices, decorated with the basis atoms that turn it into a real material.

This page is the reference for which lattice to pick, how to set it up in vibe-qc, and what the Brillouin zone of each looks like. For the sampling of the BZ (Monkhorst-Pack, IBZ reduction, mesh choice, band paths), see k-point meshes.

Lattice + basis = crystal

A Bravais lattice is the set of points

\[ \mathbf{R} = n_1 \mathbf{a}_1 + n_2 \mathbf{a}_2 + n_3 \mathbf{a}_3 \quad \text{with } n_i \in \mathbb{Z}, \]

where the lattice vectors \(\mathbf{a}_1, \mathbf{a}_2, \mathbf{a}_3\) define the parallelepiped unit cell. The basis is the list of atoms inside one unit cell:

\[ \boldsymbol\tau_j \quad j = 1, \ldots, N_\text{atoms in cell}. \]

The full crystal is then \(\{\boldsymbol\tau_j + \mathbf{R}\}\) for every \(\mathbf{R}\) on the lattice and every atom \(j\) in the basis.

Every Bravais lattice has a single point per unit cell. Crystals with 2+ atoms per cell are not multi-atom Bravais lattices — they’re a Bravais lattice with a multi-atom basis. Diamond is FCC + 2-atom basis; NaCl is FCC + 2-atom basis; HCP is hexagonal + 2-atom basis.

In vibe-qc:

import numpy as np
import vibeqc as vq

# 1. Lattice vectors as the rows of a 3×3 array (Å or bohr).
lattice = np.array([
    [3.92,  0.00,  0.00],
    [0.00,  3.92,  0.00],
    [0.00,  0.00,  3.92],
])

# 2. Basis atoms in fractional coordinates of the lattice vectors.
sysp = vq.PeriodicSystem(
    dim=3,
    lattice=lattice,
    unit_cell=[
        vq.Atom(11, [0.0, 0.0, 0.0]),  # Na at corner
        vq.Atom(17, [0.5, 0.5, 0.5]),  # Cl at body-centre
    ],
)

The lattice vectors, the choice of which atom sits at the origin, and the orientation are all conventions. Different choices are equivalent if related by a lattice translation; always physically equivalent if related by a point-group operation.

The 14 Bravais lattices

The Bravais classification groups every possible periodic 3D lattice into 14 types, organized under the 7 crystal systems:

#

Crystal system

Bravais lattices

Constraint on \(a, b, c\)

Constraint on \(\alpha, \beta, \gamma\)

1

Triclinic

aP

none

none

2

Monoclinic

mP, mC (or mB)

none

\(\alpha = \gamma = 90°\), \(\beta \neq 90°\)

3

Orthorhombic

oP, oC, oI, oF

none

\(\alpha = \beta = \gamma = 90°\)

4

Tetragonal

tP, tI

\(a = b \neq c\)

\(\alpha = \beta = \gamma = 90°\)

5

Trigonal (rhombohedral)

hR

\(a = b = c\)

\(\alpha = \beta = \gamma \neq 90°\)

6

Hexagonal

hP

\(a = b \neq c\)

\(\alpha = \beta = 90°\), \(\gamma = 120°\)

7

Cubic

cP, cI, cF

\(a = b = c\)

\(\alpha = \beta = \gamma = 90°\)

Letter codes: P = primitive, I = body-centered, F = face- centered, C (or A, B) = base-centered (one face), R = rhombohedral primitive.

The 14 unique lattices are:

Cubic:           cP    cI (BCC)    cF (FCC)
Tetragonal:      tP    tI
Orthorhombic:    oP    oC          oI            oF
Hexagonal:       hP
Trigonal:        hR
Monoclinic:      mP    mC
Triclinic:       aP

Why 14 and not (say) 7 × 4 = 28? Some centrings are equivalent to other Bravais lattices under a different choice of axes — e.g. “face-centered tetragonal” is the same as “body-centered tetragonal” with a 45° in-plane rotation.

Note

The Bravais classification covers only point groups of the lattice. The full classification of crystals (which adds the basis

  • point-group symmetries) gives the 230 space groups, of which the 14 Bravais lattices are the translational backbone. vibe-qc uses spglib to identify the space group of a PeriodicSystemattach_symmetry(sysp) returns the spglib classification + the symmetry operations.

Common materials lattices

Most QC use cases reduce to a small list of Bravais lattices with specific basis decorations. Below: the name, primitive lattice vectors (rows of the 3×3 matrix in units of the lattice constant \(a\)), and basis (in fractional coordinates of the primitive lattice).

Cubic family

Simple cubic (cP)

The textbook lattice — corners of a cube, one atom per cell.

a₁ = (a, 0, 0)
a₂ = (0, a, 0)
a₃ = (0, 0, a)

Basis: (0, 0, 0)

Real materials: α-polonium is the only stable elemental simple-cubic solid at ambient conditions. A useful toy model for periodic SCF testing — large, isotropic, easy to converge.

a = 3.50  # bohr
sysp = vq.PeriodicSystem(
    dim=3,
    lattice=a * np.eye(3),
    unit_cell=[vq.Atom(84, [0.0, 0.0, 0.0])],   # Po
)

Body-centered cubic / BCC (cI)

Conventional cubic cell with extra atom at the body center. The primitive cell is a rhombohedron one-half the volume of the conventional cubic cell.

Conventional cubic (2-atom basis):
  a₁ = (a, 0, 0); a₂ = (0, a, 0); a₃ = (0, 0, a)
  Basis: (0, 0, 0), (½, ½, ½)

Primitive (1-atom basis):
  a₁ = (a/2)·(-1,  1,  1)
  a₂ = (a/2)·( 1, -1,  1)
  a₃ = (a/2)·( 1,  1, -1)
  Basis: (0, 0, 0)

Real materials: alpha-Fe (3.16 Å), Na, K, Cr, Mo, W, V, Nb, Ta — many transition metals and alkali metals.

Face-centered cubic / FCC (cF)

Conventional cubic cell with atoms at the corners + face centers. 4 conventional-cell atoms, primitive cell is one quarter the volume.

Conventional cubic (4-atom basis):
  a₁ = (a, 0, 0); a₂ = (0, a, 0); a₃ = (0, 0, a)
  Basis: (0, 0, 0), (½, ½, 0), (½, 0, ½), (0, ½, ½)

Primitive (1-atom basis):
  a₁ = (a/2)·(0, 1, 1)
  a₂ = (a/2)·(1, 0, 1)
  a₃ = (a/2)·(1, 1, 0)
  Basis: (0, 0, 0)

Real materials: Cu, Ag, Au, Al, Ni, Pt, Pd, gamma-Fe, Pb — most ductile FCC metals plus the noble metals.

Diamond / zincblende (cF + 2-atom basis)

FCC primitive lattice + 2-atom basis at \((0,0,0)\) and \((¼,¼,¼)\). Same for diamond (both atoms = C), zincblende (one Zn, one S), GaAs (Ga + As), AlP, BN cubic, etc.

Primitive (FCC):
  a₁ = (a/2)·(0, 1, 1)
  a₂ = (a/2)·(1, 0, 1)
  a₃ = (a/2)·(1, 1, 0)
  Basis: (0, 0, 0), (¼, ¼, ¼)
a = 6.74  # bohr (Si)
sysp = vq.PeriodicSystem(
    dim=3,
    lattice=(a / 2) * np.array([
        [0, 1, 1],
        [1, 0, 1],
        [1, 1, 0],
    ]),
    unit_cell=[
        vq.Atom(14, [0.00, 0.00, 0.00]),  # Si at primitive-cell origin
        vq.Atom(14, [0.25, 0.25, 0.25]),  # Si at (¼,¼,¼)
    ],
)

Real materials: diamond (C, a = 6.74 bohr), Si (a = 10.26 bohr), Ge (a = 10.69 bohr); zincblende ZnS, GaAs, InP, InSb, AlP, AlAs, BN(c).

Rocksalt (cF + 2-atom basis)

Same FCC primitive lattice, but the second atom sits at \((½,½,½)\) — i.e. two interpenetrating FCC lattices offset by half the conventional cube diagonal.

Primitive (FCC):
  a₁ = (a/2)·(0, 1, 1)
  a₂ = (a/2)·(1, 0, 1)
  a₃ = (a/2)·(1, 1, 0)
  Basis: (0, 0, 0), (½, ½, ½)

Real materials: NaCl (a = 10.65 bohr), KCl, LiF, MgO, CaO — all the “alkali halide” / “alkaline-earth oxide” prototypes. The Madelung constant cross-validation in examples/input-madelung-constants.py runs on rocksalt + zincblende + CsCl.

CsCl (cP + 2-atom basis)

Simple cubic (not BCC!) with one atom at the corner, one at the body center. Distinct from BCC because the two basis atoms are different elements — the center atom isn’t related by translation to the corner atom.

a₁ = (a, 0, 0); a₂ = (0, a, 0); a₃ = (0, 0, a)
Basis: Cs at (0, 0, 0), Cl at (½, ½, ½)

Real materials: CsCl (a = 7.78 bohr), CsBr, NH₄Cl (high-T), TlBr.

Cubic perovskite (cP + 5-atom basis)

ABX₃ — one A cation at the corner, one B cation at the center, three X anions on the face centers.

a₁ = (a, 0, 0); a₂ = (0, a, 0); a₃ = (0, 0, a)
Basis:
  A at (0, 0, 0)        # large cation, e.g. Sr, Ba, Ca
  B at (½, ½, ½)        # small cation, e.g. Ti, Zr, Mn
  X at (½, ½, 0)        # anion
  X at (½, 0, ½)
  X at (0, ½, ½)

Real materials: SrTiO₃, BaTiO₃ (rt cubic), CaTiO₃, KMnF₃ (above the ferro-distortion transition). Ferroelectrics are typically distorted perovskites — the cubic phase is the high-symmetry parent.

Hexagonal family

Simple hexagonal / hP

a₁ = (a, 0, 0)
a₂ = (-a/2, a√3/2, 0)
a₃ = (0, 0, c)

Basis: (0, 0, 0)

Real materials: rare on its own (graphene is hP 2D); most hexagonal solids have a 2-atom basis (HCP) or 4-atom (graphite).

Hexagonal close-packed / HCP (hP + 2-atom basis)

Same lattice vectors as hP.
Basis: (0, 0, 0), (⅓, ⅔, ½)

For ideal HCP:  c/a = sqrt(8/3) ≈ 1.633

Real materials: Mg (c/a = 1.624), Zn (c/a = 1.86 — non-ideal), Be, Ti (alpha-phase), Co (alpha), Os, Ru.

Wurtzite (hP + 4-atom basis)

Hexagonal binary structure analogous to zincblende. ABAB stacking of close-packed planes (vs ABCABC for zincblende).

Same lattice vectors as hP.
Basis (X = small atom, Y = large atom):
  X at (0, 0, 0)
  X at (⅓, ⅔, ½)
  Y at (0, 0, u)            # u ≈ 3/8 ideal
  Y at (⅓, ⅔, ½ + u)

Real materials: ZnO, GaN, AlN, CdS, SiC (4H/6H polytypes), BN(h).

Graphene (2D) and graphite (3D)

Graphene is a 2D hexagonal lattice with a 2-atom basis (the two sublattices A and B). vibe-qc handles 2D systems via dim=2 on PeriodicSystem:

a = 4.65  # bohr (graphene C-C ≈ 1.42 Å → a ≈ 2.46 Å = 4.65 bohr)
sysp = vq.PeriodicSystem(
    dim=2,
    lattice=np.array([
        [a,    0.0,  0.0],
        [-a/2, a * np.sqrt(3)/2,  0.0],
        [0.0,  0.0, 30.0],   # 30 bohr vacuum perpendicular
    ]),
    unit_cell=[
        vq.Atom(6, [0.0,    0.0,    0.0]),
        vq.Atom(6, [1.0/3,  2.0/3,  0.0]),
    ],
)

Graphite is graphene stacked along \(c\) with AB Bernal stacking; 4-atom basis.

Tetragonal, orthorhombic, monoclinic, triclinic

These cover lower-symmetry structures: distorted perovskites, elemental tin (β-Sn is tI), molecular crystals (most are monoclinic or triclinic), high-Tc superconductors (oC, oI).

vibe-qc accepts arbitrary lattice vectors, so any of these works out of the box. For symmetry analysis, attach_symmetry(sysp) runs spglib and returns the space group regardless of which crystal-system label you’d pick by eye.

Primitive vs conventional cells

The primitive cell is the smallest cell that tiles all of space under lattice translations. For BCC + FCC lattices, the primitive cell is not the cubic cell most textbooks draw — it’s a rhombohedron half the volume (BCC) or quarter the volume (FCC).

You’d think you always want the primitive cell — fewer atoms = faster SCF. Two reasons to use the conventional cell instead:

  1. Symmetry. Cubic cells make x, y, z directions explicit. The k-mesh is naturally aligned to the conventional cell’s axes; band paths are conventionally specified in conventional-cell coordinates (Γ, X, M, R, K).

  2. Multi-atom bases. Diamond, NaCl, etc. need 2-atom bases on the FCC primitive cell. Some users find it easier to build the conventional cell with a multi-atom basis (4 + 4 = 8 atoms for diamond) than to set up the primitive cell + 2-atom basis. The energy is identical; the multi-k cost is roughly 4× higher for the same physical system.

vibe-qc respects whichever cell you build. Use attach_symmetry + spglib to convert between them when needed:

from vibeqc import attach_symmetry, analyze_symmetry

attach_symmetry(sysp)
sg = analyze_symmetry(sysp)
print(f"Space group: {sg.international} (#{sg.number})")
print(f"Hall:        {sg.hall}")
print(f"#operations: {len(sg.rotations)}")
# Spglib's primitive cell is sg.primitive_lattice + sg.primitive_basis

The reciprocal lattice and the Brillouin zone

For lattice vectors \(\mathbf{a}_1, \mathbf{a}_2, \mathbf{a}_3\), the reciprocal lattice is generated by

\[ \mathbf{b}_1 = 2\pi \frac{\mathbf{a}_2 \times \mathbf{a}_3}{V} \quad \mathbf{b}_2 = 2\pi \frac{\mathbf{a}_3 \times \mathbf{a}_1}{V} \quad \mathbf{b}_3 = 2\pi \frac{\mathbf{a}_1 \times \mathbf{a}_2}{V} \]

with \(V = \mathbf{a}_1 \cdot (\mathbf{a}_2 \times \mathbf{a}_3)\) the unit-cell volume. A general reciprocal-lattice vector is

\[ \mathbf{G} = m_1 \mathbf{b}_1 + m_2 \mathbf{b}_2 + m_3 \mathbf{b}_3 \quad m_i \in \mathbb{Z}. \]

The first Brillouin zone (BZ) is the Wigner-Seitz cell of the reciprocal lattice — the set of \(\mathbf{k}\) closer to \(\mathbf{G}=0\) than to any other reciprocal-lattice point. Bloch’s theorem says every electronic state in a periodic crystal can be labeled by a \(\mathbf{k}\) in this region.

The BZ shape depends on the Bravais lattice:

Real-space lattice

BZ shape

Simple cubic (cP)

Cube

BCC (cI)

Rhombic dodecahedron

FCC (cF)

Truncated octahedron

Hexagonal (hP)

Hexagonal prism

Trigonal (hR)

Distorted hexagonal prism

vibe-qc doesn’t currently provide a BZ-shape visualization tool; external packages like seekpath or pymatgen do this well.

High-symmetry points

These are the special \(\mathbf{k}\)-points where the BZ has extra symmetry. Band structures conventionally trace a path through them. The Setyawan-Curtarolo (2010) convention is what almost everyone uses today:

BZ shape

High-symmetry points

Cube (cP)

Γ, X, M, R

Rhombic dodecahedron (cI / BCC)

Γ, H, N, P

Truncated octahedron (cF / FCC)

Γ, X, K, L, U, W

Hexagonal prism (hP)

Γ, M, K, A, L, H

The \(\Gamma\) point is always at the BZ center — \(\mathbf{k} = (0,0,0)\) in any units. Other points are at half- or quarter-cell positions in reciprocal space.

For example, on FCC (using conventional-cell reciprocal vectors):

Γ  =  (0,    0,    0   )         # BZ centre
X  =  (½,    0,    ½   )         # face centre of cube
L  =  (½,    ½,    ½   )         # body-diagonal corner of cube
K  =  (3/8,  3/8,  3/4 )         # midpoint of two adjacent X's
W  =  (½,    ¼,    ¾   )         # corner of square face

The standard FCC band path is Γ X W K Γ L U W L K, which traces all of the BZ’s irreducible piece.

Setting up lattices in vibe-qc

Tip

For any non-trivial structure, import from a CIF / POSCAR file rather than typing lattice vectors by hand. Vibe-qc reads:

  • Crystal.from_cif(path) — Crystallographic Information File (the standard format for published structures).

  • read_poscar(path) — VASP 5 POSCAR.

  • Molecule.from_xyz(path) — for molecules in periodic boxes.

The Materials Project (https://materialsproject.org/), Crystallography Open Database (https://crystallography.net/cod/), and ICSD (commercial) all export CIF directly.

For test systems and small examples, building by hand is straightforward:

import numpy as np
import vibeqc as vq

# --- FCC primitive (Cu, a = 3.61 Å) -----------------------------
a = 6.82  # bohr
fcc_primitive = (a / 2) * np.array([
    [0, 1, 1],
    [1, 0, 1],
    [1, 1, 0],
])
cu_fcc = vq.PeriodicSystem(
    dim=3, lattice=fcc_primitive,
    unit_cell=[vq.Atom(29, [0, 0, 0])],
)

# --- Diamond (Si, a = 5.43 Å) -----------------------------------
a = 10.26  # bohr
si_diamond = vq.PeriodicSystem(
    dim=3, lattice=(a / 2) * np.array([
        [0, 1, 1], [1, 0, 1], [1, 1, 0],
    ]),
    unit_cell=[
        vq.Atom(14, [0.00, 0.00, 0.00]),
        vq.Atom(14, [0.25, 0.25, 0.25]),
    ],
)

# --- Rocksalt (NaCl, a = 5.64 Å) --------------------------------
a = 10.65  # bohr
nacl = vq.PeriodicSystem(
    dim=3, lattice=(a / 2) * np.array([
        [0, 1, 1], [1, 0, 1], [1, 1, 0],
    ]),
    unit_cell=[
        vq.Atom(11, [0.0, 0.0, 0.0]),
        vq.Atom(17, [0.5, 0.5, 0.5]),
    ],
)

# --- Hexagonal close packed (Mg, a = 3.21 Å, c = 5.21 Å) --------
a, c = 6.06, 9.85   # bohr
mg_hcp = vq.PeriodicSystem(
    dim=3, lattice=np.array([
        [a,    0.0,                 0.0],
        [-a/2, a * np.sqrt(3) / 2,  0.0],
        [0.0,  0.0,                 c  ],
    ]),
    unit_cell=[
        vq.Atom(12, [0.0,    0.0,    0.0]),
        vq.Atom(12, [1.0/3,  2.0/3,  0.5]),
    ],
)

What’s next

References

  • M. I. Aroyo, ed., International Tables for Crystallography, Vol. A, “Space-Group Symmetry” (8th ed., Wiley, 2024) — the authoritative reference for space groups, Wyckoff positions, and crystallographic conventions.

  • W. Setyawan and S. Curtarolo, “High-throughput electronic band structure calculations: Challenges and tools,” Comput. Mater. Sci. 49, 299 (2010) — defines the Bravais-lattice-specific k-paths used today.

  • C. Bradley and A. Cracknell, The Mathematical Theory of Symmetry in Solids (Oxford, 1972) — the textbook reference for space-group representations.

  • Spglib documentation, https://spglib.readthedocs.io/ — the symmetry-finder vibe-qc uses internally; particularly the “Definition of unit cell” page on conventions.