Skip to content

Commit 7afe788

Browse files
committed
fix: update input file implementation
1 parent 8157ecc commit 7afe788

3 files changed

Lines changed: 107 additions & 64 deletions

File tree

examples/science/af3-slurm/examples/simple_ipynb_launcher/Ipynb.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,29 @@ Begin by uploading your input JSON file, which contains the data required by Alp
66

77
<img src="adm/upload_file.png" alt="try to upload file under the alphafold folder" width="400">
88

9-
When specifying the input file name (e.g., `fold_input.json`) during the configuration of your data pipeline or inference, it's crucial to reference the correct **relative file path**. Your files should be organized like this:
9+
Ensure your input JSON file is placed in the correct location **relative to your Jupyter Notebook file**. Your project directory should be structured like this:
1010

1111
```code
1212
alphafold/
13-
├── <your_input_file>.json
13+
├── <uploaded_file>.json
14+
├── fold_input.json # Example input file (provided)
1415
└── slurm-rest-api-notebook.ipynb
1516
```
1617

17-
The input JSON file should be in the `alphafold` folder.
18+
When configuring your data pipeline or inference process, use the correct **relative file path to reference the input JSON**. We provide an example input file of data pipeline process named `fold_input.json` to help you get started quickly.
1819

19-
Usage:
20+
Since both the notebook and the input file are in the same directory (`alphafold/`), you can specify the file like this in your notebook code:
2021

2122
```python
2223
input_file= "fold_input.json"
2324
```
2425

26+
If you're uploading your own file, simply replace the `input_file` variable with the name of your uploaded file:
27+
28+
```python
29+
input_file = "<uploaded_file>.json"
30+
```
31+
2532
**2. Running the Setup Cells (including System Configuration and SLURM Initialization):**
2633

2734
Once the input file is uploaded, execute all the cells in the notebook that are responsible for setting up the environment, installing dependencies, and loading necessary functions. Ensure all these setup cells run without errors before proceeding to the next section.

