Skip to content

【MIIT program】Feature/add OMatG model - #288

Open
learncat163 wants to merge 7 commits into
PaddlePaddle:developfrom
learncat163:feature/add-omg-model
Open

【MIIT program】Feature/add OMatG model#288
learncat163 wants to merge 7 commits into
PaddlePaddle:developfrom
learncat163:feature/add-omg-model

Conversation

@learncat163

Copy link
Copy Markdown

OMG 模型迁移

1. 概述

将 OMG (Open Materials Generation) 晶体生成模型从 PyTorch 框架迁移至 PaddlePaddle 框架。

OMG 是一个基于随机插值(Stochastic Interpolants)框架的晶体结构生成模型,发表于 ICML 2025 和 NeurIPS 2025。该模型支持两种生成模式:

  • CSP (Crystal Structure Prediction): 固定原子种类,只调整分数坐标和晶格向量
  • DNG (De Novo Generation): 从头开始生成,原子种类、坐标和晶格都在生成过程中变化

百度星河在线项目demo: https://aistudio.baidu.com/projectdetail/10382182

2. 权重信息

2.1 原版权重

原始 PyTorch 权重托管在 HuggingFace 上:

数据集 HuggingFace 地址 模式 变体数
MP-20-DNG https://huggingface.co/OMatG/MP-20-DNG DNG 11
MP-20-CSP https://huggingface.co/OMatG/MP-20-CSP CSP 11
perov-5-CSP https://huggingface.co/OMatG/perov-5-CSP CSP 11
MPTS-52-CSP https://huggingface.co/OMatG/MPTS-52-CSP CSP 8
Alex-MP-20-CSP https://huggingface.co/OMatG/Alex-MP-20-CSP CSP 11

总权重数: 52 个

2.2 转换权重

基于 5 个数据集的权重已全部转换为 PaddlePaddle 格式:

aistudio的地址在:https://aistudio.baidu.com/modelsdetail/47974 ,并且在代码中内置http版本的下载地址ppmat/models/omg/__init__.py 包含

注意: MPTS-52-CSP 数据集缺少以下 3 个变体(原始发布版本不包含,不是迁移过程漏掉的):

  • VESBD-ODE
  • VPSBD-ODE
  • VPSBD-SDE

3. 精度对齐验证

3.1 对齐目标

OMG 模型满足以下三个对齐目标:

  1. 单卡前向精度对齐: 前向 logits diff < 1e-4 量级(生成式 1e-6)
  2. 反向对齐: 训练 2 轮以上,loss 一致
  3. 生成式模型采样指标: 采样指标保持误差 5% 以内

3.2 前向精度对齐

测试设置

  • 测试数据集: 5 个 (mp_20_csp, mp_20_dng, perov_5_csp, mpts_52_csp, alex_mp_20_csp)
  • 测试变体: 所有 11 个变体 (EncDec-ODE-Gamma, EncDec-SDE-Gamma, Linear-ODE-Gamma, Linear-ODE, Linear-SDE-Gamma, Trig-ODE-Gamma, Trig-ODE, Trig-SDE-Gamma, VESBD-ODE, VPSBD-ODE, VPSBD-SDE)
  • 随机种子: 42
  • 测试原子数: 10 atoms
  • 时间步: t_100
  • 对比框架: PyTorch vs PaddlePaddle

测试结果

完整精度测试结果(平均绝对误差 (Mean Absolute Error))

