Customized Report with an AI Agent¶
Partially AI-generated content, see the disclaimer.
This example demonstrates how to let an AI agent using the ams2026 skill render a customized Markdown report from a Jinja template. Note: The variables and fields in the template are arbitrarily named. The agent makes sure to populate them with reasonable values. The prompt and the template are shown at the end.
You may want to use this approach to get consistently rendered reports with the data that you need.
Requires: AMS2026 or later
Related documentation
Related examples
Related tutorials
Introduction¶
This example shows how the energy and C-C bond length for ethane (SMILES CC) varies as a function of frame when using the default AMS geometry optimizer with the UFF force field.
Method¶
Ethane was created from smiles CC, and the coordinates were perturbed randomly by up to 0.05 angstrom.
Results¶
Energy versus step¶
Figure 1: Energy in eV vs. step number.
C-C bond length versus step¶
Figure 2: C-C bond length in angstrom vs. step number.
Additional Figures¶
Figure 3: Final optimized ethane structure
Tables¶
Table 1: UFF geometry-optimization history. Data in tables/optimization_history.csv.
Step |
Energy (eV) |
C-C bond length (angstrom) |
|---|---|---|
1 |
0.349953 |
1.507598 |
2 |
0.336099 |
1.508151 |
3 |
0.296968 |
1.509783 |
4 |
0.239415 |
1.512406 |
5 |
0.173525 |
1.515877 |
6 |
0.110833 |
1.519987 |
7 |
0.062249 |
1.524456 |
8 |
0.034775 |
1.529320 |
9 |
0.038871 |
1.533157 |
10 |
0.038595 |
1.533076 |
11 |
0.037784 |
1.532836 |
12 |
0.036486 |
1.532441 |
13 |
0.034780 |
1.531900 |
14 |
0.032766 |
1.531223 |
15 |
0.030559 |
1.530426 |
16 |
0.028054 |
1.529428 |
17 |
0.025389 |
1.528217 |
18 |
0.022779 |
1.526794 |
19 |
0.020490 |
1.525198 |
20 |
0.018751 |
1.523551 |
21 |
0.017611 |
1.522058 |
22 |
0.017577 |
1.522075 |
23 |
0.017479 |
1.522126 |
24 |
0.017317 |
1.522209 |
25 |
0.017099 |
1.522324 |
26 |
0.016829 |
1.522467 |
27 |
0.016515 |
1.522635 |
28 |
0.016129 |
1.522844 |
29 |
0.015668 |
1.523094 |
30 |
0.015134 |
1.523383 |
31 |
0.014533 |
1.523699 |
32 |
0.013873 |
1.524016 |
33 |
0.013164 |
1.524290 |
34 |
0.012406 |
1.524458 |
35 |
0.011594 |
1.524451 |
36 |
0.010720 |
1.524208 |
37 |
0.009790 |
1.523685 |
38 |
0.008856 |
1.522870 |
39 |
0.008030 |
1.521807 |
40 |
0.007457 |
1.520670 |
41 |
0.007187 |
1.519816 |
42 |
0.007111 |
1.519386 |
43 |
0.007114 |
1.519349 |
44 |
0.007210 |
1.519469 |
45 |
0.007191 |
1.519446 |
46 |
0.007138 |
1.519383 |
47 |
0.007053 |
1.519285 |
48 |
0.006946 |
1.519167 |
49 |
0.006825 |
1.519046 |
50 |
0.006697 |
1.518941 |
51 |
0.006558 |
1.518866 |
52 |
0.006418 |
1.518854 |
53 |
0.006292 |
1.518934 |
54 |
0.006201 |
1.519112 |
Conclusion¶
The optimization contains 54 stored steps. The final UFF energy is 0.0062 eV and the final C-C bond length is 1.519 angstrom.
AMS Jobs¶
ethane_uff_optimization¶
Path: 01-run_workdir/ethane_uff_optimization
Elapsed time: 0.6 s
UFF geometry optimization of randomly perturbed ethane. This job provides the complete optimization history and final structure.
Task GeometryOptimization
System
Atoms
C -0.7125144885 -0.0353733512 0.0782009170
C 0.7904491320 0.0161435287 -0.0280875554
H -1.2208114243 0.1782613175 -0.9910626077
H -1.1613170436 0.6517431022 0.7610152982
H -1.0485308381 -1.0341335001 0.3365061019
H 1.1791437088 -0.1392159691 0.9734877502
H 1.1595506739 -0.6672605251 -0.8075175853
H 1.1043735263 1.1102780791 -0.3555186743
End
BondOrders
1 2 1.0
1 3 1.0
1 4 1.0
1 5 1.0
2 6 1.0
2 7 1.0
2 8 1.0
End
End
Engine ForceField
Type UFF
EndEngine
Prompts and Python scripts¶
Prompt (instruction for AI agent)
Use the ams2026 skill. Create scripts that run the required jobs and that can
render the jinja2 template in `report.template.md.j2` by passing the required
context.
report.template.md.j2
# {{ title }}
## Introduction
This example shows how the energy and C-C bond length for **ethane** (SMILES
`CC`) varies as a function of frame when using the default AMS geometry
optimizer with the UFF force field.
## Method
Ethane was created from smiles `CC`, and the coordinates were perturbed
randomly by up to 0.05 angstrom.
{% set NUMFIGS = 0 %}
## Results
### Energy versus step

