bingyan user Cursor commited on
Commit
8619a66
·
1 Parent(s): 3f4e2ae

Rebrand TRACE -> SPARK

Browse files

- Full name: 'Simulation-based Posterior Amortization for Reaction Kinetics'
- Class TRACEPredictor -> SPARKPredictor; env vars TRACE_* -> SPARK_*
- Updated README, app.py title/info, all docstrings and comments.
- No model architecture or checkpoint format changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (7) hide show
  1. README.md +3 -3
  2. app.py +18 -18
  3. image_encoder.py +1 -1
  4. image_preprocessing.py +2 -2
  5. inference.py +14 -14
  6. plotting.py +1 -1
  7. preprocessing.py +3 -3
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: TRACE
3
  emoji: ⚡
4
  colorFrom: blue
5
  colorTo: purple
@@ -8,10 +8,10 @@ sdk_version: "5.29.0"
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: TRACE — Bayesian inference for CV & TPD analysis
12
  ---
13
 
14
- # TRACETrace Recognition and Amortized Conditional Estimation
15
 
16
  Amortized Bayesian inference for electrochemistry and catalysis.
17
  Upload cyclic voltammetry (CV) or temperature-programmed desorption (TPD) data to automatically:
 
1
  ---
2
+ title: SPARK
3
  emoji: ⚡
4
  colorFrom: blue
5
  colorTo: purple
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: SPARK — Bayesian inference for CV & TPD analysis
12
  ---
13
 
14
+ # SPARKSimulation-based Posterior Amortization for Reaction Kinetics
15
 
16
  Amortized Bayesian inference for electrochemistry and catalysis.
17
  Upload cyclic voltammetry (CV) or temperature-programmed desorption (TPD) data to automatically:
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- TRACE (Trace Recognition and Amortized Conditional Estimation)
3
 
4
  Gradio web interface for mechanism classification and parameter inference
5
  from cyclic voltammetry (CV) and temperature-programmed desorption (TPD) data.
@@ -16,7 +16,7 @@ import gradio as gr
16
 
17
 
18
 
