« home

Materials Informatics

machine learninginfo-graphicscetztikz

Structure-based materials informatics workflow. Inspired by fig. 1 in https://doi.org/10.1016/j.cpc.2019.106949.


Materials Informatics

  Download

PNGPDFSVG

  Code

  LaTeX

materials-informatics.tex (81 lines)

\documentclass[tikz]{standalone}

\usetikzlibrary{positioning, arrows.meta, calc}

\begin{document}
\begin{tikzpicture}[
  neuron/.style={circle,fill=black!25,minimum size=20,inner sep=0},
  label/.style={font=\large\bfseries, minimum size=3em},
  arrow/.style={>={LaTeX[width=5mm,length=5mm]}, ->, line width=1ex, gray, shorten <=1em, shorten >=1em},
  ]

  \begin{scope}[local bounding box=struct]
    \node[ball color=black!75, circle, white, scale=1.3] (C1) at (0, 0) {C};
    \node[ball color=black!75, circle, white, scale=1.3] (C2) at (0, -1.5) {C};
    \node[ball color=blue!75, circle, scale=1.3] (N1) at (-0.5, 1.5) {N};
    \node[ball color=red!75, circle, scale=1.3] (O1) at (1.8, 0.5) {O};
    \node[ball color=white, circle] (H1) at (1.5, -2.5) {H};
    \node[ball color=white, circle] (H2) at (0, -3) {H};
    \node[ball color=white, circle] (H3) at (-1.5, -2.5) {H};
    \node[ball color=white, circle] (H4) at (-2, 0.75) {H};
    \node[ball color=white, circle] (H5) at (1, 2) {H};
    \draw[gray, line width=1mm] (H1) -- (C2) -- (C1) -- (N1) (C1) -- (O1) (H2) -- (C2) (H3) -- (C2) (N1) -- (H4) (N1) -- (H5);
  \end{scope}

  % \draw[rounded corners=1em, thick] (current bounding box.south west)++(-1,-1) rectangle (current bounding box.north east);

  \node[label] at (0,-4.5) (structure) {Molecular Structure\vphantom{p}};
  \node[label, right=4.5cm of structure] (descriptor) {Descriptor};
  \node[label, right=6cm of descriptor] (model) {Model};
  \node[label, right=4.5cm of model] (property) {Property};
  \node[scale=7, above=1.8cm of property] (alpha) {$\alpha$};

  \begin{scope}[shift={($(struct.east)+(2.5,0)$)}, scale=0.6, local bounding box=desc]
    \foreach \y [count=\n] in {
        {74,25,39,20,3,3,3,3,3},
        {25,53,31,17,7,7,2,3,2},
        {39,31,37,24,3,3,3,3,3},
        {20,17,24,37,2,2,6,5,5},
        {3,7,3,2,0,1,0,0,0},
        {3,7,3,2,1,0,0,0,0},
        {3,2,3,6,0,0,0,1,1},
        {3,3,3,5,0,0,1,0,1},
        {3,2,3,5,0,0,1,1,0},
      } {
        \foreach \x [count=\m] in \y {
          \node[fill=yellow!\x!purple, minimum size=6mm, text=white] at (\m,5-\n) {\x};
        }
      }
  \end{scope}

  \begin{scope}[shift={($(desc.east)+(3,2.5)$)}, local bounding box=mod]
    \def\layersep{2.5}

    % Input layer
    \foreach \y in {1,2,3}
    \node[neuron, fill=teal!60] (i\y) at (0,-\y-0.5) {$i\y$};

    % Hidden layer
    \foreach \y in {1,...,4}
    \path node[neuron, fill=blue!50] (h\y) at (\layersep,-\y) {$h\y$};

    % Output node
    \node[neuron, fill=orange!60] (o) at (2*\layersep,-2.5) {$o$};

    % Connect every node in the input layer with every node in the hidden layer.
    \foreach \source in {1,2,3}
    \foreach \dest in {1,...,4}
    \path (i\source) edge (h\dest);

    % Connect every node in the hidden layer with the output layer
    \foreach \source in {1,...,4}
    \path (h\source) edge (o);
  \end{scope}

  \draw[arrow] (struct.east) -- ++(2.5,0);
  \draw[arrow] (desc.east) -- ++(2.5,0);
  \draw[arrow] (mod.east) -- ++(2.5,0);