{% set NUMFIGS = NUMFIGS + 1 %}
**Figure {{ NUMFIGS }}**: *Energy in eV vs. step number*.
### C-C bond length versus step

{% set NUMFIGS = NUMFIGS + 1 %}
**Figure {{ NUMFIGS }}**: *C-C bond length in angstrom vs. step number*.
{% if extra_figures %}
### Additional Figures
{% for fig in extra_figures %}

{% set NUMFIGS = NUMFIGS + 1 %}
**Figure {{ NUMFIGS }}**: *{{ fig.caption }}*
{% endfor %}
{% endif %}
{% if extra_tables %}
### Tables
{% for table in extra_tables %}
**Table {{ loop.index }}**: *{{ table.caption }}*. Data in `{{ table.path }}`.
{{ table.df_markdown }}
{% endfor %}
{% endif %}
## Conclusion
The optimization contains {{ num_steps }} stored steps. The final UFF energy is
{{ "%.4f" | format(final_energy) }} eV and the final C-C bond length is
{{ "%.3f" | format(final_bond_length) }} angstrom.
## AMS Jobs
{% for job_info in job_infos %}
### `{{ job_info.name }}`
**Path**: `{{ job_info.path }}`
{% if job_info.elapsed_time %}
**Elapsed time**: {{ "%.1f" | format(job_info.elapsed_time) }} s
{% endif %}
{{ job_info.description }}
```ams
{{ job_info.input }}
```
{% endfor %}
01-run.py
#!/usr/bin/env amspython
"""Run the UFF geometry optimization used by the report."""
from __future__ import annotations
import os
from pathlib import Path
from scm.base import ChemicalSystem, InputParser
from scm.plams import AMSJob, Settings, finish, init
WORKDIR = "01-run_workdir"
JOB_NAME = "ethane_uff_optimization"
def build_system() -> ChemicalSystem:
"""Build ethane and apply the requested random coordinate displacement."""
system = ChemicalSystem.from_smiles("CC")
system.perturb_coordinates(0.05, unit="angstrom")
return system
def build_settings() -> Settings:
"""Return the default AMS geometry optimizer with the UFF engine."""
settings = Settings()
settings.input.ams.Task = "GeometryOptimization"
settings.input.ForceField.Type = "UFF"
return settings
def main() -> None:
os.chdir(Path(__file__).resolve().parent)
init(folder=WORKDIR)
try:
job = AMSJob(
name=JOB_NAME,
molecule=build_system(),
settings=build_settings(),
)
InputParser().to_dict("ams", job.get_input())
print(job.get_input())
job.run()
finally:
finish()
if __name__ == "__main__":
main()
report.py
#!/usr/bin/env amspython
"""Read the latest finished AMS job and render report.template.md.j2."""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any, Dict, List
import matplotlib.pyplot as plt
import pandas as pd
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from scm.base import Units
from scm.plams import AMSJob, Molecule, view
ROOT = Path(__file__).resolve().parent
WORKDIR_STEM = "01-run_workdir"
JOB_NAME = "ethane_uff_optimization"
def natural_key(path: Path) -> List[Any]:
"""Return a key that sorts numeric suffixes by numeric value."""
return [
int(part) if part.isdigit() else part
for part in re.split(r"(\d+)", path.name)
]
def latest_workdir(stem: str) -> Path:
"""Find the newest PLAMS work directory by its natural-sort suffix."""
candidates = [path for path in ROOT.glob("{}*".format(stem)) if path.is_dir()]
if not candidates:
raise FileNotFoundError(
"No {} directory found. Run 01-run.py first.".format(stem)
)
return max(candidates, key=natural_key)
def carbon_bond_length(molecule: Molecule) -> float:
"""Return the distance between the two carbon atoms in angstrom."""
carbon_atoms = [atom for atom in molecule.atoms if atom.symbol == "C"]
if len(carbon_atoms) != 2:
raise ValueError("Expected exactly two carbon atoms in ethane")
return float(carbon_atoms[0].distance_to(carbon_atoms[1]))
def save_line_plot(
steps: List[int],
values: List[float],
ylabel: str,
output_path: Path,
) -> None:
"""Write one optimization-history line plot."""
fig, ax = plt.subplots(figsize=(6.4, 4.0))
ax.plot(steps, values, marker="o")
ax.set_xlabel("Optimization step")
ax.set_ylabel(ylabel)
ax.grid(alpha=0.25)
fig.tight_layout()
fig.savefig(str(output_path), dpi=160)
plt.close(fig)
def main() -> None:
workdir = latest_workdir(WORKDIR_STEM)
job_path = workdir / JOB_NAME
# This job supplies every numerical result and structure cited in the report.
job = AMSJob.load_external(str(job_path))
num_steps = job.results.get_history_length()
raw_energies = job.results.get_history_property("Energy")
if raw_energies is None or len(raw_energies) != num_steps:
raise ValueError("The AMS history does not contain one energy per step")
hartree_to_ev = Units.conversion_factor("hartree", "eV")
energies = [float(value) * hartree_to_ev for value in raw_energies]
molecules = [job.results.get_history_molecule(step) for step in range(1, num_steps + 1)]
if any(molecule is None for molecule in molecules):
raise ValueError("The AMS history is missing one or more molecular frames")
bond_lengths = [carbon_bond_length(molecule) for molecule in molecules]
steps = list(range(1, num_steps + 1))
figures_dir = ROOT / "figures"
tables_dir = ROOT / "tables"
figures_dir.mkdir(exist_ok=True)
tables_dir.mkdir(exist_ok=True)
energy_figure = figures_dir / "energy_vs_step.png"
bond_figure = figures_dir / "c_c_bond_length_vs_step.png"
structure_figure = figures_dir / "optimized_ethane.png"
save_line_plot(steps, energies, "Energy (eV)", energy_figure)
save_line_plot(steps, bond_lengths, "C-C bond length (angstrom)", bond_figure)
final_system = job.results.get_main_system()
view(
final_system,
guess_bonds=len(final_system.bonds) == 0,
direction="along_pca3",
width=600,
height=450,
picture_path=str(structure_figure),
)
history = pd.DataFrame(
{
"Step": steps,
"Energy (eV)": energies,
"C-C bond length (angstrom)": bond_lengths,
}
)
table_path = tables_dir / "optimization_history.csv"
history.to_csv(table_path, index=False)
elapsed_time = job.results.get_timings().get("elapsed")
context: Dict[str, Any] = {
"title": "UFF geometry optimization of ethane",
"fig_e_vs_step": {"path": energy_figure.relative_to(ROOT).as_posix()},
"fig_c_c_bond_length_vs_step": {
"path": bond_figure.relative_to(ROOT).as_posix()
},
"extra_figures": [
{
"path": structure_figure.relative_to(ROOT).as_posix(),
"caption": "Final optimized ethane structure",
}
],
"extra_tables": [
{
"path": table_path.relative_to(ROOT).as_posix(),
"caption": "UFF geometry-optimization history",
"df_markdown": history.to_markdown(
index=False, floatfmt=(".0f", ".6f", ".6f")
),
}
],
"num_steps": num_steps,
"final_energy": energies[-1],
"final_bond_length": bond_lengths[-1],
"job_infos": [
{
"name": JOB_NAME,
"path": job_path.relative_to(ROOT).as_posix(),
"elapsed_time": elapsed_time,
"description": (
"UFF geometry optimization of randomly perturbed ethane. "
"This job provides the complete optimization history and final structure."
),
"input": job.get_input().rstrip(),
}
],
}
environment = Environment(
loader=FileSystemLoader(str(ROOT)),
undefined=StrictUndefined,
autoescape=False,
keep_trailing_newline=True,
)
report = environment.get_template("report.template.md.j2").render(**context)
(ROOT / "report.md").write_text(report, encoding="utf-8")
if __name__ == "__main__":
main()
Original Markdown report
# UFF geometry optimization of ethane
## Introduction
This example shows how the energy and C-C bond length for **ethane** (SMILES
`CC`) varies as a function of frame when using the default AMS geometry
optimizer with the UFF force field.
## Method
Ethane was created from smiles `CC`, and the coordinates were perturbed
randomly by up to 0.05 angstrom.
## Results
### Energy versus step

