Repository: MDIL-SNU/SevenNet

Issue:
Remove SevenNet_MF SCAN checkpoint (also from commit history if possible)
+ check pip install of this checkpoint

Discussion:

Solution patch:
diff --git a/setup.cfg b/setup.cfg
index 78408d7..84ac35f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -10,5 +10,5 @@ include_trailing_comma=True
 force_grid_wrap=0
 use_parentheses=True
 line_length=80
-known_third_party=ase,braceexpand,e3nn,numpy,pandas,pytest,sklearn,torch,torch_geometric,tqdm,yaml
+known_third_party=ase,braceexpand,e3nn,numpy,packaging,pandas,pytest,sklearn,torch,torch_geometric,tqdm,yaml
 known_first_party=
diff --git a/sevenn/checkpoint.py b/sevenn/checkpoint.py
index 050ff21..9d8e284 100644
--- a/sevenn/checkpoint.py
+++ b/sevenn/checkpoint.py
@@ -7,6 +7,7 @@ from datetime import datetime
 from typing import Any, Dict, Optional, Union
 
 import pandas as pd
+from packaging.version import Version
 from torch import Tensor
 from torch import load as torch_load
 
@@ -51,6 +52,12 @@ def copy_state_dict(state_dict) -> dict:
 
 
 def _config_cp_routine(config):
+    cp_ver = Version(config.get('version', None))
+    this_ver = Version(sevenn.__version__)
+    if cp_ver > this_ver:
+        warnings.warn(f'The checkpoint version ({cp_ver}) is newer than this source'
+                      f'({this_ver}). This may cause unexpected behaviors')
+
     defaults = {**consts.model_defaults(config)}
     config = compat.patch_old_config(config)  # type: ignore
 
diff --git a/sevenn/error_recorder.py b/sevenn/error_recorder.py
index b46f6ea..26f9537 100644
--- a/sevenn/error_recorder.py
+++ b/sevenn/error_recorder.py
@@ -369,9 +369,9 @@ class ErrorRecorder:
             if is_stress:
                 stress_metric = CustomError(criteria, **get_err_type('Stress'))
                 metrics.append((stress_metric, config[KEY.STRESS_WEIGHT]))
-        else:
+        else:  # TODO: this is hard-coded
             for efs in ['Energy', 'Force', 'Stress']:
-                if efs == 'stress' and not is_stress:
+                if efs == 'Stress' and not is_stress:
                     continue
                 lf, w = _get_loss_function_from_name(loss_functions, efs)
                 if lf is None:
diff --git a/sevenn/scripts/deploy.py b/sevenn/scripts/deploy.py
index c5f9a32..51ded15 100644
--- a/sevenn/scripts/deploy.py
+++ b/sevenn/scripts/deploy.py
@@ -36,7 +36,7 @@ def deploy(checkpoint, fname='deployed_serial.pt', modal: Optional[str] = None):
 
     if modal:
         model.prepare_modal_deploy(modal)
-    elif model.modal_map is not None:
+    elif model.modal_map is not None and len(model.modal_map) >= 1:
         raise ValueError(
             f'Modal is not given. It has: {list(model.modal_map.keys())}'
         )
diff --git a/sevenn/scripts/processing_continue.py b/sevenn/scripts/processing_continue.py
index 1029025..6671177 100644
--- a/sevenn/scripts/processing_continue.py
+++ b/sevenn/scripts/processing_continue.py
@@ -66,12 +66,11 @@ def processing_continue_v2(config):  # simpler
     if config_cp.get(KEY.USE_MODALITY, False) != config.get(KEY.USE_MODALITY):
         raise ValueError('use_modality is not same. Check sevenn_cp')
 
-    modal_map = config_cp.get(KEY.MODAL_MAP, {})
-    config.update({KEY.MODAL_MAP: modal_map})
-    if len(modal_map) > 0:
+    modal_map = config_cp.get(KEY.MODAL_MAP, None)  # dict | None
+    if modal_map and len(modal_map) > 0:
         modalities = list(modal_map.keys())
         log.writeline(f'Multimodal model found: {modalities}')
-        log.writeline(f'{KEY.USE_MODALITY}: True')
+        log.writeline('use_modality: True')
         config[KEY.USE_MODALITY] = True
 
     from_epoch = checkpoint.epoch or 0
diff --git a/sevenn/train/atoms_dataset.py b/sevenn/train/atoms_dataset.py
index 81b9b4b..adb028e 100644
--- a/sevenn/train/atoms_dataset.py
+++ b/sevenn/train/atoms_dataset.py
@@ -268,11 +268,11 @@ def from_config(
 
     if not config[KEY.COMPUTE_STATISTICS]:
         log.writeline(
-            """
-            Computing statistics is skipped, note that if any of other
-            configurations requires statistics (shift, scale, avg_num_neigh,
-            chemical_species as auto), SevenNet eventually raise an error!
-            """
+            (
+                'Computing statistics is skipped, note that if any of other'
+                'configurations requires statistics (shift, scale, avg_num_neigh,'
+                'chemical_species as auto), SevenNet eventually raise an error!'
+            )
         )
         return datasets
 
diff --git a/sevenn/train/graph_dataset.py b/sevenn/train/graph_dataset.py
index 03d8fbc..349ace4 100644
--- a/sevenn/train/graph_dataset.py
+++ b/sevenn/train/graph_dataset.py
@@ -645,7 +645,7 @@ def from_config(
             dataset_path = os.path.join(working_dir, 'sevenn_data', f'{name}.pt')
             if os.path.exists(dataset_path) and 'force_reload' not in dataset_args:
                 log.writeline(
-                    f'Dataset will be loaded from {dataset_path}, without update.'
+                    f'Dataset will be loaded from {dataset_path}, without update. '
                     + 'If you have changed your files to read, put force_reload=True'
                     + ' under the data_format_args key'
                 )


Test patch:
diff --git a/tests/unit_tests/test_errors.py b/tests/unit_tests/test_errors.py
index 100c2b1..36024a8 100644
--- a/tests/unit_tests/test_errors.py
+++ b/tests/unit_tests/test_errors.py
@@ -261,6 +261,25 @@ def test_error_recorder_from_config(conf):
         if metric.name == 'TotalLoss':
             total_loss_flag = True
             for loss_metric, _ in metric.metrics:  # type: ignore
-                print(loss_metric.func)
                 assert isinstance(loss_metric.func, loss_dict[conf['loss']])
     assert total_loss_flag
+
+
+@pytest.mark.parametrize(
+    'conf', [config(), config(is_train_stress=False), config(loss='huber')]
+)
+def test_error_recorder_from_config_and_loss_functions(conf):
+    loss_functions = loss.get_loss_functions_from_config(conf)
+    recorder = erc.ErrorRecorder.from_config(conf, loss_functions)
+
+    total_loss_flag = False
+    for metric in recorder.metrics:
+        if conf['is_train_stress'] is False:
+            assert 'stress' not in metric.name
+        if metric.name == 'TotalLoss':
+            total_loss_flag = True
+            for loss_metric, _ in metric.metrics:  # type: ignore
+                assert isinstance(
+                    loss_metric.loss_def.criterion, loss_dict[conf['loss']]
+                )
+    assert total_loss_flag