sid-betalol commited on
Commit
26b272c
Β·
verified Β·
1 Parent(s): 3c2a001

fix sorting

Browse files
Files changed (1) hide show
  1. app.py +249 -101
app.py CHANGED
@@ -1,61 +1,68 @@
1
- from pathlib import Path
2
  import ast
3
  import json
 
4
  import traceback
5
- import pandas as pd
6
- import numpy as np
7
-
8
- import gradio as gr
9
- from datasets import Features, Value, load_dataset
10
  from datetime import datetime
11
- import os
12
 
 
 
 
13
  from about import (
14
- PROBLEM_TYPES, TOKEN, CACHE_PATH, API, submissions_repo, results_repo,
15
- COLUMN_DISPLAY_NAMES, COUNT_BASED_METRICS, METRIC_GROUPS,
16
- METRIC_GROUP_COLORS, COLUMN_TO_GROUP, TRAINING_DATASETS
 
 
 
 
 
 
 
17
  )
 
18
 
19
-
20
- RESULT_FEATURES = Features({
21
- "run_name": Value("string"),
22
- "timestamp": Value("string"),
23
- "n_structures": Value("int64"),
24
- "overall_valid_count": Value("int64"),
25
- "charge_neutral_count": Value("int64"),
26
- "distance_valid_count": Value("int64"),
27
- "plausibility_valid_count": Value("int64"),
28
- "unique_count": Value("int64"),
29
- "novel_count": Value("int64"),
30
- "mean_formation_energy": Value("float64"),
31
- "formation_energy_std": Value("float64"),
32
- "stability_mean_above_hull": Value("float64"),
33
- "stability_std_e_above_hull": Value("float64"),
34
- "stability_mean_ensemble_std": Value("float64"),
35
- "mean_relaxation_RMSD": Value("float64"),
36
- "relaxation_RMSE_std": Value("float64"),
37
- "stable_count": Value("int64"),
38
- "unique_in_stable_count": Value("int64"),
39
- "sun_count": Value("int64"),
40
- "metastable_count": Value("int64"),
41
- "unique_in_metastable_count": Value("int64"),
42
- "msun_count": Value("int64"),
43
- "JSDistance": Value("float64"),
44
- "MMD": Value("float64"),
45
- "FrechetDistance": Value("float64"),
46
- "element_diversity": Value("float64"),
47
- "space_group_diversity": Value("float64"),
48
- "site_diversity": Value("float64"),
49
- "physical_size_diversity": Value("float64"),
50
- "hhi_production_mean": Value("float64"),
51
- "hhi_reserve_mean": Value("float64"),
52
- "hhi_combined_mean": Value("float64"),
53
- "model_name": Value("string"),
54
- "relaxed": Value("bool"),
55
- "training_set": Value("string"),
56
- "paper_link": Value("string"),
57
- "notes": Value("string"),
58
- })
 
59
 
60
 
61
  def get_leaderboard():
@@ -80,15 +87,46 @@ def get_leaderboard():
80
  return full_df
81
 
82
 