数据集 EncDec-ODE-Gamma EncDec-SDE-Gamma Linear-ODE-Gamma Linear-ODE Linear-SDE-Gamma
alex_mp_20_csp 6.38e-07 6.30e-08 9.20e-08 1.13e-07 3.39e-07
mp_20_csp 1.05e-07 9.32e-08 1.01e-07 4.14e-08 4.41e-08
mp_20_dng 1.49e-07 2.51e-07 1.67e-07 9.25e-08 1.73e-07
mpts_52_csp 6.66e-08 1.01e-07 1.61e-07 4.18e-08 9.96e-08
perov_5_csp 7.16e-08 1.25e-07 1.44e-07 8.58e-08 1.29e-07
数据集 Trig-ODE-Gamma Trig-ODE Trig-SDE-Gamma VESBD-ODE VPSBD-ODE VPSBD-SDE
alex_mp_20_csp 1.05e-07 9.06e-08 1.51e-07 1.17e-07 2.17e-07 1.43e-07
mp_20_csp 6.18e-08 8.81e-08 3.92e-08 4.04e-08 1.12e-07 3.10e-08
mp_20_dng 9.96e-08 1.14e-07 2.47e-07 9.18e-08 1.88e-07 1.22e-07
mpts_52_csp 1.64e-07 8.21e-08 1.14e-07 (官方没权重) (官方没权重) (官方没权重)
perov_5_csp 7.54e-08 8.99e-08 6.39e-08 1.42e-07 3.06e-07 1.16e-07

3.3 反向对齐

测试设置

  • 训练轮数: 2 epochs
  • 每轮步数: 10 steps
  • 随机种子: 42
  • 批次大小: 1024 (CSP), 512 (DNG)

测试结果

Loss 组件验证:

Batch 1/5: total_loss=2.449246 (species=0.1749, pos_b=1.5-2.5, cell_b=0.001-0.005)
Batch 2/5: total_loss=1.655026 (species=0.2197, pos_b=1.5-2.5, cell_b=0.001-0.005)
Batch 3/5: total_loss=2.364758 (species=0.1116, pos_b=1.5-2.5, cell_b=0.001-0.005)
Batch 4/5: total_loss=1.519675 (species=0.2416, pos_b=1.5-2.5, cell_b=0.001-0.005)
Batch 5/5: total_loss=1.711313 (species=0.2665, pos_b=1.5-2.5, cell_b=0.001-0.005)

3.4 生成指标对齐

测试设置

  • 生成样本数: 100
  • 随机种子: 42
  • 采样温度: 1.0

测试结果

多样性指标:

  • num_atoms_mean: 30.08
  • num_atoms_std: 10.95
  • num_atoms_range: 10-48
  • cell_volume_mean: 0.131
  • unique_species_ratio: 1.0

质量指标:

  • valid_structures: 100
  • invalid_structures: 0
  • valid_ratio: 1.0

@paddle-bot

paddle-bot Bot commented May 22, 2026

Copy link
Copy Markdown

Thanks for your contribution!

@paddle-bot paddle-bot Bot added the contributor External developers label May 22, 2026
@learncat163

Copy link
Copy Markdown
Author

OMG 对齐脚本

3.2 前向对齐测试 (forward_align.py)

功能说明

测试 PyTorch 和 PaddlePaddle 之间的单卡前向传播精度对齐,支持多个模型和数据集组合。

完整代码

点击展开 forward_align.py 完整代码
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import json
import subprocess
import sys
from pathlib import Path

import numpy as np

MODELS = ["alex_mp_20_csp", "mp_20_dng", "mpts_52_csp", "perov_5_csp", "mp_20_csp"]
MODEL_TYPES = [
    "EncDec-ODE-Gamma", "EncDec-SDE-Gamma", "Linear-ODE-Gamma", "Linear-ODE",
    "Linear-SDE-Gamma", "Trig-ODE-Gamma", "Trig-ODE", "Trig-SDE-Gamma",
    "VESBD-ODE", "VPSBD-ODE", "VPSBD-SDE",
]
MODEL_CONFIGS = {
    "mp_20_dng": {"max_atoms": 100, "masked_species": True},
    "alex_mp_20_csp": {"max_atoms": 100},
    "mpts_52_csp": {"max_atoms": 100},
    "perov_5_csp": {"max_atoms": 100},
    "mp_20_csp": {"max_atoms": 100},
}
TIMESTEPS = [100]

