From fc94e7969ef05766b62caa1c78016cfe132d8821 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sat, 22 Jun 2024 09:24:55 -0700 Subject: [PATCH 01/19] improve TensorNet model coverage --- pyproject.toml | 2 +- tests/models/test_tensornet.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b25dd4d7..7d7cefa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ classifiers = [ ] dependencies = [ "ase", - "dgl>=2.0.0", + "dgl", "pymatgen", "lightning", "torch<=2.2.1", diff --git a/tests/models/test_tensornet.py b/tests/models/test_tensornet.py index 4edb1a86..2a8dfee7 100644 --- a/tests/models/test_tensornet.py +++ b/tests/models/test_tensornet.py @@ -21,6 +21,7 @@ def test_model(self, graph_MoS): os.remove("model.pt") os.remove("model.json") os.remove("state.pt") + model = TensorNet(is_intensive=False, equivariance_invariance_group="SO(3)") def test_exceptions(self): with pytest.raises(ValueError, match="Invalid activation type"): From 53357d77fea999a36afa9fb85d00a10c0cd54161 Mon Sep 17 00:00:00 2001 From: Tsz Wai Ko <47970742+kenko911@users.noreply.github.com> Date: Sat, 22 Jun 2024 09:30:39 -0700 Subject: [PATCH 02/19] Update pyproject.toml Signed-off-by: Tsz Wai Ko <47970742+kenko911@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7d7cefa2..b25dd4d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ classifiers = [ ] dependencies = [ "ase", - "dgl", + "dgl>=2.0.0", "pymatgen", "lightning", "torch<=2.2.1", From f65cdba5d1e34e656cff74789965a8d069169bd2 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sat, 22 Jun 2024 09:34:43 -0700 Subject: [PATCH 03/19] Improve the unit test for SO(3) equivarance in TensorNet class --- tests/models/test_tensornet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/models/test_tensornet.py b/tests/models/test_tensornet.py index 2a8dfee7..f7767046 100644 --- a/tests/models/test_tensornet.py +++ b/tests/models/test_tensornet.py @@ -22,6 +22,7 @@ def test_model(self, graph_MoS): os.remove("model.json") os.remove("state.pt") model = TensorNet(is_intensive=False, equivariance_invariance_group="SO(3)") + assert torch.numel(output) == 1 def test_exceptions(self): with pytest.raises(ValueError, match="Invalid activation type"): From 16abc3840201bd9c30f645669d862dc694ff844c Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sat, 22 Jun 2024 18:12:32 -0700 Subject: [PATCH 04/19] improve SO3Net model class coverage and simplify TensorNet implementations --- src/matgl/models/_tensornet.py | 5 +---- tests/models/test_so3net.py | 10 ++++++---- tests/models/test_tensornet.py | 2 -- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/matgl/models/_tensornet.py b/src/matgl/models/_tensornet.py index bfed4610..e1129b09 100644 --- a/src/matgl/models/_tensornet.py +++ b/src/matgl/models/_tensornet.py @@ -123,10 +123,7 @@ def __init__( f"Invalid activation type, please try using one of {[af.name for af in ActivationFunction]}" ) from None - if element_types is None: - self.element_types = DEFAULT_ELEMENTS - else: - self.element_types = element_types # type: ignore + self.element_types = element_types # type: ignore self.bond_expansion = BondExpansion( cutoff=cutoff, diff --git a/tests/models/test_so3net.py b/tests/models/test_so3net.py index 9f8c6b0d..4f2c9c0e 100644 --- a/tests/models/test_so3net.py +++ b/tests/models/test_so3net.py @@ -37,16 +37,18 @@ def test_model_intensive(self, graph_MoS): output = model(g=graph) assert torch.numel(output) == 2 - def test_model_intensive_with_weighted_atom(self, graph_MoS): + def test_model_intensive_reduce_atom_classification(self, graph_MoS): structure, graph, state = graph_MoS lat = torch.tensor(np.array([structure.lattice.matrix]), dtype=matgl.float_th) graph.edata["pbc_offshift"] = torch.matmul(graph.edata["pbc_offset"], lat[0]) graph.ndata["pos"] = graph.ndata["frac_coords"] @ lat[0] - model = SO3Net(element_types=["Mo", "S"], is_intensive=True, readout_type="weighted_atom") + model = SO3Net( + element_types=["Mo", "S"], is_intensive=True, readout_type="reduce_atom", target_property="graph" + ) output = model(g=graph) - assert torch.numel(output) == 2 + assert torch.numel(output) == 1 - def test_model_intensive_with_classification(self, graph_MoS): + def test_model_intensive_weighted_atom_classification(self, graph_MoS): structure, graph, state = graph_MoS lat = torch.tensor(np.array([structure.lattice.matrix]), dtype=matgl.float_th) graph.edata["pbc_offshift"] = torch.matmul(graph.edata["pbc_offset"], lat[0]) diff --git a/tests/models/test_tensornet.py b/tests/models/test_tensornet.py index f7767046..4edb1a86 100644 --- a/tests/models/test_tensornet.py +++ b/tests/models/test_tensornet.py @@ -21,8 +21,6 @@ def test_model(self, graph_MoS): os.remove("model.pt") os.remove("model.json") os.remove("state.pt") - model = TensorNet(is_intensive=False, equivariance_invariance_group="SO(3)") - assert torch.numel(output) == 1 def test_exceptions(self): with pytest.raises(ValueError, match="Invalid activation type"): From 2798176026ec99b8deb0f85c8b1e0e02f79e0cfe Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sun, 23 Jun 2024 21:53:02 -0700 Subject: [PATCH 05/19] improve the coverage in MLP_norm class --- tests/layers/test_core_and_embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/layers/test_core_and_embedding.py b/tests/layers/test_core_and_embedding.py index 1d74d938..8d94689f 100644 --- a/tests/layers/test_core_and_embedding.py +++ b/tests/layers/test_core_and_embedding.py @@ -48,7 +48,7 @@ def test_gated_mlp(self, x): @pytest.mark.parametrize("normalization", ["layer", "graph"]) def test_mlp_norm(self, x, graph, normalization): - layer = MLP_norm(dims=[10, 3], normalization=normalization) + layer = MLP_norm(dims=[10, 3], normalization=normalization, normalize_hidden=True) out = layer(x, g=graph).double() assert [out.size()[0], out.size()[1]] == [4, 3] assert out.mean().item() == pytest.approx(0, abs=1e-6) From 9b179635b755a112125d08c0e509b9e2fe9bd554 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Wed, 3 Jul 2024 11:03:03 -0700 Subject: [PATCH 06/19] Improve the implementation of three-body interactions --- src/matgl/layers/_three_body.py | 63 +++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/matgl/layers/_three_body.py b/src/matgl/layers/_three_body.py index 7a8151d1..6d196093 100644 --- a/src/matgl/layers/_three_body.py +++ b/src/matgl/layers/_three_body.py @@ -19,7 +19,8 @@ class ThreeBodyInteractions(nn.Module): """Include 3D interactions to the bond update.""" def __init__(self, update_network_atom: nn.Module, update_network_bond: nn.Module, **kwargs): - """Init ThreeBodyInteractions. + """ + Initialize ThreeBodyInteractions. Args: update_network_atom: MLP for node features in Eq.2 @@ -31,45 +32,63 @@ def __init__(self, update_network_atom: nn.Module, update_network_bond: nn.Modul self.update_network_bond = update_network_bond def forward( - self, - graph: dgl.DGLGraph, - line_graph: dgl.DGLGraph, - three_basis: torch.Tensor, - three_cutoff: float, - node_feat: torch.Tensor, - edge_feat: torch.Tensor, + self, + graph: dgl.DGLGraph, + line_graph: dgl.DGLGraph, + three_basis: torch.Tensor, + three_cutoff: torch.Tensor, + node_feat: torch.Tensor, + edge_feat: torch.Tensor, ): """ Forward function for ThreeBodyInteractions. Args: graph: dgl graph - line_graph: line graph. + line_graph: line graph three_basis: three body basis expansion three_cutoff: cutoff radius node_feat: node features - edge_feat: edge features. + edge_feat: edge features """ - end_atom_index = torch.gather(graph.edges()[1], 0, line_graph.edges()[1].to(torch.int64)) - atoms = self.update_network_atom(node_feat) - end_atom_index = torch.unsqueeze(end_atom_index, 1) - atoms = torch.squeeze(atoms[end_atom_index]) - basis = three_basis * atoms - three_cutoff = torch.unsqueeze(three_cutoff, dim=1) # type: ignore - weights = three_cutoff[torch.stack(list(line_graph.edges()), dim=1)].view(-1, 2) # type: ignore - weights = torch.prod(weights, dim=-1) # type: ignore + # Get the indices of the end atoms for each bond in the line graph + end_atom_indices = graph.edges()[1][line_graph.edges()[1]].to(matgl.int_th) + + # Update node features using the atom update network + updated_atoms = self.update_network_atom(node_feat) + + # Gather updated atom features for the end atoms + end_atom_features = updated_atoms[end_atom_indices] + + # Compute the basis term + basis = three_basis * end_atom_features + + # Reshape and compute weights based on the three-cutoff tensor + three_cutoff = three_cutoff.unsqueeze(1) + edge_indices = torch.stack(list(line_graph.edges()), dim=1) + weights = three_cutoff[edge_indices].view(-1, 2) + weights = weights.prod(dim=-1) + + # Compute the weighted basis basis = basis * weights[:, None] + + # Aggregate the new bonds using scatter_sum + segment_ids = get_segment_indices_from_n(line_graph.ndata["n_triple_ij"]) new_bonds = scatter_sum( basis.to(matgl.float_th), - segment_ids=get_segment_indices_from_n(line_graph.ndata["n_triple_ij"]), + segment_ids=segment_ids, num_segments=graph.num_edges(), dim=0, ) - if not new_bonds.data.shape[0]: + + # If no new bonds are generated, return the original edge features + if new_bonds.shape[0] == 0: return edge_feat - edge_feat_updated = edge_feat + self.update_network_bond(new_bonds) - return edge_feat_updated + # Update edge features using the bond update network + updated_edge_feat = edge_feat + self.update_network_bond(new_bonds) + + return updated_edge_feat def combine_sbf_shf(sbf, shf, max_n: int, max_l: int, use_phi: bool): """Combine the spherical Bessel function and the spherical Harmonics function. From dc6ed594ca275bb0f69e7634b543770aa25e9364 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Wed, 3 Jul 2024 11:09:50 -0700 Subject: [PATCH 07/19] fixed black --- src/matgl/layers/_three_body.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/matgl/layers/_three_body.py b/src/matgl/layers/_three_body.py index 6d196093..38235ae2 100644 --- a/src/matgl/layers/_three_body.py +++ b/src/matgl/layers/_three_body.py @@ -32,13 +32,13 @@ def __init__(self, update_network_atom: nn.Module, update_network_bond: nn.Modul self.update_network_bond = update_network_bond def forward( - self, - graph: dgl.DGLGraph, - line_graph: dgl.DGLGraph, - three_basis: torch.Tensor, - three_cutoff: torch.Tensor, - node_feat: torch.Tensor, - edge_feat: torch.Tensor, + self, + graph: dgl.DGLGraph, + line_graph: dgl.DGLGraph, + three_basis: torch.Tensor, + three_cutoff: torch.Tensor, + node_feat: torch.Tensor, + edge_feat: torch.Tensor, ): """ Forward function for ThreeBodyInteractions. @@ -90,6 +90,7 @@ def forward( return updated_edge_feat + def combine_sbf_shf(sbf, shf, max_n: int, max_l: int, use_phi: bool): """Combine the spherical Bessel function and the spherical Harmonics function. From 522809cdd591cf83c5a63c233080844db0804563 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Fri, 5 Jul 2024 10:32:36 -0700 Subject: [PATCH 08/19] Optimize the speed of _compute_3body class --- src/matgl/graph/compute.py | 45 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/matgl/graph/compute.py b/src/matgl/graph/compute.py index 9a5645cb..8af2a857 100644 --- a/src/matgl/graph/compute.py +++ b/src/matgl/graph/compute.py @@ -88,7 +88,7 @@ def create_line_graph(g: dgl.DGLGraph, threebody_cutoff: float, directed: bool = if directed: lg = _create_directed_line_graph(graph_with_three_body, threebody_cutoff) else: - lg, triple_bond_indices, n_triple_ij, n_triple_i, n_triple_s = _compute_3body(graph_with_three_body) + lg = _compute_3body(graph_with_three_body) return lg @@ -174,49 +174,44 @@ def _compute_3body(g: dgl.DGLGraph): n_triple_s (np.ndarray): number of three-body angles for each structure """ n_atoms = g.num_nodes() - first_col = g.edges()[0].cpu().numpy().reshape(-1, 1) - all_indices = np.arange(n_atoms).reshape(1, -1) - n_bond_per_atom = np.count_nonzero(first_col == all_indices, axis=0) + first_col = g.edges()[0].cpu().numpy() + + # Count bonds per atom efficiently + n_bond_per_atom = np.bincount(first_col, minlength=n_atoms) + n_triple_i = n_bond_per_atom * (n_bond_per_atom - 1) - n_triple = np.sum(n_triple_i) + n_triple = n_triple_i.sum() n_triple_ij = np.repeat(n_bond_per_atom - 1, n_bond_per_atom) - triple_bond_indices = np.empty((n_triple, 2), dtype=matgl.int_np) # type: ignore + + triple_bond_indices = np.empty((n_triple, 2), dtype=matgl.int_np) start = 0 cs = 0 for n in n_bond_per_atom: if n > 0: - """ - triple_bond_indices is generated from all pair permutations of atom indices. The - numpy version below does this with much greater efficiency. The equivalent slow - code is: - - ``` - for j, k in itertools.permutations(range(n), 2): - triple_bond_indices[index] = [start + j, start + k] - ``` - """ r = np.arange(n) x, y = np.meshgrid(r, r, indexing="xy") - c = np.stack([y.ravel(), x.ravel()], axis=1) - final = c[c[:, 0] != c[:, 1]] - triple_bond_indices[start : start + (n * (n - 1)), :] = final + cs + final = np.stack([y.ravel(), x.ravel()], axis=1) + mask = final[:, 0] != final[:, 1] + final = final[mask] + triple_bond_indices[start : start + n * (n - 1)] = final + cs start += n * (n - 1) cs += n - n_triple_s = [np.sum(n_triple_i[0:n_atoms])] src_id = torch.tensor(triple_bond_indices[:, 0], dtype=matgl.int_th) dst_id = torch.tensor(triple_bond_indices[:, 1], dtype=matgl.int_th) l_g = dgl.graph((src_id, dst_id)).to(g.device) - three_body_id = torch.concatenate(l_g.edges()) - n_triple_ij = torch.tensor(n_triple_ij, dtype=matgl.int_th).to(g.device) - max_three_body_id = torch.max(three_body_id) + 1 if three_body_id.numel() > 0 else 0 + three_body_id = torch.cat(l_g.edges()) + n_triple_ij = torch.tensor(n_triple_ij, dtype=matgl.int_th, device=g.device) + + max_three_body_id = three_body_id.max().item() + 1 if three_body_id.numel() > 0 else 0 + l_g.ndata["bond_dist"] = g.edata["bond_dist"][:max_three_body_id] l_g.ndata["bond_vec"] = g.edata["bond_vec"][:max_three_body_id] l_g.ndata["pbc_offset"] = g.edata["pbc_offset"][:max_three_body_id] l_g.ndata["n_triple_ij"] = n_triple_ij[:max_three_body_id] - n_triple_s = torch.tensor(n_triple_s, dtype=matgl.int_th) # type: ignore - return l_g, triple_bond_indices, n_triple_ij, n_triple_i, n_triple_s + + return l_g def _create_directed_line_graph(graph: dgl.DGLGraph, threebody_cutoff: float) -> dgl.DGLGraph: From 27c728f8e7a2c6f05c36c9d3f5734704e4fe632b Mon Sep 17 00:00:00 2001 From: kenko911 Date: Mon, 8 Jul 2024 09:03:44 -0700 Subject: [PATCH 09/19] type checking is added for scheduler --- src/matgl/utils/training.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/matgl/utils/training.py b/src/matgl/utils/training.py index 17953e12..27e15283 100644 --- a/src/matgl/utils/training.py +++ b/src/matgl/utils/training.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: import dgl import numpy as np - from torch.optim import Optimizer + from torch.optim import LRScheduler, Optimizer class MatglLightningModuleMixin: @@ -147,7 +147,7 @@ def __init__( data_std: float = 1.0, loss: str = "mse_loss", optimizer: Optimizer | None = None, - scheduler=None, + scheduler: LRScheduler | None = None, lr: float = 0.001, decay_steps: int = 1000, decay_alpha: float = 0.01, @@ -270,7 +270,7 @@ def __init__( loss: str = "mse_loss", loss_params: dict | None = None, optimizer: Optimizer | None = None, - scheduler=None, + scheduler: LRScheduler | None = None, lr: float = 0.001, decay_steps: int = 1000, decay_alpha: float = 0.01, From 3ac4f396cf98b5f6dbda6ca8d1a878b9d283ddfa Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sun, 14 Jul 2024 12:26:38 -0700 Subject: [PATCH 10/19] update M3GNet Potential training notebook for the demonstration of obtaining and using element offsets --- .../Training a M3GNet Potential with PyTorch Lightning.ipynb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/Training a M3GNet Potential with PyTorch Lightning.ipynb b/examples/Training a M3GNet Potential with PyTorch Lightning.ipynb index 28325197..9f4832cc 100644 --- a/examples/Training a M3GNet Potential with PyTorch Lightning.ipynb +++ b/examples/Training a M3GNet Potential with PyTorch Lightning.ipynb @@ -269,7 +269,10 @@ "# download a pre-trained M3GNet\n", "m3gnet_nnp = matgl.load_model(\"M3GNet-MP-2021.2.8-PES\")\n", "model_pretrained = m3gnet_nnp.model\n", - "lit_module_finetune = PotentialLightningModule(model=model_pretrained, lr=1e-4, include_line_graph=True)" + "# obtain element energy offset\n", + "property_offset = m3gnet_nnp.element_refs.property_offset\n", + "# you should test whether including the original property_offset helps improve training and validation accuracy\n", + "lit_module_finetune = PotentialLightningModule(model=model_pretrained, element_refs=property_offset, lr=1e-4, include_line_graph=True)" ] }, { From 992c17e9a4bf09475f82f7309b9e192e4a57f802 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sun, 14 Jul 2024 12:47:35 -0700 Subject: [PATCH 11/19] Downgrade sympy to avoid crash of SO3 operations --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index b3f54ece..0208189b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ ase==3.23.0 pydantic==2.7.1 boto3==1.34.101 numpy==1.26.4 +sympy==1.12.1 From 38df1003efed0ed3cd296a97f5419785940b13e7 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Tue, 16 Jul 2024 21:01:20 -0700 Subject: [PATCH 12/19] Smooth l1 loss function is added and united tests are improved --- src/matgl/utils/training.py | 11 ++++++++++- tests/utils/test_training.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/matgl/utils/training.py b/src/matgl/utils/training.py index 27e15283..6c84eecb 100644 --- a/src/matgl/utils/training.py +++ b/src/matgl/utils/training.py @@ -146,6 +146,7 @@ def __init__( data_mean: float = 0.0, data_std: float = 1.0, loss: str = "mse_loss", + loss_params: dict | None = None, optimizer: Optimizer | None = None, scheduler: LRScheduler | None = None, lr: float = 0.001, @@ -163,6 +164,7 @@ def __init__( data_mean: average of training data data_std: standard deviation of training data loss: loss function used for training + loss_params: parameters for loss function optimizer: optimizer for training scheduler: scheduler for training lr: learning rate for training @@ -184,11 +186,16 @@ def __init__( self.decay_alpha = decay_alpha if loss == "mse_loss": self.loss = F.mse_loss + elif loss == "huber_loss": + self.loss = F.huber_loss + elif loss == "smooth_l1_loss": + self.loss = F.smooth_l1_loss else: self.loss = F.l1_loss self.optimizer = optimizer self.scheduler = scheduler self.sync_dist = sync_dist + self.loss_params = loss_params if loss_params is not None else {} self.save_hyperparameters(ignore=["model"]) def forward( @@ -243,7 +250,7 @@ def loss_fn(self, loss: nn.Module, labels: torch.Tensor, preds: torch.Tensor): {"Total_Loss": total_loss, "MAE": mae, "RMSE": rmse} """ scaled_pred = torch.reshape(preds * self.data_std + self.data_mean, labels.size()) - total_loss = loss(labels, scaled_pred) + total_loss = loss(labels, scaled_pred, **self.loss_params) mae = self.mae(labels, scaled_pred) rmse = self.rmse(labels, scaled_pred) return {"Total_Loss": total_loss, "MAE": mae, "RMSE": rmse} @@ -339,6 +346,8 @@ def __init__( self.loss = F.mse_loss elif loss == "huber_loss": self.loss = F.huber_loss + elif loss == "smooth_l1_loss": + self.loss = F.smooth_l1_loss else: self.loss = F.l1_loss self.loss_params = loss_params if loss_params is not None else {} diff --git a/tests/utils/test_training.py b/tests/utils/test_training.py index 0bb4761a..83c165ff 100644 --- a/tests/utils/test_training.py +++ b/tests/utils/test_training.py @@ -192,7 +192,9 @@ def test_so3net_training(self, LiFePO4, BaNiO3): generator=torch.Generator(device=device), ) model = SO3Net(element_types=element_types, lmax=2, is_intensive=False) - lit_model = PotentialLightningModule(model=model, stress_weight=0.0001) + lit_model = PotentialLightningModule( + model=model, stress_weight=0.0001, loss="huber_loss", loss_params={"delta": 1.0} + ) # We will use CPU if MPS is available since there is a serious bug. trainer = pl.Trainer(max_epochs=2, accelerator=device, inference_mode=False) @@ -264,7 +266,9 @@ def test_tensornet_training(self, LiFePO4, BaNiO3): generator=torch.Generator(device=device), ) model = TensorNet(element_types=element_types, is_intensive=False) - lit_model = PotentialLightningModule(model=model, stress_weight=0.0001) + lit_model = PotentialLightningModule( + model=model, stress_weight=0.0001, loss="smooth_l1_loss", loss_params={"beta": 1.0} + ) # We will use CPU if MPS is available since there is a serious bug. trainer = pl.Trainer(max_epochs=2, accelerator=device, inference_mode=False) @@ -387,7 +391,9 @@ def test_m3gnet_property_training(self, LiFePO4, BaNiO3): is_intensive=True, readout_type="set2set", ) - lit_model = ModelLightningModule(model=model, include_line_graph=True) + lit_model = ModelLightningModule( + model=model, include_line_graph=True, loss="huber_loss", loss_params={"delta": 1.0} + ) # We will use CPU if MPS is available since there is a serious bug. trainer = pl.Trainer(max_epochs=2, accelerator=device) @@ -458,7 +464,7 @@ def test_so3net_property_training(self, LiFePO4, BaNiO3): target_property="graph", readout_type="set2set", ) - lit_model = ModelLightningModule(model=model) + lit_model = ModelLightningModule(model=model, loss="smooth_l1_loss", loss_params={"beta": 1.0}) # We will use CPU if MPS is available since there is a serious bug. trainer = pl.Trainer(max_epochs=2, accelerator=device) From 40133c0cce3585b20bafb2dbf9f750caa5eeaa2c Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sat, 20 Jul 2024 17:57:41 -0700 Subject: [PATCH 13/19] merge the method predict_structure and featurize_structure into a function including both --- ...ctions using MEGNet or M3GNet Models.ipynb | 13 ++++++ src/matgl/models/_m3gnet.py | 44 +++++++------------ tests/models/test_m3gnet.py | 8 ++-- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/examples/Property Predictions using MEGNet or M3GNet Models.ipynb b/examples/Property Predictions using MEGNet or M3GNet Models.ipynb index 9c512097..fd006bcb 100644 --- a/examples/Property Predictions using MEGNet or M3GNet Models.ipynb +++ b/examples/Property Predictions using MEGNet or M3GNet Models.ipynb @@ -103,6 +103,19 @@ "print(f\"The predicted formation energy for CsCl is {float(eform):.3f} eV/atom.\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0245162", + "metadata": {}, + "outputs": [], + "source": [ + "# Extract the structure features of a structure\n", + "feat_dict = model.model.predict_structure(struct, return_features=True)\n", + "# Print out structure-wise features, it should be the dimension of node_features * 2 from set2set layer\n", + "print(feat_dict[\"readout\"].shape)" + ] + }, { "cell_type": "markdown", "id": "90e95671", diff --git a/src/matgl/models/_m3gnet.py b/src/matgl/models/_m3gnet.py index 1c7cd936..e4d30e84 100644 --- a/src/matgl/models/_m3gnet.py +++ b/src/matgl/models/_m3gnet.py @@ -294,14 +294,15 @@ def forward( return fea_dict return torch.squeeze(output) - def featurize_structure( + def predict_structure( self, structure, state_feats: torch.Tensor | None = None, graph_converter: GraphConverter | None = None, output_layers: list | None = None, + return_features: bool = False, ): - """Convenience method to featurize a structure with M3GNet model. + """Convenience method to featurize or predict properties of a structure with M3GNet model. Args: structure: An input crystal/molecule. @@ -309,10 +310,12 @@ def featurize_structure( graph_converter: Object that implements a get_graph_from_structure. output_layers: List of names for the layer of GNN as output. Choose from "bond_expansion", "embedding", "three_body_basis", "gc_1", "gc_2", "gc_3", "readout", and "final". By default, all M3GNet layer - outputs are returned. + outputs are returned. Ignored if `return_features` is False. + return_features (bool): If True, return specified layer outputs. If False, only return final output. Returns: - output (dict): M3GNet intermediate and final layer outputs for a structure. + output (dict or torch.tensor): M3GNet intermediate and final layer outputs for a structure, or final + predicted property if `return_features` is False. """ allowed_output_layers = [ "bond_expansion", @@ -321,7 +324,10 @@ def featurize_structure( "readout", "final", ] + [f"gc_{i + 1}" for i in range(self.n_blocks)] - if output_layers is None: + + if not return_features: + output_layers = ["final"] + elif output_layers is None: output_layers = allowed_output_layers elif not isinstance(output_layers, list) or set(output_layers).difference(allowed_output_layers): raise ValueError(f"Invalid output_layers, it must be a sublist of {allowed_output_layers}.") @@ -330,33 +336,17 @@ def featurize_structure( from matgl.ext.pymatgen import Structure2Graph graph_converter = Structure2Graph(element_types=self.element_types, cutoff=self.cutoff) # type: ignore + g, lat, state_feats_default = graph_converter.get_graph(structure) g.edata["pbc_offshift"] = torch.matmul(g.edata["pbc_offset"], lat[0]) g.ndata["pos"] = g.ndata["frac_coords"] @ lat[0] + if state_feats is None: state_feats = torch.tensor(state_feats_default) - if output_layers == ["final"]: - return self(g=g, state_attr=state_feats).detach() - return { - k: v - for k, v in self(g=g, state_attr=state_feats, return_all_layer_output=True).items() - if k in output_layers - } - def predict_structure( - self, - structure, - state_feats: torch.Tensor | None = None, - graph_converter: GraphConverter | None = None, - ): - """Convenient method to directly predict property from structure. + model_output = self(g=g, state_attr=state_feats, return_all_layer_output=True) - Args: - structure: An input crystal/molecule. - state_feats (torch.tensor): Graph attributes - graph_converter: Object that implements a get_graph_from_structure. + if not return_features: + return model_output["final"].detach() - Returns: - output(torch.tensor): output property for a structure - """ - return self.featurize_structure(structure, state_feats, graph_converter, ["final"]) + return {k: v for k, v in model_output.items() if k in output_layers} diff --git a/tests/models/test_m3gnet.py b/tests/models/test_m3gnet.py index f7ed87a1..e7d81ba3 100644 --- a/tests/models/test_m3gnet.py +++ b/tests/models/test_m3gnet.py @@ -82,8 +82,8 @@ def test_featurize_structure(self, graph_MoS): model_extensive = M3GNet(is_intensive=False) for model in [model_extensive, model_intensive]: with pytest.raises(ValueError, match="Invalid output_layers"): - model.featurize_structure(structure, output_layers=["whatever"]) - features = model.featurize_structure(structure) + model.predict_structure(structure, output_layers=["whatever"], return_features=True) + features = model.predict_structure(structure, return_features=True) assert torch.numel(features["bond_expansion"]) == 252 assert torch.numel(features["three_body_basis"]) == 3276 for output_layer in ["embedding", "gc_1", "gc_2", "gc_3"]: @@ -95,4 +95,6 @@ def test_featurize_structure(self, graph_MoS): else: assert torch.numel(features["readout"]) == 2 assert torch.numel(features["final"]) == 1 - assert list(model.featurize_structure(structure, output_layers=["gc_1"]).keys()) == ["gc_1"] + assert list(model.predict_structure(structure, output_layers=["gc_1"], return_features=True).keys()) == [ + "gc_1" + ] From c469ef7134ea60025b5fb2261c00eb9261bdd2d2 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Sun, 28 Jul 2024 14:22:45 -0700 Subject: [PATCH 14/19] remove unnecessary else statement for training magmoms --- src/matgl/utils/training.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/matgl/utils/training.py b/src/matgl/utils/training.py index 6c84eecb..e8c03e22 100644 --- a/src/matgl/utils/training.py +++ b/src/matgl/utils/training.py @@ -510,9 +510,6 @@ def loss_fn( m_rmse = self.rmse(labels_3, preds_3) total_loss = total_loss + self.magmom_weight * m_loss - else: - m_mae = torch.zeros(1) - m_rmse = torch.zeros(1) return { "Total_Loss": total_loss, From e5764b38b53184f66dbcd67ae52972002986678b Mon Sep 17 00:00:00 2001 From: kenko911 Date: Tue, 13 Aug 2024 21:44:28 -0700 Subject: [PATCH 15/19] modify so3 operation implementation to make united tests pass due to the update of sympy --- changes.md | 2 +- src/matgl/utils/so3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changes.md b/changes.md index a6a49862..bc0290ea 100644 --- a/changes.md +++ b/changes.md @@ -10,7 +10,7 @@ nav_order: 3 - Improve the memory efficiency and speed of three-body interactions. (@kenko911) - FrechetCellFilter is added for variable cell relaxation in Relaxer class. (@kenko911) - Smooth l1 loss function is added for training. (@kenko911) - + ## 1.1.2 - Move AtomRef Fitting to numpy to avoid bug (@BowenD-UCB) - NVE ensemble added (@kenko911) diff --git a/src/matgl/utils/so3.py b/src/matgl/utils/so3.py index 7618feef..6a74d360 100644 --- a/src/matgl/utils/so3.py +++ b/src/matgl/utils/so3.py @@ -72,7 +72,7 @@ def generate_clebsch_gordan(lmax: int) -> torch.Tensor: m2 - m1, -m1 - m2, }: - coeff = clebsch_gordan(l1, l2, l3, m1, m2, m3) + coeff = clebsch_gordan(l1.item(), l2.item(), l3.item(), m1.item(), m2.item(), m3.item()) cg[c1, c2, c3] = float(coeff) return cg From ef0ce51a1ec4249960e2689d28f9811a748fb99d Mon Sep 17 00:00:00 2001 From: kenko911 Date: Wed, 14 Aug 2024 13:24:38 -0700 Subject: [PATCH 16/19] skip test_load_all_models for MacOS pytest now --- tests/test_integration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index e4f5519c..25239889 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,6 +1,8 @@ """This is an integration test file that checks on pre-trained models to ensure they still work.""" from __future__ import annotations +import os + import matgl import pytest @@ -12,6 +14,7 @@ def test_form_e(LiFePO4): assert model.predict_structure(LiFePO4) == pytest.approx(-2.5489, 3) +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Unreliable in CI environments.") def test_loading_all_models(): """ Test that all pre-trained models at least load. From 7f34ed91214c9556f8c2d104ad40b8110f37f2ca Mon Sep 17 00:00:00 2001 From: kenko911 Date: Wed, 4 Sep 2024 10:10:30 -0700 Subject: [PATCH 17/19] Reference for CHGNet is added --- README.md | 1 + docs/index.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 4a012972..485e2d08 100644 --- a/README.md +++ b/README.md @@ -296,3 +296,4 @@ ACI-1548562. [tutorials]: https://matgl.ai/tutorials "Tutorials" [tensornet]: https://arxiv.org/abs/2306.06482 "TensorNet" [so3net]: https://pubs.aip.org/aip/jcp/article-abstract/158/14/144801/2877924/SchNetPack-2-0-A-neural-network-toolbox-for "SO3Net" +[chgnet]: https://www.nature.com/articles/s42256-023-00716-3 "CHGNet" diff --git a/docs/index.md b/docs/index.md index 06a0d566..d72c5aeb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -304,3 +304,4 @@ ACI-1548562. [tutorials]: https://matgl.ai/tutorials "Tutorials" [tensornet]: https://arxiv.org/abs/2306.06482 "TensorNet" [so3net]: https://pubs.aip.org/aip/jcp/article-abstract/158/14/144801/2877924/SchNetPack-2-0-A-neural-network-toolbox-for "SO3Net" +[chgnet]: https://www.nature.com/articles/s42256-023-00716-3 "CHGNet" From fdae7a1c0362548088aced203f6f6bbc4c322d9a Mon Sep 17 00:00:00 2001 From: Tsz Wai Ko <47970742+kenko911@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:24:14 -0700 Subject: [PATCH 18/19] Update README.md and index.md for including CHGNet Signed-off-by: Tsz Wai Ko <47970742+kenko911@users.noreply.github.com> --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 485e2d08..dfc2a308 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Nassar, Carmelo Gonzales). Major milestones are summarized below. Please refer to the [changelog] for details. +- v1.1.0 (May 7 2024): Implementation of [CHGNet] + pre-trained models. - v1.0.0 (Feb 14 2024): Implementation of [TensorNet] and [SO3Net]. - v0.5.1 (Jun 9 2023): Model versioning implemented. - v0.5.0 (Jun 8 2023): Simplified saving and loading of models. Now models can be loaded with one line of code! @@ -87,6 +88,8 @@ We have implemented other models in matgl as well. A non-exhaustive list is give - [TensorNet], an O(3)-equivariant message-passing neural network architecture that leverages Cartesian tensor representations. - [SO3Net], a minimalist SO(3)-equivariant neural network. +- [CHGNet], an invariant message-passing neural network architecture that can predict + PES properties and magmoms. ## Installation @@ -226,6 +229,21 @@ information. If you are using any of the pretrained models, please cite the rele > Chen, C., Ong, S.P. *A universal graph deep learning interatomic potential for the periodic table.* Nature > Computational Science, 2023, 2, 718–728. DOI: [10.1038/s43588-022-00349-3][m3gnet]. +>**CHGNet** +> +> Deng, B., Zhong, P., Jun, K. et al. *CHGNet: as a pretrained universal neural network potential for charge-informed atomistic modelling.* +> Nat Mach Intell 5, 1031–1041 (2023). DOI:[10.1038/s42256-023-00716-3][chgnet] + +>**TensorNet** +> +> Simeon, G. De Fabritiis, G. *Tensornet: Cartesian tensor representations for efficient learning of molecular potentials.* +> Adv. Neural Info. Process. Syst. 36, (2024). DOI: [10.48550/arXiv.2306.06482][tensornet] + +>**SO3Net** +> +> Schütt, K. T., Hessmann, S. S. P., Gebauer, N. W. A., Lederer, J., Gastegger, M. *SchNetPack 2.0: A neural network toolbox for atomistic machine learning.* +> J. Chem. Phys. 158, 144801 (2023). DOI: [10.1063/5.0138367][so3net] + ## FAQs 1. **The `M3GNet-MP-2021.2.8-PES` differs from the original TensorFlow (TF) implementation!** From 64386495d3fc1ba4b419d4bc74c9746b0b7387c9 Mon Sep 17 00:00:00 2001 From: kenko911 Date: Wed, 4 Sep 2024 16:09:27 -0700 Subject: [PATCH 19/19] add more description for using CHGNet pretrained models in Relaxations and Simulations using the M3GNet Universal Potential.ipynb --- ...ations using the M3GNet Universal Potential.ipynb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/Relaxations and Simulations using the M3GNet Universal Potential.ipynb b/examples/Relaxations and Simulations using the M3GNet Universal Potential.ipynb index fbb49856..4d67babb 100644 --- a/examples/Relaxations and Simulations using the M3GNet Universal Potential.ipynb +++ b/examples/Relaxations and Simulations using the M3GNet Universal Potential.ipynb @@ -7,7 +7,7 @@ "source": [ "# Introduction\n", "\n", - "This notebook demonstrates the use of the pre-trained M3GNet model to perform structural relaxations, molecular dynamics simulations and single-point calculations.\n", + "This notebook demonstrates the use of the pre-trained universal potentials to perform structural relaxations, molecular dynamics simulations and single-point calculations.\n", "\n", "Author: Tsz Wai Ko (Kenko)\n", "Email: t1ko@ucsd.edu" @@ -42,7 +42,7 @@ "source": [ "# Loading the pre-trained M3GNet PES model\n", "\n", - "We will first load the M3GNet PES model, which is trained on the MP-2021.2.8 dataset. This can be done with a single line of code." + "We will first load the M3GNet PES model, which is trained on the MP-2021.2.8 dataset. This can be done with a single line of code. Here we only use M3GNet for demonstration and users can choose other available models." ] }, { @@ -52,7 +52,9 @@ "metadata": {}, "outputs": [], "source": [ - "pot = matgl.load_model(\"M3GNet-MP-2021.2.8-PES\")" + "# You can load any pretrained potentials such as CHGNet ('CHGNet-MPtrj-2023.12.1-PES-2.7M', 'CHGNet-MPtrj-2024.2.13-PES-11M')\n", + "# To see availabe models, use get_available_pretrained_models()\n", + "pot = matgl.load_model(\"M3GNet-MP-2021.2.8-PES\") " ] }, { @@ -120,7 +122,7 @@ "source": [ "# Single point energy calculation\n", "\n", - "Perform a single-point calculation for final structure using M3GNetCalculator." + "Perform a single-point calculation for final structure using PESCalculator." ] }, { @@ -154,7 +156,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.9" } }, "nbformat": 4,