Docs are under construction. Content may be incomplete or change.

Analysis, Reports, and Plots

Page type: Reference · Generated from src/script/apiDocs · Verified 2026-07-13T00:00:00.000Z
On this page

tako.analysis.* pulls typed values out of raw calculation results returned by tako.singlePoint/optimize/molecularDynamics/reactionPath/etc., plus two pure numeric helpers (reactionEnergy, lowerHull) for downstream thermochemistry. energyEv and reactionBarrier are convergence-guarded: they throw rather than silently reporting a number from an optimizer or NEB band that did not converge, so a script cannot accidentally report an unconverged energy as if it were final.

tako.plot.* covers two renderer families. stepSvg, heatmap, contour, scatter, and line are pure functions that return standalone, dependency-free SVG markup with no external assets, embeddable directly in a Markdown report. plotly instead returns a JSON figure spec string for the in-app interactive viewer: it renders nothing inside the script sandbox (Plotly needs a DOM the sandbox does not have), so a script writes the spec to a *.plotly.json file and the host renders it when that file is opened. heatmap and contour share a z[y][x] grid convention with row 0 plotted at the bottom.

tako.report.* assembles plain-text deliverables from data: csv serializes a column/row table, and markdown assembles a title/intro/image/sections/tables/files document. Both return plain strings; the caller writes them into the workspace with tako.write or tako.output.

tako.analysis.energyEv

tako.analysis.energyEv(result: unknown) => number

Extracts the final energy in eV from a calculation result, throwing on an engine error or a non-converged result rather than returning an untrustworthy number.

ParameterTypeDefaultContract
resultunknownA resolved result object from tako.singlePoint/optimize/molecularDynamics/phonon/vibration/stda/xrd/etc.

Returns: number — energy in eV.

  • Checked in order: result.energy_ev / energyEv / final_energy_ev / finalEnergyEv (used directly); else result.energy_hartree / energyHartree / final_energy_hartree / finalEnergyHartree (converted with *27.211386245988); else result.energy.ev; else, if result.frames is a non-empty array, recurses into the last frame.
  • Throws ‘Calculation result did not include an energy in eV.’ if result is not a record or none of the above fields are present as finite numbers.
  • Throws first if the record carries an engine error payload (message/detail/reason/error fields, or ok:false / success:false / status: “error” | “failed”).
  • Throws when result.converged === false (message includes final_fmax_ev_per_angstrom when present) — strict equality, so a result with no converged field at all (e.g. a plain single point) is NOT treated as unconverged.
const result = await tako.optimize(structure, { calculator });
const energyEv = tako.analysis.energyEv(result);

tako.analysis.finalStructure

tako.analysis.finalStructure(input: Structure, result: unknown) => Structure

Extracts the final structure produced by a calculation result, falling back to the input structure when no final geometry is present.

ParameterTypeDefaultContract
inputStructureThe structure passed into the calculation; used as the atom-shape template when the result only carries frame data, and returned unchanged as the last-resort fallback.
resultunknownA resolved calculation result.

Returns: Structure — a deep clone (JSON round-tripped), never the same object as input.

  • Checks result.final_structure / result.finalStructure / result.structure in that order for anything shaped like a Structure (has atoms/elements/elementCounts arrays) and returns a clone of the first match.
  • Otherwise, if result.frames is a non-empty array, clones input as the base and overlays the last frame’s atoms onto it by index: each atom field (element/x/y/z/fx/fy/fz/layerId) that the frame atom provides overwrites the base atom’s value, and any field the frame omits keeps the base atom’s value (or a default for atoms beyond the base’s length); frame.cell replaces the base cell when present.
  • If result is not record-shaped and has none of the above, returns input as-is (not a clone) — never throws.
const result = await tako.optimize(structure, { calculator });
await tako.output('output/final.extxyz', tako.analysis.finalStructure(structure, result));

tako.analysis.reactionBarrier

tako.analysis.reactionBarrier(result: unknown, options?: { allowUnconverged?: boolean }) => { barrierEv: number; reactionEnergyEv: number; saddleImage: number; energiesEv: number[]; relativeEnergiesEv: number[]; converged: boolean; residualEvPerAngstrom: number | null }

Convergence-guarded extractor for tako.reactionPath results: the barrier height, reaction energy, saddle-image index, and per-image energies.

ParameterTypeDefaultContract
resultunknownA resolved tako.reactionPath result; must carry both result.neb and result.reaction_barrier.
options.allowUnconvergedbooleanfalseWhen true, returns the barrier data with converged: false instead of throwing on an unconverged band.