83
- def format_dataframe(df, show_percentage=False, selected_groups=None, compact_view=True):
84
- """Format the dataframe with proper column names and optional percentages."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  if len(df) == 0:
86
- return df
87
 
88
  selected_cols = ["model_name"]
89
 
90
  if compact_view:
91
  from about import COMPACT_VIEW_COLUMNS
 
92
  selected_cols = [col for col in COMPACT_VIEW_COLUMNS if col in df.columns]
93
  else:
94
  if "training_set" in df.columns:
@@ -120,7 +158,9 @@ def format_dataframe(df, show_percentage=False, selected_groups=None, compact_vi
120
  if "paper_link" in df.columns:
121
  paper_val = row.get("paper_link", None)
122
  if paper_val and isinstance(paper_val, str) and paper_val.strip():
123
- symbols.append(f'<a href="{paper_val.strip()}" target="_blank">πŸ“„</a>')
 
 
124
 
125
  if "relaxed" in df.columns and row.get("relaxed", False):
126
  symbols.append("⚑")
@@ -141,6 +181,7 @@ def format_dataframe(df, show_percentage=False, selected_groups=None, compact_vi
141
  display_df["model_name"] = df.apply(add_model_symbols, axis=1)
142
 
143
  if "training_set" in display_df.columns:
 
144
  def format_training_set(val):
145
  if val is None or (isinstance(val, float) and np.isnan(val)):
146
  return ""
@@ -151,13 +192,20 @@ def format_dataframe(df, show_percentage=False, selected_groups=None, compact_vi
151
  val = val.replace("'", "").replace('"', "")
152
  return val
153
 
154
- display_df["training_set"] = display_df["training_set"].apply(format_training_set)
 
 
155
 
 
 
 
 
 
156
  if show_percentage and "n_structures" in df.columns:
157
  n_structures = df["n_structures"]
158
  for col in COUNT_BASED_METRICS:
159
  if col in display_df.columns:
160
- display_df[col] = (df[col] / n_structures * 100).round(1).astype(str) + "%"
161
 
162
  for col in display_df.columns:
163
  if display_df[col].dtype in ["float64", "float32"]:
@@ -165,14 +213,26 @@ def format_dataframe(df, show_percentage=False, selected_groups=None, compact_vi
165
 
166
  baseline_indices = set()
167
  if "notes" in df.columns:
168
- is_baseline = df["notes"].fillna("").str.contains("baseline", case=False, na=False)
 
 
169
  non_baseline_df = display_df[~is_baseline]
170
  baseline_df = display_df[is_baseline]
171
  display_df = pd.concat([non_baseline_df, baseline_df]).reset_index(drop=True)
172
  baseline_indices = set(range(len(non_baseline_df), len(display_df)))
173
 
174
- display_df = display_df.rename(columns=COLUMN_DISPLAY_NAMES)
175
- return apply_color_styling(display_df, selected_cols, baseline_indices)
 
 
 
 
 
 
 
 
 
 
176
 
177
 
178
  def apply_color_styling(display_df, original_cols, baseline_indices=None):
@@ -214,16 +274,31 @@ def parse_training_set(val):
214
  return []
215
 
216
 
217
- def update_leaderboard(show_percentage, selected_groups, compact_view, cached_df, sort_by, sort_direction, training_set_filter):
218
- """Update the leaderboard based on user selections."""
 
 
 
 
 
 
 
 
 
 
 
 
219
  df_to_format = cached_df.copy()
220
 
221
  ALWAYS_SHOW_MODELS = {"AFLOW", "Alexandria", "OQMD"}
222
- if training_set_filter and training_set_filter != "All" and "training_set" in df_to_format.columns:
223
- mask = (
224
- df_to_format["training_set"].apply(lambda x: training_set_filter in parse_training_set(x))
225
- | df_to_format["model_name"].isin(ALWAYS_SHOW_MODELS)
226
- )
 
 
 
227
  df_to_format = df_to_format[mask]
228
 
229
  if sort_by and sort_by != "None":
@@ -232,16 +307,33 @@ def update_leaderboard(show_percentage, selected_groups, compact_view, cached_df
232
 
233
  if raw_column_name in df_to_format.columns:
234
  ascending = sort_direction == "Ascending"
235
- df_to_format = df_to_format.sort_values(by=raw_column_name, ascending=ascending)
 
 
236
 
237
- return format_dataframe(df_to_format, show_percentage, selected_groups, compact_view)
 
 
 
238
 
239
 
240
  def show_output_box(message):
241
  return gr.update(value=message, visible=True)
242
 
243
 
244
- def submit_cif_files(model_name, problem_type, cif_files, relaxed, relaxation_settings, training_datasets, training_dataset_other, paper_link, hf_model_link, email, profile: gr.OAuthProfile | None):
 
 
 
 
 
 
 
 
 
 
 
 
245
  """Submit structures to the leaderboard."""
246
  from huggingface_hub import upload_file
247
 
@@ -269,9 +361,13 @@ def submit_cif_files(model_name, problem_type, cif_files, relaxed, relaxation_se
269
  "model_name": model_name.strip(),
270
  "problem_type": problem_type,
271
  "relaxed": relaxed,
272
- "relaxation_settings": relaxation_settings.strip() if relaxed and relaxation_settings else None,
 
 
273
  "training_datasets": training_datasets or [],
274
- "training_dataset_other": training_dataset_other.strip() if training_dataset_other else None,
 
 
275
  "paper_link": paper_link.strip() if paper_link else None,
276
  "hf_model_link": hf_model_link.strip() if hf_model_link else None,
277
  "email": email.strip(),
@@ -308,7 +404,11 @@ def submit_cif_files(model_name, problem_type, cif_files, relaxed, relaxation_se
308
 
309
  os.unlink(temp_metadata_path)
310
 
311
- return f"Success! Submitted {model_name} for {problem_type} evaluation. Submission ID: {submission_id}", submission_id
 
 
 
 
312
 
313
  except Exception as e:
314
  return f"Error during submission: {str(e)}", None
@@ -317,20 +417,36 @@ def submit_cif_files(model_name, problem_type, cif_files, relaxed, relaxation_se
317
  def generate_metric_legend_html():
318
  """Generate HTML table with color-coded metric group legend."""
319
  metric_details = {
320
- "Validity ↑": ("Valid, Charge Neutral, Distance Valid, Plausibility Valid", "↑ Higher is better"),
 
 
 
321
  "Uniqueness & Novelty ↑": ("Unique, Novel", "↑ Higher is better"),
322
- "Energy Metrics ↓": ("E Above Hull, Formation Energy, Relaxation RMSD (with std)", "↓ Lower is better"),
 
 
 
323
  "Stability ↑": ("Stable, Unique in Stable, SUN", "↑ Higher is better"),
324
- "Metastability ↑": ("Metastable, Unique in Metastable, MSUN", "↑ Higher is better"),
 
 
 
325
  "Distribution ↓": ("JS Distance, MMD, FID", "↓ Lower is better"),
326
- "Diversity ↑": ("Element, Space Group, Atomic Site, Crystal Size", "↑ Higher is better"),
 
 
 
327
  "HHI ↓": ("HHI Production, HHI Reserve", "↓ Lower is better"),
328
  }
329
 
330
  html = '<table style="width: 100%; border-collapse: collapse;">'
331
  html += "<thead><tr>"
332
- html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Color</th>'
333
- html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Group</th>'
 
 
 
 
334
  html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Metrics</th>'
335
  html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Direction</th>'
336
  html += "</tr></thead><tbody>"
@@ -338,9 +454,12 @@ def generate_metric_legend_html():
338
  for group, color in METRIC_GROUP_COLORS.items():
339
  metrics, direction = metric_details.get(group, ("", ""))
340
  group_name = group.replace("↑", "").replace("↓", "").strip()
341
-
342
  html += "<tr>"
343
- html += f'<td style="border: 1px solid #ddd; padding: 8px;"><div style="width: 30px; height: 20px; background-color: {color}; border: 1px solid #999;"></div></td>'
 
 
 
 
344
  html += f'<td style="border: 1px solid #ddd; padding: 8px;"><strong>{group_name}</strong></td>'
345
  html += f'<td style="border: 1px solid #ddd; padding: 8px;">{metrics}</td>'
346
  html += f'<td style="border: 1px solid #ddd; padding: 8px;">{direction}</td>'
@@ -352,7 +471,8 @@ def generate_metric_legend_html():
352
 
353
  def gradio_interface() -> gr.Blocks:
354
  with gr.Blocks() as demo:
355
- gr.Markdown("""
 
