StructureOptimizer#
- class pasted._optimizer.StructureOptimizer(*, n_atoms: int, charge: int, mult: int, objective: dict[str, float] | Callable[[dict[str, float]], float], elements: str | list[str] | None = None, method: str = 'annealing', max_steps: int = 5000, T_start: float = 1.0, T_end: float = 0.01, frag_threshold: float = 0.3, move_step: float = 0.5, allow_composition_moves: bool = True, allow_displacements: bool = True, lcc_threshold: float = 0.0, cov_scale: float = 1.0, relax_cycles: int = 1500, cutoff: float | None = None, n_bins: int = 20, w_atom: float = 0.5, w_spatial: float = 0.5, n_restarts: int = 1, n_replicas: int = 4, pt_swap_interval: int = 10, seed: int | None = None, verbose: bool = False)[source]#
Bases:
objectOptimise a single structure to maximise a disorder objective.
- Parameters:
n_atoms – Number of atoms.
charge – Total system charge.
mult – Spin multiplicity 2S+1.
objective – Weight dict
{"METRIC": weight, ...}or any callable(metrics: dict[str, float]) -> float. The optimizer maximises the scalar value. Use negative weights to penalise a metric.elements – Element pool — spec string (
"6,7,8"), list of symbols, orNonefor all Z = 1–106.method –
"annealing"(default),"basin_hopping", or"parallel_tempering".max_steps – Number of MC steps per restart (or per replica per restart for
"parallel_tempering"; default: 5000).T_start – Initial temperature (default: 1.0). For
"parallel_tempering"this is the highest replica temperature.T_end – Final temperature for SA (default: 0.01). For
"parallel_tempering"this is the lowest replica temperature (the coldest, most selective replica). BH uses T_start throughout.n_replicas – Number of temperature replicas for
"parallel_tempering"(default: 4). Ignored for other methods. Temperatures are spaced geometrically between T_end and T_start.pt_swap_interval – Attempt a replica-exchange swap every this many MC steps (default: 10). Ignored for other methods.
allow_displacements – When
True(default), atomic-position moves (fragment moves) are included in the MC step pool. WhenFalse, only composition moves are performed — atomic coordinates are held fixed and only element types are optimised. Set toFalsewhen exploring compositional disorder at fixed geometry (e.g. a pre-relaxed lattice). Cannot beFalsesimultaneously with allow_composition_moves.allow_composition_moves – When
True(default), each MC step randomly chooses between a fragment move (atomic displacement) and a composition move (element-type swap) with equal probability. WhenFalse, only fragment moves are performed — element types are held fixed and only atomic positions are optimised. Set toFalsewhen the composition is predetermined and should not change during optimisation. Cannot beFalsesimultaneously with allow_displacements.frag_threshold – Local Q6 threshold for fragment selection (default: 0.3). Atoms with local Q6 > threshold are preferentially displaced.
move_step – Maximum displacement magnitude per coordinate step (Å, default: 0.5).
lcc_threshold – Minimum
graph_lccrequired to accept a step (default: 0.0, i.e. no connectivity constraint). Set to 0.8 to enforce that at least 80 % of atoms remain connected.cov_scale – Minimum distance scale factor for
relax_positions().relax_cycles – Max repulsion-relaxation cycles per step. Basin-Hopping uses 3× this value for its local-minimisation step.
cutoff – Distance cutoff (Å) for Steinhardt / graph metrics. Auto-computed from the element pool when
None.n_bins – Histogram bins for
H_spatial/RDF_dev(default: 20).w_atom – Weight of
H_atominH_total(default: 0.5).w_spatial – Weight of
H_spatialinH_total(default: 0.5).n_restarts – Independent optimisation runs (default: 1). The best result across all restarts is returned.
seed – Random seed (
None→ non-deterministic).verbose – Print per-step progress to stderr (default:
False).
Examples
Class API:
from pasted import StructureOptimizer opt = StructureOptimizer( n_atoms=50, charge=0, mult=1, elements="24,25,26,27,28", # Cantor alloy objective={"H_atom": 1.0, "H_spatial": 1.0, "Q6": -2.0}, method="annealing", max_steps=5000, lcc_threshold=0.8, seed=42, ) best = opt.run()
Callable objective:
opt = StructureOptimizer( ..., objective=lambda m: m["H_spatial"] - 2.0 * m["Q6"], )
- run(initial: Structure | None = None) OptimizationResult[source]#
Run
n_restartsoptimizations and return anOptimizationResult.Each restart begins from an independently generated random gas-mode structure (or from initial if provided). All per-restart results are collected, sorted by objective value (highest first), and returned together in an
OptimizationResult.OptimizationResultis list-compatible:result[0]andresult.bestboth return the highest-scoring structure, andfor s in resultiterates all restarts in rank order. Existing code that callsopt.run()and uses the return value as a singleStructureshould switch toopt.run().bestoropt.run()[0].A
UserWarningis emitted when one or more restarts fail to produce a valid initial structure.- Parameters:
initial – Starting structure. When
None(default), a random gas-mode structure is generated automatically for each restart.- Returns:
All per-restart structures sorted by objective value (highest first), plus summary metadata. Raises
RuntimeErrorif every restart fails to produce a valid initial structure.- Return type:
OptimizationResult
- Raises:
RuntimeError – When all restarts fail to produce a valid initial structure.
Examples
Best structure only:
result = opt.run() print(result.best) # highest-scoring structure print(result[0]) # same — index 0 is always the best print(result.summary()) # one-line diagnostic
All restarts:
result = opt.run() for rank, s in enumerate(result, 1): print(f"rank {rank}: f={result.objective_scores[rank-1]:.4f} {s}")
- pasted._optimizer.parse_objective_spec(specs: list[str]) dict[str, float][source]#
Parse
["METRIC:WEIGHT", ...]into a weight dict.- Parameters:
specs – Each string must be of the form
"METRIC:WEIGHT", e.g.["H_atom:1.0", "Q6:-2.0"].- Return type:
- Raises:
ValueError – On malformed strings or unknown metric names.
OptimizationResult#
- class pasted._optimizer.OptimizationResult(all_structures: list[Structure] = <factory>, objective_scores: list[float] = <factory>, n_restarts_attempted: int = 0, method: str = 'annealing')[source]
Bases:
objectReturn value of
StructureOptimizer.run().Wraps all per-restart results and exposes the best structure as a first-class attribute. Behaves like a
list[Structure]— indexing, iteration,len, andboolall work — so callers that only want the best result can access it without changing existing code:result = opt.run() best = result.best # highest-scoring Structure best = result[0] # same — index 0 is always the best for s in result: # iterate all restarts, best first print(s.metrics["H_total"])
- all_structures
All structures produced by each restart, sorted by objective value (highest first).
all_structures[0]is always the best.- Type:
list[pasted._generator.Structure]
- objective_scores
Scalar objective values corresponding to each entry in
all_structures.
- n_restarts_attempted
Number of restarts that were actually run (may be less than
n_restartswhen initial-structure generation fails).- Type:
- method
The optimization method used (
"annealing","basin_hopping", or"parallel_tempering").- Type:
Examples
Single-structure usage (backward-compatible):
result = opt.run() result.best.to_xyz() # best structure result[0].to_xyz() # same
All-restarts usage:
result = opt.run() print(result.summary()) for rank, s in enumerate(result, 1): print(f"rank {rank}: H_total={s.metrics['H_total']:.3f}")
- all_structures: list[Structure]
- property best: Structure
The structure with the highest objective value.
- method: str = 'annealing'
- n_restarts_attempted: int = 0