Returns: { barrierEv, reactionEnergyEv, saddleImage, energiesEv, relativeEnergiesEv, converged, residualEvPerAngstrom }: barrierEv (eV) — highest image energy minus the first-endpoint (reactant) energy; reactionEnergyEv (eV) — final-endpoint energy minus first-endpoint energy (positive = endothermic, same sign convention as tako.analysis.reactionEnergy); saddleImage — index of the highest-energy image used for the barrier summary; energiesEv — absolute energy of every image, eV; relativeEnergiesEv — image energies relative to the first endpoint, eV; converged — result.neb.converged === true; residualEvPerAngstrom — result.neb.residual_ev_per_angstrom, or null when absent/non-finite.

  • Throws ‘Value is not a tako.reactionPath result.’ if result is not record-shaped.
  • Throws first if the record carries an engine error payload (same check as tako.analysis.energyEv).
  • Throws ‘Value is not a tako.reactionPath result: expected result.neb and result.reaction_barrier.’ if either object is missing — this is also what happens for a reactionInterpolateOnly result, which has no reaction_barrier at all.
  • When the band did not converge (neb.converged !== true) and options.allowUnconverged is not true, throws with the residual (when available) and instructs the caller to pass { allowUnconverged: true } and to state the convergence status in any report.
  • After the convergence gate, throws individually for any of reaction_barrier.barrier_ev / reaction_energy_ev / saddle_image / energies_ev / relative_energies_ev that is missing or non-finite.
const result = await tako.reactionPath(initial, { calculator, reactionFinalStructure: final });
const { barrierEv, converged } = tako.analysis.reactionBarrier(result);

tako.analysis.reactionEnergy

tako.analysis.reactionEnergy(input: { products: number[]; reactants: number[] }) => number

Computes a reaction energy in eV as the sum of product-side energies minus the sum of reactant-side energies.

ParameterTypeDefaultContract
input.productsnumber[]Product-side energies in eV (one entry per product species/phase).
input.reactantsnumber[]Reactant-side energies in eV.

Returns: number — eV. Positive means the products are higher in energy than the reactants (endothermic); negative means exothermic.

  • Plain summation with no validation: empty arrays sum to 0, and a non-finite entry propagates unchecked into the result.
  • Sign convention used by Tako’s own battery-voltage scripts: voltageV = -reactionEnergyEv / transferredCharge, with the sodiated/lithiated phase as the product and the bare host + metal reference as reactants.
const reactionEnergyEv = tako.analysis.reactionEnergy({
  products: [sodiatedResult.energyEv],
  reactants: [hostResult.energyEv, naPerAtomEv],
});

tako.analysis.lowerHull

tako.analysis.lowerHull(points: Array<{ label: string; x: number; y: number }>) => { points: HullPoint[]; stable: HullPoint[]; rejected: HullPoint[]; segments: Array<{ from: string; to: string; dx: number; dy: number; slope: number }> }

Computes the lower convex hull of labeled (x, y) points (e.g. composition vs. energy) to identify thermodynamically stable compositions along a hull of segments.

ParameterTypeDefaultContract
pointsArray<{ label: string; x: number; y: number } & Record<string, unknown>>Points to hull; each requires a non-empty label and finite x/y. Extra properties on a point pass through untouched into the returned points/stable/rejected arrays.

Returns: { points, stable, rejected, segments }: points — the input sorted by (x, then y, then label); stable — the points on the lower hull, in x order (the stable compositions); rejected — every point not on the lower hull, restored to original input order; segments — one entry per consecutive pair of stable points: { from, to, dx, dy, slope } with slope = dy/dx (e.g. eV per ion for a composition-vs-energy hull).

  • Throws ‘lowerHull points require finite x and y values.’ if any point’s x or y is not finite.
  • Throws ‘lowerHull points require a label.’ if any point’s label is falsy (including an empty string).
  • When two or more points share the same x (within 1e-9), only the lowest-y point at that x competes for the hull; every other same-x point goes straight to rejected.
const hull = tako.analysis.lowerHull([
  { label: 'Na4', x: 4, y: -98 }, { label: 'Na5', x: 5, y: -103 }, { label: 'Na6', x: 6, y: -104 },
]);

tako.plot.stepSvg

tako.plot.stepSvg(input: { xLabel: string; yLabel: string; yValue: number; label?: string }) => string

Renders a single flat step-line SVG for one scalar metric (e.g. a computed voltage), positioned along a small fixed-range axis.

