cyrusyc commited on
Commit
aa496c2
1 Parent(s): 3cdbbc7

add version tag

Browse files
.github/README.md CHANGED
@@ -10,7 +10,7 @@
10
  > [!NOTE]
11
  > If you're interested in joining the effort, please reach out to Yuan at [cyrusyc@berkeley.edu](mailto:cyrusyc@berkeley.edu).
12
 
13
- MLIP Arena is an open-source platform for benchmarking machine learning interatomic potentials (MLIPs). The platform provides a unified interface for users to evaluate the performance of their models on a variety of tasks, including single-point density functional theory calculations and molecular dynamics simulations. The platform is designed to be extensible, allowing users to contribute new models, benchmarks, and training data to the platform.
14
 
15
  ## Contribute
16
 
 
10
  > [!NOTE]
11
  > If you're interested in joining the effort, please reach out to Yuan at [cyrusyc@berkeley.edu](mailto:cyrusyc@berkeley.edu).
12
 
13
+ MLIP Arena is a platform for evaluating foundation machine learning interatomic potentials (MLIPs) beyond conventional energy and force error metrics. It focuses on revealing the underlying physics and chemistry learned by these models and assessing their performance in molecular dynamics (MD) simulations. The platform's benchmarks are specifically designed to evaluate the readiness and reliability of open-source, open-weight models in accurately reproducing both qualitative and quantitative behaviors of atomic systems.
14
 
15
  ## Contribute
16
 