\end{tikzpicture}
\end{document}

  Typst

materials-informatics.typ (347 lines)

#import "@preview/cetz:0.3.4": canvas, draw
#import draw: line, content, rect, circle

#set page(width: auto, height: auto, margin: 5pt)

#let neuron(pos, fill: white, text: none, name: none) = {
  draw.content(
    pos,
    text,
    frame: "circle",
    fill: fill,
    stroke: none,
    padding: 4pt,
    name: name,
  )
}

#let atom(pos, element, color: white, text-color: black, padding: 6pt, name: none) = {
  // Calculate the radius based on padding to match the original size
  let radius = padding + 7pt // Approximation of text size + padding

  // Draw base circle with the main color
  circle(
    pos,
    radius: radius,
    stroke: none,
    fill: color,
  )

  // Draw gradient overlay for 3D shading effect
  circle(
    pos,
    radius: radius,
    stroke: none,
    fill: gradient.radial(
      color.lighten(75%),
      color,
      color.darken(15%),
      focal-center: (30%, 25%),
      focal-radius: 5%,
      center: (35%, 30%),
    ),
  )

  // Draw the element text on top
  content(
    pos,
    text(fill: text-color, weight: "bold", size: 14pt)[#element],
    anchor: "center",
    name: name,
  )
}

