Unified structure-relaxation entry point.
The force field is selected by the potential config (LJ / tojax-MLFF /
torch-MLFF). The potential is built directly with its parameters, so a single
force-field-agnostic RelaxState and driver serve every backend.
Config
Bases: BaseModel
Top-level configuration for a structure relaxation run.
Source code in src/kups/application/simulations/relax.py
| class Config(BaseModel):
"""Top-level configuration for a structure relaxation run."""
run: RelaxRunConfig
potential: PotentialConfig
inp_files: tuple[str | Path, ...]
|
main()
CLI entry point for structure relaxation.
Source code in src/kups/application/simulations/relax.py
| def main() -> None:
"""CLI entry point for structure relaxation."""
cli = NanoArgs(Config)
config = cli.parse()
rich.print(config)
run(config)
rich.print(analyze_relax_file(config.run.out_file))
|
run(config)
Run a structure relaxation for the configured force field.
Source code in src/kups/application/simulations/relax.py
| def run(config: Config) -> None:
"""Run a structure relaxation for the configured force field."""
key = jax.random.key(config.run.seed or time.time_ns())
state_lens = identity_lens(RelaxState)
optimizer = make_optimizer(config.run.optimizer)
gradient = FRECHET_FILTER if config.run.optimize_cell else POSITIONS_ONLY
potential, cutoff = config.potential.build(state_lens, gradient)
propagator, opt_init = make_relax_propagator(
state_lens, potential, optimizer, gradient
)
all_particles: list[Table[ParticleId, RelaxParticles]] = []
all_systems: list[Table[SystemId, RelaxSystems]] = []
for inp_file in config.inp_files:
logging.info(f"Loading structure from {inp_file}")
particles_i, systems_i = relax_state_from_ase(inp_file)
all_particles.append(particles_i)
all_systems.append(systems_i)
particles, systems = Table.union(all_particles, all_systems)
# Torch MLFF models need extra neighbor-list capacity headroom.
multiplier = 2.0 if isinstance(config.potential, MaceConfig | UmaConfig) else 1.0
neighborlist_params = UniversalNeighborlistParameters.estimate(
particles.data.system.counts, systems, cutoff, multiplier=multiplier
)
opt_state = opt_init(particles, systems)
state = RelaxState(
particles=particles,
systems=systems,
neighborlist_params=neighborlist_params,
opt_state=opt_state,
step=jnp.array([0]),
)
logging.info("Starting relaxation")
run_relax(key, propagator, state, config.run)
|