Skip to content

Commit 2767008

Browse files
authored
Merge pull request #17 from zhaiwenxi/dpa-adapt-resolve-pretrained-all-strategies
Update dpa-adapt docs and CLI naming
2 parents 9f1a26b + 5a6400c commit 2767008

6 files changed

Lines changed: 118 additions & 38 deletions

File tree

doc/dpa_adapt/README.md

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ model.fit(train_data="/data/train", target_key="homo")
3939
model = DPAFineTuner(
4040
pretrained="DPA-3.1-3M", strategy="frozen_head", property_name="homo"
4141
)
42-
model.fit(train_data="/data/train", valid_data="/data/valid", target_key="homo")
42+
model.fit(train_data="/data/train", valid_data="/data/valid")
4343

4444
# mft — downstream property head + auxiliary force-field head jointly
4545
model = DPAFineTuner(
@@ -53,41 +53,98 @@ model.fit(train_data="/data/qm9", aux_data="/data/spice2")
5353

5454
## Data preparation
5555

56-
Your data must be in `deepmd/npy` format. `auto_convert` detects the input format automatically:
56+
DPA-ADAPT trains on `deepmd/npy` data. Use `dpa-adapt data convert` (or the Python
57+
`auto_convert` helper) to route common inputs into the right conversion pipeline:
58+
59+
- **SMILES CSV**: a `.csv` file with a `SMILES`/`smiles` column. RDKit generates 3D
60+
conformers, or existing `.mol`/`.sdf`/`.xyz`/`.pdb` files can be supplied with
61+
`mol_dir`.
62+
- **Formula CSV + POSCAR template**: pass `fmt="formula"` and `poscar=...` to create
63+
doped structures by random substitution on the host-element sublattice.
64+
- **Structure files / trajectories**: POSCAR, OUTCAR, `*.xyz`, `vasprun.xml`, ABACUS,
65+
CP2K, Gaussian, LAMMPS, ASE, `deepmd/raw`, `deepmd/npy`, LMDB, and other dpdata
66+
formats. Omit `fmt` when dpdata can infer it; set `fmt` explicitly for ambiguous
67+
inputs.
5768

5869
```python
5970
from dpa_adapt import auto_convert
6071

61-
# Structure file → dpdata (POSCAR, OUTCAR, extxyz, cif, …)
72+
# Structure file / trajectory → dpdata → deepmd/npy
6273
auto_convert("POSCAR", "./npy")
63-
auto_convert("calcs/**/OUTCAR", "./npy", fmt="vasp/outcar") # glob → batch
74+
auto_convert("OUTCAR", "./npy", fmt="vasp/outcar")
75+
auto_convert("traj.extxyz", "./npy", fmt="extxyz")
76+
77+
# Glob patterns: one match is converted as one system; multiple matches are batched.
78+
auto_convert("calcs/**/OUTCAR", "./npy_root", fmt="vasp/outcar")
79+
80+
# CSV with a SMILES column → RDKit 3D conformers → deepmd/npy.
81+
# property_col names the input target column and output label name.
82+
auto_convert(
83+
"molecules.csv",
84+
"./npy",
85+
fmt="smiles", # optional when a SMILES/smiles column is present
86+
smiles_col="SMILES",
87+
property_col="HOMO",
88+
train_ratio=0.9,
89+
)
6490

65-
# CSV with SMILES column → RDKit 3D conformers → deepmd/npy
66-
auto_convert("data.csv", "./npy", property_name="homo", property_col="HOMO")
91+
# CSV + pre-generated molecular structures: skip RDKit conformer generation.
92+
auto_convert(
93+
"molecules.csv",
94+
"./npy",
95+
fmt="smiles",
96+
smiles_col="SMILES",
97+
property_col="GAP",
98+
mol_dir="./mol_files",
99+
mol_template="id{row}.sdf",
100+
)
67101

68-
# Composition formula CSV + template POSCAR → random atomic substitution → deepmd/npy
69-
# CSV: two columns, formula and property value (header optional)
70-
# e.g. Ni0.65Gd0.15Fe0.10Co0.05Yb0.05O2H1 291.9
102+
# Composition formula CSV + template POSCAR → random atomic substitution → deepmd/npy.
103+
# CSV: header required; defaults are formula_col="formula" and property_col="Property".
104+
# e.g. formula,Property
105+
# Ni0.65Gd0.15Fe0.10Co0.05Yb0.05O2H1,291.9
71106
auto_convert(
72107
"compositions.csv",
73108
"./npy",
74109
fmt="formula",
75110
poscar="template.POSCAR",
76-
property_name="overpotential",
77-
sets=3, # random doped structures per composition (default: 1)
111+
formula_col="formula",
112+
property_col="bandgap",
113+
sets=3, # random doped structures per composition row (default: 1)
114+
seed=42,
78115
)
79116
```
80117

118+
CLI equivalents:
119+
120+
```bash
121+
# SMILES table
122+
dpa-adapt data convert --input molecules.csv --output ./npy \
123+
--fmt smiles --smiles-col SMILES --property-col HOMO --train-ratio 0.9
124+
125+
# Formula table + POSCAR template
126+
dpa-adapt data convert --input compositions.csv --output ./npy --fmt formula \
127+
--poscar template.POSCAR --formula-col formula --property-col bandgap --sets 3
128+
129+
# Structure file or glob of calculation outputs
130+
dpa-adapt data convert --input POSCAR --output ./npy
131+
dpa-adapt data convert --input "calcs/**/OUTCAR" --output ./npy_root --fmt vasp/outcar
132+
```
133+
81134
Lower-level helpers:
82135

83136
```python
84-
from dpa_adapt import convert, attach_labels, check_data
137+
from dpa_adapt import convert, batch_convert, attach_labels, check_data
85138

86-
convert("calcs/**/OUTCAR", "./npy", fmt="vasp/outcar")
139+
convert("OUTCAR", "./npy", fmt="vasp/outcar")
140+
batch_convert("calcs/**/OUTCAR", "./npy_root", fmt="vasp/outcar")
87141
attach_labels(system, head="bandgap", values=np.array([1.0, 2.0, 3.0]))
88142
check_data("/data/system") # → list[Issue]
89143
```
90144

145+
For the full option list and supported dpdata formats, see
146+
[`input_formats.md`](input_formats.md).
147+
91148
### Context features (fparam)
92149

93150
fparam lets you condition the model on system-level context such as temperature, pressure, or experimental conditions.
@@ -161,7 +218,7 @@ from dpa_adapt import (
161218
train_test_split, # formula-grouped splitting
162219
auto_convert, # format-sniffing data conversion
163220
smiles_to_npy, # CSV+SMILES → deepmd/npy
164-
formula_csv_to_npy, # composition formula CSV + POSCAR → deepmd/npy
221+
formula_to_npy, # composition formula CSV + POSCAR → deepmd/npy
165222
convert, # structure file → deepmd/npy
166223
batch_convert, # glob-based batch conversion
167224
check_data, # data sanity checks
@@ -196,10 +253,16 @@ X = extract_descriptors(
196253

197254
```bash
198255
# Data conversion
256+
# Structure file
199257
dpa-adapt data convert --input POSCAR --output ./npy
200-
dpaad data convert --input data.csv --output ./npy --property-name homo
201-
dpa-adapt data convert --input comps.csv --output ./npy \
202-
--fmt formula --poscar template.POSCAR --sets 3
258+
259+
# SMILES CSV: --property-col names the input target column and output label name.
260+
dpaad data convert --input data.csv --output ./npy --fmt smiles \
261+
--property-col homo
262+
263+
# Formula CSV + POSCAR template
264+
dpa-adapt data convert --input comps.csv --output ./npy --fmt formula \
265+
--poscar template.POSCAR --formula-col formula --property-col bandgap --sets 3
203266

204267
# Fine-tune
205268
dpa-adapt fit --train-data ./npy/train --pretrained DPA-3.1-3M \
@@ -210,7 +273,7 @@ dpaad fit --train-data /data/qm9 --aux-data /data/spice2 \
210273
--pretrained /path/to/DPA-3.1-3M.pt --strategy mft --target-key homo
211274

212275
# Predict / evaluate
213-
dpa-adapt predict --model model.pth --data ./npy/test
276+
dpa-adapt predict --model model.pth --data ./npy/test --output pred.npy
214277
dpa-adapt evaluate --model model.pth --data ./npy/test
215278
```
216279

dpa_adapt/_backend.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,27 @@ def _is_url_or_name(path: str) -> bool:
4141
def resolve_pretrained_path(pretrained: str, cache_dir: str | None = None) -> str:
4242
"""Resolve *pretrained* to a local file path, downloading if necessary.
4343
44-
If *pretrained* is a local path that exists, it is returned unchanged.
45-
Otherwise it is treated as a built-in model name (e.g. ``"DPA-3.1-3M"``)
46-
and resolved via :func:`deepmd.pretrained.download.resolve_model_path`.
44+
If *pretrained* is a local checkpoint path, it is returned unchanged. This
45+
includes non-existing path-like values so callers can raise their own
46+
context-specific ``not found`` errors or tests can monkeypatch checkpoint
47+
loading. Bare names (e.g. ``"DPA-3.1-3M"``) are resolved via
48+
:func:`deepmd.pretrained.download.resolve_model_path`.
4749
"""
4850
import os as _os
51+
from pathlib import Path as _Path
4952

5053
if _os.path.isfile(pretrained):
5154
return pretrained
5255

56+
p = _Path(pretrained)
57+
is_path_like = (
58+
p.is_absolute()
59+
or any(sep and sep in pretrained for sep in (_os.sep, _os.altsep))
60+
or p.suffix.lower() in {".pt", ".pth"}
61+
)
62+
if is_path_like:
63+
return pretrained
64+
5365
from deepmd.pretrained.download import resolve_model_path as _download
5466

5567
path = _download(pretrained, cache_dir=cache_dir)

dpa_adapt/cli.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# SPDX-License-Identifier: LGPL-3.0-or-later
2-
"""CLI entry point for the ``dpa`` command.
2+
"""CLI entry point for the ``dpa-adapt`` and ``dpaad`` commands.
33
4-
Unlike the deepmd-kit ``dp`` command, ``dpa`` is a standalone CLI that
4+
Unlike the deepmd-kit ``dp`` command, ``dpa-adapt`` is a standalone CLI that
55
focuses solely on DPA model fine-tuning, descriptor extraction,
66
cross-validation, prediction, evaluation, and data preparation.
77
8-
``dpa --help`` does not load torch — the parser is pure argparse and the
9-
handlers (and the DPA stack) are imported lazily only when a subcommand
10-
actually runs.
8+
``dpa-adapt --help`` and ``dpaad --help`` do not load torch — the parser is
9+
pure argparse and the handlers (and the DPA stack) are imported lazily only
10+
when a subcommand actually runs.
1111
"""
1212

1313
from __future__ import (
@@ -287,6 +287,7 @@ def _cmd_data_convert(args: argparse.Namespace) -> int:
287287
formula_col=args.formula_col,
288288
base_element=args.base_element,
289289
sets=args.sets,
290+
seed=args.seed,
290291
overwrite=args.overwrite,
291292
validate=args.validate,
292293
strict=args.strict,
@@ -376,12 +377,12 @@ def _cmd_data_attach_labels(args: argparse.Namespace) -> int:
376377

377378

378379
def get_parser() -> argparse.ArgumentParser:
379-
"""Build the standalone ``dpa`` argument parser.
380+
"""Build the standalone ``dpa-adapt`` / ``dpaad`` argument parser.
380381
381382
Returns
382383
-------
383384
argparse.ArgumentParser
384-
The fully configured parser for the ``dpa`` CLI.
385+
The fully configured parser for the ``dpa-adapt`` / ``dpaad`` CLI.
385386
"""
386387
try:
387388
from dpa_adapt import (
@@ -651,6 +652,9 @@ def get_parser() -> argparse.ArgumentParser:
651652
parser_data_convert.add_argument("--sets", type=int, default=1,
652653
help="Random structures per formula "
653654
"(fmt=formula, default: 1).")
655+
parser_data_convert.add_argument("--seed", type=int, default=42,
656+
help="Random seed for selecting substituted host-atom sites "
657+
"(fmt=formula, default: 42).")
654658
parser_data_convert.add_argument("--overwrite", action="store_true")
655659

656660
# data validate
@@ -682,7 +686,7 @@ def get_parser() -> argparse.ArgumentParser:
682686

683687

684688
def main(args: Sequence[str] | None = None) -> None:
685-
"""Entry point for the ``dpa`` CLI.
689+
"""Entry point for the ``dpa-adapt`` / ``dpaad`` CLI.
686690
687691
Parameters
688692
----------
@@ -712,7 +716,7 @@ def main(args: Sequence[str] | None = None) -> None:
712716
else:
713717
handler = _DISPATCH.get(parsed_args.command)
714718
if handler is None:
715-
print(f"Unknown dpa command: {parsed_args.command}", file=sys.stderr)
719+
print(f"Unknown dpa-adapt command: {parsed_args.command}", file=sys.stderr)
716720
sys.exit(1)
717721
sys.exit(handler(parsed_args))
718722
except Exception as exc:

dpa_adapt/data/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"""Data loading, conversion, validation, and SMILES/type-map utilities.
33
44
All public names are lazily imported so that ``import dpa_adapt.data``
5-
(and therefore ``dpa --help``) does not pull in dpdata, torch, or rdkit.
5+
(and therefore ``dpa-adapt --help`` / ``dpaad --help``) does not pull in
6+
dpdata, torch, or rdkit.
67
"""
78

89
__all__ = [

dpa_adapt/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SPDX-License-Identifier: LGPL-3.0-or-later
2-
"""Entry point for the ``dpa`` CLI.
2+
"""Entry point for the ``dpa-adapt`` and ``dpaad`` CLIs.
33
44
This is the console_script target registered in pyproject.toml.
55
"""

source/tests/dpa_adapt/test_cli_smoke.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# SPDX-License-Identifier: LGPL-3.0-or-later
2-
"""Smoke tests for the standalone ``dpa`` CLI.
2+
"""Smoke tests for the standalone ``dpa-adapt`` / ``dpaad`` CLI.
33
44
Test that all verbs are reachable, ``--help`` does not trigger eager loading
55
of torch or any DPA implementation, and dispatch tables cover all verbs.
@@ -12,8 +12,8 @@
1212
import sys
1313

1414

15-
class TestDpaParserRegistration:
16-
"""Verify all dpa verbs are registered in the standalone parser."""
15+
class TestDpaAdaptParserRegistration:
16+
"""Verify all dpa-adapt verbs are registered in the standalone parser."""
1717

1818
def test_dpa_verbs_registered(self):
1919
from dpa_adapt.cli import (
@@ -50,8 +50,8 @@ def test_data_subcommands_registered(self):
5050
assert expected in data_verbs, f"{expected!r} missing from {data_verbs}"
5151

5252

53-
class TestDpaHelpNoTorch:
54-
"""``dpa --help`` must NOT trigger a torch import."""
53+
class TestDpaAdaptHelpNoTorch:
54+
"""``dpa-adapt --help`` must NOT trigger a torch import."""
5555

5656
def test_help_does_not_load_torch(self):
5757
from unittest.mock import (
@@ -80,7 +80,7 @@ def test_help_does_not_load_torch(self):
8080

8181
if not torch_already:
8282
assert "torch" not in sys.modules, (
83-
"torch was loaded during dpa --help path!"
83+
"torch was loaded during dpa-adapt --help path!"
8484
)
8585

8686

0 commit comments

Comments
 (0)