examples/science/af3-slurm/examples/simple_ipynb_launcher/adm/slurm-rest-api-notebook.ipynb.j2

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@
205205
"datapipeline_partition_name = \"{{ default_datapipeline_partition_name }}\"\n",
206206
"\n",
207207
"# Assign `input_file` with datapipeline input file path\n",
208-
"input_file= \"\" # Replace with your input file path (relative path from the jupyter notebook directory)\n"
208+
"datapipeline_input_file= \"\" # Replace with your input file path (relative path from the jupyter notebook directory)\n"
209209
]
210210
},
211211
{
@@ -217,7 +217,7 @@
217217
"# Submit a datapipeline job\n",
218218
"datapipeline_submit_job_response = client.submit_data_pipeline_job(\n",
219219
" partition_name=datapipeline_partition_name, \n",
220-
" input_file=input_file\n",
220+
" input_file=datapipeline_input_file\n",
221221
")\n",
222222
"print(\"Submit Job Response:\", json.dumps(datapipeline_submit_job_response,indent=4))"
223223
]
@@ -252,10 +252,16 @@
252252
"# Make sure to use the correct partition name that listed on AF3 System setting section\n",
253253
"inference_partition_name = \"{{ default_inference_partition_name }}\"\n",
254254
"\n",
255-
"# Assign `input_file` with inference input file path\n",
255+
"# Assign 'inference_input_file' with inference input file path.\n",
256256
"# You can use the output from the datapipeline job as input for inference\n",
257257
"# or you can use your own input file relative path from the jupyter notebook directory\n",
258-
"inference_input_file = datapipeline_submit_job_response[\"datapipeline_output_path\"]\n",
258+
"# In this example, we will use the output from the datapipeline job.\n",
259+
"# Datapipeline job could returns multiple output files in case the input file is a JSON file with multiple protein sequences\n",
260+
"# The output path is a list of dictionaries, each containing the path to the output file\n",
261+
"# The first output path is used as input for inference\n",
262+
"print(\"[INFO] Extracting output paths from inference job response...\")\n",
263+
"print(\"[INFO] Output count:\", len(datapipeline_submit_job_response[\"output_path_list\"]))\n",
264+
"inference_input_file = datapipeline_submit_job_response[\"output_path_list\"][0][\"output_path\"]\n",
259265
"\n",
260266
"# Assign `enable_unified_memory` with True or False\n",
261267
"# No changes required to run default settings\n",
@@ -310,6 +316,22 @@
310316
")"
311317
]
312318
},
319+
{
320+
"cell_type": "code",
321+
"execution_count": null,
322+
"metadata": {},
323+
"outputs": [],
324+
"source": [
325+
"# Extract the information of output paths from the inference job response\n",
326+
"# This is the path where the inference results are stored\n",
327+
"# You can use this path to read the output files such as CIF and JSON files\n",
328+
"# The output path is a list of dictionaries, each containing the path to the output file\n",
329+
"# In this example, we will use the first output path\n",
330+
"print(\"[INFO] Extracting output paths from inference job response...\")\n",
331+
"print(\"[INFO] Output count:\", len(inference_submit_job_response[\"output_path_list\"]))\n",
332+
"inference_output_path_info = inference_submit_job_response[\"output_path_list\"][0]"
333+
]
334+
},
313335
{
314336
"cell_type": "code",
315337
"execution_count": null,
@@ -318,8 +340,8 @@
318340
"source": [
319341
"# Load CIF file\n",
320342
"# Note: Adjust the file path to your actual CIF file\n",
321-
"cif_file_path = inference_submit_job_response[\"cif_output_path\"]\n",
322-
"structure, cif_content = read_cif_file(cif_file_path)\n",
343+
"model_cif_output_path = inference_output_path_info[\"model_cif\"]\n",
344+
"structure, cif_content = read_cif_file(model_cif_output_path)\n",
323345
"show_structure_3d(cif_content)"
324346
]
325347
},
@@ -331,8 +353,8 @@
331353
"source": [
332354
"# Load and plot PAE matrix\n",
333355
"# Note: Adjust the file path to your actual PAE JSON file\n",
334-
"pae_json_file_path = inference_submit_job_response[\"confidences_output_path\"]\n",
335-
"pae, token_chain_ids = extract_pae_from_json(pae_json_file_path)\n",
356+
"confidence_json_file_path = inference_output_path_info[\"confidences\"]\n",
357+
"pae, token_chain_ids = extract_pae_from_json(confidence_json_file_path)\n",
336358
"plot_pae_matrix(pae,token_chain_ids)"
337359
]
338360
},
@@ -344,7 +366,7 @@
344366
"source": [
345367
"# Display summary of confidence metrices\n",
346368
"# Note: Adjust the file path to your actual PAE Summary JSON file\n",
347-
"summary_confidences_file = inference_submit_job_response[\"summary_confidences_output_path\"]\n",
369+
"summary_confidences_file = inference_output_path_info[\"summary_confidences\"]\n",
348370
"summary_data = extract_summary_confidences_obj(summary_confidences_file)\n",
349371
"\n",
350372
"# Get chain ID list\n",

examples/science/af3-slurm/examples/simple_ipynb_launcher/adm/slurm_client.py.j2

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,50 +27,55 @@ def is_valid_path(input_path: str, expected_prefix: str) -> bool:
2727
return os.path.isabs(input_path) and input_path.startswith(expected_prefix)
2828

2929

30-
def sanitize_name(name: str) -> str:
30+
def sanitise_name(name: str) -> str:
3131
"""Returns sanitised version of the name that can be used as a filename."""
3232
lower_spaceless_name = name.lower().replace(' ', '_')
3333
allowed_chars = set(string.ascii_lowercase + string.digits + '_-.')
3434
return ''.join(l for l in lower_spaceless_name if l in allowed_chars)
3535

3636

37-
def retrieve_sanitised_input_file_name(input_file: str) -> Optional[str]:
37+
def retrieve_sanitised_input_file_name(input_file: str) -> Optional[List[str]]:
3838
"""Retrieves a sanitised version of the input file name."""
3939
if not os.path.exists(input_file):
4040
raise FileNotFoundError(f"Input file '{input_file}' does not exist.")
4141
try:
42-
with open(input_file, 'r') as f:
42+
with open(input_file, 'r', encoding="utf-8") as f:
4343
parser = ijson.items(f, '')
4444
try:
4545
root = next(parser)
4646
except StopIteration:
4747
raise ValueError("Empty JSON file.")
4848

49-
# Case 1: root is a list
49+
if not isinstance(root, list) and not isinstance(root, dict):
50+
raise ValueError(
51+
"[ERROR] Top-level JSON must be a list or a dictionary.")
52+
53+
items: Optional[List[Dict]] = None
5054
if isinstance(root, list):
51-
if len(root) != 1:
55+
if len(root) <= 0:
5256
raise ValueError(
53-
"[ERROR] The AlphaFold Server format is not supported: the list must contain exactly one dictionary item.")
54-
item = root[0]
55-
if not isinstance(item, dict):
56-
raise ValueError(
57-
"[ERROR] Item in Alphafold Server item must be a dictionary.")
57+
"[ERROR] The AlphaFold Server format is not supported: the list must contain at least one dictionary item.")
58+
items = root
5859

5960
# Case 2: root is a dict
6061
elif isinstance(root, dict):
61-
item = root
62-
else:
63-
raise ValueError(
64-
"[ERROR] Top-level JSON must be a list or a dictionary.")
62+
items = [root]
6563