ParameterTypeDefaultContract
input.xLabelstringX-axis caption.
input.yLabelstringY-axis caption.
input.yValuenumberThe scalar value to plot.
input.labelstring'Value'Caption drawn next to the value at the top-right of the plot.

Returns: string — standalone markup (760x360 viewBox), no external assets.

  • The y-axis is a FIXED, hardcoded range with tick labels 2.0/3.0/4.0, not autoscaled to yValue: values below ≈1.94 clamp to the plot’s bottom edge and values above ≈4.78 clamp to its top edge instead of rescaling further. This generator is tuned for small known-range metrics like a computed voltage, not a general-purpose scalar.
  • The printed title text is ${label} ${yValue.toFixed(2)}.
  • No input validation or throwing.
await tako.write('output/metric.svg', tako.plot.stepSvg({
  xLabel: 'reaction', yLabel: 'voltage / V', yValue: voltageV, label: 'Na intercalation',
}), { mimeType: 'image/svg+xml' });

tako.plot.heatmap

tako.plot.heatmap(input: { z: number[][]; x?: number[]; y?: number[]; xLabel?: string; yLabel?: string; title?: string; colorscale?: 'viridis' | 'plasma' | 'inferno' | 'turbo'; colorbarLabel?: string; width?: number; height?: number }) => string

Renders a 2D scalar grid as a colored cell heatmap with axes and a colorbar, as a standalone SVG. The primary presenter for a 2D potential-energy surface or scan.

ParameterTypeDefaultContract
input.znumber[][]Required, non-empty rectangular grid z[row][col] (every row the same length). Row maps to y, column maps to x.
input.xnumber[]Per-column axis values; length must equal z[0].length. Defaults to 0..nx-1.
input.ynumber[]Per-row axis values; length must equal z.length. Defaults to 0..ny-1.
input.xLabelstringX-axis caption.
input.yLabelstringY-axis caption.
input.titlestringPlot title, also used as the SVG aria-label when set.
input.colorscale'viridis' | 'plasma' | 'inferno' | 'turbo''viridis'Sequential color interpolator for cell fill and colorbar.
input.colorbarLabelstringUnits/quantity caption drawn alongside the colorbar (e.g. “energy / kcal mol-1”).
input.widthnumber720SVG viewBox width in px.
input.heightnumber480SVG viewBox height in px.

Returns: string — standalone markup with a colored cell grid, axes, and a colorbar; no external assets.

  • Throws ‘tako.plot.heatmap requires a non-empty rectangular z grid’ if z is missing, empty, or ragged; throws if x/y length does not match the grid; throws if any z/x/y value is not finite.
  • Row 0 is plotted at the BOTTOM of the chart (rows increase upward) — the conventional orientation for a 2D scan/PES, opposite of normal array row order.
  • The color domain is the flattened z grid’s min/max (a perfectly flat grid gets a +1 domain pad so the interpolator never divides by zero).
  • Axis tick count is capped at 8 per axis (always including the last index) so long axes stay legible.
await tako.write('output/pes.heatmap.svg', tako.plot.heatmap({
  z: zKcalMol, x: bondLengthsAngstrom, y: anglesDegrees,
  xLabel: 'O-H bond length / A', yLabel: 'H-O-H angle / deg', title: 'Water 2D PES',
}), { mimeType: 'image/svg+xml' });

tako.plot.contour

tako.plot.contour(input: { z: number[][]; x?: number[]; y?: number[]; thresholds?: number; xLabel?: string; yLabel?: string; title?: string; colorscale?: 'viridis' | 'plasma' | 'inferno' | 'turbo'; colorbarLabel?: string; width?: number; height?: number }) => string

Renders a 2D scalar grid as filled contour bands with outlines (d3-contour marching squares), as a standalone SVG.

ParameterTypeDefaultContract
input.znumber[][]Required, non-empty rectangular grid z[row][col], at least 2 rows and 2 columns. Row maps to y, column maps to x.
input.xnumber[]Per-column axis values; length must equal z[0].length. Defaults to 0..nx-1.
input.ynumber[]Per-row axis values; length must equal z.length. Defaults to 0..ny-1.
input.thresholdsnumber10Number of contour bands (rounded to an integer). A value that is undefined or not > 0 falls back to 10.
input.xLabelstringX-axis caption.
input.yLabelstringY-axis caption.
input.titlestringPlot title, also used as the SVG aria-label when set.
input.colorscale'viridis' | 'plasma' | 'inferno' | 'turbo''viridis'Sequential color interpolator for band fill and colorbar.
input.colorbarLabelstringUnits/quantity caption drawn alongside the colorbar.
input.widthnumber640SVG viewBox width in px.
input.heightnumber480SVG viewBox height in px.

