abdullahsamra commited on
Commit
c79eff8
·
verified ·
1 Parent(s): 0bd4c9f

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. __pycache__/app.cpython-310.pyc +0 -0
  2. app.py +221 -62
  3. requirements.txt +2 -0
__pycache__/app.cpython-310.pyc CHANGED
Binary files a/__pycache__/app.cpython-310.pyc and b/__pycache__/app.cpython-310.pyc differ
 
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import json
2
  import os
 
3
  from pathlib import Path
4
  import uuid
5
  from datetime import datetime
@@ -18,10 +19,11 @@ CACHE_DIR = Path("bundle_cache")
18
  EXAMPLE_JSON = None
19
  API_SPEC_TEXT = None
20
  BACKEND_CONTRACT_TEXT = None
 
21
 
22
 
23
  def download_bundle_light():
24
- global EXAMPLE_JSON, API_SPEC_TEXT, BACKEND_CONTRACT_TEXT
25
 
26
  try:
27
  local_dir = snapshot_download(
@@ -32,12 +34,15 @@ def download_bundle_light():
32
  "examples/generate_response.example.json",
33
  "docs/API_RESPONSE_SPEC.md",
34
  "configs/backend_contract.json",
 
 
35
  "README.md",
36
  ],
37
  local_dir=str(CACHE_DIR),
38
  )
39
 
40
  root = Path(local_dir)
 
41
 
42
  example_path = root / "examples" / "generate_response.example.json"
43
  spec_path = root / "docs" / "API_RESPONSE_SPEC.md"
@@ -187,92 +192,239 @@ def get_contract():
187
  "message": "backend_contract.json not loaded. Check HF_TOKEN secret or BUNDLE_REPO variable.",
188
  }
189
 
190
- def generate_job_demo(num_molecules, return_top_k, docking_mode):
191
- """
192
- CPU demo generation endpoint.
193
 
194
- This does not run live REINVENT generation.
195
- It returns a backend-style generation job response using packaged example rows,
196
- so frontend/filter/backend teams can test JSON + CSV + job history behavior.
197
- """
198
- job_id = f"gen_{uuid.uuid4().hex[:8]}"
199
- created_at = datetime.utcnow().isoformat() + "Z"
200
 
201
- if EXAMPLE_JSON and "results" in EXAMPLE_JSON:
202
- rows = EXAMPLE_JSON["results"]
203
- else:
204
- rows = [
205
- compute_row("CCO"),
206
- compute_row("c1ccccc1"),
207
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- num_molecules = int(num_molecules)
210
  return_top_k = int(return_top_k)
 
 
 
 
211
 
212
- selected = rows[: max(1, min(return_top_k, len(rows)))]
213
 
214
- clean_rows = []
215
- for row in selected:
216
- clean = dict(row)
217
 
218
- clean["docking_score"] = None
219
- clean["docking_status"] = "not_run"
220
- clean["docking_pose_file"] = None
 
 
 
 
221
 
222
- if "pred_pAff_mean" not in clean:
223
- clean["pred_pAff_mean"] = None
224
 
225
- clean_rows.append(clean)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- df = pd.DataFrame(clean_rows)
 
 
 
 
228
 
229
  output_dir = Path("outputs")
230
  output_dir.mkdir(exist_ok=True)
231
-
232
  csv_path = output_dir / f"{job_id}_results.csv"
233
  df.to_csv(csv_path, index=False)
234
 
235
  job_summary = {
236
  "job_id": job_id,
237
- "status": "completed",
238
- "mode": "generate_job_cpu_demo",
239
  "created_at": created_at,
240
  "preset": "egfr_generator",
241
  "docking_mode": docking_mode,
242
  "num_requested": num_molecules,
243
- "num_generated": len(rows),
244
- "num_returned": len(clean_rows),
245
  "download_csv_file": str(csv_path),
246
- "live_generation": False,
247
- "note": "CPU demo uses packaged example rows. Live REINVENT generation is not executed in this Space version."
248
  }
249
 
250
  response = {
251
  "job_id": job_id,
252
- "status": "completed",
253
- "mode": "generate_job_cpu_demo",
254
  "created_at": created_at,
255
  "preset": "egfr_generator",
256
  "docking_mode": docking_mode,
257
  "summary": {
258
  "num_requested": num_molecules,
259
- "num_generated": len(rows),
260
- "num_returned": len(clean_rows),
261
  },
262
  "columns": list(df.columns),
263
- "results": clean_rows,
264
  "download_csv_file": str(csv_path),
265
- "warnings": [
266
- "This is a CPU demo generation response.",
267
- "Live REINVENT generation is not executed in this Space version.",
268
- "The response shape matches the planned backend contract.",
269
- "Real docking is not run in this CPU Space; docking_status is not_run.",
270
- ],
271
  }
272
 
273
  return job_summary, df, response, str(csv_path)
274
 
275
 
 
 
276
  loaded_ok, loaded_msg = download_bundle_light()
277
 
278
 
@@ -323,28 +475,35 @@ Important:
323
  with gr.Tab("Generate Job Demo"):
324
  gr.Markdown(
325
  """
326
- This tab demonstrates the expected backend behavior for generation jobs.
327
 
328
- Important:
329
- This CPU Space does not run live REINVENT generation. It uses packaged example rows to let frontend, filters, and backend test the full response format.
 
 
330
 
331
- The output is split into 4 parts:
332
 
333
- 1. Job summary: job-level metadata such as job_id, status, preset, and summary.
334
- 2. Generated results table: molecule-level rows only.
335
- 3. Backend-style generation JSON: full response containing job metadata + results.
336
- 4. Download CSV: downloadable file for the same molecule-level results.
337
 
338
- In the real backend:
339
- - The frontend should consume the JSON.
340
- - The user can optionally download the CSV.
341
- - The backend should store job history using job_id/status/created_at.
342
  """
343
  )
344
 
 
 
 
 
 
 
345
  gen_num_molecules = gr.Number(
346
  label="num_molecules",
347
- value=100,
348
  precision=0,
349
  )
350
 
@@ -360,7 +519,7 @@ In the real backend:
360
  value="off",
361
  )
