# /// script # requires-python = ">=3.11,<3.14" # dependencies = [ # "mepd[gxtb,aam] @ git+https://github.com/atomsforhumanity/mepd.git@257dbb74a8b1e92563ea174a741579a2a5fc86a6", # "h5py>=3.10", # "matplotlib>=3.8", # ] # [tool.uv] # # mepd's own locked versions at the pinned commit (its uv.lock, via `uv export`): the # # environment mepd is tested in. Regenerate with update_pin.sh when the pin moves. # constraint-dependencies = [ # "absl-py==2.5.0 ; python_full_version >= '3.11'", # "aimnet==0.2.0 ; python_full_version >= '3.11'", # "annotated-doc==0.0.5", # "annotated-types==0.8.0", # "antlr4-python3-runtime==4.9.3 ; python_full_version >= '3.11'", # "anyio==4.15.1", # "asciitree==0.3.3", # "ase==3.29.0", # "ase-db-backends==0.11.0 ; python_full_version >= '3.11'", # "asttokens==3.0.2", # "backoff==2.2.1 ; python_full_version >= '3.11'", # "certifi==2026.7.22", # "cffi==2.1.1", # "charset-normalizer==3.5.2 ; python_full_version >= '3.11'", # "chemcloud==0.17.0", # "click==8.5.0", # "cloudpickle==3.1.2 ; python_full_version >= '3.11'", # "clusterscope==0.0.18 ; python_full_version >= '3.11'", # "colorama==0.4.6 ; sys_platform == 'win32'", # "contourpy==1.3.2 ; python_full_version < '3.11'", # "contourpy==1.3.3 ; python_full_version == '3.11.*'", # "contourpy==1.4.0 ; python_full_version >= '3.12'", # "cryptography==50.0.2 ; python_full_version >= '3.11'", # "cuda-bindings==13.4.3 ; sys_platform == 'linux'", # "cuda-pathfinder==1.8.3 ; sys_platform == 'linux'", # "cuda-toolkit==13.0.3.0 ; sys_platform == 'linux'", # "cycler==0.12.1", # "decorator==5.3.1 ; python_full_version < '3.11'", # "deprecated==1.3.1 ; python_full_version == '3.11.*'", # "deprecated==3.0.0 ; python_full_version >= '3.12'", # "e3nn==0.6.0 ; python_full_version >= '3.11'", # "eval-type-backport==0.4.0", # "exceptiongroup==1.3.1 ; python_full_version < '3.11'", # "executing==2.2.1", # "fairchem-core==2.23.0 ; python_full_version >= '3.11'", # "fastapi==0.143.0", # "fasteners==0.20 ; sys_platform != 'emscripten'", # "filelock==4.0.12", # "flatbuffers==25.12.19", # "flexcache==0.3", # "flexparser==0.4", # "fonttools==4.65.0 ; python_full_version < '3.11'", # "fonttools==4.66.1 ; python_full_version >= '3.11'", # "fsspec==2026.9.0", # "geometric==1.1.1", # "googleapis-common-protos==1.75.5 ; python_full_version >= '3.11'", # "grpcio==1.84.0 ; python_full_version >= '3.11'", # "h11==0.16.0", # "h5py==3.16.0", # "hf-xet==1.7.0 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'", # "httpcore==1.0.9", # "httpx==0.28.1", # "huggingface-hub==1.33.0", # "hydra-core==1.3.7 ; python_full_version >= '3.11'", # "idna==3.20", # "ipython==8.39.0 ; python_full_version < '3.11'", # "ipython==9.17.1 ; python_full_version >= '3.11'", # "ipython-pygments-lexers==1.1.1 ; python_full_version >= '3.11'", # "jax==0.6.2 ; python_full_version < '3.11'", # "jax==0.10.2 ; python_full_version == '3.11.*'", # "jax==0.11.2 ; python_full_version >= '3.12'", # "jaxlib==0.6.2 ; python_full_version < '3.11'", # "jaxlib==0.10.2 ; python_full_version == '3.11.*'", # "jaxlib==0.11.2 ; python_full_version >= '3.12'", # "jedi==0.20.0", # "jinja2==3.1.6", # "kiwisolver==1.5.1", # "legacy-cgi==2.6.4 ; python_full_version >= '3.13'", # "llvmlite==0.50.0", # "lmdb==1.7.3 ; python_full_version >= '3.11'", # "markdown==3.10.3 ; python_full_version < '3.11'", # "markdown==3.11 ; python_full_version >= '3.11'", # "markdown-it-py==4.2.0", # "markupsafe==3.0.4", # "matplotlib==3.10.9 ; python_full_version < '3.11'", # "matplotlib==3.11.2 ; python_full_version >= '3.11'", # "matplotlib-inline==0.2.2", # "mdurl==0.1.2", # "ml-dtypes==0.6.0", # "monty==2026.7.16 ; python_full_version >= '3.11'", # "mpmath==1.3.0", # "mypy-extensions==1.1.0 ; python_full_version >= '3.11'", # "networkx==3.4.2 ; python_full_version < '3.11'", # "networkx==3.6.1 ; python_full_version == '3.11.*'", # "networkx==3.7 ; python_full_version >= '3.12'", # "ninja==1.13.2", # "numba==0.68.0", # "numcodecs==0.13.1 ; python_full_version < '3.11'", # "numcodecs==0.15.1 ; python_full_version >= '3.11'", # "numpy==2.2.6 ; python_full_version < '3.11'", # "numpy==2.4.6 ; python_full_version == '3.11.*'", # "numpy==2.5.3 ; python_full_version >= '3.12'", # "nvalchemi-toolkit-ops==0.4.1 ; python_full_version >= '3.11'", # "nvidia-cublas==13.1.1.3 ; sys_platform == 'linux'", # "nvidia-cuda-cupti==13.0.85 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cuda-nvrtc==13.0.88 ; sys_platform == 'linux'", # "nvidia-cuda-runtime==13.0.96 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cudnn-cu13==9.20.0.48 ; sys_platform == 'linux'", # "nvidia-cufft==12.0.0.61 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cufile==1.15.1.6 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-curand==10.4.0.35 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cusolver==12.0.4.66 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cusparse==12.6.3.3 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-cusparselt-cu13==0.8.1 ; sys_platform == 'linux'", # "nvidia-nccl-cu13==2.29.7 ; sys_platform == 'linux'", # "nvidia-nvjitlink==13.4.92 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "nvidia-nvshmem-cu13==3.4.5 ; sys_platform == 'linux'", # "nvidia-nvtx==13.0.85 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')", # "omegaconf==2.3.1 ; python_full_version >= '3.11'", # "onnxruntime==1.24.3 ; python_full_version < '3.11'", # "onnxruntime==1.30.0 ; python_full_version >= '3.11'", # "openbabel-wheel==3.1.1.23", # "openmm==8.6.1", # "opentelemetry-api==1.45.1", # "opentelemetry-exporter-http-transport==0.66b1 ; python_full_version >= '3.11'", # "opentelemetry-exporter-otlp-common==0.66b1 ; python_full_version >= '3.11'", # "opentelemetry-exporter-otlp-proto-common==1.45.1 ; python_full_version >= '3.11'", # "opentelemetry-exporter-otlp-proto-http==1.45.1 ; python_full_version >= '3.11'", # "opentelemetry-proto==1.45.1 ; python_full_version >= '3.11'", # "opentelemetry-sdk==1.45.1 ; python_full_version >= '3.11'", # "opentelemetry-semantic-conventions==0.66b1 ; python_full_version >= '3.11'", # "opt-einsum==3.4.0", # "opt-einsum-fx==0.1.4 ; python_full_version >= '3.11'", # "orjson==3.13.0 ; python_full_version >= '3.11'", # "packaging==26.3", # "pandas==2.3.3 ; python_full_version < '3.11'", # "pandas==3.0.6 ; python_full_version >= '3.11'", # "parso==0.8.7", # "pdbfixer==1.12.0", # "pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32'", # "pillow==12.3.0", # "pint==0.24.4 ; python_full_version < '3.11'", # "pint==0.25.3 ; python_full_version == '3.11.*'", # "pint==0.26.1 ; python_full_version >= '3.12'", # "platformdirs==4.12.4", # "prompt-toolkit==3.0.53", # "protobuf==7.36.2", # "psutil==7.2.2", # "psycopg2-binary==2.9.13 ; python_full_version >= '3.11'", # "ptyprocess==0.7.0 ; sys_platform != 'emscripten' and sys_platform != 'win32'", # "pure-eval==0.2.4", # "py-cpuinfo==9.0.0", # "py3dmol==2.5.5", # "pyarrow==25.0.1", # "pycparser==3.1 ; implementation_name != 'PyPy'", # "pydantic==2.14.0", # "pydantic-core==2.50.0", # "pydantic-settings==2.15.0", # "pygments==2.21.0", # "pymysql==1.2.3 ; python_full_version >= '3.11'", # "pynauty==2.8.8.1", # "pyparsing==3.3.3", # "pyre-extensions==0.0.32 ; python_full_version >= '3.11'", # "pysmiles==2.1.1", # "python-dateutil==2.9.0.post0", # "python-dotenv==1.2.4", # "python-multipart==0.0.32", # "pytz==2026.5 ; python_full_version < '3.11'", # "pyyaml==6.0.3", # "qccodec==0.12.0", # "qccompute==0.14.0", # "qcconst==0.2.2", # "qcdata==0.19.0", # "qcelemental==0.51.2", # "qcengine==0.51.0", # "qcinf==0.4.1", # "qcref==0.3.0", # "rdchiral==1.1.0", # "rdkit==2026.3.6", # "regex==2026.9.29", # "requests==2.34.2 ; python_full_version >= '3.11'", # "rich==15.0.0", # "ruamel-yaml==0.19.1 ; python_full_version >= '3.11'", # "rustworkx==0.18.1", # "safetensors==0.8.0", # "scipy==1.15.3 ; python_full_version < '3.11'", # "scipy==1.17.1 ; python_full_version == '3.11.*'", # "scipy==1.18.1 ; python_full_version >= '3.12'", # "sella==2.6.0", # "setuptools==80.10.2", # "shellingham==1.5.4", # "six==1.17.0", # "stack-data==0.6.3", # "starlette==1.7.0", # "submitit==1.5.4 ; python_full_version >= '3.11'", # "sympy==1.14.0", # "tabulate==0.10.0 ; python_full_version >= '3.11'", # "tblite==0.7.0", # "tensorboard==2.21.0 ; python_full_version >= '3.11'", # "tensorboard-data-server==0.7.2 ; python_full_version >= '3.11'", # "timeout-timer==0.2.0", # "tokenizers==0.23.2", # "toml==0.10.2", # "tomli==2.5.0", # "tomli-w==1.2.0", # "torch==2.13.0", # "torchani==2.9.0", # "torchtnt==0.2.4 ; python_full_version >= '3.11'", # "tqdm==4.70.1", # "traitlets==5.16.1", # "transformers==5.19.0", # "triton==3.7.1 ; sys_platform == 'linux'", # "typer==0.27.3", # "typing-extensions==4.16.0", # "typing-inspect==0.9.0 ; python_full_version >= '3.11'", # "typing-inspection==0.4.4", # "tzdata==2026.5 ; python_full_version < '3.11' or sys_platform == 'emscripten' or sys_platform == 'win32'", # "urllib3==2.8.0 ; python_full_version >= '3.11'", # "uvicorn==0.54.0", # "vina==1.2.7", # "wandb==0.30.0 ; python_full_version >= '3.11'", # "warp-lang==1.18.0 ; python_full_version >= '3.11'", # "wcwidth==0.9.2", # "websockets==17.2 ; python_full_version >= '3.11'", # "werkzeug==3.1.9 ; python_full_version >= '3.11'", # "wheel==0.48.0", # "wrapt==2.5.0 ; python_full_version >= '3.11'", # "xxhash==4.0.1 ; python_full_version >= '3.11'", # "zarr==2.18.3 ; python_full_version < '3.11'", # "zarr==2.18.7 ; python_full_version >= '3.11'", # ] # /// """The all-bonds side of the Transition1x-TMC screen with the IRC retry (the comparison the benchmark set uses: every bond counts, metal-ligand contacts too). uv run --no-config screen_all_bonds.py retry-ids ws ALL_BONDS.jsonl > retry_ids.txt uv run --no-config screen_all_bonds.py decide ws ALL_BONDS.jsonl [--jobs 8] [--out FILE] ALL_BONDS.jsonl is ws/screen/results.jsonl as `benchmark.py screen` left it (all bonds), saved before `benchmark.py recompare --no-metal-ligand-contacts` re-decided ws/screen/results.jsonl by covalent bonds only. retry-ids: the reactions with an IRC end that changes species on minimization under either comparison; give them to `benchmark.py reirc --ids`, which retries their IRC at fmax 0.01 and decides them covalent-only. decide: the same retried IRCs (ws/reirc/) decided with all bonds (benchmark.py's own decision; the kept ends it writes go to a temporary folder, not to the workspace). Writes ws/screen/results.all-bonds-retried.jsonl (or --out): the all-bonds screen with the retry, which selects the benchmark set. A reaction whose retry did not finish keeps its first IRC's record (still left out). """ import json import sys import tempfile from concurrent.futures import ProcessPoolExecutor from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) def jl(fp): out = {} for ln in open(fp): if ln.strip(): r = json.loads(ln) out[r["id"]] = r return out def changes_species(r): return bool(r.get("irc_ends")) and not all(e.get("same_species_as_irc_end", True) for e in r["irc_ends"]) def one(args): import benchmark as bm import mepd from mepd.structures import read_frames ws, rid, row, old, new, scratch = args q, m = int(row["charge"]), int(row["multiplicity"]) d = ws / "reirc" / rid path, irc = (read_frames(d / f, charge=q, multiplicity=m) for f in ("path.xyz", "irc.xyz")) a, b, raw_a, raw_b = path[0], path[-1], irc[0], irc[-1] start, end = (mepd.load(ws / row[k], charge=q, multiplicity=m) for k in ("start", "end")) known = {id(a): new["end_energies_hartree"][0], id(b): new["end_energies_hartree"][1]} rec = {"id": rid, "ts_lowest_freqs_cm": old.get("ts_lowest_freqs_cm"), "irc_ends": [dict(e) for e in new["irc_ends"]], "ts_energy_hartree": old["ts_energy_hartree"], "seconds": old.get("seconds"), "host": old.get("host")} out = Path(scratch) / rid out.mkdir(parents=True, exist_ok=True) rec = bm._decide(rec, out, old["ts_energy_hartree"], a, b, raw_a, raw_b, start, end, lambda n: known[id(n)], {}) return {**rec, "irc_retry": {"fmax": new["irc_fmax"], "steps": new["irc_steps"]}, "irc_loose": {k: v for k, v in old.items() if k != "id"}} def decide(ws, strict, jobs, out_fp): import csv rows = {r["id"]: r for r in csv.DictReader(open(ws / "reactions.csv"))} new = jl(ws / "reirc" / "results.jsonl") todo = [i for i, r in new.items() if r.get("status") == "irc_done"] out = dict(strict) with tempfile.TemporaryDirectory() as scratch, ProcessPoolExecutor(jobs) as pool: args = [(ws, i, rows[i], strict[i], new[i], scratch) for i in todo] for k, rec in enumerate(pool.map(one, args, chunksize=20), 1): out[rec["id"]] = rec if k % 1000 == 0: print(f" {k}/{len(todo)}", file=sys.stderr, flush=True) for i, r in new.items(): # the retry failed: the first IRC's record stands (still left out) if i not in todo: out[i] = {**strict[i], "irc_retry": {"status": r["status"]}} out_fp.write_text("".join(json.dumps(r) + "\n" for r in out.values())) ok = lambda r: r.get("status") == "match" and not changes_species(r) print(f"{len(todo)} re-decided; kept {sum(ok(r) for r in strict.values())} -> {sum(ok(r) for r in out.values())}" f" -> {out_fp}") def main(): cmd, ws, strict_fp = sys.argv[1], Path(sys.argv[2]), Path(sys.argv[3]) strict = jl(strict_fp) if cmd == "retry-ids": cov = jl(ws / "screen" / "results.jsonl") ids = sorted({i for i, r in strict.items() if changes_species(r)} | {i for i, r in cov.items() if changes_species(r)}) print("\n".join(ids)) elif cmd == "decide": jobs = int(sys.argv[sys.argv.index("--jobs") + 1]) if "--jobs" in sys.argv else 4 out_fp = Path(sys.argv[sys.argv.index("--out") + 1]) if "--out" in sys.argv else \ ws / "screen" / "results.all-bonds-retried.jsonl" decide(ws, strict, jobs, out_fp) else: sys.exit(f"unknown command {cmd!r}: retry-ids or decide") if __name__ == "__main__": main()