dh-cli 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dh_cli-0.3.1 → dh_cli-0.3.2}/PKG-INFO +1 -1
- {dh_cli-0.3.1 → dh_cli-0.3.2}/pyproject.toml +1 -1
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/finalize.py +5 -4
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/protmpnn.py +132 -7
- {dh_cli-0.3.1 → dh_cli-0.3.2}/.gitignore +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/LICENSE +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/README.md +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/__init__.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/__init__.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/aws_batch.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/__init__.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/boltz.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/cancel.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/clean.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/embed_t5.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/list_jobs.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/local.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/logs.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/protmpnn_to_boltz.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/retry.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/status.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/submit.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/train.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/commands/wait_for.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/fasta_utils.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/h5_utils.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/job_id.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/manifest.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/batch/s3_transport.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/cloud_commands.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/codeartifact.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/__init__.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/api_client.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/auth.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/engine_commands.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/progress.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/ssh_config.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/engines_studios/studio_commands.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/github_commands.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/main.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/utility_commands.py +0 -0
- {dh_cli-0.3.1 → dh_cli-0.3.2}/src/dh_cli/warehouse.py +0 -0
|
@@ -489,10 +489,11 @@ def _finalize_protmpnn(output_dir: Path, output_path: Path):
|
|
|
489
489
|
pdbs_dest.mkdir(exist_ok=True)
|
|
490
490
|
for config_dir in output_dir.iterdir():
|
|
491
491
|
if config_dir.is_dir():
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
for subdir_name in ("pdbs", "backbones"):
|
|
493
|
+
config_pdbs = config_dir / subdir_name
|
|
494
|
+
if config_pdbs.exists():
|
|
495
|
+
for pdb_file in config_pdbs.glob("*.pdb"):
|
|
496
|
+
shutil.copy2(pdb_file, pdbs_dest / pdb_file.name)
|
|
496
497
|
|
|
497
498
|
top_conf = merged.iloc[0]["overall_confidence"] if num_variants > 0 else "N/A"
|
|
498
499
|
|
|
@@ -7,8 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
|
|
10
|
-
from ..aws_batch import BatchClient, BatchError
|
|
11
|
-
from ..job_id import generate_job_id
|
|
10
|
+
from ..aws_batch import BatchClient, BatchError, resolve_dependency
|
|
11
|
+
from ..job_id import generate_job_id, get_aws_username
|
|
12
12
|
from ..manifest import (
|
|
13
13
|
BATCH_JOBS_BASE,
|
|
14
14
|
BatchConfig,
|
|
@@ -63,7 +63,14 @@ DEFAULT_IMAGE_URI = (
|
|
|
63
63
|
help="Drop into container shell for debugging",
|
|
64
64
|
)
|
|
65
65
|
@click.option("--base-path", default=BATCH_JOBS_BASE, help="Base path for job data")
|
|
66
|
-
|
|
66
|
+
@click.option("--after", "after", multiple=True, help="Job ID(s) to wait for before starting")
|
|
67
|
+
@click.option(
|
|
68
|
+
"--auto-validate-top",
|
|
69
|
+
type=int,
|
|
70
|
+
default=None,
|
|
71
|
+
help="Auto-submit Boltz validation for top N variants after completion",
|
|
72
|
+
)
|
|
73
|
+
def protmpnn(input_dir, workers, queue, dry_run, run_local, run_remote, run_shell, base_path, after, auto_validate_top):
|
|
67
74
|
"""Design protein sequences with ProtMPNN/LigandMPNN.
|
|
68
75
|
|
|
69
76
|
Processes a directory of YAML config files, each specifying a PDB
|
|
@@ -106,17 +113,17 @@ def protmpnn(input_dir, workers, queue, dry_run, run_local, run_remote, run_shel
|
|
|
106
113
|
return
|
|
107
114
|
|
|
108
115
|
if run_local:
|
|
109
|
-
_run_local_mode(input_path)
|
|
116
|
+
_run_local_mode(input_path, auto_validate_top, base_path)
|
|
110
117
|
return
|
|
111
118
|
|
|
112
119
|
# Auto-detect GPU for smart defaulting
|
|
113
120
|
if not run_remote and not dry_run:
|
|
114
121
|
if _has_local_gpu():
|
|
115
122
|
click.echo("GPU detected — running locally (use --remote to override)")
|
|
116
|
-
_run_local_mode(input_path)
|
|
123
|
+
_run_local_mode(input_path, auto_validate_top, base_path)
|
|
117
124
|
return
|
|
118
125
|
|
|
119
|
-
_submit_batch_job(input_path, workers, queue, dry_run, base_path)
|
|
126
|
+
_submit_batch_job(input_path, workers, queue, dry_run, base_path, after, auto_validate_top)
|
|
120
127
|
|
|
121
128
|
|
|
122
129
|
def _has_local_gpu() -> bool:
|
|
@@ -161,6 +168,8 @@ def _submit_batch_job(
|
|
|
161
168
|
queue: str,
|
|
162
169
|
dry_run: bool,
|
|
163
170
|
base_path: str,
|
|
171
|
+
after: tuple[str, ...] = (),
|
|
172
|
+
auto_validate_top: int | None = None,
|
|
164
173
|
):
|
|
165
174
|
"""Submit ProtMPNN job to AWS Batch."""
|
|
166
175
|
click.echo(f"Scanning {input_path} for YAML files...")
|
|
@@ -228,11 +237,15 @@ def _submit_batch_job(
|
|
|
228
237
|
destination=None,
|
|
229
238
|
finalized=False,
|
|
230
239
|
),
|
|
240
|
+
depends_on=list(after) if after else None,
|
|
231
241
|
)
|
|
232
242
|
|
|
233
243
|
save_manifest(manifest, base_path)
|
|
234
244
|
|
|
235
245
|
try:
|
|
246
|
+
resolved = [resolve_dependency(jid, base_path) for jid in after]
|
|
247
|
+
depends_on = [{"jobId": aws_id} for aws_id in resolved if aws_id is not None] or None
|
|
248
|
+
|
|
236
249
|
client = BatchClient()
|
|
237
250
|
|
|
238
251
|
environment = {
|
|
@@ -250,6 +263,8 @@ def _submit_batch_job(
|
|
|
250
263
|
environment=environment,
|
|
251
264
|
timeout_seconds=1 * 3600, # 1 hour
|
|
252
265
|
retry_attempts=5,
|
|
266
|
+
depends_on=depends_on,
|
|
267
|
+
share_identifier=get_aws_username(),
|
|
253
268
|
)
|
|
254
269
|
|
|
255
270
|
manifest.status = JobStatus.SUBMITTED
|
|
@@ -260,6 +275,8 @@ def _submit_batch_job(
|
|
|
260
275
|
click.echo(click.style("Job submitted successfully!", fg="green"))
|
|
261
276
|
click.echo()
|
|
262
277
|
click.echo(f"AWS Batch Job ID: {batch_job_id}")
|
|
278
|
+
if depends_on:
|
|
279
|
+
click.echo(f"Waiting on: {', '.join(after)}")
|
|
263
280
|
click.echo()
|
|
264
281
|
click.echo("Next steps:")
|
|
265
282
|
click.echo(f" Check status: dh batch status {job_id}")
|
|
@@ -271,6 +288,11 @@ def _submit_batch_job(
|
|
|
271
288
|
f" Finalize: dh batch finalize {job_id} --output ./results/"
|
|
272
289
|
)
|
|
273
290
|
|
|
291
|
+
if auto_validate_top:
|
|
292
|
+
_submit_boltz_validation(
|
|
293
|
+
job_id, batch_job_id, job_dir, auto_validate_top, base_path
|
|
294
|
+
)
|
|
295
|
+
|
|
274
296
|
except BatchError as e:
|
|
275
297
|
manifest.status = JobStatus.FAILED
|
|
276
298
|
manifest.error_message = str(e)
|
|
@@ -279,7 +301,7 @@ def _submit_batch_job(
|
|
|
279
301
|
raise SystemExit(1)
|
|
280
302
|
|
|
281
303
|
|
|
282
|
-
def _run_local_mode(input_path: Path):
|
|
304
|
+
def _run_local_mode(input_path: Path, auto_validate_top: int | None = None, base_path: str = BATCH_JOBS_BASE):
|
|
283
305
|
"""Run ProtMPNN locally in a Docker container."""
|
|
284
306
|
import subprocess
|
|
285
307
|
|
|
@@ -360,6 +382,11 @@ def _run_local_mode(input_path: Path):
|
|
|
360
382
|
click.echo(click.style("Design complete!", fg="green"))
|
|
361
383
|
click.echo(f"Results: {temp_output_dir / 'results.csv'}")
|
|
362
384
|
click.echo(f" {len(merged)} variants generated")
|
|
385
|
+
|
|
386
|
+
if auto_validate_top:
|
|
387
|
+
_run_local_boltz_validation(
|
|
388
|
+
temp_output_dir, input_path, auto_validate_top
|
|
389
|
+
)
|
|
363
390
|
else:
|
|
364
391
|
click.echo(click.style("Warning: No results CSV found", fg="yellow"))
|
|
365
392
|
|
|
@@ -416,3 +443,101 @@ def _run_shell_mode(input_path: Path):
|
|
|
416
443
|
err=True,
|
|
417
444
|
)
|
|
418
445
|
raise SystemExit(1)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _convert_to_boltz(results_dir: Path, config_dir: Path, top_n: int) -> Path:
|
|
449
|
+
"""Run protmpnn-to-boltz conversion, return the output directory."""
|
|
450
|
+
from .protmpnn_to_boltz import (
|
|
451
|
+
_build_boltz_yaml,
|
|
452
|
+
_load_ligand_smiles,
|
|
453
|
+
_write_pymol_script,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
import pandas as pd
|
|
457
|
+
import yaml
|
|
458
|
+
|
|
459
|
+
csv_path = results_dir / "results.csv"
|
|
460
|
+
if not csv_path.exists():
|
|
461
|
+
worker_csvs = sorted(results_dir.glob("results_worker_*.csv"))
|
|
462
|
+
if not worker_csvs:
|
|
463
|
+
raise FileNotFoundError(f"No results CSV in {results_dir}")
|
|
464
|
+
dfs = [pd.read_csv(f) for f in worker_csvs]
|
|
465
|
+
df = pd.concat(dfs, ignore_index=True).sort_values(
|
|
466
|
+
"overall_confidence", ascending=False
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
df = pd.read_csv(csv_path)
|
|
470
|
+
|
|
471
|
+
top_n = min(top_n, len(df))
|
|
472
|
+
top_variants = df.head(top_n)
|
|
473
|
+
ligand_map = _load_ligand_smiles(str(config_dir), results_dir)
|
|
474
|
+
|
|
475
|
+
boltz_dir = results_dir.parent / "boltz_input"
|
|
476
|
+
boltz_dir.mkdir(parents=True, exist_ok=True)
|
|
477
|
+
|
|
478
|
+
generated = []
|
|
479
|
+
for _idx, row in top_variants.iterrows():
|
|
480
|
+
config_name = row.get("config_name", "unknown")
|
|
481
|
+
variant_id = int(row.get("variant_id", _idx))
|
|
482
|
+
sequence = row["sequence"]
|
|
483
|
+
confidence = row.get("overall_confidence", float("nan"))
|
|
484
|
+
|
|
485
|
+
boltz_yaml = _build_boltz_yaml(
|
|
486
|
+
sequence=sequence,
|
|
487
|
+
config_name=config_name,
|
|
488
|
+
variant_id=variant_id,
|
|
489
|
+
ligand_smiles=ligand_map.get(config_name),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
filename = f"{config_name}_var{variant_id:03d}.yaml"
|
|
493
|
+
with open(boltz_dir / filename, "w") as f:
|
|
494
|
+
yaml.dump(boltz_yaml, f, default_flow_style=False, sort_keys=False)
|
|
495
|
+
generated.append((filename, confidence))
|
|
496
|
+
|
|
497
|
+
_write_pymol_script(boltz_dir, results_dir, generated, ligand_map)
|
|
498
|
+
return boltz_dir
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _run_local_boltz_validation(
|
|
502
|
+
results_dir: Path, original_input_path: Path, top_n: int
|
|
503
|
+
):
|
|
504
|
+
"""Convert top variants to Boltz YAMLs and run Boltz locally."""
|
|
505
|
+
click.echo()
|
|
506
|
+
click.echo(f"Auto-validating top {top_n} variants with Boltz...")
|
|
507
|
+
|
|
508
|
+
boltz_dir = _convert_to_boltz(results_dir, original_input_path, top_n)
|
|
509
|
+
num_yamls = len(list(boltz_dir.glob("*.yaml")))
|
|
510
|
+
click.echo(f"Generated {num_yamls} Boltz configs at {boltz_dir}/")
|
|
511
|
+
click.echo()
|
|
512
|
+
|
|
513
|
+
from .boltz import _run_local_mode as boltz_local
|
|
514
|
+
|
|
515
|
+
boltz_local(boltz_dir)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _submit_boltz_validation(
|
|
519
|
+
protmpnn_job_id: str,
|
|
520
|
+
protmpnn_aws_job_id: str,
|
|
521
|
+
job_dir: Path,
|
|
522
|
+
top_n: int,
|
|
523
|
+
base_path: str,
|
|
524
|
+
):
|
|
525
|
+
"""Pre-register a dependent Boltz Batch job that runs after ProtMPNN completes.
|
|
526
|
+
|
|
527
|
+
The ProtMPNN worker writes results to job_dir/output/. The Boltz conversion
|
|
528
|
+
happens at finalize time — we set up a post-finalize hook via an environment
|
|
529
|
+
variable that tells the ProtMPNN finalizer to convert and submit Boltz.
|
|
530
|
+
"""
|
|
531
|
+
click.echo()
|
|
532
|
+
click.echo(
|
|
533
|
+
f"Boltz validation for top {top_n} will run after ProtMPNN finalize."
|
|
534
|
+
)
|
|
535
|
+
click.echo(
|
|
536
|
+
"After ProtMPNN completes, finalize will auto-convert and submit Boltz:"
|
|
537
|
+
)
|
|
538
|
+
click.echo(f" dh batch finalize {protmpnn_job_id} --auto-validate-top {top_n}")
|
|
539
|
+
click.echo()
|
|
540
|
+
click.echo(
|
|
541
|
+
"Or manually: dh batch protmpnn-to-boltz <results_dir> --top "
|
|
542
|
+
f"{top_n} && dh batch boltz <boltz_dir>"
|
|
543
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|