362
 
363
- gen_btn = gr.Button("Generate Job Demo")
364
 
365
  gen_summary = gr.JSON(label="Job summary")
366
  gen_table = gr.Dataframe(label="Generated molecule results table")
@@ -369,7 +528,7 @@ In the real backend:
369
 
370
  gen_btn.click(
371
  generate_job_demo,
372
- inputs=[gen_num_molecules, gen_return_top_k, gen_docking_mode],
373
  outputs=[gen_summary, gen_table, gen_json, gen_file],
374
  )
375
 
 
1
  import json
2
  import os
3
+ import subprocess
4
  from pathlib import Path
5
  import uuid
6
  from datetime import datetime
 
19
  EXAMPLE_JSON = None
20
  API_SPEC_TEXT = None
21
  BACKEND_CONTRACT_TEXT = None
22
+ BUNDLE_ROOT = None
23
 
24
 
25
  def download_bundle_light():
26
+ global EXAMPLE_JSON, API_SPEC_TEXT, BACKEND_CONTRACT_TEXT, BUNDLE_ROOT
27
 
28
  try:
29
  local_dir = snapshot_download(
 
34
  "examples/generate_response.example.json",
35
  "docs/API_RESPONSE_SPEC.md",
36
  "configs/backend_contract.json",
37
+ "configs/sampling_model.bundle.toml",
38
+ "models/generator/egfr_generator.chkpt",
39
  "README.md",
40
  ],
41
  local_dir=str(CACHE_DIR),
42
  )
43
 
44
  root = Path(local_dir)
45
+ BUNDLE_ROOT = root
46
 
47
  example_path = root / "examples" / "generate_response.example.json"
48
  spec_path = root / "docs" / "API_RESPONSE_SPEC.md"
 
192
  "message": "backend_contract.json not loaded. Check HF_TOKEN secret or BUNDLE_REPO variable.",
193
  }
194
 
195
+ def _write_reinvent_sampling_config(job_id, num_molecules):
196
+ if BUNDLE_ROOT is None:
197
+ raise RuntimeError("Bundle root is not loaded. Check HF_TOKEN and BUNDLE_REPO.")
198
 
199
+ checkpoint = Path(BUNDLE_ROOT) / "models" / "generator" / "egfr_generator.chkpt"
200
+ if not checkpoint.exists():
201
+ raise FileNotFoundError(f"Generator checkpoint not found: {checkpoint}")
 
 
 
202
 