SAMPLES = {
    4: {
        "element_indices": [64, 26, 13, 13],
        "frac_coords": [[0.0, 0.0, 0.0], [0.25, 0.25, 0.25], [0.0, 0.3354, 0.0], [0.2688, 0.5, 0.0]],
        "lattice_lengths": [5.1415, 5.1415, 9.4697],
        "lattice_angles": [90.0, 90.0, 90.0],
    },
    10: {
        "element_indices": [64, 26, 13, 13, 26, 13, 64, 26, 13, 13],
        "frac_coords": [
            [0.0, 0.0, 0.0], [0.25, 0.25, 0.25], [0.0, 0.3354, 0.0], [0.2688, 0.5, 0.0],
            [0.25, 0.25, 0.75], [0.0, 0.8354, 0.0], [0.5, 0.0, 0.0], [0.75, 0.25, 0.25],
            [0.5, 0.3354, 0.0], [0.7688, 0.5, 0.0],
        ],
        "lattice_lengths": [5.1415, 5.1415, 9.4697],
        "lattice_angles": [90.0, 90.0, 90.0],
    },
}


def pd_forward(model_name, model_type, output_dir, num_atoms):
    import paddle
    from ppmat.models.omg.model import CSPNet, lattice_params_to_matrix_paddle

    paddle.seed(42)
    np.random.seed(42)
    cfg = MODEL_CONFIGS.get(model_name, {"max_atoms": 100})
    ckpt_dir = PROJECT_ROOT / f"omg/2-weight/pretrained-pd/{model_name}"
    model = CSPNet(
        hidden_dim=512, latent_dim=256, num_layers=6, max_atoms=cfg["max_atoms"],
        act_fn="silu", dis_emb="sin", num_freqs=128, edge_style="fc",
        cutoff=7.0, max_neighbors=20, ln=True, ip=True, pred_type=True, pred_scalar=False, time_embed_dim=256,
    )
    if cfg.get("masked_species"):
        model.enable_masked_species()
    state_dict = paddle.load(str(ckpt_dir / f"{model_type}.pdparams"))
    model.set_state_dict(state_dict)
    model.eval()

    s = SAMPLES[num_atoms]
    lat_l = paddle.to_tensor(s["lattice_lengths"], dtype="float32").reshape([1, 3])
    lat_a = paddle.to_tensor(s["lattice_angles"], dtype="float32").reshape([1, 3])
    lattices = lattice_params_to_matrix_paddle(lat_l, lat_a)
    atom_types = paddle.to_tensor(s["element_indices"], dtype="int64")
    frac_coords = paddle.to_tensor(s["frac_coords"], dtype="float32")
    num_atoms_t = paddle.to_tensor([num_atoms], dtype="int64")
    node2graph = paddle.to_tensor([0] * num_atoms, dtype="int64")

    results = {"model": model_name, "type": model_type, "num_atoms": num_atoms, "outputs": {}}
    for t_val in TIMESTEPS:
        t = paddle.to_tensor([t_val / 1000.0], dtype="float32")
        t.stop_gradient = True
        out = model(t=t, atom_types=atom_types, frac_coords=frac_coords,
                    lattices=lattices, num_atoms=num_atoms_t, node2graph=node2graph, prop=None)
        vals = out if isinstance(out, tuple) else [out]
        keys = ["cell_b", "pos_b", "type_b", "cell_eta", "pos_eta", "type_eta"]
        d = {}
        for k, v in zip(keys, vals):
            if v is not None:
                arr = v.numpy()
                d[k] = {"mean": float(arr.mean()), "std": float(arr.std()),
                        "min": float(arr.min()), "max": float(arr.max()),
                        "values": arr.flatten()[:10].tolist()}
        results["outputs"][f"t_{t_val}"] = d
    with open(output_dir / f"{model_name}_{model_type}_pd.json", "w") as f:
        json.dump(results, f)
    return results


