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 SCGOValidationError 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/
├── go_ts_timing.json # optional (write_timing_json in go/ts params)
├── 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>/
On-disk layout¶
GO and TS write sibling {formula}_searches/ and {formula}_ts_results/
trees (see Output directories for path resolution). Each tree contains
datetime-tagged run_* work directories, campaign summaries, and deduplicated
exports.
Run IDs¶
Each invocation uses run_YYYYMMDD_HHMMSS_ffffff (microsecond granularity,
UTC-based):
Runner |
|
|---|---|
|
One new |
|
One shared |
|
TS mints a fresh |
Repeat run_go to add sibling run_* directories; SCGO merges prior minima
via database discovery and deduplication. get_run_directories()
lists only datetime-pattern run_* dirs; custom IDs work at runtime but are
omitted by that helper.
Per-run files¶
Under each run_* directory:
metadata.json— composition, params snapshot, provenance header (schema_version,scgo_version,created_atUTC ISO-8601 withZ,python_version,timestamp)ga_go.db/bh_go.db/simple_go.db— optimizer database (GO only)timing.json— optional wall-time breakdown (write_timing_json=True); includesrun_id,timing_schema_version, and the same provenance header fieldspair_<i>_<j>/— NEB artifacts,neb_{i}_{j}_metadata.json, and optionaltiming_{i}_{j}.jsonper pair (TS only)
Campaign-level files:
results_summary.json— run statistics and serializable TS pair resultsfinal_unique_minima/orfinal_unique_ts/— deduplicated structure exportsts_network_metadata.json— minima connectivity graph (TS only)go_ts_timing.json— GO+TS pipeline rollup at the campaign root when timing JSON is enabled ingo_paramsand/orts_params; includescurrent_go_run_id,current_ts_run_id, and relative paths to per-runtiming.jsonfiles when present
See scgo.utils.timing_report for timing JSON layout.
Provenance¶
TS results record endpoint lineage in minima_provenance (in
results_summary.json, NEB metadata, and ts_network_metadata.json):
Field |
Meaning |
|---|---|
|
Shared JSON provenance header on summaries, metadata, and timing files |
|
GO run that produced the endpoint minimum |
|
Optimizer database (basename and campaign-relative path) |
|
Row identifiers in the GO database |
|
Dedup and final-export identifiers when present |
|
Endpoint energy at pairing time (eV) |
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)
SCGODatabaseManager caching uses both TTL and a lightweight filesystem
fingerprint (database file count/path/mtime), so cached reads are invalidated
when matching database files are added, removed, or updated.
See Database access 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 (see also examples/README.md):
examples/example_pt5_gas.py: Pt5 in gas phase (timing JSON + on-disk layout documented in-script)examples/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
Each example enables write_timing_json in both go_params and ts_params
so per-run timing.json and campaign go_ts_timing.json are written. See
On-disk layout above.