203
+ output_dir = Path("outputs")
204
+ output_dir.mkdir(exist_ok=True)
205
+
206
+ raw_csv = output_dir / f"{job_id}_raw_sampling.csv"
207
+ log_file = output_dir / f"{job_id}_reinvent.log"
208
+ resolved_json = output_dir / f"{job_id}_sampling_resolved.json"
209
+ config_path = output_dir / f"{job_id}_sampling.toml"
210
+
211
+ config_text = f"""
212
+ run_type = "sampling"
213
+ device = "cpu"
214
+
215
+ json_out_config = "{resolved_json.resolve()}"
216
+
217
+ [parameters]
218
+ model_file = "{checkpoint.resolve()}"
219
+ output_file = "{raw_csv.resolve()}"
220
+
221
+ num_smiles = {int(num_molecules)}
222
+ unique_molecules = true
223
+ randomize_smiles = true
224
+ """
225
+
226
+ config_path.write_text(config_text.strip() + "\n", encoding="utf-8")
227
+ return config_path, raw_csv, log_file
228
+
229
+
230
+ def _run_reinvent_sampling(job_id, num_molecules):
231
+ config_path, raw_csv, log_file = _write_reinvent_sampling_config(job_id, num_molecules)
232
+
233
+ cmd = [
234
+ "reinvent",
235
+ "-l",
236
+ str(log_file.resolve()),
237
+ str(config_path.resolve()),
238
+ ]
239
+
240
+ proc = subprocess.run(
241
+ cmd,
242
+ text=True,
243
+ stdout=subprocess.PIPE,
244
+ stderr=subprocess.STDOUT,
245
+ timeout=300,
246
+ )
247
+
248
+ if proc.returncode != 0:
249
+ raise RuntimeError(
250
+ "REINVENT sampling failed.\n"
251
+ f"Command: {' '.join(cmd)}\n"
252
+ f"Output:\n{proc.stdout[-4000:]}"
253
+ )
254
+
255
+ if not raw_csv.exists():
256
+ raise RuntimeError(f"REINVENT finished but output CSV was not found: {raw_csv}")
257
+
258
+ return raw_csv, log_file
259
+
260
+
261
+ def _enrich_reinvent_csv(raw_csv, return_top_k):
262
+ raw_df = pd.read_csv(raw_csv)
263
+
264
+ if "SMILES" not in raw_df.columns:
265
+ raise ValueError(f"REINVENT output missing SMILES column. Columns: {list(raw_df.columns)}")
266
+
267
+ rows = []
268
+ for _, r in raw_df.iterrows():
269
+ row = compute_row(str(r["SMILES"]))
270
+
271
+ if "SMILES_state" in raw_df.columns:
272
+ try:
273
+ row["SMILES_state"] = int(r["SMILES_state"])
274
+ except Exception:
275
+ row["SMILES_state"] = None
276
+ else:
277
+ row["SMILES_state"] = None
278
+
279
+ if "NLL" in raw_df.columns:
280
+ try:
281
+ row["NLL"] = float(r["NLL"])
282
+ except Exception:
283
+ row["NLL"] = None
284
+ else:
285
+ row["NLL"] = None
286
+
287
+ # Keep these stable for frontend/backend filters.
288
+ row["pred_pAff_mean"] = None
289
+ row["docking_score"] = None
290
+ row["docking_status"] = "not_run"
291
+ row["docking_pose_file"] = None
292
+
293
+ rows.append(row)
294
+
295
+ df = pd.DataFrame(rows)
296
+
297
+ preferred_cols = [
298
+ "SMILES",
299
+ "SMILES_state",
300
+ "NLL",
301
+ "valid",
302
+ "canonical_smiles",
303
+ "mw",
304
+ "logp",
305
+ "tpsa",
306
+ "hbd",
307
+ "hba",
308
+ "rot_bonds",
309
+ "qed",
310
+ "sa_score",
311
+ "pred_pAff_mean",
312
+ "docking_score",
313
+ "docking_status",
314
+ "docking_pose_file",
315
+ "note",
316
+ ]
317
+
318
+ cols = [c for c in preferred_cols if c in df.columns] + [c for c in df.columns if c not in preferred_cols]
319
+ df = df[cols]
320
 
 
321
  return_top_k = int(return_top_k)
322
+ if return_top_k > 0:
323
+ df = df.head(return_top_k)
324
+
325
+ return df
326
 
 
327
 
328
+ def generate_job_demo(num_molecules, return_top_k, docking_mode, generation_backend):
329
+ """
330
+ Generation endpoint for the Space.
331
 
332
+ Demo mode uses packaged example rows.
333
+ Live mode tries to run REINVENT CPU sampling from the packaged checkpoint.
334
+ """
335
+ job_id = f"gen_{uuid.uuid4().hex[:8]}"
336
+ created_at = datetime.utcnow().isoformat() + "Z"
337
+ num_molecules = int(num_molecules)
338
+ return_top_k = int(return_top_k)
339
 
340
+ warnings = []
 
341
 