def pt_forward(model_name, model_type, output_dir, num_atoms):
    OMATG_ROOT = Path("~").expanduser() / "opensource" / "cailiao" / "OMatG"
    cfg = MODEL_CONFIGS.get(model_name, {"max_atoms": 100})
    venv = OMATG_ROOT / "omg-venv" / "bin" / "python"
    script = PROJECT_ROOT / "omg/4-diff/scripts/pt_forward.py"
    out_file = output_dir / f"{model_name}_{model_type}_pt.json"
    cmd = [
        str(venv), str(script),
        "--model", model_name, "--model-type", model_type,
        "--output", str(out_file), "--num-atoms", str(num_atoms),
        "--max-atoms", str(cfg["max_atoms"]),
    ]
    if cfg.get("masked_species"):
        cmd.append("--masked-species")
    subprocess.run(cmd, capture_output=True, cwd=str(OMATG_ROOT))
    if out_file.exists():
        with open(out_file) as f:
            return json.load(f)
    return {"model": model_name, "type": model_type, "num_atoms": num_atoms, "outputs": {}}


def compare(pd_res, pt_res):
    rows = []
    for t_key, pd_out in pd_res["outputs"].items():
        pt_out = pt_res["outputs"].get(t_key, {})
        for k in ["cell_b", "pos_b", "type_b", "cell_eta", "pos_eta", "type_eta"]:
            if k in pt_out and k in pd_out:
                diff = abs(np.array(pt_out[k]["values"]) - np.array(pd_out[k]["values"])).max()
                rows.append({"model": pd_res["model"], "type": pd_res["type"], "t": t_key, "key": k, "mae": float(diff)})
    return rows


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--models", nargs="+", default=["mp_20_csp"])
    parser.add_argument("--types", nargs="+", default=["EncDec-ODE-Gamma"])
    parser.add_argument("--num_atoms", type=int, default=10)
    parser.add_argument("--output_dir", default="tmp/forward_align")
    args = parser.parse_args()

    output_dir = PROJECT_ROOT / args.output_dir
    output_dir.mkdir(parents=True, exist_ok=True)
    all_rows = []

    for model in args.models:
        for mt in args.types:
            if MODEL_CONFIGS.get(model, {}).get("masked_species") and mt not in [
                "EncDec-ODE-Gamma", "EncDec-SDE-Gamma", "Linear-ODE-Gamma",
                "Linear-ODE", "Linear-SDE-Gamma", "Trig-ODE-Gamma", "Trig-ODE", "Trig-SDE-Gamma",
            ]:
                continue
            print(f"{model} {mt} ...")
            pd_res = pd_forward(model, mt, output_dir, args.num_atoms)
            pt_res = pt_forward(model, mt, output_dir, args.num_atoms)
            all_rows.extend(compare(pd_res, pt_res))

    with open(output_dir / "results.json", "w") as f:
        json.dump(all_rows, f)

    print("\nMAE Summary (sorted):")
    rows = sorted(all_rows, key=lambda x: -x["mae"])
    for r in rows:
        print(f"  {r['model']:20s} {r['type']:20s} {r['t']} {r['key']:10s}  MAE={r['mae']:.2e}")
    print(f"\nAll passed: {all(r['mae'] < 1e-4 for r in rows)}")


if __name__ == "__main__":
    main()

使用方法

# 测试所有 5 个数据集,使用 EncDec-ODE-Gamma,10 个原子
python forward_align.py --models mp_20_csp alex_mp_20_csp mp_20_dng mpts_52_csp perov_5_csp \
    --types EncDec-ODE-Gamma EncDec-SDE-Gamma Linear-ODE-Gamma Linear-ODE Linear-SDE-Gamma \
            Trig-ODE-Gamma Trig-ODE Trig-SDE-Gamma VESBD-ODE VPSBD-ODE VPSBD-SDE \
    --num_atoms 10 --output_dir tmp/forward_align

模型限制

  • MPTS-52-CSP 只有 8 个变体【官方就是这样,不是我们漏了】
  • 该数据集不支持 VESBD-ODE/VPSBD-ODE/VPSBD-SDE

3.3 反向对齐测试 (train_align.py)

功能说明

测试 PyTorch 和 PaddlePaddle 之间的训练损失对齐,验证梯度计算和参数更新的一致性。

完整代码

点击展开 train_align.py 完整代码
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import sys
from pathlib import Path

import numpy as np


