home

Convex Hull of Stability

Adapted from Bartel (2022). The convex hull of stability represents the lowest energy surface in composition space for a given chemical system. It is constructed by connecting the energies of the most thermodynamically stable compounds at each composition. For visual clarity, only ground-state phases at each composition are shown. Legend:

  • Blue circles: Stable phases lying on the convex hull (blue line)
  • Red squares: Unstable phases above the hull
  • Orange dashed line: Extension of the AX–A₂X₅ hull facet to x = 0. Its intercept and the energy of A bound the chemical potential range of A (orange arrow) where AX is stable
  • ΔE_d: Decomposition enthalpy, the energy above the hull for unstable phases (A₂X), or the negative distance below the hull constructed without it for stable phases (A₂X₅)

Materials on the hull are stable against decomposition into other phases. The energy difference between a material and the hull (hull distance) quantifies its thermodynamic stability, with larger distances meaning less stable and harder to synthesize, making the convex hull a powerful tool for guiding experimental synthesis efforts.


Convex Hull of Stability

Download

PNG PDF SVG

Code

convex-hull-of-stability.typ (145 lines)
#import "@preview/cetz:0.5.3": canvas, draw
#import draw: circle, content, intersections, line, rect

#set page(width: auto, height: auto, margin: 3pt, fill: none)
#set text(size: 12pt)

