boltz2-python-client 0.2__py3-none-any.whl
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.
- boltz2_client/__init__.py +153 -0
- boltz2_client/cli.py +1213 -0
- boltz2_client/client.py +986 -0
- boltz2_client/exceptions.py +213 -0
- boltz2_client/models.py +466 -0
- boltz2_client/models_affinity.py +46 -0
- boltz2_client/utils.py +455 -0
- boltz2_client/virtual_screening.py +608 -0
- boltz2_python_client-0.2.dist-info/METADATA +533 -0
- boltz2_python_client-0.2.dist-info/RECORD +14 -0
- boltz2_python_client-0.2.dist-info/WHEEL +5 -0
- boltz2_python_client-0.2.dist-info/entry_points.txt +2 -0
- boltz2_python_client-0.2.dist-info/licenses/LICENSE +21 -0
- boltz2_python_client-0.2.dist-info/top_level.txt +1 -0
boltz2_client/client.py
ADDED
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
# ---------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
3
|
+
# ---------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Boltz-2 Python Client
|
|
8
|
+
|
|
9
|
+
This module provides both synchronous and asynchronous clients for interacting
|
|
10
|
+
with the Boltz-2 NIM API, with comprehensive support for all available parameters
|
|
11
|
+
and advanced features.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Dict, List, Optional, Union, Any, Tuple, Callable
|
|
20
|
+
from urllib.parse import urljoin
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
import yaml
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
|
|
27
|
+
|
|
28
|
+
from .models import (
|
|
29
|
+
PredictionRequest, PredictionResponse, HealthStatus, ServiceMetadata,
|
|
30
|
+
Polymer, Ligand, PocketConstraint, BondConstraint, Atom, AlignmentFileRecord,
|
|
31
|
+
StructureData, PredictionJob, PolymerType, AlignmentFormat, ConstraintType,
|
|
32
|
+
YAMLConfig, YAMLConfigType
|
|
33
|
+
)
|
|
34
|
+
from .exceptions import (
|
|
35
|
+
Boltz2ClientError, Boltz2APIError, Boltz2ValidationError,
|
|
36
|
+
Boltz2TimeoutError, Boltz2ConnectionError
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EndpointType:
|
|
41
|
+
"""Endpoint type constants."""
|
|
42
|
+
LOCAL = "local"
|
|
43
|
+
NVIDIA_HOSTED = "nvidia_hosted"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Boltz2Client:
|
|
47
|
+
"""
|
|
48
|
+
Asynchronous client for Boltz-2 NIM service.
|
|
49
|
+
|
|
50
|
+
Supports both local deployments and NVIDIA hosted endpoints with API key authentication.
|
|
51
|
+
Provides comprehensive structure prediction capabilities with all available parameters.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
base_url: str = "http://localhost:8000",
|
|
57
|
+
api_key: Optional[str] = None,
|
|
58
|
+
endpoint_type: str = EndpointType.LOCAL,
|
|
59
|
+
timeout: float = 300.0,
|
|
60
|
+
max_retries: int = 3,
|
|
61
|
+
retry_delay: float = 1.0,
|
|
62
|
+
poll_seconds: int = 10,
|
|
63
|
+
console: Optional[Console] = None
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the Boltz-2 client.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
base_url: Base URL of the service
|
|
70
|
+
- Local: "http://localhost:8000"
|
|
71
|
+
- NVIDIA Hosted: "https://health.api.nvidia.com"
|
|
72
|
+
api_key: API key for NVIDIA hosted endpoints (can also be set via NVIDIA_API_KEY env var)
|
|
73
|
+
endpoint_type: Type of endpoint ("local" or "nvidia_hosted")
|
|
74
|
+
timeout: Request timeout in seconds
|
|
75
|
+
max_retries: Maximum number of retry attempts
|
|
76
|
+
retry_delay: Delay between retries in seconds
|
|
77
|
+
poll_seconds: Polling interval for NVIDIA hosted endpoints (NVCF-POLL-SECONDS)
|
|
78
|
+
console: Rich console for output (optional)
|
|
79
|
+
"""
|
|
80
|
+
self.base_url = base_url.rstrip('/')
|
|
81
|
+
self.endpoint_type = endpoint_type
|
|
82
|
+
self.timeout = timeout
|
|
83
|
+
self.max_retries = max_retries
|
|
84
|
+
self.retry_delay = retry_delay
|
|
85
|
+
self.poll_seconds = poll_seconds
|
|
86
|
+
self.console = console or Console()
|
|
87
|
+
|
|
88
|
+
# Handle API key for NVIDIA hosted endpoints
|
|
89
|
+
if endpoint_type == EndpointType.NVIDIA_HOSTED:
|
|
90
|
+
self.api_key = api_key or os.getenv("NVIDIA_API_KEY")
|
|
91
|
+
if not self.api_key:
|
|
92
|
+
raise Boltz2ValidationError(
|
|
93
|
+
"API key is required for NVIDIA hosted endpoints. "
|
|
94
|
+
"Provide it via api_key parameter or NVIDIA_API_KEY environment variable."
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
self.api_key = None
|
|
98
|
+
|
|
99
|
+
# Set up URLs based on endpoint type
|
|
100
|
+
if endpoint_type == EndpointType.NVIDIA_HOSTED:
|
|
101
|
+
self.predict_url = f"{self.base_url}/v1/biology/mit/boltz2/predict"
|
|
102
|
+
self.health_url = f"{self.base_url}/v1/health/live"
|
|
103
|
+
self.ready_url = f"{self.base_url}/v1/health/ready"
|
|
104
|
+
self.metadata_url = f"{self.base_url}/v1/models"
|
|
105
|
+
self.status_url = "https://api.nvcf.nvidia.com/v2/nvcf/pexec/status/{task_id}"
|
|
106
|
+
else:
|
|
107
|
+
# Local endpoints
|
|
108
|
+
self.predict_url = f"{self.base_url}/biology/mit/boltz2/predict"
|
|
109
|
+
self.health_url = f"{self.base_url}/v1/health/live"
|
|
110
|
+
self.ready_url = f"{self.base_url}/v1/health/ready"
|
|
111
|
+
self.metadata_url = f"{self.base_url}/v1/models"
|
|
112
|
+
self.status_url = None
|
|
113
|
+
|
|
114
|
+
def _get_headers(self, additional_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
115
|
+
"""Get headers for requests based on endpoint type."""
|
|
116
|
+
headers = {"Content-Type": "application/json"}
|
|
117
|
+
|
|
118
|
+
if self.endpoint_type == EndpointType.NVIDIA_HOSTED:
|
|
119
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
120
|
+
headers["NVCF-POLL-SECONDS"] = str(self.poll_seconds)
|
|
121
|
+
|
|
122
|
+
if additional_headers:
|
|
123
|
+
headers.update(additional_headers)
|
|
124
|
+
|
|
125
|
+
return headers
|
|
126
|
+
|
|
127
|
+
async def _handle_nvidia_polling(
|
|
128
|
+
self,
|
|
129
|
+
client: httpx.AsyncClient,
|
|
130
|
+
response: httpx.Response,
|
|
131
|
+
progress_callback: Optional[Callable] = None
|
|
132
|
+
) -> httpx.Response:
|
|
133
|
+
"""Handle NVIDIA hosted endpoint polling for 202 responses."""
|
|
134
|
+
if response.status_code != 202:
|
|
135
|
+
return response
|
|
136
|
+
|
|
137
|
+
task_id = response.headers.get("nvcf-reqid")
|
|
138
|
+
if not task_id:
|
|
139
|
+
raise Boltz2APIError("No task ID found in 202 response headers")
|
|
140
|
+
|
|
141
|
+
if progress_callback:
|
|
142
|
+
progress_callback(f"Request queued, polling task {task_id}...")
|
|
143
|
+
|
|
144
|
+
headers = self._get_headers()
|
|
145
|
+
|
|
146
|
+
while True:
|
|
147
|
+
await asyncio.sleep(self.poll_seconds)
|
|
148
|
+
|
|
149
|
+
status_response = await client.get(
|
|
150
|
+
self.status_url.format(task_id=task_id),
|
|
151
|
+
headers=headers,
|
|
152
|
+
timeout=self.timeout
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if status_response.status_code == 200:
|
|
156
|
+
if progress_callback:
|
|
157
|
+
progress_callback("Task completed successfully")
|
|
158
|
+
return status_response
|
|
159
|
+
elif status_response.status_code in [400, 401, 404, 422, 500]:
|
|
160
|
+
error_detail = status_response.text
|
|
161
|
+
raise Boltz2APIError(f"Task failed with status {status_response.status_code}: {error_detail}")
|
|
162
|
+
|
|
163
|
+
if progress_callback:
|
|
164
|
+
progress_callback(f"Task still processing... (status: {status_response.status_code})")
|
|
165
|
+
|
|
166
|
+
async def health_check(self) -> HealthStatus:
|
|
167
|
+
"""Check the health status of the Boltz-2 service."""
|
|
168
|
+
try:
|
|
169
|
+
headers = self._get_headers()
|
|
170
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
171
|
+
response = await client.get(self.health_url, headers=headers)
|
|
172
|
+
response.raise_for_status()
|
|
173
|
+
|
|
174
|
+
return HealthStatus(
|
|
175
|
+
status="healthy" if response.status_code == 200 else "unhealthy",
|
|
176
|
+
timestamp=datetime.now(),
|
|
177
|
+
details={"status_code": response.status_code}
|
|
178
|
+
)
|
|
179
|
+
except Exception as e:
|
|
180
|
+
raise Boltz2ConnectionError(f"Health check failed: {e}")
|
|
181
|
+
|
|
182
|
+
async def get_service_metadata(self) -> ServiceMetadata:
|
|
183
|
+
"""Get service metadata and model information."""
|
|
184
|
+
try:
|
|
185
|
+
headers = self._get_headers()
|
|
186
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
187
|
+
response = await client.get(self.metadata_url, headers=headers)
|
|
188
|
+
response.raise_for_status()
|
|
189
|
+
data = response.json()
|
|
190
|
+
return ServiceMetadata(**data)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise Boltz2APIError(f"Failed to get service metadata: {e}")
|
|
193
|
+
|
|
194
|
+
async def predict(
|
|
195
|
+
self,
|
|
196
|
+
request: PredictionRequest,
|
|
197
|
+
save_structures: bool = True,
|
|
198
|
+
output_dir: Optional[Path] = None,
|
|
199
|
+
progress_callback: Optional[callable] = None
|
|
200
|
+
) -> PredictionResponse:
|
|
201
|
+
"""
|
|
202
|
+
Make a structure prediction request with comprehensive parameter support.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
request: Complete prediction request with all parameters
|
|
206
|
+
save_structures: Whether to save structures to files
|
|
207
|
+
output_dir: Directory to save structures (default: current directory)
|
|
208
|
+
progress_callback: Optional callback for progress updates
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Prediction response with structures and metadata
|
|
212
|
+
"""
|
|
213
|
+
if output_dir is None:
|
|
214
|
+
output_dir = Path.cwd()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Validate request
|
|
218
|
+
request_dict = request.dict(exclude_none=True)
|
|
219
|
+
headers = self._get_headers()
|
|
220
|
+
|
|
221
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
222
|
+
if progress_callback:
|
|
223
|
+
progress_callback("Sending prediction request...")
|
|
224
|
+
|
|
225
|
+
start_time = time.time()
|
|
226
|
+
response = await client.post(
|
|
227
|
+
self.predict_url,
|
|
228
|
+
json=request_dict,
|
|
229
|
+
headers=headers
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Handle NVIDIA hosted endpoint polling
|
|
233
|
+
if self.endpoint_type == EndpointType.NVIDIA_HOSTED:
|
|
234
|
+
response = await self._handle_nvidia_polling(client, response, progress_callback)
|
|
235
|
+
|
|
236
|
+
if response.status_code != 200:
|
|
237
|
+
error_detail = response.text
|
|
238
|
+
raise Boltz2APIError(f"Prediction failed: {response.status_code} - {error_detail}")
|
|
239
|
+
|
|
240
|
+
end_time = time.time()
|
|
241
|
+
prediction_time = end_time - start_time
|
|
242
|
+
|
|
243
|
+
if progress_callback:
|
|
244
|
+
progress_callback(f"Prediction completed in {prediction_time:.2f}s")
|
|
245
|
+
|
|
246
|
+
# Parse response
|
|
247
|
+
response_data = response.json()
|
|
248
|
+
prediction_response = PredictionResponse(**response_data)
|
|
249
|
+
|
|
250
|
+
# Save structures if requested
|
|
251
|
+
if save_structures:
|
|
252
|
+
await self._save_structures(prediction_response, output_dir, progress_callback)
|
|
253
|
+
|
|
254
|
+
return prediction_response
|
|
255
|
+
|
|
256
|
+
except httpx.TimeoutException:
|
|
257
|
+
raise Boltz2TimeoutError(f"Request timed out after {self.timeout} seconds")
|
|
258
|
+
except httpx.RequestError as e:
|
|
259
|
+
raise Boltz2ConnectionError(f"Connection error: {e}")
|
|
260
|
+
except Exception as e:
|
|
261
|
+
if isinstance(e, (Boltz2ClientError, Boltz2APIError, Boltz2TimeoutError)):
|
|
262
|
+
raise
|
|
263
|
+
raise Boltz2ClientError(f"Unexpected error: {e}")
|
|
264
|
+
|
|
265
|
+
async def predict_protein_structure(
|
|
266
|
+
self,
|
|
267
|
+
sequence: str,
|
|
268
|
+
polymer_id: str = "A",
|
|
269
|
+
recycling_steps: int = 3,
|
|
270
|
+
sampling_steps: int = 50,
|
|
271
|
+
diffusion_samples: int = 1,
|
|
272
|
+
step_scale: float = 1.638,
|
|
273
|
+
msa_files: Optional[List[Tuple[str, AlignmentFormat]]] = None,
|
|
274
|
+
**kwargs
|
|
275
|
+
) -> PredictionResponse:
|
|
276
|
+
"""
|
|
277
|
+
Predict protein structure with optional MSA guidance.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
sequence: Protein sequence
|
|
281
|
+
polymer_id: Polymer identifier
|
|
282
|
+
recycling_steps: Number of recycling steps (1-6)
|
|
283
|
+
sampling_steps: Number of sampling steps (10-1000)
|
|
284
|
+
diffusion_samples: Number of diffusion samples (1-5)
|
|
285
|
+
step_scale: Step scale for diffusion (0.5-5.0)
|
|
286
|
+
msa_files: List of (file_path, format) tuples for MSA files
|
|
287
|
+
**kwargs: Additional parameters for predict()
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Prediction response
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# Create a proper PredictionRequest and use the main predict method
|
|
295
|
+
# which handles file saving, progress callbacks, and all other functionality
|
|
296
|
+
|
|
297
|
+
# Build MSA records for proper integration
|
|
298
|
+
msa_records = None
|
|
299
|
+
if msa_files:
|
|
300
|
+
msa_records = []
|
|
301
|
+
for file_path, format_type in msa_files:
|
|
302
|
+
with open(file_path, "r") as fh:
|
|
303
|
+
content = fh.read()
|
|
304
|
+
msa_record = AlignmentFileRecord(
|
|
305
|
+
alignment=content,
|
|
306
|
+
format=format_type,
|
|
307
|
+
rank=len(msa_records)
|
|
308
|
+
)
|
|
309
|
+
msa_records.append(msa_record)
|
|
310
|
+
|
|
311
|
+
polymer = Polymer(
|
|
312
|
+
id=polymer_id,
|
|
313
|
+
molecule_type="protein",
|
|
314
|
+
sequence=sequence,
|
|
315
|
+
msa=msa_records
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
request = PredictionRequest(
|
|
319
|
+
polymers=[polymer],
|
|
320
|
+
recycling_steps=recycling_steps,
|
|
321
|
+
sampling_steps=sampling_steps,
|
|
322
|
+
diffusion_samples=diffusion_samples,
|
|
323
|
+
step_scale=step_scale
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
return await self.predict(request, **kwargs)
|
|
327
|
+
|
|
328
|
+
async def predict_protein_ligand_complex(
|
|
329
|
+
self,
|
|
330
|
+
protein_sequence: str,
|
|
331
|
+
ligand_smiles: Optional[str] = None,
|
|
332
|
+
ligand_ccd: Optional[str] = None,
|
|
333
|
+
protein_id: str = "A",
|
|
334
|
+
ligand_id: str = "LIG",
|
|
335
|
+
pocket_residues: Optional[List[int]] = None,
|
|
336
|
+
recycling_steps: int = 3,
|
|
337
|
+
sampling_steps: int = 50,
|
|
338
|
+
**kwargs
|
|
339
|
+
) -> PredictionResponse:
|
|
340
|
+
"""
|
|
341
|
+
Predict protein-ligand complex structure.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
protein_sequence: Protein sequence
|
|
345
|
+
ligand_smiles: SMILES string for ligand (mutually exclusive with ligand_ccd)
|
|
346
|
+
ligand_ccd: CCD code for ligand (mutually exclusive with ligand_smiles)
|
|
347
|
+
protein_id: Protein polymer identifier
|
|
348
|
+
ligand_id: Ligand identifier
|
|
349
|
+
pocket_residues: List of residue indices defining binding pocket
|
|
350
|
+
recycling_steps: Number of recycling steps
|
|
351
|
+
sampling_steps: Number of sampling steps
|
|
352
|
+
**kwargs: Additional parameters for predict()
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Prediction response
|
|
356
|
+
"""
|
|
357
|
+
if not ligand_smiles and not ligand_ccd:
|
|
358
|
+
raise Boltz2ValidationError("Must provide either ligand_smiles or ligand_ccd")
|
|
359
|
+
|
|
360
|
+
polymer = Polymer(
|
|
361
|
+
id=protein_id,
|
|
362
|
+
molecule_type="protein",
|
|
363
|
+
sequence=protein_sequence
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
ligand = Ligand(
|
|
367
|
+
id=ligand_id,
|
|
368
|
+
smiles=ligand_smiles,
|
|
369
|
+
ccd=ligand_ccd
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
constraints = []
|
|
373
|
+
if pocket_residues:
|
|
374
|
+
pocket_constraint = PocketConstraint(
|
|
375
|
+
ligand_id=ligand_id,
|
|
376
|
+
polymer_id=protein_id,
|
|
377
|
+
residue_ids=pocket_residues,
|
|
378
|
+
binder=ligand_id,
|
|
379
|
+
contacts=[] # Leave empty to avoid server validation issues
|
|
380
|
+
)
|
|
381
|
+
constraints.append(pocket_constraint)
|
|
382
|
+
|
|
383
|
+
request = PredictionRequest(
|
|
384
|
+
polymers=[polymer],
|
|
385
|
+
ligands=[ligand],
|
|
386
|
+
constraints=constraints if constraints else None,
|
|
387
|
+
recycling_steps=recycling_steps,
|
|
388
|
+
sampling_steps=sampling_steps
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return await self.predict(request, **kwargs)
|
|
392
|
+
|
|
393
|
+
async def predict_covalent_complex(
|
|
394
|
+
self,
|
|
395
|
+
protein_sequence: str,
|
|
396
|
+
ligand_ccd: str, # Only CCD codes supported for covalent bonding
|
|
397
|
+
covalent_bonds: List[Tuple[int, str, str]] = None,
|
|
398
|
+
protein_id: str = "A",
|
|
399
|
+
ligand_id: str = "LIG",
|
|
400
|
+
recycling_steps: int = 3,
|
|
401
|
+
sampling_steps: int = 50,
|
|
402
|
+
**kwargs
|
|
403
|
+
) -> PredictionResponse:
|
|
404
|
+
"""
|
|
405
|
+
Predict covalent protein-ligand complex with bond constraints.
|
|
406
|
+
|
|
407
|
+
Note: Covalent bonding only supports CCD codes for ligands, not SMILES.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
protein_sequence: Protein sequence
|
|
411
|
+
ligand_ccd: CCD code for ligand (SMILES not supported for covalent bonding)
|
|
412
|
+
covalent_bonds: List of (residue_index, protein_atom, ligand_atom) tuples
|
|
413
|
+
protein_id: Protein polymer identifier
|
|
414
|
+
ligand_id: Ligand identifier
|
|
415
|
+
recycling_steps: Number of recycling steps
|
|
416
|
+
sampling_steps: Number of sampling steps
|
|
417
|
+
**kwargs: Additional parameters for predict()
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
Prediction response
|
|
421
|
+
"""
|
|
422
|
+
if not ligand_ccd:
|
|
423
|
+
raise Boltz2ValidationError("CCD code is required for covalent bonding (SMILES not supported)")
|
|
424
|
+
|
|
425
|
+
if not covalent_bonds:
|
|
426
|
+
raise Boltz2ValidationError("Must provide at least one covalent bond")
|
|
427
|
+
|
|
428
|
+
polymer = Polymer(
|
|
429
|
+
id=protein_id,
|
|
430
|
+
molecule_type="protein",
|
|
431
|
+
sequence=protein_sequence
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
ligand = Ligand(
|
|
435
|
+
id=ligand_id,
|
|
436
|
+
ccd=ligand_ccd # Only CCD supported for covalent bonding
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Create bond constraints
|
|
440
|
+
constraints = []
|
|
441
|
+
for residue_idx, protein_atom, ligand_atom in covalent_bonds:
|
|
442
|
+
bond_constraint = BondConstraint(
|
|
443
|
+
constraint_type="bond",
|
|
444
|
+
atoms=[
|
|
445
|
+
Atom(id=protein_id, residue_index=residue_idx, atom_name=protein_atom),
|
|
446
|
+
Atom(id=ligand_id, residue_index=1, atom_name=ligand_atom)
|
|
447
|
+
]
|
|
448
|
+
)
|
|
449
|
+
constraints.append(bond_constraint)
|
|
450
|
+
|
|
451
|
+
request = PredictionRequest(
|
|
452
|
+
polymers=[polymer],
|
|
453
|
+
ligands=[ligand],
|
|
454
|
+
constraints=constraints,
|
|
455
|
+
recycling_steps=recycling_steps,
|
|
456
|
+
sampling_steps=sampling_steps
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
return await self.predict(request, **kwargs)
|
|
460
|
+
|
|
461
|
+
async def predict_dna_protein_complex(
|
|
462
|
+
self,
|
|
463
|
+
protein_sequences: List[str],
|
|
464
|
+
dna_sequences: List[str],
|
|
465
|
+
protein_ids: Optional[List[str]] = None,
|
|
466
|
+
dna_ids: Optional[List[str]] = None,
|
|
467
|
+
recycling_steps: int = 3,
|
|
468
|
+
sampling_steps: int = 50,
|
|
469
|
+
concatenate_msas: bool = False,
|
|
470
|
+
**kwargs
|
|
471
|
+
) -> PredictionResponse:
|
|
472
|
+
"""
|
|
473
|
+
Predict DNA-protein complex structure.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
protein_sequences: List of protein sequences
|
|
477
|
+
dna_sequences: List of DNA sequences
|
|
478
|
+
protein_ids: List of protein identifiers (default: A, B, ...)
|
|
479
|
+
dna_ids: List of DNA identifiers (default: C, D, ...)
|
|
480
|
+
recycling_steps: Number of recycling steps
|
|
481
|
+
sampling_steps: Number of sampling steps
|
|
482
|
+
concatenate_msas: Whether to concatenate MSAs
|
|
483
|
+
**kwargs: Additional parameters for predict()
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Prediction response
|
|
487
|
+
"""
|
|
488
|
+
if not protein_ids:
|
|
489
|
+
protein_ids = [chr(65 + i) for i in range(len(protein_sequences))] # A, B, C...
|
|
490
|
+
|
|
491
|
+
if not dna_ids:
|
|
492
|
+
start_idx = len(protein_sequences)
|
|
493
|
+
dna_ids = [chr(65 + start_idx + i) for i in range(len(dna_sequences))]
|
|
494
|
+
|
|
495
|
+
polymers = []
|
|
496
|
+
|
|
497
|
+
# Add proteins
|
|
498
|
+
for seq, pid in zip(protein_sequences, protein_ids):
|
|
499
|
+
polymers.append(Polymer(
|
|
500
|
+
id=pid,
|
|
501
|
+
molecule_type="protein",
|
|
502
|
+
sequence=seq
|
|
503
|
+
))
|
|
504
|
+
|
|
505
|
+
# Add DNA
|
|
506
|
+
for seq, did in zip(dna_sequences, dna_ids):
|
|
507
|
+
polymers.append(Polymer(
|
|
508
|
+
id=did,
|
|
509
|
+
molecule_type="dna",
|
|
510
|
+
sequence=seq
|
|
511
|
+
))
|
|
512
|
+
|
|
513
|
+
request = PredictionRequest(
|
|
514
|
+
polymers=polymers,
|
|
515
|
+
recycling_steps=recycling_steps,
|
|
516
|
+
sampling_steps=sampling_steps,
|
|
517
|
+
concatenate_msas=concatenate_msas
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return await self.predict(request, **kwargs)
|
|
521
|
+
|
|
522
|
+
async def predict_with_advanced_parameters(
|
|
523
|
+
self,
|
|
524
|
+
polymers: List[Polymer],
|
|
525
|
+
ligands: Optional[List[Ligand]] = None,
|
|
526
|
+
constraints: Optional[List[Union[PocketConstraint, BondConstraint]]] = None,
|
|
527
|
+
recycling_steps: int = 3,
|
|
528
|
+
sampling_steps: int = 50,
|
|
529
|
+
diffusion_samples: int = 1,
|
|
530
|
+
step_scale: float = 1.638,
|
|
531
|
+
without_potentials: bool = False,
|
|
532
|
+
concatenate_msas: bool = False,
|
|
533
|
+
**kwargs
|
|
534
|
+
) -> PredictionResponse:
|
|
535
|
+
"""
|
|
536
|
+
Predict structure with full control over all advanced parameters.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
polymers: List of polymers (proteins, DNA, RNA)
|
|
540
|
+
ligands: Optional list of ligands
|
|
541
|
+
constraints: Optional list of constraints
|
|
542
|
+
recycling_steps: Number of recycling steps (1-6)
|
|
543
|
+
sampling_steps: Number of sampling steps (10-1000)
|
|
544
|
+
diffusion_samples: Number of diffusion samples (1-5)
|
|
545
|
+
step_scale: Step scale for diffusion sampling (0.5-5.0)
|
|
546
|
+
without_potentials: Whether to run without potentials
|
|
547
|
+
concatenate_msas: Whether to concatenate MSAs
|
|
548
|
+
**kwargs: Additional parameters for predict()
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Prediction response
|
|
552
|
+
"""
|
|
553
|
+
request = PredictionRequest(
|
|
554
|
+
polymers=polymers,
|
|
555
|
+
ligands=ligands,
|
|
556
|
+
constraints=constraints,
|
|
557
|
+
recycling_steps=recycling_steps,
|
|
558
|
+
sampling_steps=sampling_steps,
|
|
559
|
+
diffusion_samples=diffusion_samples,
|
|
560
|
+
step_scale=step_scale,
|
|
561
|
+
without_potentials=without_potentials,
|
|
562
|
+
concatenate_msas=concatenate_msas
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
return await self.predict(request, **kwargs)
|
|
566
|
+
|
|
567
|
+
async def predict_from_yaml_config(
|
|
568
|
+
self,
|
|
569
|
+
yaml_config: Union[str, Path, YAMLConfig],
|
|
570
|
+
msa_dir: Optional[Path] = None,
|
|
571
|
+
save_structures: bool = True,
|
|
572
|
+
output_dir: Optional[Path] = None,
|
|
573
|
+
progress_callback: Optional[callable] = None,
|
|
574
|
+
recycling_steps: Optional[int] = None,
|
|
575
|
+
sampling_steps: Optional[int] = None,
|
|
576
|
+
diffusion_samples: Optional[int] = None,
|
|
577
|
+
step_scale: Optional[float] = None,
|
|
578
|
+
without_potentials: Optional[bool] = None,
|
|
579
|
+
concatenate_msas: Optional[bool] = None,
|
|
580
|
+
**kwargs
|
|
581
|
+
) -> PredictionResponse:
|
|
582
|
+
"""
|
|
583
|
+
Predict structure from YAML configuration file (official Boltz format).
|
|
584
|
+
|
|
585
|
+
This method supports the official Boltz YAML configuration format as used
|
|
586
|
+
in the original Boltz repository examples.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
yaml_config: YAML configuration (file path, string content, or YAMLConfig object)
|
|
590
|
+
msa_dir: Directory containing MSA files referenced in YAML
|
|
591
|
+
save_structures: Whether to save structures to files
|
|
592
|
+
output_dir: Directory to save structures
|
|
593
|
+
progress_callback: Optional callback for progress updates
|
|
594
|
+
recycling_steps: Override recycling steps parameter
|
|
595
|
+
sampling_steps: Override sampling steps parameter
|
|
596
|
+
diffusion_samples: Override diffusion samples parameter
|
|
597
|
+
step_scale: Override step scale parameter
|
|
598
|
+
without_potentials: Override without potentials parameter
|
|
599
|
+
concatenate_msas: Override concatenate MSAs parameter
|
|
600
|
+
**kwargs: Additional parameters for predict()
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Prediction response
|
|
604
|
+
|
|
605
|
+
Example YAML format:
|
|
606
|
+
version: 1
|
|
607
|
+
sequences:
|
|
608
|
+
- protein:
|
|
609
|
+
id: A
|
|
610
|
+
sequence: "MKTVRQERLK..."
|
|
611
|
+
msa: "protein_A.a3m" # optional
|
|
612
|
+
- ligand:
|
|
613
|
+
id: B
|
|
614
|
+
smiles: "CC(=O)O"
|
|
615
|
+
properties: # optional
|
|
616
|
+
affinity:
|
|
617
|
+
binder: B
|
|
618
|
+
"""
|
|
619
|
+
# Parse YAML config
|
|
620
|
+
if isinstance(yaml_config, YAMLConfig):
|
|
621
|
+
config = yaml_config
|
|
622
|
+
else:
|
|
623
|
+
if isinstance(yaml_config, (str, Path)):
|
|
624
|
+
yaml_path = Path(yaml_config)
|
|
625
|
+
if yaml_path.exists():
|
|
626
|
+
# Load from file
|
|
627
|
+
yaml_content = yaml_path.read_text()
|
|
628
|
+
yaml_data = yaml.safe_load(yaml_content)
|
|
629
|
+
config_dir = yaml_path.parent
|
|
630
|
+
else:
|
|
631
|
+
# Treat as YAML string content
|
|
632
|
+
yaml_data = yaml.safe_load(yaml_config)
|
|
633
|
+
config_dir = Path.cwd()
|
|
634
|
+
else:
|
|
635
|
+
raise ValueError("yaml_config must be a file path, YAML string, or YAMLConfig object")
|
|
636
|
+
|
|
637
|
+
config = YAMLConfig(**yaml_data)
|
|
638
|
+
|
|
639
|
+
# Convert to PredictionRequest
|
|
640
|
+
request = config.to_prediction_request()
|
|
641
|
+
|
|
642
|
+
# Override parameters if provided
|
|
643
|
+
if recycling_steps is not None:
|
|
644
|
+
request.recycling_steps = recycling_steps
|
|
645
|
+
if sampling_steps is not None:
|
|
646
|
+
request.sampling_steps = sampling_steps
|
|
647
|
+
if diffusion_samples is not None:
|
|
648
|
+
request.diffusion_samples = diffusion_samples
|
|
649
|
+
if step_scale is not None:
|
|
650
|
+
request.step_scale = step_scale
|
|
651
|
+
if without_potentials is not None:
|
|
652
|
+
request.without_potentials = without_potentials
|
|
653
|
+
if concatenate_msas is not None:
|
|
654
|
+
request.concatenate_msas = concatenate_msas
|
|
655
|
+
|
|
656
|
+
# Handle MSA files
|
|
657
|
+
if msa_dir is None:
|
|
658
|
+
msa_dir = config_dir if 'config_dir' in locals() else Path.cwd()
|
|
659
|
+
|
|
660
|
+
# Load MSA files for proteins that reference them
|
|
661
|
+
for i, seq in enumerate(config.sequences):
|
|
662
|
+
if seq.protein and seq.protein.msa and seq.protein.msa != "empty":
|
|
663
|
+
msa_path = msa_dir / seq.protein.msa
|
|
664
|
+
if msa_path.exists():
|
|
665
|
+
msa_content = msa_path.read_text()
|
|
666
|
+
# Determine format from extension
|
|
667
|
+
format_map = {
|
|
668
|
+
'.a3m': 'a3m',
|
|
669
|
+
'.sto': 'sto',
|
|
670
|
+
'.fasta': 'fasta',
|
|
671
|
+
'.csv': 'csv'
|
|
672
|
+
}
|
|
673
|
+
format_type = format_map.get(msa_path.suffix.lower(), 'a3m')
|
|
674
|
+
|
|
675
|
+
msa_record = AlignmentFileRecord(
|
|
676
|
+
alignment=msa_content,
|
|
677
|
+
format=format_type,
|
|
678
|
+
rank=0
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Update the corresponding polymer with MSA
|
|
682
|
+
polymer_idx = sum(1 for s in config.sequences[:i] if s.protein)
|
|
683
|
+
if polymer_idx < len(request.polymers):
|
|
684
|
+
request.polymers[polymer_idx].msa = [msa_record]
|
|
685
|
+
else:
|
|
686
|
+
self.console.print(f"⚠️ MSA file not found: {msa_path}", style="yellow")
|
|
687
|
+
|
|
688
|
+
return await self.predict(
|
|
689
|
+
request,
|
|
690
|
+
save_structures=save_structures,
|
|
691
|
+
output_dir=output_dir,
|
|
692
|
+
progress_callback=progress_callback,
|
|
693
|
+
**kwargs
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
async def predict_from_yaml_file(
|
|
697
|
+
self,
|
|
698
|
+
yaml_file: Union[str, Path],
|
|
699
|
+
**kwargs
|
|
700
|
+
) -> PredictionResponse:
|
|
701
|
+
"""
|
|
702
|
+
Predict structure from YAML configuration file.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
yaml_file: Path to YAML configuration file
|
|
706
|
+
**kwargs: Additional parameters for predict()
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
Prediction response
|
|
710
|
+
"""
|
|
711
|
+
yaml_path = Path(yaml_file)
|
|
712
|
+
if not yaml_path.exists():
|
|
713
|
+
raise FileNotFoundError(f"YAML file not found: {yaml_path}")
|
|
714
|
+
|
|
715
|
+
# Set msa_dir to yaml parent directory only if not already provided
|
|
716
|
+
if 'msa_dir' not in kwargs:
|
|
717
|
+
kwargs['msa_dir'] = yaml_path.parent
|
|
718
|
+
|
|
719
|
+
return await self.predict_from_yaml_config(
|
|
720
|
+
yaml_path,
|
|
721
|
+
**kwargs
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
def create_yaml_config(
|
|
725
|
+
self,
|
|
726
|
+
proteins: Optional[List[Tuple[str, str, Optional[str]]]] = None,
|
|
727
|
+
ligands: Optional[List[Tuple[str, str]]] = None,
|
|
728
|
+
predict_affinity: bool = False,
|
|
729
|
+
binder_id: Optional[str] = None
|
|
730
|
+
) -> YAMLConfig:
|
|
731
|
+
"""
|
|
732
|
+
Create a YAML configuration object programmatically.
|
|
733
|
+
|
|
734
|
+
Args:
|
|
735
|
+
proteins: List of (id, sequence, msa_file) tuples
|
|
736
|
+
ligands: List of (id, smiles) tuples
|
|
737
|
+
predict_affinity: Whether to predict binding affinity
|
|
738
|
+
binder_id: ID of the binding molecule for affinity prediction
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
YAMLConfig object
|
|
742
|
+
|
|
743
|
+
Example:
|
|
744
|
+
config = client.create_yaml_config(
|
|
745
|
+
proteins=[("A", "MKTVRQERLK...", None)],
|
|
746
|
+
ligands=[("B", "CC(=O)O")],
|
|
747
|
+
predict_affinity=True,
|
|
748
|
+
binder_id="B"
|
|
749
|
+
)
|
|
750
|
+
"""
|
|
751
|
+
from .models import YAMLProtein, YAMLLigand, YAMLSequence, YAMLAffinity, YAMLProperties
|
|
752
|
+
|
|
753
|
+
sequences = []
|
|
754
|
+
|
|
755
|
+
# Add proteins
|
|
756
|
+
if proteins:
|
|
757
|
+
for protein_id, sequence, msa_file in proteins:
|
|
758
|
+
protein = YAMLProtein(
|
|
759
|
+
id=protein_id,
|
|
760
|
+
sequence=sequence,
|
|
761
|
+
msa=msa_file
|
|
762
|
+
)
|
|
763
|
+
sequences.append(YAMLSequence(protein=protein))
|
|
764
|
+
|
|
765
|
+
# Add ligands
|
|
766
|
+
if ligands:
|
|
767
|
+
for ligand_id, smiles in ligands:
|
|
768
|
+
ligand = YAMLLigand(
|
|
769
|
+
id=ligand_id,
|
|
770
|
+
smiles=smiles
|
|
771
|
+
)
|
|
772
|
+
sequences.append(YAMLSequence(ligand=ligand))
|
|
773
|
+
|
|
774
|
+
# Add properties
|
|
775
|
+
properties = None
|
|
776
|
+
if predict_affinity:
|
|
777
|
+
if not binder_id:
|
|
778
|
+
raise ValueError("binder_id must be specified when predict_affinity=True")
|
|
779
|
+
properties = YAMLProperties(
|
|
780
|
+
affinity=YAMLAffinity(binder=binder_id)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
return YAMLConfig(
|
|
784
|
+
version=1,
|
|
785
|
+
sequences=sequences,
|
|
786
|
+
properties=properties
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
def save_yaml_config(
|
|
790
|
+
self,
|
|
791
|
+
config: YAMLConfig,
|
|
792
|
+
output_path: Union[str, Path]
|
|
793
|
+
) -> Path:
|
|
794
|
+
"""
|
|
795
|
+
Save YAML configuration to file.
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
config: YAMLConfig object
|
|
799
|
+
output_path: Output file path
|
|
800
|
+
|
|
801
|
+
Returns:
|
|
802
|
+
Path to saved file
|
|
803
|
+
"""
|
|
804
|
+
output_path = Path(output_path)
|
|
805
|
+
|
|
806
|
+
# Convert to dict and save as YAML
|
|
807
|
+
config_dict = config.dict(exclude_none=True)
|
|
808
|
+
|
|
809
|
+
with open(output_path, 'w') as f:
|
|
810
|
+
yaml.dump(config_dict, f, default_flow_style=False, sort_keys=False)
|
|
811
|
+
|
|
812
|
+
return output_path
|
|
813
|
+
|
|
814
|
+
async def _save_structures(
|
|
815
|
+
self,
|
|
816
|
+
response: PredictionResponse,
|
|
817
|
+
output_dir: Path,
|
|
818
|
+
progress_callback: Optional[callable] = None
|
|
819
|
+
) -> List[Path]:
|
|
820
|
+
"""Save prediction structures to files."""
|
|
821
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
822
|
+
saved_files = []
|
|
823
|
+
|
|
824
|
+
for i, structure in enumerate(response.structures):
|
|
825
|
+
# Save structure file
|
|
826
|
+
if structure.format.lower() == 'mmcif':
|
|
827
|
+
structure_file = output_dir / f"structure_{i}.cif"
|
|
828
|
+
else:
|
|
829
|
+
structure_file = output_dir / f"structure_{i}.pdb"
|
|
830
|
+
|
|
831
|
+
structure_file.write_text(structure.structure)
|
|
832
|
+
saved_files.append(structure_file)
|
|
833
|
+
|
|
834
|
+
if progress_callback:
|
|
835
|
+
progress_callback(f"Saved structure to {structure_file}")
|
|
836
|
+
|
|
837
|
+
# Save metadata
|
|
838
|
+
metadata = {
|
|
839
|
+
"confidence_scores": response.confidence_scores,
|
|
840
|
+
"metrics": response.metrics,
|
|
841
|
+
"timestamp": datetime.now().isoformat()
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
metadata_file = output_dir / "prediction_metadata.json"
|
|
845
|
+
metadata_file.write_text(json.dumps(metadata, indent=2))
|
|
846
|
+
saved_files.append(metadata_file)
|
|
847
|
+
|
|
848
|
+
if progress_callback:
|
|
849
|
+
progress_callback(f"Saved metadata to {metadata_file}")
|
|
850
|
+
|
|
851
|
+
return saved_files
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
class Boltz2SyncClient:
|
|
855
|
+
"""
|
|
856
|
+
Synchronous wrapper for the Boltz-2 client.
|
|
857
|
+
|
|
858
|
+
Provides the same functionality as Boltz2Client but with synchronous methods.
|
|
859
|
+
Supports both local deployments and NVIDIA hosted endpoints.
|
|
860
|
+
"""
|
|
861
|
+
|
|
862
|
+
def __init__(
|
|
863
|
+
self,
|
|
864
|
+
base_url: str = "http://localhost:8000",
|
|
865
|
+
api_key: Optional[str] = None,
|
|
866
|
+
endpoint_type: str = EndpointType.LOCAL,
|
|
867
|
+
timeout: float = 300.0,
|
|
868
|
+
max_retries: int = 3,
|
|
869
|
+
retry_delay: float = 1.0,
|
|
870
|
+
poll_seconds: int = 10,
|
|
871
|
+
console: Optional[Console] = None
|
|
872
|
+
):
|
|
873
|
+
"""Initialize the synchronous client."""
|
|
874
|
+
self._async_client = Boltz2Client(
|
|
875
|
+
base_url=base_url,
|
|
876
|
+
api_key=api_key,
|
|
877
|
+
endpoint_type=endpoint_type,
|
|
878
|
+
timeout=timeout,
|
|
879
|
+
max_retries=max_retries,
|
|
880
|
+
retry_delay=retry_delay,
|
|
881
|
+
poll_seconds=poll_seconds,
|
|
882
|
+
console=console
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
@property
|
|
886
|
+
def base_url(self) -> str:
|
|
887
|
+
"""Get the base URL."""
|
|
888
|
+
return self._async_client.base_url
|
|
889
|
+
|
|
890
|
+
@property
|
|
891
|
+
def timeout(self) -> float:
|
|
892
|
+
"""Get the timeout value."""
|
|
893
|
+
return self._async_client.timeout
|
|
894
|
+
|
|
895
|
+
def health_check(self) -> HealthStatus:
|
|
896
|
+
"""Check the health status of the Boltz-2 service."""
|
|
897
|
+
return asyncio.run(self._async_client.health_check())
|
|
898
|
+
|
|
899
|
+
def get_service_metadata(self) -> ServiceMetadata:
|
|
900
|
+
"""Get service metadata and model information."""
|
|
901
|
+
return asyncio.run(self._async_client.get_service_metadata())
|
|
902
|
+
|
|
903
|
+
def predict(self, request: PredictionRequest, **kwargs) -> PredictionResponse:
|
|
904
|
+
"""Make a structure prediction request."""
|
|
905
|
+
return asyncio.run(self._async_client.predict(request, **kwargs))
|
|
906
|
+
|
|
907
|
+
def predict_protein_structure(self, **kwargs) -> PredictionResponse:
|
|
908
|
+
"""Predict protein structure."""
|
|
909
|
+
return asyncio.run(self._async_client.predict_protein_structure(**kwargs))
|
|
910
|
+
|
|
911
|
+
def predict_protein_ligand_complex(self, **kwargs) -> PredictionResponse:
|
|
912
|
+
"""Predict protein-ligand complex structure."""
|
|
913
|
+
return asyncio.run(self._async_client.predict_protein_ligand_complex(**kwargs))
|
|
914
|
+
|
|
915
|
+
def predict_covalent_complex(self, **kwargs) -> PredictionResponse:
|
|
916
|
+
"""Predict covalent protein-ligand complex."""
|
|
917
|
+
return asyncio.run(self._async_client.predict_covalent_complex(**kwargs))
|
|
918
|
+
|
|
919
|
+
def predict_dna_protein_complex(self, **kwargs) -> PredictionResponse:
|
|
920
|
+
"""Predict DNA-protein complex structure."""
|
|
921
|
+
return asyncio.run(self._async_client.predict_dna_protein_complex(**kwargs))
|
|
922
|
+
|
|
923
|
+
def predict_with_advanced_parameters(self, **kwargs) -> PredictionResponse:
|
|
924
|
+
"""Make prediction with full parameter control."""
|
|
925
|
+
return asyncio.run(self._async_client.predict_with_advanced_parameters(**kwargs))
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
# Convenience functions for quick predictions
|
|
929
|
+
async def predict_protein(
|
|
930
|
+
sequence: str,
|
|
931
|
+
base_url: str = "http://localhost:8000",
|
|
932
|
+
api_key: Optional[str] = None,
|
|
933
|
+
endpoint_type: str = EndpointType.LOCAL,
|
|
934
|
+
**kwargs
|
|
935
|
+
) -> PredictionResponse:
|
|
936
|
+
"""Quick protein structure prediction."""
|
|
937
|
+
client = Boltz2Client(
|
|
938
|
+
base_url=base_url,
|
|
939
|
+
api_key=api_key,
|
|
940
|
+
endpoint_type=endpoint_type
|
|
941
|
+
)
|
|
942
|
+
return await client.predict_protein_structure(sequence=sequence, **kwargs)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
async def predict_protein_ligand(
|
|
946
|
+
protein_sequence: str,
|
|
947
|
+
ligand_smiles: str,
|
|
948
|
+
base_url: str = "http://localhost:8000",
|
|
949
|
+
api_key: Optional[str] = None,
|
|
950
|
+
endpoint_type: str = EndpointType.LOCAL,
|
|
951
|
+
**kwargs
|
|
952
|
+
) -> PredictionResponse:
|
|
953
|
+
"""Quick protein-ligand complex prediction."""
|
|
954
|
+
client = Boltz2Client(
|
|
955
|
+
base_url=base_url,
|
|
956
|
+
api_key=api_key,
|
|
957
|
+
endpoint_type=endpoint_type
|
|
958
|
+
)
|
|
959
|
+
return await client.predict_protein_ligand_complex(
|
|
960
|
+
protein_sequence=protein_sequence,
|
|
961
|
+
ligand_smiles=ligand_smiles,
|
|
962
|
+
**kwargs
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
async def predict_covalent(
|
|
967
|
+
protein_sequence: str,
|
|
968
|
+
ligand_ccd: str,
|
|
969
|
+
covalent_bonds: List[Tuple[int, str, str]],
|
|
970
|
+
base_url: str = "http://localhost:8000",
|
|
971
|
+
api_key: Optional[str] = None,
|
|
972
|
+
endpoint_type: str = EndpointType.LOCAL,
|
|
973
|
+
**kwargs
|
|
974
|
+
) -> PredictionResponse:
|
|
975
|
+
"""Quick covalent complex prediction."""
|
|
976
|
+
client = Boltz2Client(
|
|
977
|
+
base_url=base_url,
|
|
978
|
+
api_key=api_key,
|
|
979
|
+
endpoint_type=endpoint_type
|
|
980
|
+
)
|
|
981
|
+
return await client.predict_covalent_complex(
|
|
982
|
+
protein_sequence=protein_sequence,
|
|
983
|
+
ligand_ccd=ligand_ccd,
|
|
984
|
+
covalent_bonds=covalent_bonds,
|
|
985
|
+
**kwargs
|
|
986
|
+
)
|