mlip_arena/models/chgnet.py DELETED
@@ -1,54 +0,0 @@
1
- from typing import Optional, Tuple
2
-
3
- import numpy as np
4
- import torch
5
- from ase import Atoms
6
- from ase.calculators.calculator import all_changes
7
- from huggingface_hub import hf_hub_download
8
- from torch_geometric.data import Data
9
-
10
- from mlip_arena.models import MLIP, MLIPCalculator
11
-
12
- # TODO: WIP
13
-
14
-
15
- class CHGNet(MLIPCalculator):
16
- def __init__(
17
- self,
18
- device: torch.device | None = None,
19
- restart=None,
20
- atoms=None,
21
- directory=".",
22
- **kwargs,
23
- ):
24
- self.device = device or torch.device(
25
- "cuda" if torch.cuda.is_available() else "cpu"
26
- )
27
-
28
- super().__init__(
29
- model=model, restart=restart, atoms=atoms, directory=directory, **kwargs
30
- )
31
-
32
- self.name: str = self.__class__.__name__
33
- self.implemented_properties = ["energy", "forces", "stress"]
34
-
35
- def calculate(
36
- self, atoms: Atoms, properties: list[str], system_changes: list = all_changes
37
- ):
38
- """Calculate energies and forces for the given Atoms object"""
39
- super().calculate(atoms, properties, system_changes)
40
-
41
- output = self.forward(atoms)
42
-
43
- self.results = {}
44
- if "energy" in properties:
45
- self.results["energy"] = output["energy"].item()
46
- if "forces" in properties:
47
- self.results["forces"] = output["forces"].cpu().detach().numpy()
48
- if "stress" in properties:
49
- self.results["stress"] = output["stress"].cpu().detach().numpy()
50
-
51
- def forward(self, x: Data | Atoms) -> dict[str, torch.Tensor]:
52
- """Implement data conversion, graph creation, and model forward pass"""
53
- # TODO
54
- raise NotImplementedError
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mlip_arena/models/externals.py DELETED
@@ -1,283 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- from pathlib import Path
5
- from typing import Literal
6
-
7
- import matgl
8
- import requests
9
- import torch
10
- from alignn.ff.ff import AlignnAtomwiseCalculator, get_figshare_model_ff, default_path
11
- from ase import Atoms
12
- from chgnet.model.dynamics import CHGNetCalculator
13
- from chgnet.model.model import CHGNet as CHGNetModel
14
- from fairchem.core import OCPCalculator
15
- from mace.calculators import MACECalculator
16
- from matgl.ext.ase import PESCalculator
17
- from orb_models.forcefield import pretrained
18
- from orb_models.forcefield.calculator import ORBCalculator
19
- from sevenn.sevennet_calculator import SevenNetCalculator
20
-
21
-
22
- # Avoid circular import
23
- def get_freer_device() -> torch.device:
24
- """Get the GPU with the most free memory, or use MPS if available.
25
- s
26
- Returns:
27
- torch.device: The selected GPU device or MPS.
28
-
29
- Raises:
30
- ValueError: If no GPU or MPS is available.
31
- """
32
- device_count = torch.cuda.device_count()
33
- if device_count > 0:
34
- # If CUDA GPUs are available, select the one with the most free memory
35
- mem_free = [
36
- torch.cuda.get_device_properties(i).total_memory
37
- - torch.cuda.memory_allocated(i)
38
- for i in range(device_count)
39
- ]
40
- free_gpu_index = mem_free.index(max(mem_free))
41
- device = torch.device(f"cuda:{free_gpu_index}")
42
- print(
43
- f"Selected GPU {device} with {mem_free[free_gpu_index] / 1024**2:.2f} MB free memory from {device_count} GPUs"
44
- )
45
- elif torch.backends.mps.is_available():
46
- # If no CUDA GPUs are available but MPS is, use MPS
47
- print("No GPU available. Using MPS.")
48
- device = torch.device("mps")
49
- else:
50
- # Fallback to CPU if neither CUDA GPUs nor MPS are available
51
- print("No GPU or MPS available. Using CPU.")
52
- device = torch.device("cpu")
53
-
54
- return device
55
-
56
-
57
- class MACE_MP_Medium(MACECalculator):
58
- def __init__(
59
- self,
60
- checkpoint="http://tinyurl.com/5yyxdm76",
61
- device: str | None = None,
62
- default_dtype="float32",
63
- **kwargs,
64
- ):
65
- cache_dir = Path.home() / ".cache" / "mace"
66
- checkpoint_url_name = "".join(
67
- c for c in os.path.basename(checkpoint) if c.isalnum() or c in "_"
68
- )
69
- cached_model_path = f"{cache_dir}/{checkpoint_url_name}"
70
- if not os.path.isfile(cached_model_path):
71
- import urllib
72
-
73
- os.makedirs(cache_dir, exist_ok=True)
74
- _, http_msg = urllib.request.urlretrieve(checkpoint, cached_model_path)
75
- if "Content-Type: text/html" in http_msg:
76
- raise RuntimeError(
77
- f"Model download failed, please check the URL {checkpoint}"
78
- )
79
- model = cached_model_path
80
-
81
- device = device or str(get_freer_device())
82
-
83
- super().__init__(
84
- model_paths=model, device=device, default_dtype=default_dtype, **kwargs
85
- )
86
-
87
-
88
- # TODO: could share the same class with MACE_MP_Medium
89
- class MACE_OFF_Medium(MACECalculator):
90
- def __init__(
91
- self,
92
- checkpoint="https://github.com/ACEsuit/mace-off/raw/main/mace_off23/MACE-OFF23_medium.model?raw=true",
93
- device: str | None = None,
94
- default_dtype="float32",
95
- **kwargs,
96
- ):
97
- cache_dir = Path.home() / ".cache" / "mace"
98
- checkpoint_url_name = "".join(
99
- c for c in os.path.basename(checkpoint) if c.isalnum() or c in "_"
100
- )
101
- cached_model_path = f"{cache_dir}/{checkpoint_url_name}"
102
- if not os.path.isfile(cached_model_path):
103
- import urllib
104
-
105
- os.makedirs(cache_dir, exist_ok=True)
106
- _, http_msg = urllib.request.urlretrieve(checkpoint, cached_model_path)
107
- if "Content-Type: text/html" in http_msg:
108
- raise RuntimeError(
109
- f"Model download failed, please check the URL {checkpoint}"
110
- )
111
- model = cached_model_path
112
-
113
- device = device or str(get_freer_device())
114
-
115
- super().__init__(
116
- model_paths=model, device=device, default_dtype=default_dtype, **kwargs
117
- )
118
-
119
-
120
- class CHGNet(CHGNetCalculator):
121
- def __init__(
122
- self,
123
- checkpoint: CHGNetModel | None = None, # TODO: specifiy version
124
- device: str | None = None,
125
- stress_weight: float | None = 1 / 160.21766208,
126
- on_isolated_atoms: Literal["ignore", "warn", "error"] = "warn",
127
- **kwargs,
128
- ) -> None:
129
- use_device = device or str(get_freer_device())
130
- super().__init__(
131
- model=checkpoint,
132
- use_device=use_device,
133
- stress_weight=stress_weight,
134
- on_isolated_atoms=on_isolated_atoms,
135
- **kwargs,
136
- )
137
-
138
- def calculate(
139
- self,
140
- atoms: Atoms | None = None,
141
- properties: list | None = None,
142
- system_changes: list | None = None,
143
- ) -> None:
144
- super().calculate(atoms, properties, system_changes)
145
-
146
- # for ase.io.write compatibility
147
- self.results.pop("crystal_fea", None)
148
-
149
-
150
- class M3GNet(PESCalculator):
151
- def __init__(
152
- self,
153
- checkpoint="M3GNet-MP-2021.2.8-PES",
154
- # TODO: cannot assign device
155
- state_attr: torch.Tensor | None = None,
156
- stress_weight: float = 1.0,
157
- **kwargs,
158
- ) -> None:
159
- potential = matgl.load_model(checkpoint)
160
- super().__init__(potential, state_attr, stress_weight, **kwargs)
161
-
162
-
163
- class EquiformerV2(OCPCalculator):
164
- def __init__(
165
- self,
166
- checkpoint="EquiformerV2-lE4-lF100-S2EFS-OC22", # TODO: import from registry
167
- # TODO: cannot assign device
168
- local_cache="/tmp/ocp/",
169
- cpu=False,
170
- seed=0,
171
- **kwargs,
172
- ) -> None:
173
- super().__init__(
174
- model_name=checkpoint,
175
- local_cache=local_cache,
176
- cpu=cpu,
177
- seed=seed,
178
- **kwargs,
179
- )
180
-
181
- def calculate(self, atoms: Atoms, properties, system_changes) -> None:
182
- super().calculate(atoms, properties, system_changes)
183
-
184
- self.results.update(
185
- force=atoms.get_forces(),
186
- )
187
-
188
-
189
- class EquiformerV2OC20(OCPCalculator):
190
- def __init__(
191
- self,
192
- checkpoint="EquiformerV2-31M-S2EF-OC20-All+MD", # TODO: import from registry
193
- # TODO: cannot assign device
194
- local_cache="/tmp/ocp/",
195
- cpu=False,
196
- seed=0,
197
- **kwargs,
198
- ) -> None:
199
- super().__init__(
200
- model_name=checkpoint,
201
- local_cache=local_cache,
202
- cpu=cpu,
203
- seed=seed,
204
- **kwargs,
205
- )
206
-
207
-
208
- class eSCN(OCPCalculator):
209
- def __init__(
210
- self,
211
- checkpoint="eSCN-L6-M3-Lay20-S2EF-OC20-All+MD", # TODO: import from registry
212
- # TODO: cannot assign device
213
- local_cache="/tmp/ocp/",
214
- cpu=False,
215
- seed=0,
216
- **kwargs,
217
- ) -> None:
218
- super().__init__(
219
- model_name=checkpoint,
220
- local_cache=local_cache,
221
- cpu=cpu,
222
- seed=seed,
223
- **kwargs,
224
- )
225
-
226
- def calculate(self, atoms: Atoms, properties, system_changes) -> None:
227
- super().calculate(atoms, properties, system_changes)
228
-
229
- self.results.update(
230
- force=atoms.get_forces(),
231
- )
232
-
233
-
234
- class ALIGNN(AlignnAtomwiseCalculator):
235
- def __init__(self, device=None, **kwargs) -> None:
236
- # TODO: cannot control version
237
- # _ = get_figshare_model_ff(dir_path=dir_path)
238
- model_path = default_path()
239
-
240
- device = device or get_freer_device()
241
- super().__init__(path=model_path, device=device, **kwargs)
242
-
243
-
244
- class SevenNet(SevenNetCalculator):
245
- def __init__(
246
- self,
247
- checkpoint="7net-0", # TODO: import from registry
248
- device=None,
249
- **kwargs,
250
- ):
251
- device = device or get_freer_device()
252
- super().__init__(checkpoint, device=device, **kwargs)
253
-
254
-
255
- class ORB(ORBCalculator):
256
- def __init__(
257
- self,
258
- checkpoint="orbff-v1-20240827.ckpt",
259
- device=None,
260
- **kwargs,
261
- ):
262
- device = device or get_freer_device()
263
-
264
- cache_dir = Path.home() / ".cache" / "orb"
265
- cache_dir.mkdir(parents=True, exist_ok=True)
266
- ckpt_path = cache_dir / "orbff-v1-20240827.ckpt"
267
-
268
- url = f"https://storage.googleapis.com/orbitalmaterials-public-models/forcefields/{checkpoint}"
269
-
270
- if not ckpt_path.exists():
271
- print(f"Downloading ORB model from {url} to {ckpt_path}...")
272
- try:
273
- response = requests.get(url, stream=True, timeout=120)
274
- response.raise_for_status()
275
- with open(ckpt_path, "wb") as f:
276
- for chunk in response.iter_content(chunk_size=8192):
277
- f.write(chunk)
278
- print("Download completed.")
279
- except requests.exceptions.RequestException as e:
280
- raise RuntimeError("Failed to download ORB model.") from e
281
-
282
- orbff = pretrained.orb_v1(weights_path=ckpt_path, device=device)
283
- super().__init__(orbff, device=device, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mlip_arena/models/registry.yaml CHANGED
@@ -50,7 +50,7 @@ M3GNet:
50
  class: M3GNet
51
  family: matgl
52
  package: matgl==1.1.2
53
- checkpoint:
54
  username: cyrusyc
55
  last-update: 2024-07-08T00:00:00
56
  datetime: 2024-07-08T00:00:00
@@ -135,6 +135,7 @@ eqV2(OMat):
135
  prediction: EFS
136
  nvt: true
137
  npt: true
 
138
  github: https://github.com/FAIR-Chem/fairchem
139
  doi: https://arxiv.org/abs/2410.12771
140
 
@@ -206,6 +207,8 @@ MACE-OFF(M):
206
  module: externals
207
  class: MACE_OFF_Medium
208
  family: mace-off
 
 
209
  username: cyrusyc
210
  last-update: 2024-03-25T14:30:00
211
  datetime: 2024-03-25T14:30:00 # TODO: Fake datetime
@@ -228,7 +231,7 @@ ALIGNN:
228
  class: ALIGNN
229
  family: alignn
230
  package: alignn==2024.5.27
231
- checkpoint:
232
  username: cyrusyc
233
  last-update: 2024-07-08T00:00:00
234
  datetime: 2024-07-08T00:00:00
 
50
  class: M3GNet
51
  family: matgl
52
  package: matgl==1.1.2
53
+ checkpoint: M3GNet-MP-2021.2.8-PES
54
  username: cyrusyc
55
  last-update: 2024-07-08T00:00:00
56
  datetime: 2024-07-08T00:00:00
 
135
  prediction: EFS
136
  nvt: true
137
  npt: true
138
+ date: 2024-10-18
139
  github: https://github.com/FAIR-Chem/fairchem
140
  doi: https://arxiv.org/abs/2410.12771
141
 
 
207
  module: externals
208
  class: MACE_OFF_Medium
209
  family: mace-off
210
+ package: mace-torch==0.3.4
211
+ checkpoint: MACE-OFF23_medium.model
212
  username: cyrusyc
213
  last-update: 2024-03-25T14:30:00
214
  datetime: 2024-03-25T14:30:00 # TODO: Fake datetime
 
231
  class: ALIGNN
232
  family: alignn
233
  package: alignn==2024.5.27
234
+ checkpoint: 2024.5.27
235
  username: cyrusyc
236
  last-update: 2024-07-08T00:00:00
237
  datetime: 2024-07-08T00:00:00
pyproject.toml CHANGED
@@ -163,6 +163,7 @@ ignore = [
163
  "FBT003", # boolean positional variable in function call
164
  "PERF203", # `try`-`except` within a loop incurs performance overhead (no overhead in Py 3.11+)
165
  "F405", # 'module' may be undefined, or defined from star imports
 
166
  ]
167
  fixable = ["ALL"]
168
  pydocstyle.convention = "google"
 
163
  "FBT003", # boolean positional variable in function call
164
  "PERF203", # `try`-`except` within a loop incurs performance overhead (no overhead in Py 3.11+)
165
  "F405", # 'module' may be undefined, or defined from star imports
166
+ "C0301", # Line too long
167
  ]