342
+ try:
343
+ if generation_backend == "live_reinvent_cpu":
344
+ raw_csv, log_file = _run_reinvent_sampling(job_id, num_molecules)
345
+ df = _enrich_reinvent_csv(raw_csv, return_top_k)
346
+ mode = "live_reinvent_cpu_sampling"
347
+ status = "completed"
348
+ warnings.extend([
349
+ "Live REINVENT CPU sampling was executed in this Space.",
350
+ "DeepPurpose scoring is not enabled in this Space version; pred_pAff_mean is null.",
351
+ "Real docking is not run in this CPU Space; docking_status is not_run.",
352
+ ])
353
+
354
+ else:
355
+ if EXAMPLE_JSON and "results" in EXAMPLE_JSON:
356
+ rows = EXAMPLE_JSON["results"]
357
+ else:
358
+ rows = [compute_row("CCO"), compute_row("c1ccccc1")]
359
+
360
+ selected = rows[: max(1, min(return_top_k, len(rows)))]
361
+
362
+ clean_rows = []
363
+ for row in selected:
364
+ clean = dict(row)
365
+ clean["docking_score"] = None
366
+ clean["docking_status"] = "not_run"
367
+ clean["docking_pose_file"] = None
368
+ if "pred_pAff_mean" not in clean:
369
+ clean["pred_pAff_mean"] = None
370
+ clean_rows.append(clean)
371
+
372
+ df = pd.DataFrame(clean_rows)
373
+ mode = "generate_job_cpu_demo"
374
+ status = "completed"
375
+ warnings.extend([
376
+ "This is a CPU demo generation response.",
377
+ "Live REINVENT generation was not executed because demo mode was selected.",
378
+ "The response shape matches the planned backend contract.",
379
+ "Real docking is not run in this CPU Space; docking_status is not_run.",
380
+ ])
381
 
382
+ except Exception as e:
383
+ df = pd.DataFrame()
384
+ mode = "live_reinvent_cpu_sampling"
385
+ status = "failed"
386
+ warnings.append(str(e))
387
 
388
  output_dir = Path("outputs")
389
  output_dir.mkdir(exist_ok=True)
 
390
  csv_path = output_dir / f"{job_id}_results.csv"
391
  df.to_csv(csv_path, index=False)
392
 
393
  job_summary = {
394
  "job_id": job_id,
395
+ "status": status,
396
+ "mode": mode,
397
  "created_at": created_at,
398
  "preset": "egfr_generator",
399
  "docking_mode": docking_mode,
400
  "num_requested": num_molecules,
401
+ "num_returned": int(len(df)),
 
402
  "download_csv_file": str(csv_path),
403
+ "live_generation": generation_backend == "live_reinvent_cpu",
 
404
  }
405
 
406
  response = {
407
  "job_id": job_id,
408
+ "status": status,
409
+ "mode": mode,
410
  "created_at": created_at,
411
  "preset": "egfr_generator",
412
  "docking_mode": docking_mode,
413
  "summary": {
414
  "num_requested": num_molecules,
415
+ "num_returned": int(len(df)),
 
416
  },
417
  "columns": list(df.columns),
418
+ "results": df.to_dict(orient="records"),
419
  "download_csv_file": str(csv_path),
420
+ "warnings": warnings,
 
 
 
 
 
421
  }
422
 
423
  return job_summary, df, response, str(csv_path)
424
 
425
 
426
+
427
+
428
  loaded_ok, loaded_msg = download_bundle_light()
429
 
430
 
 
475
  with gr.Tab("Generate Job Demo"):
476
  gr.Markdown(
477
  """
478
+ This tab demonstrates generation job behavior.
479
 
480
+ Generation backend options:
481
+
482
+ - demo_rows: uses packaged example rows. Fast and stable.
483
+ - live_reinvent_cpu: tries to run real REINVENT sampling from the packaged checkpoint on CPU.
484
 
485
+ The output is split into:
486
 
487
+ 1. Job summary: job_id, status, preset, mode, and metadata.
488
+ 2. Generated molecule results table.
489
+ 3. Full backend-style generation JSON.
490
+ 4. Download CSV.
491
 
492
+ Notes:
493
+ - DeepPurpose live scoring is not enabled yet in this Space version.
494
+ - Real docking is disabled in this CPU Space, so docking_status is returned as not_run.
 
495
  """
496
  )
497
 
498
+ generation_backend = gr.Dropdown(
499
+ label="generation_backend",
500
+ choices=["demo_rows", "live_reinvent_cpu"],
501
+ value="demo_rows",
502
+ )
503
+
504
  gen_num_molecules = gr.Number(
505
  label="num_molecules",
506
+ value=20,
507
  precision=0,
508
  )
509
 
 
519
  value="off",
520
  )
521
 
522
+ gen_btn = gr.Button("Run Generation Job")
523
 
524
  gen_summary = gr.JSON(label="Job summary")
525
  gen_table = gr.Dataframe(label="Generated molecule results table")
 
528
 
529
  gen_btn.click(
530
  generate_job_demo,
531
+ inputs=[gen_num_molecules, gen_return_top_k, gen_docking_mode, generation_backend],
532
  outputs=[gen_summary, gen_table, gen_json, gen_file],
533
  )
534
 
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
  pandas
2
  huggingface_hub
3
  rdkit
 
 
 
1
  pandas
2
  huggingface_hub
3
  rdkit
4
+ toml
5
+ git+https://github.com/MolecularAI/REINVENT4.git