home

Convex Hull of Stability

Reproduced 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: Chemical potential range where AX is stable
  • ΔE_d: Decomposition enthalpy, the energy above the hull for unstable phases

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 - 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 (195 lines)

#import "@preview/cetz:0.5.2": 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-style = (stroke: blue.darken(20%) + 2.5pt)
  let hyp-hull-style = (
    stroke: (paint: gray, thickness: line-thickness, dash: "dashed"),
  )

  // 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")

  let stable-point(pos, label, anchor: "north", padding: none, ..rest) = {
    circle(pos, radius: point-radius, fill: blue.darken(20%), ..rest)
    content(pos, label, anchor: anchor, padding: padding)
  }

  for spec in (
    (pos: (0, height - 1), label: "A", args: (anchor: "west", padding: (left: 10pt), name: "a")),
    (pos: (width / 2, 2), label: "AX", args: (anchor: "north", padding: (top: 10pt), name: "ax")),
    (
      pos: (width * 5 / 7, 1.5),
      label: $A_2X_5$,
      args: (anchor: "north", padding: (top: 10pt), name: "a2x5"),
    ),
    (
      pos: (width, height - 1.5),
      label: "X",
      args: (anchor: "east", padding: (right: 10pt), name: "x"),
    ),
  ) { stable-point(spec.pos, spec.label, ..spec.args) }

  let unstable-point(pos, label, ..rest) = {
    let (x, y) = pos
    rect((x, y - 0.15), (x + 0.3, y + 0.15), fill: red, stroke: .5pt, ..rest)
    content(pos, label, anchor: "south", padding: (bottom: 8pt))
  }

  for (pos, label, name) in (
    ((width / 3, height - 1.5), $A_2X$, "a2x"),
    ((width * 7 / 9, 3.7), $A_2X_7$, "a2x7"),
  ) { unstable-point(pos, label, name: name) }

  for (start, end, name) in (
    ("a", "ax", "hull-a-ax"),
    ("ax", "a2x5", "hull-ax-a2x5"),
    ("a2x5", "x", "hull-a2x5-x"),
  ) { line(start, end, ..hull-style, name: name) }
  content(
    (rel: (-1.8, -.8), to: "ax"),
    text(fill: blue.darken(20%), size: 12pt)[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: blue.darken(20%), thickness: .5pt),
    padding: 1pt,
    name: "hull-label-line",
  )

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

  // Intersection helpers must not expand the visible diagram's bounds.
  draw.hide(line(
    (rel: (0, 3), to: "a2x"),
    (rel: (0, -3), to: "a2x"),
    stroke: none,
    name: "a2x-vertical",
  ))
  intersections("a2x-isect", "a2x-vertical", "hull-a-ax", "hull-ax-a2x5")

  line(
    "a2x-isect.0",
    "a2x",
    mark: (end: "stealth", fill: red),
    stroke: red + line-thickness,
    name: "arrow-a2x",
  )
  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, size: 12pt)[A + AX → A₂X],
    frame: "rect",
    padding: (1pt, 3pt),
    stroke: red + .3pt,
    name: "box1",
    anchor: "west",
    fill: red.lighten(90%),
  )

  // Second arrow - find intersections first
  draw.hide(line(
    (rel: (0, 3), to: "a2x5"),
    (rel: (0, -3), to: "a2x5"),
    stroke: none,
    name: "a2x5-vertical",
  ))
  intersections("a2x5-isect", "a2x5-vertical", "hyp-hull-ax-a2x7", "hyp-hull-a2x7-x")

  line(
    "a2x5-isect.0",
    "a2x5",
    mark: (end: "stealth", fill: rgb("#4d8000")),
    stroke: rgb("#4d8000") + line-thickness,
    name: "arrow-a2x5",
  )
  content(
    "arrow-a2x5.mid",
    text(fill: rgb("#4d8000"))[$Delta E_d$],
    anchor: "east",
    padding: (right: 3pt),
  )
  content(
    (rel: (0.1, 0), to: "arrow-a2x5.mid"),
    text(fill: rgb("#4d8000"), size: 12pt)[4/5 AX + 3/5 A₂X₇ → A₂X₅],
    frame: "rect",
    padding: (1pt, 3pt),
    stroke: rgb("#4d8000") + .3pt,
    name: "box2-label",
    anchor: "west",
    fill: rgb("#4d8000").lighten(90%),
  )

  line(
    (0, height - 4.5),
    "ax",
    stroke: (paint: orange, thickness: line-thickness, dash: "dashed"),
    name: "mu-line",
  )
  content((rel: (2.4, 0), to: "mu-line.start"), rotate(14deg)[#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,
    name: "mu-arrow",
  )

  circle(
    (0.5, 1),
    radius: point-radius,
    fill: blue.darken(20%),
    name: "legend-stable",
  )
  content("legend-stable.east", "stable", anchor: "west", padding: (left: 5pt))
  rect(
    (0.5 - 0.15, 1 - 0.6),
    (0.5 + 0.15, 1 - 0.3),
    fill: red,
    stroke: red,
    name: "legend-unstable",
  )
  content((0.5 + 0.15, 1 - 0.45), "unstable", anchor: "west", padding: (
    left: 5pt,
  ))

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