| |
| """Fit OC20 energy element references before starting model training.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| from pathlib import Path |
|
|
| import numpy as np |
| import yaml |
|
|
| from onescience.utils.uma.normalization.element_references import ( |
| fit_linear_references, |
| ) |
|
|
| from train import _expand_path, _loader |
|
|
|
|
| def fit_references( |
| config_path: str | Path, |
| output_path: str | Path, |
| batch_size: int = 32, |
| num_batches: int | None = None, |
| ) -> Path: |
| """Fit and save legacy ``coeff`` references consumed by the OC20 YAML.""" |
|
|
| if batch_size < 2: |
| raise ValueError("reference fitting batch_size must be at least 2") |
| if num_batches is not None and num_batches < 1: |
| raise ValueError("num_batches must be positive") |
|
|
| config_path = Path(config_path).expanduser().resolve() |
| with config_path.open(encoding="utf-8") as handle: |
| config = yaml.safe_load(handle) or {} |
|
|
| train_path = _expand_path(config.get("train")) |
| if not train_path: |
| raise ValueError(f"training data is not configured in {config_path}") |
|
|
| loader = _loader( |
| train_path, |
| batch_size=batch_size, |
| workers=int(config.get("workers", 0)), |
| max_samples=config.get("max_train_samples"), |
| train=False, |
| seed=int(config.get("seed", 0)), |
| max_atoms=config.get("max_atoms"), |
| load_balancing=False, |
| ) |
| references = fit_linear_references( |
| targets=["energy"], |
| dataset=loader.dataset, |
| batch_size=batch_size, |
| num_batches=num_batches, |
| num_workers=int(config.get("workers", 0)), |
| shuffle=False, |
| ) |
|
|
| output_path = Path(output_path).expanduser().resolve() |
| if output_path.suffix != ".npz": |
| raise ValueError("output path must use the .npz extension") |
| output_path.parent.mkdir(parents=True, exist_ok=True) |
| coefficients = ( |
| references["energy"].element_references.detach().cpu().numpy() |
| ) |
| np.savez(output_path, coeff=coefficients) |
| print(f"Saved energy element references to {output_path}") |
| return output_path |
|
|
|
|
| def _parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("--config", required=True, help="OC20 training YAML") |
| parser.add_argument("--output", required=True, help="output .npz path") |
| parser.add_argument("--batch-size", type=int, default=32) |
| parser.add_argument("--num-batches", type=int) |
| return parser.parse_args() |
|
|
|
|
| def main() -> None: |
| args = _parse_args() |
| fit_references( |
| args.config, |
| args.output, |
| batch_size=args.batch_size, |
| num_batches=args.num_batches, |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|