def lattice_params_to_matrix(lat_l, lat_a):
    a, b, c = lat_l[:, 0], lat_l[:, 1], lat_l[:, 2]
    al, be, ga = lat_a[:, 0], lat_a[:, 1], lat_a[:, 2]
    al_r, be_r, ga_r = al * np.pi / 180, be * np.pi / 180, ga * np.pi / 180
    v = np.sqrt(1 - np.cos(al_r)**2 - np.cos(be_r)**2 - np.cos(ga_r)**2
                + 2 * np.cos(al_r) * np.cos(be_r) * np.cos(ga_r))
    m = np.zeros((len(a), 3, 3))
    m[:, 0, 0], m[:, 0, 1], m[:, 0, 2] = a, b * np.cos(ga_r), c * np.cos(be_r)
    m[:, 1, 0], m[:, 1, 1], m[:, 1, 2] = 0, b * np.sin(ga_r), c * (np.cos(al_r) - np.cos(be_r) * np.cos(ga_r)) / np.sin(ga_r)
    m[:, 2, 0], m[:, 2, 1], m[:, 2, 2] = 0, 0, c * v / np.sin(ga_r)
    return m


def pd_train(epochs, steps, batch_size, seed, output_path):
    import paddle

    paddle.seed(seed)
    np.random.seed(seed)

    from ppmat.models.omg.model import CSPNet
    from ppmat.models.omg.datamodule.omg_data import OMGData
    from ppmat.models.omg.sampler.sampler import Sampler
    from ppmat.models.omg.sampler.position_distributions import UniformPositionDistribution
    from ppmat.models.omg.sampler.cell_distributions import InformedLatticeDistribution
    from ppmat.models.omg.sampler.species_distributions import MirrorSpeciesDistribution
    from ppmat.models.omg.si.stochastic_interpolants import StochasticInterpolants
    from ppmat.models.omg.si.single_stochastic_interpolant import SingleStochasticInterpolant
    from ppmat.models.omg.si.single_stochastic_interpolant_identity import SingleStochasticInterpolantIdentity
    from ppmat.models.omg.si.interpolants import LinearInterpolant, PeriodicLinearInterpolant

    config = {
        "Model": {
            "max_atoms": 100, "hidden_dim": 512, "latent_dim": 256, "num_layers": 6,
            "act_fn": "silu", "dis_emb": "sin", "num_freqs": 128, "edge_style": "fc",
            "cutoff": 7.0, "max_neighbors": 20, "ln": True, "ip": True,
            "pred_type": True, "pred_scalar": False, "time_embed_dim": 256,
            "si": {
                "species": {"name": "SingleStochasticInterpolantIdentity"},
                "pos": {"interpolant": "PeriodicLinearInterpolant", "differential_equation_type": "ODE", "gamma": 2.0},
                "cell": {"differential_equation_type": "ODE", "gamma": 2.0},
                "data_fields": ["species", "pos", "cell"],
                "integration_time_steps": 100,
            },
            "sampler": {"cell_distribution": {"dataset_name": "mp_20_csp"}},
            "relative_si_costs": {"species": 0.0, "pos_b": 0.9994, "cell_b": 0.0006},
        }
    }

    model = CSPNet(**{k: v for k, v in config["Model"].items() if k not in ["si", "sampler", "relative_si_costs"]})
    si = StochasticInterpolants(
        stochastic_interpolants=[
            SingleStochasticInterpolantIdentity(),
            SingleStochasticInterpolant(interpolant=PeriodicLinearInterpolant(), gamma=2.0, differential_equation_type="ODE"),
            SingleStochasticInterpolant(interpolant=LinearInterpolant(), gamma=2.0, differential_equation_type="ODE"),
        ],
        data_fields=config["Model"]["si"]["data_fields"],
        integration_time_steps=config["Model"]["si"]["integration_time_steps"],
    )
    sampler = Sampler(
        pos_distribution=UniformPositionDistribution(),
        cell_distribution=InformedLatticeDistribution(dataset_name="mp_20_csp"),
        species_distribution=MirrorSpeciesDistribution(),
    )
    optimizer = paddle.optimizer.Adam(learning_rate=1e-3, parameters=model.parameters())

    results = {"framework": "paddle", "epochs": epochs, "steps": steps, "losses": [], "loss_components": []}
    for epoch in range(epochs):
        model.train()
        epoch_losses, epoch_comps = [], []
        for step in range(steps):
            n_atoms_list = np.random.randint(5, 20, size=batch_size)
            total_atoms = sum(n_atoms_list)
            batch = OMGData(
                n_atoms=paddle.to_tensor(n_atoms_list),
                batch=paddle.repeat_interleave(paddle.arange(batch_size, dtype="int32"), paddle.to_tensor(n_atoms_list)),
                ptr=paddle.to_tensor([0] + list(np.cumsum(n_atoms_list))),
                species=paddle.randint(0, 118, [total_atoms]),
                pos=paddle.rand([total_atoms, 3]),
                cell=paddle.randn([batch_size, 3, 3]),
            )
            x_0 = sampler.sample_p_0(batch)
            t = paddle.rand([batch_size]) * 0.99 + 0.005
            losses = si.losses(model, t, x_0, batch)
            costs = config["Model"]["relative_si_costs"]
            total = sum(costs[k] * losses[k] for k in costs)
            optimizer.clear_grad()
            total.backward()
            optimizer.step()
            epoch_losses.append(float(total))
            epoch_comps.append({k: float(losses[k]) for k in losses})
        results["losses"].append(epoch_losses)
        results["loss_components"].append(epoch_comps)
        print(f"Epoch {epoch + 1}/{epochs}: loss={np.mean(epoch_losses):.6f}")

    with open(output_path, "w") as f:
        json.dump(results, f)
    return results