19
- from inference import TRACEPredictor
20
  from preprocessing import (
21
  nondimensionalize_cv,
22
  estimate_E0,
@@ -41,27 +41,27 @@ DEMO_RENDERS = REPO_ROOT / "demo_renders"
41
  EC_CHECKPOINT = REPO_ROOT / "checkpoints" / "ec_best.pt"
42
  TPD_CHECKPOINT = REPO_ROOT / "checkpoints" / "tpd_best.pt"
43
 
44
- # Allow override via environment variables. TRACE_*_CHECKPOINT is the
45
  # canonical name; ECFLOW_*_CHECKPOINT remains accepted for backward
46
  # compatibility with existing HF Space secrets.
47
  EC_CHECKPOINT = Path(
48
  os.environ.get(
49
- "TRACE_EC_CHECKPOINT",
50
  os.environ.get("ECFLOW_EC_CHECKPOINT", str(EC_CHECKPOINT)),
51
  )
52
  )
53
  TPD_CHECKPOINT = Path(
54
  os.environ.get(
55
- "TRACE_TPD_CHECKPOINT",
56
  os.environ.get("ECFLOW_TPD_CHECKPOINT", str(TPD_CHECKPOINT)),
57
  )
58
  )
59
 
60
  # Optional image-input checkpoints. When set and the file exists, the
61
- # "From Image" tabs use the image-input TRACE model directly instead of
62
  # digitizing the plot. If not set, the digitizer fallback is used.
63
- EC_IMAGE_CHECKPOINT_PATH = os.environ.get("TRACE_EC_IMAGE_CHECKPOINT", "")
64
- TPD_IMAGE_CHECKPOINT_PATH = os.environ.get("TRACE_TPD_IMAGE_CHECKPOINT", "")
65
  EC_IMAGE_CHECKPOINT = (
66
  Path(EC_IMAGE_CHECKPOINT_PATH) if EC_IMAGE_CHECKPOINT_PATH else None
67
  )
@@ -73,8 +73,8 @@ TPD_IMAGE_CHECKPOINT = (
73
  # "From Image" tabs expose a "Joint image+waveform (Phase 2)" inference
74
  # strategy that fuses the rendered image with the digitizer-extracted
75
  # waveform inside a single encoder. Falls back to ensemble if missing.
76
- EC_JOINT_CHECKPOINT_PATH = os.environ.get("TRACE_EC_JOINT_CHECKPOINT", "")
77
- TPD_JOINT_CHECKPOINT_PATH = os.environ.get("TRACE_TPD_JOINT_CHECKPOINT", "")
78
  EC_JOINT_CHECKPOINT = (
79
  Path(EC_JOINT_CHECKPOINT_PATH) if EC_JOINT_CHECKPOINT_PATH else None
80
  )
@@ -245,7 +245,7 @@ def get_predictor():
245
  tpd_joint = (str(TPD_JOINT_CHECKPOINT)
246
  if (TPD_JOINT_CHECKPOINT and TPD_JOINT_CHECKPOINT.exists())
247
  else None)
248
- predictor = TRACEPredictor(
249
  ec_checkpoint=ec_ckpt,
250
  tpd_checkpoint=tpd_ckpt,
251
  ec_image_checkpoint=ec_img,
@@ -359,7 +359,7 @@ def analyze_cv_image(files, scan_rate_text, E0_V, threshold, current_unit,
359
  method_label="Ensemble (recommended)"):
360
  """Analyze CV from uploaded plot images (one per scan rate).
361
 
362
- When the image-input TRACE model is available we run a hybrid pipeline:
363
  image-mode prediction in parallel with the digitizer + waveform-mode path,
364
  and combine via `method_label` (Ensemble / Image-direct / Digitize-then-infer
365
  / Auto-fallback). When only the waveform model is available, falls back
@@ -477,7 +477,7 @@ def analyze_cv_image(files, scan_rate_text, E0_V, threshold, current_unit,
477
  )
478
 
479
 
480
- OOD_THRESHOLD = float(os.environ.get("TRACE_OOD_THRESHOLD", "0.5"))
481
 
482
 
483
  def _ood_banner_update(ood_score, threshold=OOD_THRESHOLD):
@@ -636,7 +636,7 @@ def _method_comparison_html(hybrid_out, threshold=OOD_THRESHOLD):
636
  "methods disagree \u2014 review carefully</span>")
637
 
638
  footnote = (
639
- "Image-direct (Phase 1) is the image-only TRACE; Digitize-then-infer "
640
  "uses the headline waveform model on a curve extracted from the plot; "
641
  "Joint (Phase 2) fuses both signals inside a single encoder trained "
642
  "with real-world distortion augmentation. When available, the joint "
@@ -1254,7 +1254,7 @@ footer { display: none !important; }
1254
 
1255
  def build_app():
1256
  with gr.Blocks(
1257
- title="TRACE — Bayesian Inference for Electrochemistry & Catalysis",
1258
  theme=gr.themes.Soft(
1259
  primary_hue="blue",
1260
  secondary_hue="slate",
@@ -1265,7 +1265,7 @@ def build_app():
1265
  gr.HTML("<div class='trace-page'>")
1266
  gr.HTML(
1267
  "<div class='main-header'>"
1268
- "<h1><span class='accent'>TRACE</span></h1>"
1269
  "<p><strong>T</strong>race <strong>R</strong>ecognition and "
1270
  "<strong>A</strong>mortized <strong>C</strong>onditional "
1271
  "<strong>E</strong>stimation &mdash; Bayesian inference for "
@@ -1757,7 +1757,7 @@ def build_app():
1757
  gr.Markdown("""
1758
  ## How it works
1759
 
1760
- **TRACE** (Trace Recognition and Amortized Conditional Estimation) uses
1761
  **conditional normalizing flows** with a **Set Transformer** encoder to
1762
  perform amortized Bayesian inference. Given one or more experimental
1763
  curves, it simultaneously classifies the reaction mechanism and produces
@@ -1778,7 +1778,7 @@ weighting.
1778
  ### Citation
1779
 
1780
  ```
1781
- Yan, B. (2026). TRACE: Amortized Bayesian Inference for
1782
  Mechanism Identification and Parameter Estimation in
1783
  Electrochemistry and Catalysis via Conditional
1784
  Normalizing Flows. [Preprint]
 
1
  """
2
+ SPARK (Simulation-based Posterior Amortization for Reaction Kinetics)
3
 
4
  Gradio web interface for mechanism classification and parameter inference
5
  from cyclic voltammetry (CV) and temperature-programmed desorption (TPD) data.
 
16
 
17
 
18
 
19
+ from inference import SPARKPredictor
20
  from preprocessing import (
21
  nondimensionalize_cv,
22
  estimate_E0,
 
41
  EC_CHECKPOINT = REPO_ROOT / "checkpoints" / "ec_best.pt"
42
  TPD_CHECKPOINT = REPO_ROOT / "checkpoints" / "tpd_best.pt"
43
 
44
+ # Allow override via environment variables. SPARK_*_CHECKPOINT is the
45
  # canonical name; ECFLOW_*_CHECKPOINT remains accepted for backward
46
  # compatibility with existing HF Space secrets.
47
  EC_CHECKPOINT = Path(
48
  os.environ.get(
49
+ "SPARK_EC_CHECKPOINT",
50
  os.environ.get("ECFLOW_EC_CHECKPOINT", str(EC_CHECKPOINT)),
51
  )
52
  )
53
  TPD_CHECKPOINT = Path(
54
  os.environ.get(
55
+ "SPARK_TPD_CHECKPOINT",
56
  os.environ.get("ECFLOW_TPD_CHECKPOINT", str(TPD_CHECKPOINT)),
57
  )
58
  )
59
 
60
  # Optional image-input checkpoints. When set and the file exists, the
61
+ # "From Image" tabs use the image-input SPARK model directly instead of
62
  # digitizing the plot. If not set, the digitizer fallback is used.
63
+ EC_IMAGE_CHECKPOINT_PATH = os.environ.get("SPARK_EC_IMAGE_CHECKPOINT", "")
64
+ TPD_IMAGE_CHECKPOINT_PATH = os.environ.get("SPARK_TPD_IMAGE_CHECKPOINT", "")
65
  EC_IMAGE_CHECKPOINT = (
66
  Path(EC_IMAGE_CHECKPOINT_PATH) if EC_IMAGE_CHECKPOINT_PATH else None
67
  )
 
73
  # "From Image" tabs expose a "Joint image+waveform (Phase 2)" inference
74
  # strategy that fuses the rendered image with the digitizer-extracted
75
  # waveform inside a single encoder. Falls back to ensemble if missing.
76
+ EC_JOINT_CHECKPOINT_PATH = os.environ.get("SPARK_EC_JOINT_CHECKPOINT", "")
77
+ TPD_JOINT_CHECKPOINT_PATH = os.environ.get("SPARK_TPD_JOINT_CHECKPOINT", "")
78
  EC_JOINT_CHECKPOINT = (
79
  Path(EC_JOINT_CHECKPOINT_PATH) if EC_JOINT_CHECKPOINT_PATH else None
80
  )
 
245
  tpd_joint = (str(TPD_JOINT_CHECKPOINT)
246
  if (TPD_JOINT_CHECKPOINT and TPD_JOINT_CHECKPOINT.exists())
247
  else None)
248
+ predictor = SPARKPredictor(
249
  ec_checkpoint=ec_ckpt,
250
  tpd_checkpoint=tpd_ckpt,
251
  ec_image_checkpoint=ec_img,
 
359
  method_label="Ensemble (recommended)"):
360
  """Analyze CV from uploaded plot images (one per scan rate).
361
 
362
+ When the image-input SPARK model is available we run a hybrid pipeline:
363
  image-mode prediction in parallel with the digitizer + waveform-mode path,
364
  and combine via `method_label` (Ensemble / Image-direct / Digitize-then-infer
365
  / Auto-fallback). When only the waveform model is available, falls back
 
477
  )
478
 
479
 
480
+ OOD_THRESHOLD = float(os.environ.get("SPARK_OOD_THRESHOLD", "0.5"))
481
 
482
 
483
  def _ood_banner_update(ood_score, threshold=OOD_THRESHOLD):
 
636
  "methods disagree \u2014 review carefully</span>")
637
 
638
  footnote = (
639
+ "Image-direct (Phase 1) is the image-only SPARK; Digitize-then-infer "
640
  "uses the headline waveform model on a curve extracted from the plot; "
641
  "Joint (Phase 2) fuses both signals inside a single encoder trained "
642
  "with real-world distortion augmentation. When available, the joint "
 
1254
 
1255
  def build_app():
1256
  with gr.Blocks(
1257
+ title="SPARK — Bayesian Inference for Electrochemistry & Catalysis",
1258
  theme=gr.themes.Soft(
1259
  primary_hue="blue",
1260
  secondary_hue="slate",
 
1265
  gr.HTML("<div class='trace-page'>")
1266
  gr.HTML(
1267
  "<div class='main-header'>"
1268
+ "<h1><span class='accent'>SPARK</span></h1>"
1269
  "<p><strong>T</strong>race <strong>R</strong>ecognition and "
1270
  "<strong>A</strong>mortized <strong>C</strong>onditional "
1271
  "<strong>E</strong>stimation &mdash; Bayesian inference for "
 
1757
  gr.Markdown("""
1758
  ## How it works
1759
 
1760
+ **SPARK** (Simulation-based Posterior Amortization for Reaction Kinetics) uses
1761
  **conditional normalizing flows** with a **Set Transformer** encoder to
1762
  perform amortized Bayesian inference. Given one or more experimental
1763
  curves, it simultaneously classifies the reaction mechanism and produces
 
1778
  ### Citation
1779
 
1780
  ```
1781
+ Yan, B. (2026). SPARK: Amortized Bayesian Inference for
1782
  Mechanism Identification and Parameter Estimation in
1783
  Electrochemistry and Catalysis via Conditional
1784
  Normalizing Flows. [Preprint]
image_encoder.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Image encoder for the image-input TRACE variants (CV and TPD).
3
 
4
  Replaces the per-scan 1-D `SignalEncoder` with a small 2-D CNN that maps a
5
  single rasterized plot image (grayscale, 224x224 by default) to a context
 
1
  """
2
+ Image encoder for the image-input SPARK variants (CV and TPD).
3
 
4
  Replaces the per-scan 1-D `SignalEncoder` with a small 2-D CNN that maps a
5
  single rasterized plot image (grayscale, 224x224 by default) to a context
image_preprocessing.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Image preprocessing for image-input TRACE on real-world uploads.
3
 
4
  Real user uploads (paper-figure crops, software screenshots, photos of
5
  lab monitors) live in a much wider image distribution than the rendered
@@ -250,7 +250,7 @@ def prepare_for_image_mode(
250
  do_clean: bool = True,
251
  target_size: int = 224,
252
  ) -> Tuple[Image.Image, Dict[str, object]]:
253
- """Full preprocessing pipeline for image-mode TRACE.
254
 
255
  Steps (any can be skipped):
256
  crop_to_plot_region -> remove_gridlines_and_background -> resize.
 
1
  """
2
+ Image preprocessing for image-input SPARK on real-world uploads.
3
 
4
  Real user uploads (paper-figure crops, software screenshots, photos of
5
  lab monitors) live in a much wider image distribution than the rendered
 
250
  do_clean: bool = True,
251
  target_size: int = 224,
252
  ) -> Tuple[Image.Image, Dict[str, object]]:
253
+ """Full preprocessing pipeline for image-mode SPARK.
254
 
255
  Steps (any can be skipped):
256
  crop_to_plot_region -> remove_gridlines_and_background -> resize.
inference.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- TRACE inference engine.
3
 
4
  Loads trained EC and TPD models and runs end-to-end inference
5
  from preprocessed arrays (dimensionless for CV, physical for TPD).
@@ -37,7 +37,7 @@ def _fix_actnorm_initialized(model):
37
  module.initialized = True
38
 
39
 
40
- class TRACEPredictor:
41
  """Unified predictor for both EC (cyclic voltammetry) and TPD domains."""
42
 
43
  def __init__(self, ec_checkpoint=None, tpd_checkpoint=None, device=None,
@@ -145,7 +145,7 @@ class TRACEPredictor:
145
  k for k in unexpected if not k.endswith("_initialized")
146
  ]
147
  if suspicious_missing or suspicious_unexpected:
148
- print(f"[TRACEPredictor] WARNING: state_dict mismatch on EC ckpt.")
149
  if suspicious_missing:
150
  print(f" missing ({len(suspicious_missing)}): "
151
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -231,7 +231,7 @@ class TRACEPredictor:
231
  k for k in unexpected if not k.endswith("_initialized")
232
  ]
233
  if suspicious_missing or suspicious_unexpected:
234
- print(f"[TRACEPredictor] WARNING: state_dict mismatch on TPD ckpt.")
235
  if suspicious_missing:
236
  print(f" missing ({len(suspicious_missing)}): "
237
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -338,7 +338,7 @@ class TRACEPredictor:
338
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
339
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
340
  if suspicious_missing or suspicious_unexpected:
341
- print("[TRACEPredictor] WARNING: state_dict mismatch on EC image ckpt.")
342
  if suspicious_missing:
343
  print(f" missing ({len(suspicious_missing)}): "
344
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -393,7 +393,7 @@ class TRACEPredictor:
393
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
394
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
395
  if suspicious_missing or suspicious_unexpected:
396
- print("[TRACEPredictor] WARNING: state_dict mismatch on TPD image ckpt.")
397
  if suspicious_missing:
398
  print(f" missing ({len(suspicious_missing)}): "
399
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -447,7 +447,7 @@ class TRACEPredictor:
447
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
448
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
449
  if suspicious_missing or suspicious_unexpected:
450
- print("[TRACEPredictor] WARNING: state_dict mismatch on EC joint ckpt.")
451
  if suspicious_missing:
452
  print(f" missing ({len(suspicious_missing)}): "
453
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -503,7 +503,7 @@ class TRACEPredictor:
503
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
504
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
505
  if suspicious_missing or suspicious_unexpected:
506
- print("[TRACEPredictor] WARNING: state_dict mismatch on TPD joint ckpt.")
507
  if suspicious_missing:
508
  print(f" missing ({len(suspicious_missing)}): "
509
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
@@ -900,7 +900,7 @@ class TRACEPredictor:
900
  n_samples=n_samples, temperature=temperature,
901
  )
902
  except Exception as exc:
903
- print(f"[TRACEPredictor] image-mode CV failed: {exc}")
904
 
905
  if self.ec_model is not None and potentials is not None and fluxes is not None:
906
  try:
@@ -909,7 +909,7 @@ class TRACEPredictor:
909
  n_samples=n_samples, temperature=temperature,
910
  )
911
  except Exception as exc:
912
- print(f"[TRACEPredictor] waveform CV failed: {exc}")
913
 
914
  if (self.has_ec_joint_model and preprocessed is not None
915
  and potentials is not None and fluxes is not None):
@@ -919,7 +919,7 @@ class TRACEPredictor:
919
  n_samples=n_samples, temperature=temperature,
920
  )
921
  except Exception as exc:
922
- print(f"[TRACEPredictor] joint CV failed: {exc}")
923
 
924
  return image_result, waveform_result, joint_result, preproc_meta
925
 
@@ -1014,7 +1014,7 @@ class TRACEPredictor:
1014
  n_samples=n_samples, temperature=temperature,
1015
  )
1016
  except Exception as exc:
1017
- print(f"[TRACEPredictor] image-mode TPD failed: {exc}")
1018
 
1019
  if self.tpd_model is not None and temperatures is not None and rates is not None:
1020
  try:
@@ -1023,7 +1023,7 @@ class TRACEPredictor:
1023
  n_samples=n_samples, temperature=temperature,
1024
  )
1025
  except Exception as exc:
1026
- print(f"[TRACEPredictor] waveform TPD failed: {exc}")
1027
 
1028
  if (self.has_tpd_joint_model and preprocessed is not None
1029
  and temperatures is not None and rates is not None):
@@ -1033,7 +1033,7 @@ class TRACEPredictor:
1033
  n_samples=n_samples, temperature=temperature,
1034
  )
1035
  except Exception as exc:
1036
- print(f"[TRACEPredictor] joint TPD failed: {exc}")
1037
 
1038
  return image_result, waveform_result, joint_result, preproc_meta
1039
 
 
1
  """
2
+ SPARK inference engine.
3
 
4
  Loads trained EC and TPD models and runs end-to-end inference
5
  from preprocessed arrays (dimensionless for CV, physical for TPD).
 
37
  module.initialized = True
38
 
39
 
40
+ class SPARKPredictor:
41
  """Unified predictor for both EC (cyclic voltammetry) and TPD domains."""
42
 
43
  def __init__(self, ec_checkpoint=None, tpd_checkpoint=None, device=None,
 
145
  k for k in unexpected if not k.endswith("_initialized")
146
  ]
147
  if suspicious_missing or suspicious_unexpected:
148
+ print(f"[SPARKPredictor] WARNING: state_dict mismatch on EC ckpt.")
149
  if suspicious_missing:
150
  print(f" missing ({len(suspicious_missing)}): "
151
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
231
  k for k in unexpected if not k.endswith("_initialized")
232
  ]
233
  if suspicious_missing or suspicious_unexpected:
234
+ print(f"[SPARKPredictor] WARNING: state_dict mismatch on TPD ckpt.")
235
  if suspicious_missing:
236
  print(f" missing ({len(suspicious_missing)}): "
237
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
338
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
339
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
340
  if suspicious_missing or suspicious_unexpected:
341
+ print("[SPARKPredictor] WARNING: state_dict mismatch on EC image ckpt.")
342
  if suspicious_missing:
343
  print(f" missing ({len(suspicious_missing)}): "
344
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
393
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
394
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
395
  if suspicious_missing or suspicious_unexpected:
396
+ print("[SPARKPredictor] WARNING: state_dict mismatch on TPD image ckpt.")
397
  if suspicious_missing:
398
  print(f" missing ({len(suspicious_missing)}): "
399
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
447
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
448
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
449
  if suspicious_missing or suspicious_unexpected:
450
+ print("[SPARKPredictor] WARNING: state_dict mismatch on EC joint ckpt.")
451
  if suspicious_missing:
452
  print(f" missing ({len(suspicious_missing)}): "
453
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
503
  suspicious_missing = [k for k in missing if not k.endswith("_initialized")]
504
  suspicious_unexpected = [k for k in unexpected if not k.endswith("_initialized")]
505
  if suspicious_missing or suspicious_unexpected:
506
+ print("[SPARKPredictor] WARNING: state_dict mismatch on TPD joint ckpt.")
507
  if suspicious_missing:
508
  print(f" missing ({len(suspicious_missing)}): "
509
  f"{suspicious_missing[:6]}{' ...' if len(suspicious_missing) > 6 else ''}")
 
900
  n_samples=n_samples, temperature=temperature,
901
  )
902
  except Exception as exc:
903
+ print(f"[SPARKPredictor] image-mode CV failed: {exc}")
904
 
905
  if self.ec_model is not None and potentials is not None and fluxes is not None:
906
  try:
 
909
  n_samples=n_samples, temperature=temperature,
910
  )
911
  except Exception as exc:
912
+ print(f"[SPARKPredictor] waveform CV failed: {exc}")
913
 
914
  if (self.has_ec_joint_model and preprocessed is not None
915
  and potentials is not None and fluxes is not None):
 
919
  n_samples=n_samples, temperature=temperature,
920
  )
921
  except Exception as exc:
922
+ print(f"[SPARKPredictor] joint CV failed: {exc}")
923
 
924
  return image_result, waveform_result, joint_result, preproc_meta
925
 
 
1014
  n_samples=n_samples, temperature=temperature,
1015
  )
1016
  except Exception as exc:
1017
+ print(f"[SPARKPredictor] image-mode TPD failed: {exc}")
1018
 
1019
  if self.tpd_model is not None and temperatures is not None and rates is not None:
1020
  try:
 
1023
  n_samples=n_samples, temperature=temperature,
1024
  )
1025
  except Exception as exc:
1026
+ print(f"[SPARKPredictor] waveform TPD failed: {exc}")
1027
 
1028
  if (self.has_tpd_joint_model and preprocessed is not None
1029
  and temperatures is not None and rates is not None):
 
1033
  n_samples=n_samples, temperature=temperature,
1034
  )
1035
  except Exception as exc:
1036
+ print(f"[SPARKPredictor] joint TPD failed: {exc}")
1037
 
1038
  return image_result, waveform_result, joint_result, preproc_meta
1039
 
plotting.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Visualization utilities for the TRACE web app.
3
 
4
  Generates matplotlib figures for mechanism classification, parameter
5
  posteriors, and signal reconstruction overlays.
 
1
  """
2
+ Visualization utilities for the SPARK web app.
3
 
4
  Generates matplotlib figures for mechanism classification, parameter
5
  posteriors, and signal reconstruction overlays.
preprocessing.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Preprocessing utilities for TRACE web app.
3
 
4
  Handles:
5
  - CSV/NPZ parsing for both CV and TPD data
@@ -24,7 +24,7 @@ def nondimensionalize_cv(E_volts, i_amps, v_Vs, E0_V, T_K=298.15,
24
  A_cm2=0.0707, C_A_molcm3=1e-6, D_A_cm2s=1e-5, n=1,
25
  v_ref_Vs=0.1):
26
  """
27
- Convert physical CV data to dimensionless units for the TRACE model.
28
 
29
  Potential and current are nondimensionalized using the Compton convention
30
  with the scan-rate-dependent diffusion length d = sqrt(D·RT/(nFv)):
@@ -33,7 +33,7 @@ def nondimensionalize_cv(E_volts, i_amps, v_Vs, E0_V, T_K=298.15,
33
 
34
  The dimensionless scan rate σ = v / v_ref is computed separately.
35
  In the Compton convention σ ≡ 1 by construction (d absorbs v), but the
36
- TRACE model uses σ as an explicit conditioning variable to distinguish
37
  experiments at different scan rates. Setting v_ref so that σ spans the
38
  training range (~0.1–100) gives the model the scan-rate information.
39
 
 
1
  """
2
+ Preprocessing utilities for SPARK web app.
3
 
4
  Handles:
5
  - CSV/NPZ parsing for both CV and TPD data
 
24
  A_cm2=0.0707, C_A_molcm3=1e-6, D_A_cm2s=1e-5, n=1,
25
  v_ref_Vs=0.1):
26
  """
27
+ Convert physical CV data to dimensionless units for the SPARK model.
28
 
29
  Potential and current are nondimensionalized using the Compton convention
30
  with the scan-rate-dependent diffusion length d = sqrt(D·RT/(nFv)):
 
33
 
34
  The dimensionless scan rate σ = v / v_ref is computed separately.
35
  In the Compton convention σ ≡ 1 by construction (d absorbs v), but the
36
+ SPARK model uses σ as an explicit conditioning variable to distinguish
37
  experiments at different scan rates. Setting v_ref so that σ spans the
38
  training range (~0.1–100) gives the model the scan-rate information.
39