#canvas({
  // Diagram dimensions and styles
  let width = 12
  let height = 8
  let point-radius = 0.15
  let line-thickness = 1.5pt
  let arrow-style = (mark: (end: "stealth"), stroke: line-thickness, fill: black)
  let hull-color = blue.darken(20%)
  let green = rgb("#4d8000")
  // Darker than Typst's red/orange/gray so labels stay legible on light and inverted dark pages
  let red = rgb("#c42a20")
  let orange = rgb("#b35500")
  let hypothetical = luma(110)

  // Draw axes first to establish named positions
  line((0, 0), (0, height), ..arrow-style, name: "y-axis-left")
  line((0, 0), (width, 0), stroke: line-thickness, name: "x-axis")
  line((width, 0), (width, height), ..arrow-style, name: "y-axis-right")

  // Stable phases at x = 0, 1/2, 5/7, 1. Both elemental references sit at Delta E_f = 0.
  for (pos, label, anchor, padding, name) in (
    ((0, height - 1), "A", "west", (left: 10pt), "a"),
    ((width / 2, 2), "AX", "north", (top: 10pt), "ax"),
    ((width * 5 / 7, 1.5), $A_2X_5$, "north", (top: 10pt), "a2x5"),
    ((width, height - 1), "X", "east", (right: 10pt), "x"),
  ) {
    circle(pos, radius: point-radius, fill: hull-color, name: name)
    content(pos, label, anchor: anchor, padding: padding)
  }

  // Unstable phases at x = 1/3, 7/9
  for (pos, label, name, anchor) in (
    ((width / 3, height - 1.5), $A_2X$, "a2x", "south"),
    // Upper-left label keeps clear of the hypothetical hull leaving A₂X₇ to the upper right
    ((width * 7 / 9, 3.7), $A_2X_7$, "a2x7", "south-east"),
  ) {
    rect((rel: (-0.15, -0.15), to: pos), (rel: (0.3, 0.3)), fill: red, stroke: .5pt, name: name)
    content(pos, label, anchor: anchor, padding: (bottom: 8pt))
  }

  for (start, end) in (("a", "ax"), ("ax", "a2x5"), ("a2x5", "x")) {
    line(start, end, stroke: hull-color + 2.5pt, name: "hull-" + start + "-" + end)
  }
  content(
    (rel: (-1.8, -.8), to: "ax"),
    text(fill: hull-color)[convex hull\ of stability],
    frame: "rect",
    stroke: none,
    padding: (left: 5pt),
    fill: rgb("#cdd3da"),
    name: "hull-label",
  )
  line(
    "hull-label.north",
    "hull-a-ax.90%",
    stroke: (paint: hull-color, thickness: .5pt),
    padding: 1pt,
  )

  for (start, end) in (("ax", "a2x7"), ("a2x7", "x")) {
    line(
      start,
      end,
      stroke: (paint: hypothetical, thickness: line-thickness, dash: "dashed"),
      name: "hyp-hull-" + start + "-" + end,
    )
  }
  content(
    (rel: (0, 0.3), to: "hyp-hull-a2x7-x.mid"),
    text(fill: hypothetical, size: 13pt)[hypothetical hull for\ evaluating $A_2X_5$],
    anchor: "east",
  )

  // Vertical arrow from the (hypothetical) hull down or up to a phase: its decomposition energy
  let decomposition-arrow(phase, hull-segments, color) = {
    // Intersection helpers must not expand the visible diagram's bounds.
    draw.hide(line((rel: (0, 3), to: phase), (rel: (0, -3), to: phase), name: phase + "-vertical"))
    intersections(phase + "-isect", phase + "-vertical", ..hull-segments)
    line(
      phase + "-isect.0",
      phase,
      mark: (end: "stealth", fill: color),
      stroke: color + line-thickness,
      name: "arrow-" + phase,
    )
  }
  let reaction-box = (frame: "rect", padding: (1pt, 3pt), anchor: "west")

  decomposition-arrow("a2x", ("hull-a-ax", "hull-ax-a2x5"), red)
  content((rel: (-0.5, 0), to: "arrow-a2x.60%"), text(fill: red)[$Delta E_d$])
  content(
    (rel: (.2, 0), to: "arrow-a2x.30%"),
    text(fill: red)[A + AX → A₂X],
    stroke: red + .3pt,
    fill: red.lighten(90%),
    ..reaction-box,
  )

  decomposition-arrow("a2x5", ("hyp-hull-ax-a2x7", "hyp-hull-a2x7-x"), green)
  content("arrow-a2x5.mid", text(fill: green)[$Delta E_d$], anchor: "east", padding: (right: 3pt))
  content(
    (rel: (0.1, 0), to: "arrow-a2x5.mid"),
    text(fill: green)[4/5 AX + 3/5 A₂X₇ → A₂X₅],
    stroke: green + .3pt,
    fill: green.lighten(90%),
    ..reaction-box,
  )

  // The AX–A₂X₅ hull facet extended to x = 0 bounds μ_A from below; the A–AX facet from above.
  line(
    ("a2x5", 1000% / 3, "ax"),
    "ax",
    stroke: (paint: orange, thickness: line-thickness, dash: "dashed"),
    name: "mu-line",
  )
  content((rel: (2.5, 0.15), to: "mu-line.start"), rotate(11deg, text(
    fill: orange,
    size: 13pt,
  )[$μ_A$ range\ where AX is stable]))
  line(
    "hull-a-ax.2%",
    "mu-line.4%",
    mark: (symbol: "stealth", fill: orange, offset: 0.1, scale: 0.6),
    stroke: orange + line-thickness,
  )

  // Legend: the unstable entry hangs below the stable marker
  let legend-label = (anchor: "west", padding: (left: 5pt))
  circle((0.5, 1), radius: point-radius, fill: hull-color, name: "legend-stable")
  content("legend-stable.east", "stable", ..legend-label)
  rect((rel: (-0.15, -0.6), to: "legend-stable"), (rel: (0.3, 0.3)), fill: red, stroke: .5pt)
  content((rel: (0.15, -0.45), to: "legend-stable"), "unstable", ..legend-label)

  content((rel: (-0.5, 0), to: "y-axis-left.mid"), rotate(
    -90deg,
    reflow: true,
  )[$Delta E_f$ (energy/atom)])
  content((rel: (0, -0.5), to: "x-axis.mid"), $x "in" A_(1-x)X_x$)
})