**Figure 1**: *Energy in eV vs. step number*.
### C-C bond length versus step

**Figure 2**: *C-C bond length in angstrom vs. step number*.
### Additional Figures

**Figure 3**: *Final optimized ethane structure*
### Tables
**Table 1**: *UFF geometry-optimization history*. Data in `tables/optimization_history.csv`.
| Step | Energy (eV) | C-C bond length (angstrom) |
|-------:|--------------:|-----------------------------:|
| 1 | 0.349953 | 1.507598 |
| 2 | 0.336099 | 1.508151 |
| 3 | 0.296968 | 1.509783 |
| 4 | 0.239415 | 1.512406 |
| 5 | 0.173525 | 1.515877 |
| 6 | 0.110833 | 1.519987 |
| 7 | 0.062249 | 1.524456 |
| 8 | 0.034775 | 1.529320 |
| 9 | 0.038871 | 1.533157 |
| 10 | 0.038595 | 1.533076 |
| 11 | 0.037784 | 1.532836 |
| 12 | 0.036486 | 1.532441 |
| 13 | 0.034780 | 1.531900 |
| 14 | 0.032766 | 1.531223 |
| 15 | 0.030559 | 1.530426 |
| 16 | 0.028054 | 1.529428 |
| 17 | 0.025389 | 1.528217 |
| 18 | 0.022779 | 1.526794 |
| 19 | 0.020490 | 1.525198 |
| 20 | 0.018751 | 1.523551 |
| 21 | 0.017611 | 1.522058 |
| 22 | 0.017577 | 1.522075 |
| 23 | 0.017479 | 1.522126 |
| 24 | 0.017317 | 1.522209 |
| 25 | 0.017099 | 1.522324 |
| 26 | 0.016829 | 1.522467 |
| 27 | 0.016515 | 1.522635 |
| 28 | 0.016129 | 1.522844 |
| 29 | 0.015668 | 1.523094 |
| 30 | 0.015134 | 1.523383 |
| 31 | 0.014533 | 1.523699 |
| 32 | 0.013873 | 1.524016 |
| 33 | 0.013164 | 1.524290 |
| 34 | 0.012406 | 1.524458 |
| 35 | 0.011594 | 1.524451 |
| 36 | 0.010720 | 1.524208 |
| 37 | 0.009790 | 1.523685 |
| 38 | 0.008856 | 1.522870 |
| 39 | 0.008030 | 1.521807 |
| 40 | 0.007457 | 1.520670 |
| 41 | 0.007187 | 1.519816 |
| 42 | 0.007111 | 1.519386 |
| 43 | 0.007114 | 1.519349 |
| 44 | 0.007210 | 1.519469 |
| 45 | 0.007191 | 1.519446 |
| 46 | 0.007138 | 1.519383 |
| 47 | 0.007053 | 1.519285 |
| 48 | 0.006946 | 1.519167 |
| 49 | 0.006825 | 1.519046 |
| 50 | 0.006697 | 1.518941 |
| 51 | 0.006558 | 1.518866 |
| 52 | 0.006418 | 1.518854 |
| 53 | 0.006292 | 1.518934 |
| 54 | 0.006201 | 1.519112 |
## Conclusion
The optimization contains 54 stored steps. The final UFF energy is
0.0062 eV and the final C-C bond length is
1.519 angstrom.
## AMS Jobs
### `ethane_uff_optimization`
**Path**: `01-run_workdir/ethane_uff_optimization`
**Elapsed time**: 0.6 s
UFF geometry optimization of randomly perturbed ethane. This job provides the complete optimization history and final structure.
```ams
Task GeometryOptimization
System
Atoms
C -0.7125144885 -0.0353733512 0.0782009170
C 0.7904491320 0.0161435287 -0.0280875554
H -1.2208114243 0.1782613175 -0.9910626077
H -1.1613170436 0.6517431022 0.7610152982
H -1.0485308381 -1.0341335001 0.3365061019
H 1.1791437088 -0.1392159691 0.9734877502
H 1.1595506739 -0.6672605251 -0.8075175853
H 1.1043735263 1.1102780791 -0.3555186743
End
BondOrders
1 2 1.0
1 3 1.0
1 4 1.0
1 5 1.0
2 6 1.0
2 7 1.0
2 8 1.0
End
End
Engine ForceField
Type UFF
EndEngine
```
Note on AI-generated content¶
This page was generated by a Python script. That Python script was AI-generated.
All numbers, figures, and tables are extracted or postprocessed from actual AMS calculations, and can be transparently regenerated from the provided Python scripts.
Any scientific reasoning or citations was written by AI. This page is the actual one-shot output from using the ams2026 skill with an AI coding agent.