168
  fixable = ["ALL"]
169
  pydocstyle.convention = "google"
serve/leaderboard.py CHANGED
@@ -28,6 +28,7 @@ table = pd.DataFrame(
28
  "Training Set",
29
  "Code",
30
  "Paper",
 
31
  "First Release",
32
  ]
33
  )
@@ -46,6 +47,7 @@ for model in MODELS:
46
  "Training Set": metadata.get("datasets", []),
47
  "Code": metadata.get("github", None) if metadata else None,
48
  "Paper": metadata.get("doi", None) if metadata else None,
 
49
  "First Release": metadata.get("date", None),
50
  }
51
  table = pd.concat([table, pd.DataFrame([new_row])], ignore_index=True)
@@ -73,11 +75,11 @@ st.markdown(
73
 
74
  ### :red[Introduction]
75
 
76
- Foundation machine learning interatomic potentials (fMLIPs) trained on extensive databases containing millions of density functional theory (DFT) calculations have shown remarkable zero-shot predictive capabilities for complex atomic interactions. These potentials derive quantum mechanical insights with high accuracy, expressivity, and generalizability, significantly outperforming classical empirical force fields while maintaining comparable computational efficiency.
77
 
78
- However, MLIPs trained on atomic energy and force labels do not neccessarily capture the correct atomic interactions, even though they usually excel in error-based metrics for bulk systems. To drive further advancements in this field, it is crucial to establish mechanisms that ensure fair and transparent benchmarking practices that go beyond basic regression metrics.
79
 
80
- MLIP Arena aims to provide an equitable and transparent platform for benchmarking MLIPs in a collaborative, crowd-sourced environment. Its primary goal is to uncover the learned physics and chemistry of open-source, open-weight MLIPs. The benchmarks are designed to be agnostic to both the underlying architecture and the specific training targets, such as density functionals, ensuring a comprehensive and unbiased evaluation.
81
 
82
  """,
83
  unsafe_allow_html=True,
 
28
  "Training Set",
29
  "Code",
30
  "Paper",
31
+ "Checkpoint",
32
  "First Release",
33
  ]
34
  )
 
47
  "Training Set": metadata.get("datasets", []),
48
  "Code": metadata.get("github", None) if metadata else None,
49
  "Paper": metadata.get("doi", None) if metadata else None,
50
+ "Checkpoint": metadata.get("checkpoint", None),
51
  "First Release": metadata.get("date", None),
52
  }
53
  table = pd.concat([table, pd.DataFrame([new_row])], ignore_index=True)
 
75
 
76
  ### :red[Introduction]
77
 
78
+ Foundation machine learning interatomic potentials (fMLIPs), trained on extensive databases containing millions of density functional theory (DFT) calculations, have demonstrated remarkable zero-shot predictive capabilities for complex atomic interactions. These potentials derive quantum mechanical insights with high accuracy, expressivity, and generalizability, significantly outperforming classical empirical force fields while maintaining comparable computational efficiency.
79
 
80
+ However, MLIPs trained on atomic energy and force labels do not necessarily capture the correct atomic interactions, even though they often excel in error-based metrics for bulk systems. To drive further advancements in this field, it is crucial to establish mechanisms that ensure fair and transparent benchmarking practices beyond basic regression metrics.
81
 
82
+ MLIP Arena aims to provide a fair and transparent platform for benchmarking MLIPs in a crowdsourced setting. Its primary goal is to uncover the learned physics and chemistry of open-source, open-weight MLIPs. The benchmarks are designed to be agnostic to both the underlying architecture and specific training targets, such as density functionals, ensuring a cross-comparable and unbiased evaluation.
83
 
84
  """,
85
  unsafe_allow_html=True,
serve/tasks/stability.py CHANGED
@@ -19,7 +19,7 @@ st.markdown("""
19
  Stable and accurate molecular dynamics (MD) simulations are important for understanding the properties of matters.
20
  However, many MLIPs have unphysical potential energy surface (PES) at the short-range interatomic distances or under many-body effect. These are often manifested as softened repulsion and hole in the PES and can lead to incorrect and sampling of the phase space.
21
 
22
- Here, we analyze the stability of the MD simulations under high pressure conditions by gradually increasing the pressure from 0 to 1000 GPa at 300K until the system crashes or completes 100 ps trajectory. This benchmark also explores faster the far-from-equilibrium dynamics of the system and the "durability" of the MLIPs under extreme conditions.
23
  """)
24
 
25
  st.markdown("### Methods")
@@ -80,7 +80,6 @@ method_color_mapping = {
80
  ###
81
 
82
  # Determine the bin edges for the entire dataset to keep them consistent across groups
83
- # bins = np.histogram_bin_edges(df['total_steps'], bins=10)
84
 
85
  max_steps = df["total_steps"].max()
86
  max_target_steps = df["target_steps"].max()
@@ -89,14 +88,11 @@ bins = np.append(np.arange(0, max_steps + 1, max_steps // 10), max_target_steps)
89
  bin_labels = [f"{bins[i]}-{bins[i+1]}" for i in range(len(bins) - 1)]
90
 
91
  num_bins = len(bin_labels)
92
- # colormap = px.colors.sequential.Darkmint_r
93
  colormap = px.colors.sequential.YlOrRd_r
94
  indices = np.linspace(0, len(colormap) - 1, num_bins, dtype=int)
95
  bin_colors = [colormap[i] for i in indices]
96
- # bin_colors[-1] = px.colors.sequential.Greens[-1]
97
 
98
  # Initialize a dictionary to hold the counts for each method and bin range
99
- # counts_per_method = {method: [0] * len(bin_labels) for method in df["method"].unique()}
100
  counts_per_method = {method: [0] * len(bin_labels) for method in df["method"].unique()}
101
 
102
 
@@ -160,7 +156,7 @@ plot_md_steps(counts_per_method, count_or_percetange)
160
 
161
  st.markdown(
162
  """
163
- > The histogram shows the distribution of the total number of MD steps before the system crashes or completes the trajectory. :red[The color of the bins indicates the number of steps in the bin]. :blue[The height of the bars indicates the percentage of each bin among all the runs].
164
  """
165
  )
166
 
 
19
  Stable and accurate molecular dynamics (MD) simulations are important for understanding the properties of matters.
20
  However, many MLIPs have unphysical potential energy surface (PES) at the short-range interatomic distances or under many-body effect. These are often manifested as softened repulsion and hole in the PES and can lead to incorrect and sampling of the phase space.
21
 
22
+ Here, we analyze the stability of the MD simulations under high pressure conditions by gradually increasing the pressure from 0 to 1000 GPa at 300K until the system crashes or completes 100 ps trajectory. This benchmark also explores faster the far-from-equilibrium dynamics of the system and the durability of the MLIPs under extreme conditions.
23
  """)
24
 
25
  st.markdown("### Methods")
 
80
  ###
81
 
82
  # Determine the bin edges for the entire dataset to keep them consistent across groups
 
83
 
84
  max_steps = df["total_steps"].max()
85
  max_target_steps = df["target_steps"].max()
 
88
  bin_labels = [f"{bins[i]}-{bins[i+1]}" for i in range(len(bins) - 1)]
89
 
90
  num_bins = len(bin_labels)
 
91
  colormap = px.colors.sequential.YlOrRd_r
92
  indices = np.linspace(0, len(colormap) - 1, num_bins, dtype=int)
93
  bin_colors = [colormap[i] for i in indices]
 
94
 
95
  # Initialize a dictionary to hold the counts for each method and bin range
 
96
  counts_per_method = {method: [0] * len(bin_labels) for method in df["method"].unique()}
97
 
98
 
 
156
 
157
  st.markdown(
158
  """
159
+ > The histogram shows the distribution of the total number of MD steps before the system crashes or completes the trajectory. :red[The color of the bins indicates the number of steps in the bin]. :blue[The height of the bars indicates the number or percentage of each bin among all the runs].
160
  """
161
  )
162