Returns: string — standalone markup with filled contour bands, thin outline overlays, axes, and a colorbar.

  • Same rectangular-grid / axis-length / finite-value checks as tako.plot.heatmap, plus ‘tako.plot.contour requires a z grid with at least 2 rows and 2 columns.’
  • Same z[y][x] grid convention and row-0-at-bottom orientation as tako.plot.heatmap.
  • Contour geometry is computed in grid-index space (0..nx, 0..ny) by running d3-contour on the row-major-flattened grid, then mapped onto axis pixel space — thresholds are evenly spaced levels across the value range, not caller-specified level values.
await tako.write('output/pes.contour.svg', tako.plot.contour({
  z: zKcalMol, x: bondLengthsAngstrom, y: anglesDegrees, title: 'Water 2D PES',
}), { mimeType: 'image/svg+xml' });

tako.plot.scatter

tako.plot.scatter(input: { points: Array<{ x: number; y: number; label?: string }>; xLabel?: string; yLabel?: string; title?: string; width?: number; height?: number }) => string

Renders a labeled x/y point cloud (e.g. a reaction-coordinate or correlation scan) as a standalone SVG scatter plot.

ParameterTypeDefaultContract
input.pointsArray<{ x: number; y: number; label?: string }>Required, non-empty array of points; label optionally annotates a point.
input.xLabelstringX-axis caption.
input.yLabelstringY-axis caption.
input.titlestringPlot title, also used as the SVG aria-label when set.
input.widthnumber640SVG viewBox width in px.
input.heightnumber480SVG viewBox height in px.

Returns: string — standalone markup with one marker per point (labeled points get a text tag) and linear axes.

  • Throws ‘tako.plot.scatter requires a non-empty points array’ if points is missing/empty; throws if any point’s x or y is not finite.
  • Axis domains are the data’s x/y extent, padded about 8% on each side (or ±1 / ±10% around a degenerate single value) so edge points are not clipped against the axes.
  • Point labels are drawn as plain, unrotated text offset up-and-right of the marker; there is no collision avoidance for overlapping labels.
await tako.write('output/scan.svg', tako.plot.scatter({
  points: [{ x: 1.2, y: -4.1, label: 'A' }, { x: 1.5, y: -3.8 }],
  xLabel: 'distance / A', yLabel: 'energy / eV',
}), { mimeType: 'image/svg+xml' });

tako.plot.line

tako.plot.line(input: { series: Array<{ name?: string; points: Array<[number, number]> }>; xLabel?: string; yLabel?: string; title?: string; width?: number; height?: number }) => string

Renders one or more x/y series (e.g. scans along a single coordinate) as a standalone multi-series SVG line plot with a shared house color palette.

ParameterTypeDefaultContract
input.seriesArray<{ name?: string; points: Array<[number, number]> }>Required; at least one series must have a non-empty points array of [x, y] pairs. name labels the series in the legend (defaults to “Series N”, 1-based).
input.xLabelstringX-axis caption.
input.yLabelstringY-axis caption.
input.titlestringPlot title, also used as the SVG aria-label when set.
input.widthnumber640SVG viewBox width in px.
input.heightnumber480SVG viewBox height in px.

Returns: string — standalone markup with one polyline plus point markers per series, shared linear axes, and a legend (drawn only when there is more than one series).

  • Throws ‘tako.plot.line requires at least one series with points’ if no series has a non-empty points array; throws ‘tako.plot.line requires [x, y] pairs in series points’ if a point is not a 2-element array; throws if any x/y is not finite.
  • Axis domains are computed over ALL series’ points combined (one shared pair of axes), padded the same way as tako.plot.scatter.
  • Series colors cycle through the house Okabe-Ito palette (the same TAKO_PLOT_PALETTE used as tako.plot.plotly’s default colorway) by series index, wrapping after 8 series.
await tako.write('output/scan.svg', tako.plot.line({
  series: [{ name: 'GFN2', points: [[0, -1.2], [0.1, -1.5], [0.2, -1.1]] }],
  xLabel: 'coordinate', yLabel: 'energy / eV',
}), { mimeType: 'image/svg+xml' });

tako.plot.plotly

tako.plot.plotly(input: { data: Array<Record<string, unknown>>; layout?: Record<string, unknown>; config?: Record<string, unknown> }) => string

