Quick Start¶
SCGO helps you find the lowest-energy atomic structures using global optimization. This guide shows how to use all supported workflows.
System Types¶
You must specify one of four system types:
Type |
Use when |
|---|---|
|
Optimizing a cluster in vacuum |
|
Optimizing a cluster on a surface |
|
Optimizing a cluster with adsorbates in vacuum |
|
Optimizing a cluster with adsorbates on a surface |
For surface types, you need a surface_config. For adsorbate types, you need adsorbates.
Gas Cluster¶
Optimize a simple cluster in vacuum.
Fast test with EMT:
from scgo import run_go
from scgo.param_presets import get_testing_params
results = run_go(
["Pt"] * 4,
params=get_testing_params(),
seed=42,
system_type="gas_cluster",
)
for energy, atoms in results:
print(f"Energy: {energy:.4f} eV, Formula: {atoms.get_chemical_formula()}")
For multi-element clusters (bimetallics, oxides), atom order follows the composition list you pass in so GA crossover can pair structures safely. Initialization favours placing heavier elements first while keeping diversity across the population. Details: Cluster initialization.
Production run with MACE:
from scgo import run_go
from scgo.param_presets import get_default_params
params = get_default_params()
params["calculator_kwargs"]["model_name"] = "mace_mp_small"
results = run_go(
"Pt5",
params=params,
seed=42,
system_type="gas_cluster",
)
On a Surface¶
Use the built-in graphite surface or create your own slab.
Using graphite preset:
from scgo import run_go, make_graphite_surface_config
from scgo.param_presets import get_default_params
surface_config = make_graphite_surface_config(slab_layers=3)
results = run_go(
"Pt5",
params=get_default_params(),
seed=42,
surface_config=surface_config,
system_type="surface_cluster",
)
Use adsorption_energy() to compare adsorption energies on a slab.
Using a custom slab:
from ase.build import fcc111
from scgo import run_go, make_surface_config
from scgo.param_presets import get_default_params
slab = fcc111("Pt", size=(3, 3, 3), vacuum=10.0)
surface_config = make_surface_config(slab)
results = run_go(
"Pt4",
params=get_default_params(),
seed=42,
surface_config=surface_config,
system_type="surface_cluster",
)
Defining Custom Surfaces¶
Use SurfaceSystemConfig or the simpler make_surface_config() helper.
Slab motion options:
fix_all_slab_atoms=True: Entire slab stays frozen (default)n_relax_top_slab_layers=2: Allow top 2 layers to relaxn_fix_bottom_slab_layers=1: Freeze bottom layer onlyBoth layer counts =
None: Entire slab can relax
Do not use n_relax_top_slab_layers together with n_fix_bottom_slab_layers.
Full example:
from ase.build import fcc111
from scgo import run_go
from scgo.param_presets import get_default_params
from scgo.surface import SurfaceSystemConfig
slab = fcc111("Fe", size=(4, 4, 3), vacuum=12.0)
surface_config = SurfaceSystemConfig(
slab=slab,
adsorption_height_min=1.2,
adsorption_height_max=2.5,
fix_all_slab_atoms=False,
n_relax_top_slab_layers=2,
)
results = run_go(
"Pt5",
params=get_default_params(),
seed=42,
surface_config=surface_config,
system_type="surface_cluster",
)
With Adsorbates¶
Add adsorbate molecules (OH, CO, etc.) to your cluster.
Gas phase with adsorbate:
from ase import Atoms
from scgo import run_go
from scgo.param_presets import get_default_params
oh = Atoms("OH", positions=[[0, 0, 0], [0, 0, 0.96]])
results = run_go(
["Pt"] * 5,
params=get_default_params(),
seed=42,
system_type="gas_cluster_adsorbate",
adsorbates=oh,
)
Multiple adsorbates:
from ase import Atoms
from scgo import run_go
from scgo.param_presets import get_default_params
oh1 = Atoms("OH", positions=[[0, 0, 0], [0, 0, 0.96]])
oh2 = Atoms("OH", positions=[[0, 0, 0], [0, 0, 0.96]])
results = run_go(
"Pt5",
params=get_default_params(),
seed=42,
system_type="gas_cluster_adsorbate",
adsorbates=[oh1, oh2],
)
Defining Custom Adsorbates¶
Any ASE Atoms object is a valid adsorbate fragment. The GA will:
Place fragments on cluster surface sites
Keep fragments rigid (bonds stay intact)
Optionally reposition fragments during optimization
Tuning placement:
from ase import Atoms
from scgo import run_go
from scgo.param_presets import get_default_params
from scgo.cluster_adsorbate import ClusterAdsorbateConfig
oh = Atoms("OH", positions=[[0, 0, 0], [0, 0, 0.96]])
params = get_default_params()
params["cluster_adsorbate_config"] = ClusterAdsorbateConfig(
height_min=0.9,
height_max=2.2,
max_placement_attempts=200,
)
params["freeze_adsorbate_internal_geometry"] = True # Keep fragment rigid
results = run_go(
"Pt5",
params=params,
seed=42,
system_type="gas_cluster_adsorbate",
adsorbates=oh,
)
Use is_true_minimum() or perform_local_relaxation() to
validate or re-relax candidates outside a full GO run.
Surface + Adsorbates¶
Combine surface and adsorbates.
from ase import Atoms
from scgo import run_go, make_graphite_surface_config
from scgo.param_presets import get_default_params
surface_config = make_graphite_surface_config(slab_layers=3)
oh = Atoms("OH", positions=[[0, 0, 0], [0, 0, 0.96]])
results = run_go(
"Pt5",
params=get_default_params(),
seed=42,
surface_config=surface_config,
system_type="surface_cluster_adsorbate",
adsorbates=oh,
)
Transition States¶
Find transition states between optimized structures.
TS from existing minima (after a prior run_go or manual GO output):
from scgo import run_ts_search
from scgo.param_presets import get_ts_search_params
ts_params = get_ts_search_params(system_type="gas_cluster", seed=42)
ts_params["max_pairs"] = 10
# Campaign root: reads Pt5_searches/, writes Pt5_ts_results/ as sibling
results = run_ts_search(
"Pt5",
ts_params=ts_params,
seed=42,
output_dir="results/pt5_gas_mace",
system_type="gas_cluster",
)
# Or pass the searches directory directly (parent becomes campaign root)
results = run_ts_search(
"Pt5",
ts_params=ts_params,
seed=42,
searches_dir="results/pt5_gas_mace/Pt5_searches",
system_type="gas_cluster",
)
GO + TS combined:
from scgo import run_go_ts
from scgo.param_presets import get_torchsim_ga_params, get_ts_search_params
go_params = get_torchsim_ga_params(system_type="gas_cluster", seed=42)
go_params["optimizer_params"]["ga"].update(niter=10, population_size=50)
ts_params = get_ts_search_params(system_type="gas_cluster", seed=42)
ts_params["max_pairs"] = 15
summary = run_go_ts(
"Pt5",
go_params=go_params,
ts_params=ts_params,
seed=42,
system_type="gas_cluster",
)
On a surface:
from scgo import run_go_ts, make_graphite_surface_config
from scgo.param_presets import get_torchsim_ga_params, get_ts_search_params
surface_config = make_graphite_surface_config(slab_layers=3)
go_params = get_torchsim_ga_params(
system_type="surface_cluster",
surface_config=surface_config,
seed=42,
)
ts_params = get_ts_search_params(
system_type="surface_cluster",
surface_config=surface_config,
seed=42,
)
ts_params["max_pairs"] = 10
summary = run_go_ts(
"Pt5",
go_params=go_params,
ts_params=ts_params,
seed=42,
surface_config=surface_config,
system_type="surface_cluster",
)
Campaigns¶
Run multiple compositions in one call. Composition builders
(build_one_element_compositions, build_two_element_compositions) live in
scgo.runner_api, not the top-level scgo package.
Global optimization:
from scgo import run_go_campaign
from scgo.param_presets import get_testing_params
from scgo.runner_api import build_one_element_compositions
# Pt2, Pt3, Pt4, Pt5, Pt6
compositions = build_one_element_compositions("Pt", min_atoms=2, max_atoms=6)
results = run_go_campaign(
compositions,
params=get_testing_params(),
seed=42,
system_type="gas_cluster",
)
# results is dict[formula, list[(energy, Atoms)]]
Failed compositions (e.g. initialization ValueError on extreme
stoichiometries) are logged, recorded as empty lists in the returned dict, and
skipped so the rest of the campaign continues. See Cluster initialization
for multi-element atom ordering and placement behaviour.
Binary compositions:
from scgo import run_go_campaign
from scgo.param_presets import get_testing_params
from scgo.runner_api import build_two_element_compositions
# All Au/Pt combinations with 2-4 total atoms
compositions = build_two_element_compositions("Au", "Pt", min_atoms=2, max_atoms=4)
results = run_go_campaign(
compositions,
params=get_testing_params(),
seed=42,
system_type="gas_cluster",
)
TS from existing minima (each formula needs a prior {formula}_searches/ tree):
from scgo import run_ts_campaign
from scgo.param_presets import get_ts_search_params
from scgo.runner_api import build_one_element_compositions
compositions = build_one_element_compositions("Pt", min_atoms=4, max_atoms=6)
results = run_ts_campaign(
compositions,
ts_params=get_ts_search_params(system_type="gas_cluster", seed=42),
seed=42,
output_dir="benchmark/results", # shared campaign root
system_type="gas_cluster",
)
GO + TS for multiple compositions:
from scgo import run_go_ts_campaign
from scgo.param_presets import get_testing_params, get_ts_search_params
from scgo.runner_api import build_one_element_compositions
compositions = build_one_element_compositions("Pt", min_atoms=4, max_atoms=5)
results = run_go_ts_campaign(
compositions,
go_params=get_testing_params(),
ts_params=get_ts_search_params(system_type="gas_cluster", seed=42),
seed=42,
output_dir="benchmark/results",
system_type="gas_cluster",
)
See API Reference for full signatures.
Output directories¶
output_dir means different things depending on the runner. See also
API Reference.
Runner |
|
Default when omitted |
|---|---|---|
|
The |
|
|
Campaign parent; each composition → |
Each composition → |
|
Campaign root → |
|
|
Campaign parent; each composition → |
|
|
Campaign root (or an existing |
CWD; minima from |
|
Shared campaign root for all compositions |
CWD per composition |
output_root and output_stem (run_go_ts / run_go_ts_campaign only):
when output_dir is omitted, the default root is
{output_root or ./scgo_runs}/{output_stem or formula}_{calculator_slug}/
(for example examples/results/pt5_gas_mace/).
searches_dir (run_ts_search only): explicit path to a GO searches
directory; the campaign root becomes searches_dir.parent.
Example — ``run_go_ts`` with ``output_root`` / ``output_stem``:
GO and TS use the same campaign layout: {formula}_searches/ and
{formula}_ts_results/ are siblings; each holds run_* directories,
campaign-level summaries, and deduplicated exports. GO databases and TS pair
artifacts live directly under each run_* (TS uses pair_<i>_<j>/ subdirs).
results/pt5_gas_mace/
├── Pt5_searches/
│ ├── run_20260703_120000_123456/
│ │ ├── metadata.json
│ │ ├── timing.json # optional (write_timing_json=True)
│ │ └── ga_go.db
│ ├── results_summary.json
│ └── final_unique_minima/
└── Pt5_ts_results/
├── run_20260703_130000_654321/
│ ├── metadata.json
│ ├── timing.json # optional (write_timing_json=True)
│ └── pair_0_1/
│ └── neb_0_1_metadata.json
├── results_summary.json
├── ts_network_metadata.json
└── final_unique_ts/
└── final_unique_ts_summary.json
Example — ``run_go_campaign`` with ``output_dir=”benchmark/results”``:
benchmark/results/
├── Pt4_searches/
├── Pt5_searches/
└── Pt6_searches/
Example — gas-phase MLIP benchmark default (benchmark_Pt.py):
benchmark/results/
└── pt5_mace_mace_matpes_0/
└── Pt5_searches/
└── run_<timestamp>_<microseconds>/
Output Files¶
Global optimization and transition-state search use analogous directory
hierarchies under sibling {formula}_searches/ and {formula}_ts_results/
trees (location depends on the runner — see Output directories above). Each
tree has:
One
run_<timestamp>_<microseconds>/directory per invocationCampaign-level
results_summary.jsonand a deduplicated export directoryPer-run
metadata.json, optionaltiming.json, and optimizer DB at the run rootTS pair work units under
pair_<i>_<j>/inside each run
Global optimization ({formula}_searches/):
Pt5_searches/
├── run_<timestamp>_<microseconds>/
│ ├── metadata.json
│ ├── timing.json # optional (write_timing_json=True)
│ └── ga_go.db # or bh_go.db / simple_go.db
├── results_summary.json
└── final_unique_minima/ # XYZ files of best structures
Each invocation creates one datetime-tagged run_* directory. Repeat
run_go (or a campaign loop) for additional independent runs; results merge
across runs via database discovery and deduplication.
Run-level ``timing.json`` — when write_timing_json=True, a flat payload
is written at {run_dir}/timing.json. See scgo.utils.timing_report.
Transition state search ({formula}_ts_results/):
Pt5_ts_results/
├── run_<timestamp>_<microseconds>/
│ ├── metadata.json
│ ├── timing.json # optional (write_timing_json=True)
│ └── pair_<i>_<j>/
│ └── neb_{i}_{j}_metadata.json # + trajectory, TS/endpoints
├── results_summary.json
├── ts_network_metadata.json # connectivity graph between minima
└── final_unique_ts/
└── final_unique_ts_summary.json
Run IDs and provenance¶
Each GO or TS invocation creates a datetime-tagged directory named
run_YYYYMMDD_HHMMSS_ffffff (microsecond granularity). This tag is the
primary key for tracing artifacts across combined campaigns:
``run_go`` / ``run_trials`` — one new
run_*per invocation under{formula}_searches/.``run_go_campaign`` — a single shared
run_idfor all compositions in the campaign (generated once at campaign start unless you passrun_id=).``run_ts_search`` / ``run_go_ts`` — TS always mints a fresh
run_*under{formula}_ts_results/(independent of any GOrun_id).
Combining multiple runs: repeat run_go (or a campaign loop) to add
sibling run_* directories. SCGO discovers prior databases, merges minima,
deduplicates, and writes final_unique_minima/ exports. Only directories
matching the datetime run_* pattern are listed by
scgo.utils.run_tracking.get_run_directories(); custom run_id strings
still work at runtime but are skipped by that helper.
TS minima provenance: each NEB pair records endpoint lineage in
minima_provenance inside results_summary.json, neb_{i}_{j}_metadata.json,
and ts_network_metadata.json. Each entry links back to the GO minimum via:
Field |
Meaning |
|---|---|
|
GO run that produced the endpoint minimum |
|
Optimizer database path (basename and campaign-relative path) |
|
Row identifiers inside the GO database |
|
Dedup and final-export identifiers when present |
|
Endpoint energy at pairing time (eV) |
Timing artifacts:
Per GO/TS run:
{run_dir}/timing.jsonwhenwrite_timing_json=True.GO+TS pipeline rollup:
go_ts_timing.jsonat the campaign root when timing JSON is enabled ingo_paramsand/orts_params(seescgo.utils.timing_report).
Reading prior results¶
To reload minima from completed searches without re-running GO:
from scgo import load_previous_run_results, SCGODatabaseManager
minima = load_previous_run_results("Pt5_searches")
# Or browse databases with context-manager cleanup:
with SCGODatabaseManager(base_dir="Pt5_searches") as manager:
refs = manager.load_reference_structures("**/*.db", composition=["Pt"] * 5)
See scgo.database for HPC-oriented database access patterns.
Parameters¶
Quick parameter selection:
Preset |
Use for |
|---|---|
|
Fast tests (EMT calculator) |
|
Default production (MACE) |
|
GPU-accelerated with TorchSim |
|
Transition state search |
See All Parameters for all options and Parameter Presets for details.
Examples¶
Working examples in the repository:
examples/example_pt5_gas.py: Pt5 in gas phaseexamples/example_pt5_graphite.py: Pt5 on graphiteexamples/example_pt5_oh_gas.py: Pt5 + OH in gas phaseexamples/example_pt5_2oh_graphite.py: Pt5 + 2OH on graphite