#canvas({
  // Define styles
  let arrow-style = (
    stroke: rgb("#888") + 5pt,
    mark: (end: "stealth", size: 15pt),
  )

  // Set vertical center point for all elements
  let vertical-center = 0

  // Define spacing constants
  let struct-desc-spacing = 2.5 // Closer spacing between structure and descriptor
  let model-prop-spacing = 2.5 // Closer spacing between model and property
  let component-spacing = 3.5 // Spacing between other components
  let label-offset = 4 // Vertical distance from components to labels
  let label-y = vertical-center - label-offset // Fixed y-position for all labels

  // Define vertical offsets
  let molecule-y-offset = 0.5 // Move molecule up
  let matrix-y-offset = 0.3 // Move matrix up

  // Define component positions
  let struct-x = -5.5
  let struct-y = vertical-center + molecule-y-offset
  let struct-origin = (struct-x, struct-y)

  // Draw molecular structure
  // Bonds first (so atoms appear on top)
  line(
    (rel: (1.5, -2.5), to: struct-origin),
    (rel: (0, -1.5), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond1",
  )
  line(
    (rel: (0, -1.5), to: struct-origin),
    struct-origin,
    stroke: rgb("#888") + 3pt,
    name: "bond2",
  )
  line(
    struct-origin,
    (rel: (-0.5, 1.5), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond3",
  )
  line(
    struct-origin,
    (rel: (1.8, 0.5), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond4",
  )
  line(
    (rel: (0, -3), to: struct-origin),
    (rel: (0, -1.5), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond5",
  )
  line(
    (rel: (-1.5, -2.5), to: struct-origin),
    (rel: (0, -1.5), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond6",
  )
  line(
    (rel: (-0.5, 1.5), to: struct-origin),
    (rel: (-2, 0.75), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond7",
  )
  line(
    (rel: (-0.5, 1.5), to: struct-origin),
    (rel: (1, 2), to: struct-origin),
    stroke: rgb("#888") + 3pt,
    name: "bond8",
  )

  // Now draw atoms on top of bonds - with increased size
  // Carbon atoms
  atom(struct-origin, "C", color: rgb("#404040"), text-color: white, name: "C1", padding: 5pt)
  atom((rel: (0, -1.5), to: struct-origin), "C", color: rgb("#404040"), text-color: white, name: "C2", padding: 5pt)

  // Nitrogen atom
  atom((rel: (-0.5, 1.5), to: struct-origin), "N", color: rgb("#4444ff"), name: "N1", padding: 6pt)

  // Oxygen atom
  atom((rel: (1.8, 0.5), to: struct-origin), "O", color: rgb("#ff4444"), name: "O1", padding: 7pt)

  // Hydrogen atoms
  atom((rel: (1.5, -2.5), to: struct-origin), "H", color: white, padding: 2pt, name: "H1")
  atom((rel: (0, -3), to: struct-origin), "H", color: white, padding: 2pt, name: "H2")
  atom((rel: (-1.5, -2.5), to: struct-origin), "H", color: white, padding: 2pt, name: "H3")
  atom((rel: (-2, 0.75), to: struct-origin), "H", color: white, padding: 2pt, name: "H4")
  atom((rel: (1, 2), to: struct-origin), "H", color: white, padding: 2pt, name: "H5")

  // Add structure label - using fixed label-y
  content(
    (struct-x, label-y),
    text(size: 14pt, weight: "bold")[Molecular Structure],
    anchor: "center",
    name: "struct-label-text",
  )

  // Calculate right edge of structure for arrow positioning
  let struct-right-x = struct-x + 3.5
  let struct-right = (struct-right-x, struct-y)

  // Descriptor matrix - position relative to structure with closer spacing
  let desc-x = struct-right-x + struct-desc-spacing
  let desc-y = vertical-center + matrix-y-offset
  let desc-origin = (desc-x, desc-y)

  // Matrix data
  let matrix-data = (
    (74, 25, 39, 20, 3, 3, 3, 3, 3),
    (25, 53, 31, 17, 7, 7, 2, 3, 2),
    (39, 31, 37, 24, 3, 3, 3, 3, 3),
    (20, 17, 24, 37, 2, 2, 6, 5, 5),
    (3, 7, 3, 2, 0, 1, 0, 0, 0),
    (3, 7, 3, 2, 1, 0, 0, 0, 0),
    (3, 2, 3, 6, 0, 0, 0, 1, 1),
    (3, 3, 3, 5, 0, 0, 1, 0, 1),
    (3, 2, 3, 5, 0, 0, 1, 1, 0),
  )

  let cell-size = 0.6
  let matrix-width = matrix-data.at(0).len() * cell-size
  let matrix-height = matrix-data.len() * cell-size

  // Draw matrix cells
  for (row-idx, row) in matrix-data.enumerate() {
    for (col-idx, value) in row.enumerate() {
      let x = desc-x + col-idx * cell-size
      let y = desc-y - row-idx * cell-size + 2.7 * cell-size

      // Calculate color using the approach from heatmap.typ
      let max-value = 74
      rect(
        (x, y),
        (x + cell-size, y + cell-size),
        fill: rgb(
          90%, // Red stays constant at 90% for pastel effect
          50% + value / max-value * 20%, // Green increases with value
          50% - value / max-value * 20%, // Blue decreases with value
        ),
        stroke: none,
        name: "cell-" + str(row-idx) + "-" + str(col-idx),
      )

      content(
        (x + cell-size / 2, y + cell-size / 2),
        text(fill: if value < 40 { white } else { black }, size: 8pt)[#value],
        anchor: "center",
        name: "value-" + str(row-idx) + "-" + str(col-idx),
      )
    }
  }

  // Add descriptor label - using fixed label-y
  content(
    (desc-x + matrix-width / 2, label-y),
    text(size: 14pt, weight: "bold")[Descriptor],
    anchor: "center",
    name: "desc-label-text",
  )

  // Calculate right edge of descriptor for arrow positioning
  let desc-right-x = desc-x + matrix-width
  let desc-right = (desc-right-x, desc-y)

  // Neural network model - position relative to descriptor
  let model-x = desc-right-x + component-spacing
  let model-y = vertical-center
  let model-origin = (model-x, model-y)
  let layer-sep = 2.5

  // Define neural network layers
  let layers = (
    // (x-pos, neuron-count, fill-color, label-prefix)
    (model-x, 3, rgb("#40d0d0"), "i"), // Input layer - teal
    (model-x + layer-sep, 4, rgb("#8080ff"), "h"), // Hidden layer - light blue
    (model-x + 2 * layer-sep, 1, rgb("#f08040"), "o"), // Output layer - orange
  )

  // Draw all neurons FIRST (so connections appear behind nodes)
  for (idx, (x, count, fill, prefix)) in layers.enumerate() {
    for i in range(count) {
      let y = vertical-center + (i - (count - 1) / 2) * 1.5

      if idx == 2 {
        y = vertical-center // Adjust output node position
      }

      neuron(
        (x, y),
        fill: fill,
        text: $#prefix#(i+1)$,
        name: prefix + "-" + str(i + 1),
      )
    }
  }

  // THEN draw connections using node names
  for idx in range(layers.len() - 1) {
    let (_, n1, _, prefix1) = layers.at(idx)
    let (_, n2, _, prefix2) = layers.at(idx + 1)

    // Connect every node in this layer to every node in the next layer
    for i in range(n1) {
      for j in range(n2) {
        let node1-name = prefix1 + "-" + str(i + 1)
        let node2-name = prefix2 + "-" + str(j + 1)

        line(
          (node1-name),
          (node2-name),
          stroke: rgb("#aaa") + 0.5pt,
        )
      }
    }
  }

  // Add model label - using fixed label-y
  content(
    (model-x + layer-sep, label-y),
    text(size: 14pt, weight: "bold")[Model],
    anchor: "center",
    name: "model-label-text",
  )

  // Calculate right edge of model for arrow positioning
  let model-right-x = model-x + 2 * layer-sep + 1.5
  let model-right = (model-right-x, model-y)

  // Property - position relative to model with closer spacing
  let property-x = model-right-x + model-prop-spacing
  let property-y = vertical-center
  let property-origin = (property-x, property-y)

  // Draw property (alpha)
  content(
    property-origin,
    text(size: 50pt, baseline: -3pt)[$alpha$],
    anchor: "center",
    name: "property",
  )

  // Add property label - using fixed label-y
  content(
    (property-x, label-y),
    text(size: 14pt, weight: "bold")[Property],
    anchor: "center",
    name: "property-label-text",
  )

  // Define exact arrow length and positions for consistency
  let arrow-length = 1.75

  // Calculate midpoints between components for centered arrows
  let desc-left-x = desc-x - 0.5
  let model-left-x = model-x - 0.5
  let property-left-x = property-x - 1.5

  // Calculate midpoints for arrows
  let midpoint1-x = (struct-right-x + desc-left-x) / 2
  let midpoint2-x = (desc-right-x + model-left-x) / 2
  let midpoint3-x = (model-right-x + property-left-x) / 2

  // Draw arrows connecting the components
  // First arrow: Molecular Structure to Descriptor
  line(
    (midpoint1-x - arrow-length / 2, vertical-center),
    (midpoint1-x + arrow-length / 2, vertical-center),
    ..arrow-style,
    name: "arrow1",
  )

  // Second arrow: Descriptor to Model
  line(
    (midpoint2-x - arrow-length / 2, vertical-center),
    (midpoint2-x + arrow-length / 2, vertical-center),
    ..arrow-style,
    name: "arrow2",
  )

  // Third arrow: Model to Property
  line(
    (midpoint3-x - arrow-length / 2, vertical-center),
    (midpoint3-x + arrow-length / 2, vertical-center),
    ..arrow-style,
    name: "arrow3",
  )
})