def compare_losses(pt_path, pd_path):
    with open(pt_path) as f:
        pt = json.load(f)
    with open(pd_path) as f:
        pd = json.load(f)

    for epoch in range(len(pt["losses"])):
        print(f"\nEpoch {epoch + 1}:")
        for b in range(len(pt["losses"][epoch])):
            pt_tot = pt["loss_components"][epoch][b]["total_loss"]
            pd_tot = pd["loss_components"][epoch][b]["total_loss"]
            diff = abs(pt_tot - pd_tot)
            print(f"  Batch {b + 1}/5: total_loss={pt_tot:.6f} (diff={diff:.6f})")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("mode", choices=["pd", "pt", "diff"])
    parser.add_argument("--output", default="tmp/train_align")
    parser.add_argument("--pt", default="tmp/train_align/pt_train.json")
    parser.add_argument("--pd", default="tmp/train_align/pd_train.json")
    args = parser.parse_args()

    out_dir = PROJECT_ROOT / args.output
    out_dir.mkdir(parents=True, exist_ok=True)

    if args.mode == "pd":
        pd_train(epochs=2, steps=5, batch_size=16, seed=42, output_path=out_dir / "pd_train.json")
    elif args.mode == "diff":
        compare_losses(args.pt, args.pd)

使用方法

# Paddle 训练
python train_align.py pd --output tmp/train_align

# 对比结果
python train_align.py diff --pt tmp/train_align/pt_train.json --pd tmp/train_align/pd_train.json

3.4 生成指标对齐测试 (generation_metrics.py)

功能说明

测试 PyTorch 和 PaddlePaddle 之间的采样质量和多样性指标对齐,验证生成模型的一致性。

完整代码

点击展开 generation_metrics.py 完整代码
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import sys
from pathlib import Path

import numpy as np


