Geometry Optimization with Tako Script
On this page
tako.optimize changes atomic coordinates and, when requested, selected cell degrees of freedom until its force-based convergence condition is met or its step budget is exhausted. It returns a promise and creates an Opt-<N>-calc workspace.
The algorithmic theory is in Geometry Optimization. This chapter defines Script construction, branching, and output handling.
Operation contract
const result = await tako.optimize(structure, {
calculator,
properties,
optimizer,
maxSteps,
fmaxEvPerAngstrom,
maxstepAngstrom,
alpha,
trajectoryFrameInterval,
cellRelaxation,
});
| Setting | Default | Contract |
|---|---|---|
calculator | required | GFN2 or MLIP calculator defining the energy/force surface |
properties | level-dependent | Include energy and forces; cell relaxation requires supported stress |
optimizer | 'bfgs' | 'bfgs', 'fire', or 'mdmin' |
maxSteps | 500 | Requested positive iteration budget; WASM execution is capped at 500 steps, while the current returned settings record retains the requested value |
fmaxEvPerAngstrom | 0.01 | Threshold on the maximum per-atom force norm |
maxstepAngstrom | 0.04 | Fixed cell: maximum atomic displacement; variable cell: limiter over the augmented N+3 optimizer rows, which can be controlled by a scaled cell row |
alpha | 70 | Initial BFGS Hessian scale in eV/Ų; ignored by FIRE and MDMin |
trajectoryFrameInterval | 1 | Positive frame-storage interval; first/final evidence remains operation-controlled |
cellRelaxation | { mode: 'fixed' } | Fixed, full, lengths, angles, or explicit custom cell mask |
The generated tako.optimize settings reference is authoritative for accepted values and validation.
Fixed-cell molecular relaxation
const input = await tako.input("input/structure.extxyz");
const calculator = tako.calculator.gfn2({
levelOfTheory: "gfn2",
dispersion: "d4",
charge: 0,
unpaired: 0,
});
const properties = ["energy", "forces"];
const check = calculator.validate("optimization", { properties });
if (!check.ok) throw new Error(check.reasons.join("; "));
const result = await tako.optimize(input, {
calculator,
properties,
optimizer: "bfgs",
maxSteps: 300,
fmaxEvPerAngstrom: 0.01,
maxstepAngstrom: 0.04,
});
if (!result.converged) {
throw new Error(
`Optimization stopped unconverged after ${result.steps} steps; ` +
`final fmax=${result.final_fmax_ev_per_angstrom} eV/A`,
);
}
const finalStructure = tako.analysis.finalStructure(input, result);
await tako.output("output/final.extxyz", finalStructure);
await tako.output("output/result.json", result);
The explicit convergence branch is part of the scientific contract. A normally resolved promise can contain converged: false after the step budget is exhausted.
Variable-cell relaxation
const input = await tako.input("input/crystal.extxyz");
const calculator = tako.calculator.mlip({ levelOfTheory: "nequix" });
const properties = ["energy", "forces", "stress"];
const check = calculator.validate("optimization", { properties });
if (!check.ok) throw new Error(check.reasons.join("; "));
const result = await tako.optimize(input, {
calculator,
properties,
optimizer: "bfgs",
maxSteps: 300,
fmaxEvPerAngstrom: 0.02,
maxstepAngstrom: 0.04,
cellRelaxation: {
mode: "custom",
relaxLengths: [true, true, false],
relaxAngles: [false, false, false],
},
});
if (!result.converged) throw new Error("Variable-cell relaxation did not converge.");
await tako.output("output/relaxed.extxyz", tako.analysis.finalStructure(input, result));
await tako.output("output/cell-summary.json", {
cell: result.cell,
stressEvPerAngstrom3: result.stress_ev_per_angstrom3,
finalFmaxEvPerAngstrom: result.final_fmax_ev_per_angstrom,
});
relaxLengths and relaxAngles contain three booleans each. They define the released cell subspace; they are not post-processing filters. A nonperiodic structure cannot be cell-relaxed.
Selective dynamics
Apply Cartesian mobility flags to a new structure before calling the optimizer:
let constrained = tako.structure.fix(input, [0, 1, 2, 3]);
constrained = tako.structure.fix(constrained, [4], {
move: [false, false, true],
});
Omitting move freezes all components of the listed atoms. In move, true means that component is free. Retain the constrained input artifact: the final coordinates alone do not reveal which degrees of freedom were excluded.
The current optimization convergence trace can include raw force on frozen atoms, so a plateau can reflect anchor forces rather than genuine convergence. Report the constrained-force convention and inspect free-atom motion.
Result schema
| Field | Type and unit | Meaning |
|---|---|---|
converged | boolean | Force criterion met within the budget |
steps | integer | Accepted optimizer steps; zero is possible for an already converged input |
final_energy_ev | number, eV | Final potential energy |
final_energy_hartree | number, Eh | Same final energy in Hartree when supplied |
final_fmax_ev_per_angstrom | number, eV/Å | Fixed cell: maximum per-atom force norm; variable cell: maximum over the augmented atomic and scaled cell-force rows |
frames | array | Stored optimization frames |
frames[].step | integer | Optimizer step index |
frames[].energy_ev | number, eV | Frame energy |
frames[].fmax_ev_per_angstrom | number, eV/Å | Fixed cell: frame maximum per-atom force norm; variable cell: maximum over atomic and scaled cell-force rows |
frames[].maxstep_angstrom | number, Å | Fixed cell: accepted maximum atomic displacement; variable cell: maximum generalized-row step, which need not belong to an atom |
frames[].atoms | atom array | Frame atom identities and positions |
frames[].cell.vectors | 3×3 array, Å | Periodic lattice vectors when present |
atoms | atom array | Final atom identifiers, elements, Cartesian coordinates in Å, and optional selective-dynamics flags |
cell.vectors | 3×3 array, Å | Final lattice vectors for a periodic structure; cell is null for a nonperiodic structure |
stress_ev_per_angstrom3 | 3×3 array, eV/ų | Final stress when requested and supported |
tako.analysis.energyEv(result) refuses an explicitly unconverged result. tako.analysis.finalStructure(input, result) reconstructs the final structure without asserting convergence; call it only inside an explicit branch whose purpose is clear.
Staged continuation
async function optimizeStage(input, settings) {
const result = await tako.optimize(input, settings);
return {
result,
structure: tako.analysis.finalStructure(input, result),
};
}
const coarse = await optimizeStage(input, {
calculator,
properties: ["energy", "forces"],
optimizer: "fire",
maxSteps: 300,
fmaxEvPerAngstrom: 0.05,
maxstepAngstrom: 0.02,
});
if (!coarse.result.converged) {
throw new Error("Coarse stage did not reach its declared gate.");
}
const fine = await optimizeStage(coarse.structure, {
calculator,
properties: ["energy", "forces"],
optimizer: "bfgs",
maxSteps: 300,
fmaxEvPerAngstrom: 0.01,
maxstepAngstrom: 0.04,
});
if (!fine.result.converged) throw new Error("Fine stage did not converge.");
await tako.output("output/final.extxyz", fine.structure);
await tako.output("output/stages.json", {
coarse: coarse.result,
fine: fine.result,
});
The second call begins from the first geometry but starts a new optimizer workspace and curvature state. Retaining both results prevents the staged history from being mistaken for one uninterrupted BFGS trajectory.
Failure handling
| Condition | Contract | Response |
|---|---|---|
| Calculator validation rejects optimization or stress | No run should start | Change the calculator/property request |
| Structure has no cell while cell relaxation is active | Operation rejects the request | Add a physically meaningful periodic cell or use fixed mode |
| Promise rejects | Validation, asset, engine, reset, cancellation, or worker failure | Read the workspace log and preserve the error |
Promise resolves with converged: false | Step budget ended without satisfying the force gate | Inspect trajectory and trace before continuing or changing settings |
| Geometry becomes chemically invalid | Numerical steps left the intended basin or model domain | Reject the run; repair/resample the input rather than reporting its final energy |
| Energy helper throws on unconverged result | Analysis guard is working | Branch on converged; read raw energy only for clearly labelled diagnostics |
Cancellation rejects with Canceled by user and is not a numerical checkpoint. A later optimization can begin from an explicitly saved structure, but it does not resume the cancelled optimizer’s internal state.