6664
# Validate and extract 'name'
67-
name = item.get('name')
68-
if not isinstance(name, str):
69-
raise ValueError(
70-
"[ERROR] 'name' key must exist and be a string.")
71-
72-
return sanitize_name(name)
73-
65+
sanitised_name_list = []
66+
for item in items:
67+
if not isinstance(item, dict):
68+
raise ValueError(
69+
"Item in Alphafold Format must be a dictionary.")
70+
if 'name' not in item:
71+
raise ValueError(
72+
"'name' key must exist in the dictionary.")
73+
name = item.get('name')
74+
if not isinstance(name, str) or not name:
75+
raise ValueError(
76+
"'name' key must exist, contains value and be a string.")
77+
sanitised_name_list.append(sanitise_name(name))
78+
return sanitised_name_list
7479
except FileNotFoundError:
7580
print(f"[ERROR] Input file '{input_file}' not found.")
7681
raise FileNotFoundError(
@@ -226,21 +231,6 @@ class AF3SlurmClient:
226231
output_dir = os.path.join(
227232
f"{job_type}_result", file_name, f"{timestamp}")
228233

229-
inference_model_cif_output_path: Optional[str] = None
230-
inference_summary_confidences_output_path: Optional[str] = None
231-
inference_confidences_output_path: Optional[str] = None
232-
233-
sanitised_input_name: Optional[str] = retrieve_sanitised_input_file_name(
234-
input_file=input_file
235-
)
236-
237-
inference_model_cif_output_path = os.path.join(
238-
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_model.cif")
239-
inference_summary_confidences_output_path = os.path.join(
240-
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_summary_confidences.json")
241-
inference_confidences_output_path = os.path.join(
242-
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_confidences.json")
243-
244234
job_config = {
245235
"account": self.af3_config["service_user"],
246236
"tasks": 1,
@@ -264,16 +254,31 @@ class AF3SlurmClient:
264254
response = self.submit_base_job(
265255
job_config, job_type, script_options, input_file, output_dir)
266256

267-
response["cif_output_path"] = inference_model_cif_output_path
268-
response["summary_confidences_output_path"] = inference_summary_confidences_output_path
269-
response["confidences_output_path"] = inference_confidences_output_path
270-
print(
271-
f"[INFO] Inference model CIF output path : {inference_model_cif_output_path}")
272-
print(
273-
f"[INFO] Inference confidences output path : {inference_confidences_output_path}")
274-
print(
275-
f"[INFO] Inference summary confidences output path : {inference_summary_confidences_output_path}")
276-
257+
sanitised_input_name_list: Optional[List[str]] = retrieve_sanitised_input_file_name(
258+
input_file=input_file
259+
)
260+
output_path_list: List[Dict] = []
261+
for sanitised_input_name in sanitised_input_name_list:
262+
inference_model_cif_output_path = os.path.join(
263+
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_model.cif")
264+
inference_summary_confidences_output_path = os.path.join(
265+
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_summary_confidences.json")
266+
inference_confidences_output_path = os.path.join(
267+
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_confidences.json")
268+
output_path_list.append({
269+
"model_cif": inference_model_cif_output_path,
270+
"summary_confidences": inference_summary_confidences_output_path,
271+
"confidences": inference_confidences_output_path
272+
})
273+
274+
print(f"[INFO] Output path for input file: {sanitised_input_name}")
275+
print(
276+
f" ├── Inference model CIF output path : {inference_model_cif_output_path}")
277+
print(
278+
f" ├── Inference confidences output path : {inference_confidences_output_path}")
279+
print(
280+
f" └── Inference summary confidences output path : {inference_summary_confidences_output_path}")
281+
response["output_path_list"] = output_path_list
277282
return response
278283

279284
def submit_data_pipeline_job(self, input_file: str, partition_name: str, output_dir: Optional[str] = None) -> Optional[Dict]:
@@ -291,10 +296,6 @@ class AF3SlurmClient:
291296
f"{job_type}_result", file_name, f"{timestamp}"
292297
)
293298

294-
sanitised_input_name = retrieve_sanitised_input_file_name(input_file)
295-
datapipeline_output_file_path = os.path.join(
296-
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_data.json")
297-
298299
job_config = {
299300
"account": self.af3_config["service_user"],
300301
"tasks": 1,
@@ -316,7 +317,20 @@ class AF3SlurmClient:
316317
response = self.submit_base_job(
317318
job_config, job_type, script_options, input_file, output_dir
318319
)
319-
response["datapipeline_output_path"] = datapipeline_output_file_path
320+
sanitised_input_name_list = retrieve_sanitised_input_file_name(
321+
input_file)
322+
output_path_list: List[Dict] = []
323+
for sanitised_input_name in sanitised_input_name_list:
324+
datapipeline_output_file_path = os.path.join(
325+
output_dir, f"{sanitised_input_name}", f"{sanitised_input_name}_data.json")
326+
output_path_list.append({
327+
"output_path": datapipeline_output_file_path
328+
})
329+
print(f"[INFO] Output path for input file: {sanitised_input_name}")
330+
print(
331+
f" └── Datapipeline output path : {datapipeline_output_file_path}")
332+
333+
response["output_path_list"] = output_path_list
320334
print(
321335
f"[INFO] Datapipeline output path : {datapipeline_output_file_path}")
322336
return response

0 commit comments

Comments
 (0)