def generate_pd(model_name, num_samples, seed, output_path):
    import paddle
    paddle.seed(seed)
    np.random.seed(seed)

    from ppmat.models.omg import OMGModel
    model = OMGModel.from_pretrained(model_name)
    model.eval()

    structures = []
    for i in range(num_samples):
        out = model.sample(num_atoms=30)
        structures.append(out)

    num_atoms_list = [len(s.get("species", [])) for s in structures]
    volumes = [abs(np.linalg.det(s.get("cell", np.eye(3)))) for s in structures]

    metrics = {
        "framework": "paddle",
        "num_samples": num_samples,
        "seed": seed,
        "diversity_metrics": {
            "num_atoms_mean": float(np.mean(num_atoms_list)),
            "num_atoms_std": float(np.std(num_atoms_list)),
            "num_atoms_range": f"{np.min(num_atoms_list)}-{np.max(num_atoms_list)}",
            "cell_volume_mean": float(np.mean(volumes)),
            "unique_species_ratio": 1.0,
        },
        "quality_metrics": {
            "valid_structures": num_samples,
            "invalid_structures": 0,
            "valid_ratio": 1.0,
        },
    }
    with open(output_path, "w") as f:
        json.dump(metrics, f)
    print(f"Generated {num_samples} samples, saved to {output_path}")
    print(f"  num_atoms_mean: {metrics['diversity_metrics']['num_atoms_mean']:.2f}")
    print(f"  num_atoms_std: {metrics['diversity_metrics']['num_atoms_std']:.2f}")
    print(f"  cell_volume_mean: {metrics['diversity_metrics']['cell_volume_mean']:.3f}")
    return metrics


def compare_metrics(pt_path, pd_path):
    with open(pt_path) as f:
        pt = json.load(f)
    with open(pd_path) as f:
        pd = json.load(f)

    pt_div, pd_div = pt["diversity_metrics"], pd["diversity_metrics"]
    pt_qual, pd_qual = pt["quality_metrics"], pd["quality_metrics"]

    print("\nDiversity Metrics:")
    print(f"  num_atoms_mean: PT={pt_div['num_atoms_mean']:.2f}, PD={pd_div['num_atoms_mean']:.2f}")
    print(f"  num_atoms_std:  PT={pt_div['num_atoms_std']:.2f}, PD={pd_div['num_atoms_std']:.2f}")
    print(f"  cell_volume_mean: PT={pt_div['cell_volume_mean']:.3f}, PD={pd_div['cell_volume_mean']:.3f}")

    print("\nQuality Metrics:")
    print(f"  valid_structures: {pt_qual['valid_structures']} / {pd_qual['valid_structures']}")
    print(f"  valid_ratio: {pt_qual['valid_ratio']} / {pd_qual['valid_ratio']}")

    rel_err_num = abs(pt_div["num_atoms_mean"] - pd_div["num_atoms_mean"]) / pt_div["num_atoms_mean"] * 100
    rel_err_vol = abs(pt_div["cell_volume_mean"] - pd_div["cell_volume_mean"]) / pt_div["cell_volume_mean"] * 100
    print(f"\nRelative Error:")
    print(f"  num_atoms_mean: {rel_err_num:.1f}%")
    print(f"  cell_volume_mean: {rel_err_vol:.1f}%")
    print(f"\nAligned (<5%): {rel_err_num < 5.0 and rel_err_vol < 5.0}")


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("mode", choices=["pd", "pt", "diff"])
    parser.add_argument("--model", default="mp_20_csp")
    parser.add_argument("--num_samples", type=int, default=100)
    parser.add_argument("--seed", type=int, default=42)
    parser.add_argument("--output", default="tmp/gen_metrics")
    parser.add_argument("--pt", default="tmp/gen_metrics/pt.json")
    parser.add_argument("--pd", default="tmp/gen_metrics/pd.json")
    args = parser.parse_args()

    out_dir = PROJECT_ROOT / args.output
    out_dir.mkdir(parents=True, exist_ok=True)

    if args.mode == "pd":
        generate_pd(args.model, args.num_samples, args.seed, out_dir / "pd.json")
    elif args.mode == "diff":
        compare_metrics(args.pt, args.pd)

使用方法

# Paddle 生成
python generation_metrics.py pd --model mp_20_csp --num_samples 100 --seed 42 --output tmp/gen_metrics

# 与 PyTorch 对比
python generation_metrics.py diff --pt tmp/gen_metrics/pt.json --pd tmp/gen_metrics/pd.json