Builds an interactive Plotly figure spec (a standard Plotly.newPlot data/layout/config triple) as a JSON string, for writing to a *.plotly.json artifact that opens in the in-app Plotly viewer.

ParameterTypeDefaultContract
input.dataArray<Record<string, unknown>>Required, non-empty array of Plotly trace objects (type, x, y, z, colorscale, … — any valid Plotly trace shape).
input.layoutRecord<string, unknown>Plotly layout object (title, axes, colorbar, scene, margin, …).
input.configRecord<string, unknown>Plotly config object (e.g. { responsive: true }).

Returns: string — JSON-serialized { $schema: “tako-plotly-v1”, data, layout?, config? }.

  • Throws ‘tako.plot.plotly requires a non-empty data array of Plotly traces.’ if data is missing or empty.
  • Unlike the SVG generators, this renders NOTHING inside the script sandbox (Plotly needs a DOM). Write the returned string to a path ending in .plotly.json with { mimeType: 'application/x-tako-plotly' }; the host recognizes a file as a Plotly artifact by either the .plotly.json extension or that mimeType, and its viewer parses and renders the spec on the main thread when the file is opened.
  • The viewer merges a house theme (Inter font, white background, the same 8-color Okabe-Ito colorway the SVG line generator uses, 2x-scale PNG export) UNDER whatever layout/config the script supplies — the script’s own layout/config keys always win, recursing only into nested plain objects.
  • Also write a static SVG (heatmap/contour/line/scatter) alongside an interactive Plotly figure so a report renders without requiring the viewer to be opened.
await tako.write('output/pes.plotly.json', tako.plot.plotly({
  data: [{ type: 'surface', x, y, z }], layout: { title: 'PES' },
}), { mimeType: 'application/x-tako-plotly' });

tako.report.csv

tako.report.csv(input: { columns: string[]; rows: Array<Array<string | number | null>> }) => string

Serializes a column/row table to CSV text.

ParameterTypeDefaultContract
input.columnsstring[]Header row.
input.rowsArray<Array<string | number | null>>Data rows; a null cell becomes an empty CSV field.

Returns: string — CSV text (one line per row, header first) with a trailing newline.

  • A cell is quoted (with embedded double quotes doubled) only when it contains a comma, double quote, or newline; other cells are emitted as plain String(value).
  • No validation that each row’s length matches columns.length.
await tako.write('output/results.csv', tako.report.csv({
  columns: ['phase', 'energy_ev'],
  rows: [['Na metal', -1.23], ['host', -45.6]],
}), { mimeType: 'text/csv' });

tako.report.markdown

tako.report.markdown(input: { title: string; intro?: string; image?: { alt: string; path: string }; sections?: Array<{ title: string; body: string }>; tables?: Array<{ title: string; columns: string[]; rows: Array<Array<string | number | null>> }>; files?: string[] }) => string

Assembles a structured Markdown report (title, intro, image, sections, tables, file list) into a single Markdown string.

ParameterTypeDefaultContract
input.titlestringRendered as # title.
input.introstringA paragraph inserted verbatim right after the title.
input.image{ alt: string; path: string }One ![alt](path) image reference inserted after the intro.
input.sectionsArray<{ title: string; body: string }>Each becomes a ## title heading followed by body inserted verbatim (body is itself Markdown and may contain figures, math, or fenced diagrams).
input.tablesArray<{ title: string; columns: string[]; rows: Array<Array<string | number | null>> }>Each becomes a ## title heading and a Markdown table.
input.filesstring[]Rendered as a ## Files bulleted list of [file](file) links.

Returns: string — the assembled Markdown document (all blocks joined with newlines).

  • Only table cells (header and data rows) are escaped: | becomes \| and embedded newlines become <br>. title/intro/section bodies/file paths are inserted with NO escaping, so a section body may itself contain Markdown or HTML.
  • Each generated table’s separator row left-aligns the first column and right-aligns every other column (--- then ---: × (columns.length - 1)) — the right-alignment assumes trailing columns are numeric.
  • A null table cell renders as an empty string.
  • intro, image, sections, tables, and files are each independently optional — omit any of them to skip that block entirely.
await tako.write('analysis.md', tako.report.markdown({
  title: 'Na intercalation voltage',
  intro: `Voltage: ${voltageV.toFixed(2)} V`,
  files: ['output/summary.json'],
}), { mimeType: 'text/markdown' });

Return to the Tako Script API index.