356
  # πŸ”¬ LeMat-GenBench: A Unified Benchmark for Generative Models of Crystalline Materials
357
 
358
  Generative machine learning models hold great promise for accelerating materials discovery, particularly through the inverse design of inorganic crystals, enabling an unprecedented exploration of chemical space. Yet, the lack of standardized evaluation frameworks makes it difficult to evaluate, compare and further develop these ML models meaningfully.
@@ -360,7 +480,8 @@ Generative machine learning models hold great promise for accelerating materials
360
  **LeMat-GenBench** introduces a unified benchmark for generative models of crystalline materials, with standardized evaluation metrics for meaningful model comparison, diverse tasks, and this leaderboard to encourage and track community progress.
361
 
362
  πŸ“„ **Paper**: [arXiv](https://arxiv.org/abs/2512.04562) | πŸ’» **Code**: [GitHub](https://github.com/LeMaterial/lemat-genbench) | πŸ“§ **Contact**: siddharth.betala [at] entalpic.ai, alexandre.duval [at] entalpic.ai
363
- """)
 
364
 
365
  with gr.Tabs(elem_classes="tab-buttons"):
366
  with gr.TabItem("πŸš€ Leaderboard", elem_id="boundary-benchmark-tab-table"):
@@ -421,11 +542,13 @@ Generative machine learning models hold great promise for accelerating materials
421
 
422
  ALWAYS_SHOW_MODELS = {"AFLOW", "Alexandria", "OQMD"}
423
  filtered_initial_df = initial_df[
424
- initial_df["training_set"].apply(lambda x: "MP-20" in parse_training_set(x))
 
 
425
  | initial_df["model_name"].isin(ALWAYS_SHOW_MODELS)
426
  ]
427
 
428
- formatted_df = format_dataframe(
429
  filtered_initial_df,
430
  show_percentage=True,
431
  selected_groups=list(METRIC_GROUPS.keys()),
@@ -443,8 +566,11 @@ Generative machine learning models hold great promise for accelerating materials
443
  value=formatted_df,
444
  interactive=False,
445
  wrap=True,
446
- datatype=["html"] + [None] * (len(formatted_columns) - 1) if formatted_columns else None,
447
- column_widths=["180px"] + ["160px"] * (len(formatted_columns) - 1) if formatted_columns else None,
 
 
 
448
  show_fullscreen_button=True,
449
  )
450
 
@@ -458,18 +584,33 @@ Generative machine learning models hold great promise for accelerating materials
458
  training_set_filter,
459
  ]
460
 
461
- show_percentage.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
462
- selected_groups.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
463
- compact_view.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
464
- sort_by.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
465
- sort_direction.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
466
- training_set_filter.change(fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table)
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
  except Exception as e:
469
  traceback.print_exc()
470
- gr.Markdown(f"Leaderboard is empty or error loading: {type(e).__name__}: {str(e)}")
 
 
471
 
472
- gr.Markdown("""
 
473
  **Symbol Legend:**
474
  - πŸ“„ Paper available (click to view)
475
  - βœ… Model output verified
@@ -479,14 +620,17 @@ Generative machine learning models hold great promise for accelerating materials
479
 
480
  Verified submissions mean the results came from a model submission rather than a CIF submission.
481
 
482
- Models marked as baselines appear below the separator line at the bottom of the table.
483
- """)
 
484
 
485
  with gr.TabItem("βœ‰οΈ Submit", elem_id="boundary-benchmark-tab-table"):
486
- gr.Markdown("""
 
487
  # Materials Submission
488
  Upload a ZIP of CIFs with your structures. To ensure eligibility for the leaderboard, please provide exactly 2,500 representative structures.
489
- """)
 
490
  filename = gr.State(value=None)
491
 
492
  gr.LoginButton()
@@ -516,7 +660,9 @@ Upload a ZIP of CIFs with your structures. To ensure eligibility for the leaderb
516
  problem_type = gr.Dropdown(PROBLEM_TYPES, label="Problem Type")
517
 
518
  with gr.Column():
519
- cif_file = gr.File(label="Upload a CSV, a pkl, or a ZIP of CIF files.")
 
 
520
  relaxed = gr.Checkbox(
521
  value=False,
522
  label="Structures are pre-relaxed",
@@ -548,7 +694,9 @@ Upload a ZIP of CIFs with your structures. To ensure eligibility for the leaderb
548
  )
549
 
550
  training_dataset_input.change(
551
- fn=lambda x: gr.update(visible="Others (must specify)" in (x or [])),
 
 
552
  inputs=[training_dataset_input],
553
  outputs=[training_dataset_other_input],
554
  )
@@ -585,4 +733,4 @@ Upload a ZIP of CIFs with your structures. To ensure eligibility for the leaderb
585
 
586
 
587
  if __name__ == "__main__":
588
- gradio_interface().launch(show_error=True)
 
 
1
  import ast
2
  import json
3
+ import os
4
  import traceback
 
 
 
 
 
5
  from datetime import datetime
6
+ from pathlib import Path
7
 
8
+ import gradio as gr
9
+ import numpy as np
10
+ import pandas as pd
11
  from about import (
12
+ COLUMN_DISPLAY_NAMES,
13
+ COLUMN_TO_GROUP,
14
+ COUNT_BASED_METRICS,
15
+ METRIC_GROUP_COLORS,
16
+ METRIC_GROUPS,
17
+ PROBLEM_TYPES,
18
+ TOKEN,
19
+ TRAINING_DATASETS,
20
+ results_repo,
21
+ submissions_repo,
22
  )
23
+ from datasets import Features, Value, load_dataset
24
 
25
+ RESULT_FEATURES = Features(
26
+ {
27
+ "run_name": Value("string"),
28
+ "timestamp": Value("string"),
29
+ "n_structures": Value("int64"),
30
+ "overall_valid_count": Value("int64"),
31
+ "charge_neutral_count": Value("int64"),
32
+ "distance_valid_count": Value("int64"),
33
+ "plausibility_valid_count": Value("int64"),
34
+ "unique_count": Value("int64"),
35
+ "novel_count": Value("int64"),
36
+ "mean_formation_energy": Value("float64"),
37
+ "formation_energy_std": Value("float64"),
38
+ "stability_mean_above_hull": Value("float64"),
39
+ "stability_std_e_above_hull": Value("float64"),
40
+ "stability_mean_ensemble_std": Value("float64"),
41
+ "mean_relaxation_RMSD": Value("float64"),
42
+ "relaxation_RMSE_std": Value("float64"),
43
+ "stable_count": Value("int64"),
44
+ "unique_in_stable_count": Value("int64"),
45
+ "sun_count": Value("int64"),
46
+ "metastable_count": Value("int64"),
47
+ "unique_in_metastable_count": Value("int64"),
48
+ "msun_count": Value("int64"),
49
+ "JSDistance": Value("float64"),
50
+ "MMD": Value("float64"),
51
+ "FrechetDistance": Value("float64"),
52
+ "element_diversity": Value("float64"),
53
+ "space_group_diversity": Value("float64"),
54
+ "site_diversity": Value("float64"),
55
+ "physical_size_diversity": Value("float64"),
56
+ "hhi_production_mean": Value("float64"),
57
+ "hhi_reserve_mean": Value("float64"),
58
+ "hhi_combined_mean": Value("float64"),
59
+ "model_name": Value("string"),
60
+ "relaxed": Value("bool"),
61
+ "training_set": Value("string"),
62
+ "paper_link": Value("string"),
63
+ "notes": Value("string"),
64
+ }
65
+ )
66
 
67
 
68
  def get_leaderboard():
 
87
  return full_df
88
 
89
 
90
+ def get_display_datatypes(display_df, original_cols):
91
+ """Return an explicit per-column datatype list for gr.Dataframe.
92
+
93
+ Numeric metric columns are marked as "number" so Gradio's header-click
94
+ sorting compares them numerically instead of lexicographically. The model
95
+ column stays "html" (it contains links/symbols) and training_set stays
96
+ "str".
97
+ """
98
+ datatypes = []
99
+ for i in range(len(display_df.columns)):
100
+ original_col = original_cols[i] if i < len(original_cols) else None
101
+ if original_col == "model_name":
102
+ datatypes.append("html")
103
+ elif original_col == "training_set":
104
+ datatypes.append("str")
105
+ elif original_col == "run_name":
106
+ datatypes.append("str")
107
+ else:
108
+ # Every metric column (counts, percentages, energies, distances,
109
+ # diversity, HHI, ...) is numeric -> numeric sort.
110
+ datatypes.append("number")
111
+ return datatypes
112
+
113
+
114
+ def format_dataframe(
115
+ df, show_percentage=False, selected_groups=None, compact_view=True
116
+ ):
117
+ """Format the dataframe with proper column names and optional percentages.
118
+
119
+ Returns a tuple of (styler, datatypes) so the caller can pass an explicit
120
+ datatype list to gr.Dataframe for correct numeric sorting.
121
+ """
122
  if len(df) == 0:
123
+ return df, None
124
 
125
  selected_cols = ["model_name"]
126
 
127
  if compact_view:
128
  from about import COMPACT_VIEW_COLUMNS
129
+
130
  selected_cols = [col for col in COMPACT_VIEW_COLUMNS if col in df.columns]
131
  else:
132
  if "training_set" in df.columns:
 
158
  if "paper_link" in df.columns:
159
  paper_val = row.get("paper_link", None)
160
  if paper_val and isinstance(paper_val, str) and paper_val.strip():
161
+ symbols.append(
162
+ f'<a href="{paper_val.strip()}" target="_blank">πŸ“„</a>'
163
+ )
164
 
165
  if "relaxed" in df.columns and row.get("relaxed", False):
166
  symbols.append("⚑")
 
181
  display_df["model_name"] = df.apply(add_model_symbols, axis=1)
182
 
183
  if "training_set" in display_df.columns:
184
+
185
  def format_training_set(val):
186
  if val is None or (isinstance(val, float) and np.isnan(val)):
187
  return ""
 
192
  val = val.replace("'", "").replace('"', "")
193
  return val
194
 
195
+ display_df["training_set"] = display_df["training_set"].apply(
196
+ format_training_set
197
+ )
198
 
199
+ # --- Percentage handling -------------------------------------------------
200
+ # IMPORTANT: keep count-based metrics NUMERIC (do not append a "%" string).
201
+ # Appending "%" turns the column into strings, which makes Gradio's
202
+ # header-click sorting lexicographic (e.g. 5.0 -> 3.1 -> 22.6). We instead
203
+ # store the numeric percentage and move the "%" indicator to the header.
204
  if show_percentage and "n_structures" in df.columns:
205
  n_structures = df["n_structures"]
206
  for col in COUNT_BASED_METRICS:
207
  if col in display_df.columns:
208
+ display_df[col] = (df[col] / n_structures * 100).round(1)
209
 
210
  for col in display_df.columns:
211
  if display_df[col].dtype in ["float64", "float32"]:
 
213
 
214
  baseline_indices = set()
215
  if "notes" in df.columns:
216
+ is_baseline = (
217
+ df["notes"].fillna("").str.contains("baseline", case=False, na=False)
218
+ )
219
  non_baseline_df = display_df[~is_baseline]
220
  baseline_df = display_df[is_baseline]
221
  display_df = pd.concat([non_baseline_df, baseline_df]).reset_index(drop=True)
222
  baseline_indices = set(range(len(non_baseline_df), len(display_df)))
223
 
224
+ datatypes = get_display_datatypes(display_df, selected_cols)
225
+
226
+ # Move the "%" indicator into the header so cells stay numeric & sortable.
227
+ rename_map = dict(COLUMN_DISPLAY_NAMES)
228
+ if show_percentage:
229
+ for col in COUNT_BASED_METRICS:
230
+ if col in rename_map:
231
+ rename_map[col] = f"{rename_map[col]} (%)"
232
+
233
+ display_df = display_df.rename(columns=rename_map)
234
+ styler = apply_color_styling(display_df, selected_cols, baseline_indices)
235
+ return styler, datatypes
236
 
237
 
238
  def apply_color_styling(display_df, original_cols, baseline_indices=None):
 
274
  return []
275
 
276
 
277
+ def update_leaderboard(
278
+ show_percentage,
279
+ selected_groups,
280
+ compact_view,
281
+ cached_df,
282
+ sort_by,
283
+ sort_direction,
284
+ training_set_filter,
285
+ ):
286
+ """Update the leaderboard based on user selections.
287
+
288
+ Sorting is performed here on the RAW numeric dataframe (before any string
289
+ formatting), so the "Sort By" dropdown is always numerically correct.
290
+ """
291
  df_to_format = cached_df.copy()
292
 
293
  ALWAYS_SHOW_MODELS = {"AFLOW", "Alexandria", "OQMD"}
294
+ if (
295
+ training_set_filter
296
+ and training_set_filter != "All"
297
+ and "training_set" in df_to_format.columns
298
+ ):
299
+ mask = df_to_format["training_set"].apply(
300
+ lambda x: training_set_filter in parse_training_set(x)
301
+ ) | df_to_format["model_name"].isin(ALWAYS_SHOW_MODELS)
302
  df_to_format = df_to_format[mask]
303
 
304
  if sort_by and sort_by != "None":
 
307
 
308
  if raw_column_name in df_to_format.columns:
309
  ascending = sort_direction == "Ascending"
310
+ df_to_format = df_to_format.sort_values(
311
+ by=raw_column_name, ascending=ascending
312
+ )
313
 
314
+ styler, _ = format_dataframe(
315
+ df_to_format, show_percentage, selected_groups, compact_view
316
+ )
317
+ return styler
318
 
319
 
320
  def show_output_box(message):
321
  return gr.update(value=message, visible=True)
322
 
323
 
324
+ def submit_cif_files(
325
+ model_name,
326
+ problem_type,
327
+ cif_files,
328
+ relaxed,
329
+ relaxation_settings,
330
+ training_datasets,
331
+ training_dataset_other,
332
+ paper_link,
333
+ hf_model_link,
334
+ email,
335
+ profile: gr.OAuthProfile | None,
336
+ ):
337
  """Submit structures to the leaderboard."""
338
  from huggingface_hub import upload_file
339
 
 
361
  "model_name": model_name.strip(),
362
  "problem_type": problem_type,
363
  "relaxed": relaxed,
364
+ "relaxation_settings": relaxation_settings.strip()
365
+ if relaxed and relaxation_settings
366
+ else None,
367
  "training_datasets": training_datasets or [],
368
+ "training_dataset_other": training_dataset_other.strip()
369
+ if training_dataset_other
370
+ else None,
371
  "paper_link": paper_link.strip() if paper_link else None,
372
  "hf_model_link": hf_model_link.strip() if hf_model_link else None,
373
  "email": email.strip(),
 
404
 
405
  os.unlink(temp_metadata_path)
406
 
407
+ return (
408
+ f"Success! Submitted {model_name} for {problem_type} evaluation. "
409
+ f"Submission ID: {submission_id}",
410
+ submission_id,
411
+ )
412
 
413
  except Exception as e:
414
  return f"Error during submission: {str(e)}", None
 
417
  def generate_metric_legend_html():
418
  """Generate HTML table with color-coded metric group legend."""
419
  metric_details = {
420
+ "Validity ↑": (
421
+ "Valid, Charge Neutral, Distance Valid, Plausibility Valid",
422
+ "↑ Higher is better",
423
+ ),
424
  "Uniqueness & Novelty ↑": ("Unique, Novel", "↑ Higher is better"),
425
+ "Energy Metrics ↓": (
426
+ "E Above Hull, Formation Energy, Relaxation RMSD (with std)",
427
+ "↓ Lower is better",
428
+ ),
429
  "Stability ↑": ("Stable, Unique in Stable, SUN", "↑ Higher is better"),
430
+ "Metastability ↑": (
431
+ "Metastable, Unique in Metastable, MSUN",
432
+ "↑ Higher is better",
433
+ ),
434
  "Distribution ↓": ("JS Distance, MMD, FID", "↓ Lower is better"),
435
+ "Diversity ↑": (
436
+ "Element, Space Group, Atomic Site, Crystal Size",
437
+ "↑ Higher is better",
438
+ ),
439
  "HHI ↓": ("HHI Production, HHI Reserve", "↓ Lower is better"),
440
  }
441
 
442
  html = '<table style="width: 100%; border-collapse: collapse;">'
443
  html += "<thead><tr>"
444
+ html += (
445
+ '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Color</th>'
446
+ )
447
+ html += (
448
+ '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Group</th>'
449
+ )
450
  html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Metrics</th>'
451
  html += '<th style="border: 1px solid #ddd; padding: 8px; text-align: left;">Direction</th>'
452
  html += "</tr></thead><tbody>"
 
454
  for group, color in METRIC_GROUP_COLORS.items():
455
  metrics, direction = metric_details.get(group, ("", ""))
456
  group_name = group.replace("↑", "").replace("↓", "").strip()
 
457
  html += "<tr>"
458
+ html += (
459
+ f'<td style="border: 1px solid #ddd; padding: 8px;">'
460
+ f'<div style="width: 30px; height: 20px; background-color: {color}; '
461
+ f'border: 1px solid #999;"></div></td>'
462
+ )
463
  html += f'<td style="border: 1px solid #ddd; padding: 8px;"><strong>{group_name}</strong></td>'
464
  html += f'<td style="border: 1px solid #ddd; padding: 8px;">{metrics}</td>'
465
  html += f'<td style="border: 1px solid #ddd; padding: 8px;">{direction}</td>'
 
471
 
472
  def gradio_interface() -> gr.Blocks:
473
  with gr.Blocks() as demo:
474
+ gr.Markdown(
475
+ """
476
  # πŸ”¬ LeMat-GenBench: A Unified Benchmark for Generative Models of Crystalline Materials
477
 
478
  Generative machine learning models hold great promise for accelerating materials discovery, particularly through the inverse design of inorganic crystals, enabling an unprecedented exploration of chemical space. Yet, the lack of standardized evaluation frameworks makes it difficult to evaluate, compare and further develop these ML models meaningfully.
 
480
  **LeMat-GenBench** introduces a unified benchmark for generative models of crystalline materials, with standardized evaluation metrics for meaningful model comparison, diverse tasks, and this leaderboard to encourage and track community progress.
481
 
482
  πŸ“„ **Paper**: [arXiv](https://arxiv.org/abs/2512.04562) | πŸ’» **Code**: [GitHub](https://github.com/LeMaterial/lemat-genbench) | πŸ“§ **Contact**: siddharth.betala [at] entalpic.ai, alexandre.duval [at] entalpic.ai
483
+ """
484
+ )
485
 
486
  with gr.Tabs(elem_classes="tab-buttons"):
487
  with gr.TabItem("πŸš€ Leaderboard", elem_id="boundary-benchmark-tab-table"):
 
542
 
543
  ALWAYS_SHOW_MODELS = {"AFLOW", "Alexandria", "OQMD"}
544
  filtered_initial_df = initial_df[
545
+ initial_df["training_set"].apply(
546
+ lambda x: "MP-20" in parse_training_set(x)
547
+ )
548
  | initial_df["model_name"].isin(ALWAYS_SHOW_MODELS)
549
  ]
550
 
551
+ formatted_df, formatted_datatypes = format_dataframe(
552
  filtered_initial_df,
553
  show_percentage=True,
554
  selected_groups=list(METRIC_GROUPS.keys()),
 
566
  value=formatted_df,
567
  interactive=False,
568
  wrap=True,
569
+ datatype=formatted_datatypes if formatted_datatypes else None,
570
+ column_widths=["180px"]
571
+ + ["160px"] * (len(formatted_columns) - 1)
572
+ if formatted_columns
573
+ else None,
574
  show_fullscreen_button=True,
575
  )
576
 
 
584
  training_set_filter,
585
  ]
586
 
587
+ show_percentage.change(
588
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
589
+ )
590
+ selected_groups.change(
591
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
592
+ )
593
+ compact_view.change(
594
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
595
+ )
596
+ sort_by.change(
597
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
598
+ )
599
+ sort_direction.change(
600
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
601
+ )
602
+ training_set_filter.change(
603
+ fn=update_leaderboard, inputs=inputs, outputs=leaderboard_table
604
+ )
605
 
606
  except Exception as e:
607
  traceback.print_exc()
608
+ gr.Markdown(
609
+ f"Leaderboard is empty or error loading: {type(e).__name__}: {str(e)}"
610
+ )
611
 
612
+ gr.Markdown(
613
+ """
614
  **Symbol Legend:**
615
  - πŸ“„ Paper available (click to view)
616
  - βœ… Model output verified
 
620
 
621
  Verified submissions mean the results came from a model submission rather than a CIF submission.
622
 
623
+ Models marked as baselines appear below a separator line at the bottom of the table.
624
+ """
625
+ )
626
 
627
  with gr.TabItem("βœ‰οΈ Submit", elem_id="boundary-benchmark-tab-table"):
628
+ gr.Markdown(
629
+ """
630
  # Materials Submission
631
  Upload a ZIP of CIFs with your structures. To ensure eligibility for the leaderboard, please provide exactly 2,500 representative structures.
632
+ """
633
+ )
634
  filename = gr.State(value=None)
635
 
636
  gr.LoginButton()
 
660
  problem_type = gr.Dropdown(PROBLEM_TYPES, label="Problem Type")
661
 
662
  with gr.Column():
663
+ cif_file = gr.File(
664
+ label="Upload a CSV, a pkl, or a ZIP of CIF files."
665
+ )
666
  relaxed = gr.Checkbox(
667
  value=False,
668
  label="Structures are pre-relaxed",
 
694
  )
695
 
696
  training_dataset_input.change(
697
+ fn=lambda x: gr.update(
698
+ visible="Others (must specify)" in (x or [])
699
+ ),
700
  inputs=[training_dataset_input],
701
  outputs=[training_dataset_other_input],
702
  )
 
733
 
734
 
735
  if __name__ == "__main__":
736
+ gradio_interface().launch(show_error=True)