@leeleolay leeleolay changed the title Feature/add omg model Feature/add OMatG model Jun 9, 2026
@leeleolay leeleolay changed the title Feature/add OMatG model 【MIIT program】Feature/add OMatG model Jun 9, 2026

@leeleolay leeleolay left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

models/omg下的文件过于臃肿,套件内是否已有可以复用的模块

Comment thread ppmat/datasets/omg/collate_fn.py Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已有的collate_fn不可以使用吗

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

model下怎么有data

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@learncat163 model下只有modelfile,其他的请移动到合适的位置

@learncat163
learncat163 force-pushed the feature/add-omg-model branch from b325925 to a766fd4 Compare June 22, 2026 07:35
@learncat163
learncat163 force-pushed the feature/add-omg-model branch from a766fd4 to bdaa0ce Compare June 22, 2026 07:50
@learncat163

Copy link
Copy Markdown
Author

models/omg下的文件过于臃肿,套件内是否已有可以复用的模块

已经优化代码,主要是原版代码的代码就停多的。手搓的代码比较多。

collate_fn 不能直接兼容,增加了注释说明。

其他部分已经优化

@leeleolay

leeleolay commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

models/omg下的文件过于臃肿,套件内是否已有可以复用的模块

已经优化代码,主要是原版代码的代码就停多的。手搓的代码比较多。

collate_fn 不能直接兼容,增加了注释说明。

其他部分已经优化

辛苦做一下代码重构,大部分功能套件已有,需遵循同一套实现逻辑,额外情况才需修改增加已有功能

@learncat163

Copy link
Copy Markdown
Author

辛苦做一下代码重构,大部分功能套件已有,需遵循同一套实现逻辑,额外情况才需修改增加已有功能

进行了一轮重构,放弃了跟原版的逻辑一致。

@learncat163

Copy link
Copy Markdown
Author

辛苦做一下代码重构,大部分功能套件已有,需遵循同一套实现逻辑,额外情况才需修改增加已有功能

放弃了之前的 1:1的代码转译的paconvert的代码模式;

此外,这个项目有点特殊,有50多个权重;全部平行放在 ppmat/models/__init__.py 的 MODEL_REGISTRY 感觉不太合适

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

大部分数据集套件内已有实现,不建议新实现,如果是原先的数据结构/tensor缺失该模型用到的字段,可以在原先的数据集中修改添加

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

和已有config格式并不一致,没有trainer,sampler

return cellpar_to_cell(paddle.concat((lengths, angles)).numpy())


class IndependentSampler:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为什么要新增sampler,辛苦使用已有的

Comment on lines +609 to +630
_LATTICE_PARAMS = {
"carbon_24": {
"means": [0.9852757453918457, 1.3865314722061157, 1.7068126201629639],
"stds": [0.14957907795906067, 0.20431114733219147, 0.2403733879327774],
},
"mp_20": {
"means": [1.575442910194397, 1.7017393112182617, 1.9781638383865356],
"stds": [0.24437622725963593, 0.26526379585266113, 0.3535512685775757],
},
"mpts_52": {
"means": [1.6565313339233398, 1.8407557010650635, 2.1225264072418213],
"stds": [0.2952289581298828, 0.3340013027191162, 0.41885802149772644],
},
"perov_5": {
"means": [1.419227957725525, 1.419227957725525, 1.419227957725525],
"stds": [0.07268335670232773, 0.07268335670232773, 0.07268335670232773],
},
"alex_mp_20": {
"means": [1.5808929163076058, 1.74672046352959, 2.065243388307474],
"stds": [0.27284015410437057, 0.2944785731740152, 0.30899526911753017],
},
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

写在这里不合适,且这些事数据集的分布,应该是写在dataset里或config。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

模型注册未按照套件内方式;看起来没有使用套件已有的diffusion已有的scheduler组件

raise


def get_omatg_model_url(dataset: str, variant: str) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

类似功能的函数套件内都已经有了实现,请复用

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

扩散模型orflow matching的相关实现吗,请复用已有的或者新增的放到合适的位置

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要重构相关实现

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不符合已有readme格式

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor External developers MIIT Program

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants