doragera commited on
Commit
2d4c6a2
·
2 Parent(s): bd823cc 1b5b3f2

Merge pull request #121 from biggraph/feat/bionemo-nims

Browse files
examples/Generative drug screening.lynxkite.json CHANGED
The diff for this file is too large to render. See raw diff
 
lynxkite-app/web/package-lock.json CHANGED
@@ -18,11 +18,13 @@
18
  "@syncedstore/react": "^0.6.0",
19
  "@types/node": "^22.10.1",
20
  "@xyflow/react": "^12.3.5",
 
21
  "axios": "^1.8.2",
22
  "daisyui": "^4.12.20",
23
  "echarts": "^5.5.1",
24
  "fuse.js": "^7.0.0",
25
  "json-schema-to-typescript": "^15.0.3",
 
26
  "react": "^18.3.1",
27
  "react-dom": "^18.3.1",
28
  "react-markdown": "^9.0.1",
@@ -2366,6 +2368,22 @@
2366
  "d3-zoom": "^3.0.0"
2367
  }
2368
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2369
  "node_modules/abstract-leveldown": {
2370
  "version": "6.2.3",
2371
  "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
@@ -4187,6 +4205,12 @@
4187
  "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
4188
  "license": "MIT"
4189
  },
 
 
 
 
 
 
4190
  "node_modules/is-alphabetical": {
4191
  "version": "2.0.1",
4192
  "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -5465,8 +5489,7 @@
5465
  "version": "0.52.2",
5466
  "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
5467
  "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
5468
- "license": "MIT",
5469
- "peer": true
5470
  },
5471
  "node_modules/ms": {
5472
  "version": "2.1.3",
@@ -5518,6 +5541,15 @@
5518
  "dev": true,
5519
  "license": "MIT"
5520
  },
 
 
 
 
 
 
 
 
 
5521
  "node_modules/no-case": {
5522
  "version": "3.0.4",
5523
  "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -5649,6 +5681,12 @@
5649
  "integrity": "sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==",
5650
  "license": "MIT"
5651
  },
 
 
 
 
 
 
5652
  "node_modules/parent-module": {
5653
  "version": "1.0.1",
5654
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -7107,6 +7145,21 @@
7107
  "browserslist": ">= 4.21.0"
7108
  }
7109
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7110
  "node_modules/uri-js": {
7111
  "version": "4.4.1",
7112
  "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
 
18
  "@syncedstore/react": "^0.6.0",
19
  "@types/node": "^22.10.1",
20
  "@xyflow/react": "^12.3.5",
21
+ "3dmol": "^2.4.2",
22
  "axios": "^1.8.2",
23
  "daisyui": "^4.12.20",
24
  "echarts": "^5.5.1",
25
  "fuse.js": "^7.0.0",
26
  "json-schema-to-typescript": "^15.0.3",
27
+ "monaco-editor": "^0.52.2",
28
  "react": "^18.3.1",
29
  "react-dom": "^18.3.1",
30
  "react-markdown": "^9.0.1",
 
2368
  "d3-zoom": "^3.0.0"
2369
  }
2370
  },
2371
+ "node_modules/3dmol": {
2372
+ "version": "2.4.2",
2373
+ "resolved": "https://registry.npmjs.org/3dmol/-/3dmol-2.4.2.tgz",
2374
+ "integrity": "sha512-7R6uFaF5++s9EpO+R0a3bs6igi5idI+sgrVhtozaZlOGHg6oourYkAlAwYxWRaeBGlNcAd+oEIhbhJYhNxGyZA==",
2375
+ "license": "BSD-3-Clause",
2376
+ "dependencies": {
2377
+ "iobuffer": "^5.3.1",
2378
+ "netcdfjs": "^3.0.0",
2379
+ "pako": "^2.1.0",
2380
+ "upng-js": "^2.1.0"
2381
+ },
2382
+ "engines": {
2383
+ "node": ">=16.16.0",
2384
+ "npm": ">=8.11"
2385
+ }
2386
+ },
2387
  "node_modules/abstract-leveldown": {
2388
  "version": "6.2.3",
2389
  "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
 
4205
  "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
4206
  "license": "MIT"
4207
  },
4208
+ "node_modules/iobuffer": {
4209
+ "version": "5.4.0",
4210
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
4211
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
4212
+ "license": "MIT"
4213
+ },
4214
  "node_modules/is-alphabetical": {
4215
  "version": "2.0.1",
4216
  "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
 
5489
  "version": "0.52.2",
5490
  "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
5491
  "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
5492
+ "license": "MIT"
 
5493
  },
5494
  "node_modules/ms": {
5495
  "version": "2.1.3",
 
5541
  "dev": true,
5542
  "license": "MIT"
5543
  },
5544
+ "node_modules/netcdfjs": {
5545
+ "version": "3.0.0",
5546
+ "resolved": "https://registry.npmjs.org/netcdfjs/-/netcdfjs-3.0.0.tgz",
5547
+ "integrity": "sha512-LOvT8KkC308qtpUkcBPiCMBtii7ZQCN6LxcVheWgyUeZ6DQWcpSRFV9dcVXLj/2eHZ/bre9tV5HTH4Sf93vrFw==",
5548
+ "license": "MIT",
5549
+ "dependencies": {
5550
+ "iobuffer": "^5.3.2"
5551
+ }
5552
+ },
5553
  "node_modules/no-case": {
5554
  "version": "3.0.4",
5555
  "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
 
5681
  "integrity": "sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==",
5682
  "license": "MIT"
5683
  },
5684
+ "node_modules/pako": {
5685
+ "version": "2.1.0",
5686
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
5687
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
5688
+ "license": "(MIT AND Zlib)"
5689
+ },
5690
  "node_modules/parent-module": {
5691
  "version": "1.0.1",
5692
  "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
 
7145
  "browserslist": ">= 4.21.0"
7146
  }
7147
  },
7148
+ "node_modules/upng-js": {
7149
+ "version": "2.1.0",
7150
+ "resolved": "https://registry.npmjs.org/upng-js/-/upng-js-2.1.0.tgz",
7151
+ "integrity": "sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g==",
7152
+ "license": "MIT",
7153
+ "dependencies": {
7154
+ "pako": "^1.0.5"
7155
+ }
7156
+ },
7157
+ "node_modules/upng-js/node_modules/pako": {
7158
+ "version": "1.0.11",
7159
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
7160
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
7161
+ "license": "(MIT AND Zlib)"
7162
+ },
7163
  "node_modules/uri-js": {
7164
  "version": "4.4.1",
7165
  "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
lynxkite-app/web/package.json CHANGED
@@ -21,11 +21,13 @@
21
  "@syncedstore/react": "^0.6.0",
22
  "@types/node": "^22.10.1",
23
  "@xyflow/react": "^12.3.5",
 
24
  "axios": "^1.8.2",
25
  "daisyui": "^4.12.20",
26
  "echarts": "^5.5.1",
27
  "fuse.js": "^7.0.0",
28
  "json-schema-to-typescript": "^15.0.3",
 
29
  "react": "^18.3.1",
30
  "react-dom": "^18.3.1",
31
  "react-markdown": "^9.0.1",
 
21
  "@syncedstore/react": "^0.6.0",
22
  "@types/node": "^22.10.1",
23
  "@xyflow/react": "^12.3.5",
24
+ "3dmol": "^2.4.2",
25
  "axios": "^1.8.2",
26
  "daisyui": "^4.12.20",
27
  "echarts": "^5.5.1",
28
  "fuse.js": "^7.0.0",
29
  "json-schema-to-typescript": "^15.0.3",
30
+ "monaco-editor": "^0.52.2",
31
  "react": "^18.3.1",
32
  "react-dom": "^18.3.1",
33
  "react-markdown": "^9.0.1",
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -37,6 +37,7 @@ import { LynxKiteState } from "./LynxKiteState";
37
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
38
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
39
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
 
40
  import NodeWithParams from "./nodes/NodeWithParams";
41
  import NodeWithTableView from "./nodes/NodeWithTableView.tsx";
42
  import NodeWithVisualization from "./nodes/NodeWithVisualization.tsx";
@@ -175,6 +176,7 @@ function LynxKiteFlow() {
175
  image: NodeWithImage,
176
  table_view: NodeWithTableView,
177
  graph_creation_view: NodeWithGraphCreationView,
 
178
  }),
179
  [],
180
  );
 
37
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
38
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
39
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
40
+ import NodeWithMolecule from "./nodes/NodeWithMolecule.tsx";
41
  import NodeWithParams from "./nodes/NodeWithParams";
42
  import NodeWithTableView from "./nodes/NodeWithTableView.tsx";
43
  import NodeWithVisualization from "./nodes/NodeWithVisualization.tsx";
 
176
  image: NodeWithImage,
177
  table_view: NodeWithTableView,
178
  graph_creation_view: NodeWithGraphCreationView,
179
+ molecule: NodeWithMolecule,
180
  }),
181
  [],
182
  );
lynxkite-app/web/src/workspace/nodes/NodeWithMolecule.tsx ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, type CSSProperties } from "react";
2
+ import NodeWithParams from "./NodeWithParams";
3
+
4
+ const NodeWithMolecule = (props: any) => {
5
+ const containerRef = React.useRef<HTMLDivElement>(null);
6
+ const viewerRef = React.useRef<any>(null);
7
+
8
+ useEffect(() => {
9
+ const config = props.data?.display?.value;
10
+ if (!config || !containerRef.current) return;
11
+ async function run() {
12
+ const $3Dmol = await import("3dmol");
13
+
14
+ try {
15
+ // Initialize viewer only once
16
+ if (!viewerRef.current) {
17
+ viewerRef.current = $3Dmol.createViewer(containerRef.current, {
18
+ backgroundColor: "white",
19
+ });
20
+ }
21
+
22
+ const viewer = viewerRef.current;
23
+
24
+ // Clear previous models
25
+ viewer.clear();
26
+
27
+ // Add new model and style it
28
+ viewer.addModel(config.data, config.format);
29
+ viewer.setStyle({}, { stick: {} });
30
+ viewer.zoomTo();
31
+ viewer.render();
32
+ } catch (error) {
33
+ console.error("Error rendering 3D molecule:", error);
34
+ }
35
+ }
36
+ run();
37
+ const resizeObserver = new ResizeObserver(() => {
38
+ viewerRef.current?.resize();
39
+ });
40
+
41
+ const observed = containerRef.current;
42
+ resizeObserver.observe(observed);
43
+ return () => {
44
+ resizeObserver.unobserve(observed);
45
+ if (viewerRef.current) {
46
+ viewerRef.current.clear();
47
+ }
48
+ };
49
+ }, [props.data?.display?.value]);
50
+
51
+ const nodeStyle: CSSProperties = {
52
+ display: "flex",
53
+ flexDirection: "column",
54
+ height: "100%",
55
+ };
56
+
57
+ const vizStyle: CSSProperties = {
58
+ flex: 1,
59
+ minHeight: "300px",
60
+ border: "1px solid #ddd",
61
+ borderRadius: "4px",
62
+ overflow: "hidden",
63
+ position: "relative",
64
+ };
65
+
66
+ return (
67
+ <NodeWithParams nodeStyle={nodeStyle} collapsed {...props}>
68
+ <div style={vizStyle} ref={containerRef} />
69
+ </NodeWithParams>
70
+ );
71
+ };
72
+
73
+ export default NodeWithMolecule;
lynxkite-bio/src/lynxkite_bio/nims.py CHANGED
@@ -1,107 +1,143 @@
1
  """Wrappers for BioNeMo NIMs."""
2
 
3
- from enum import Enum
4
  from lynxkite_graph_analytics import Bundle
5
  from lynxkite.core import ops
6
  import joblib
 
 
7
  import os
8
 
9
- NIM_URLS = os.environ.get("NIM_URLS", "http://localhost:8000").split(",")
10
 
11
  mem = joblib.Memory(".joblib-cache")
12
  ENV = "LynxKite Graph Analytics"
13
  op = ops.op_registration(ENV)
14
 
 
15
 
16
- class MSASearchTypes(Enum):
17
- ALPHAFOLD2 = "ALPHAFOLD2"
18
- ESM2 = "ESM2"
19
 
20
-
21
- class AlignmentFormats(Enum):
22
- FASTA = "fasta"
23
- A3M = "a3m"
24
- STOCKHOLM = "stockholm"
25
- CLUSTAL = "clustal"
26
- PDB = "pdb"
27
- PIR = "pir"
28
- MSF = "msf"
29
- TSV = "tsv"
 
 
 
 
 
 
 
 
30
 
31
 
32
  @op("MSA-search")
33
  @mem.cache
34
- def msa_search(
35
  bundle: Bundle,
36
  *,
37
  protein_table: str,
38
  protein_column: str,
39
  e_value: float = 0.0001,
40
  iterations: int = 1,
41
- search_type: MSASearchTypes = MSASearchTypes.ALPHAFOLD2,
42
- output_alignment_formats: list[AlignmentFormats] = [
43
- AlignmentFormats.FASTA,
44
- AlignmentFormats.A3M,
45
- ],
46
- databases: str = '["Uniref30_2302", "colabfold_envdb_202108", "PDB70_220313"]',
47
  ):
48
  bundle = bundle.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  return bundle
50
 
51
 
52
  @op("Query OpenFold2")
53
  @mem.cache
54
- def query_openfold2(
55
  bundle: Bundle,
56
  *,
57
  protein_table: str,
58
  protein_column: str,
59
  alignment_table: str,
60
  alignment_column: str,
61
- use_templates: bool = False,
62
- relaxed_prediction: bool = False,
63
- databases: str = '["Uniref30_2302", "colabfold_envdb_202108", "PDB70_220313"]',
64
  ):
65
  bundle = bundle.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  return bundle
67
 
68
 
69
- @op("View molecules", view="visualization")
70
- def view_molecules(
71
  bundle: Bundle,
72
  *,
73
  molecule_table: str,
74
  molecule_column: str,
75
- color="spectrum",
76
  ):
 
 
77
  return {
78
- "series": [
79
- {
80
- "type": "pie",
81
- "radius": ["40%", "70%"],
82
- "itemStyle": {
83
- "borderRadius": 10,
84
- "borderColor": "#fff",
85
- "borderWidth": 2,
86
- },
87
- "data": [
88
- {"value": 2, "name": "Hydrogen"},
89
- {"value": 1, "name": "Sulfur"},
90
- {"value": 4, "name": "Oxygen"},
91
- ],
92
- }
93
- ]
94
  }
95
 
96
 
97
- @op("Known drug")
98
- def known_drug(*, drug_name: str):
99
- return Bundle()
100
-
101
-
102
  @op("Query GenMol")
103
  @mem.cache
104
- def query_genmol(
105
  bundle: Bundle,
106
  *,
107
  molecule_table: str,
@@ -113,12 +149,26 @@ def query_genmol(
113
  scoring: str = "QED",
114
  ):
115
  bundle = bundle.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  return bundle
117
 
118
 
119
  @op("Query DiffDock")
120
  @mem.cache
121
- def query_diffdock(
122
  proteins: Bundle,
123
  ligands: Bundle,
124
  *,
@@ -126,8 +176,29 @@ def query_diffdock(
126
  protein_column: str,
127
  ligand_table: str,
128
  ligand_column: str,
 
129
  num_poses=10,
130
  time_divisions=20,
131
  num_steps=18,
132
  ):
133
- return proteins
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """Wrappers for BioNeMo NIMs."""
2
 
 
3
  from lynxkite_graph_analytics import Bundle
4
  from lynxkite.core import ops
5
  import joblib
6
+ import httpx
7
+ import pandas as pd
8
  import os
9
 
 
10
 
11
  mem = joblib.Memory(".joblib-cache")
12
  ENV = "LynxKite Graph Analytics"
13
  op = ops.op_registration(ENV)
14
 
15
+ key = os.getenv("NVCF_RUN_KEY")
16
 
 
 
 
17
 
18
+ async def query_bionemo_nim(
19
+ url: str,
20
+ payload: dict,
21
+ ):
22
+ headers = {
23
+ "Authorization": f"Bearer {key}",
24
+ "NVCF-POLL-SECONDS": "500",
25
+ "Content-Type": "application/json",
26
+ }
27
+ try:
28
+ print(f"Sending request to {url}")
29
+ async with httpx.AsyncClient(timeout=600) as client:
30
+ response = await client.post(url, json=payload, headers=headers)
31
+ print(f"Received response from {url}", response.status_code)
32
+ response.raise_for_status()
33
+ return response.json()
34
+ except httpx.RequestError as e:
35
+ raise ValueError(f"Query failed: {e}")
36
 
37
 
38
  @op("MSA-search")
39
  @mem.cache
40
+ async def msa_search(
41
  bundle: Bundle,
42
  *,
43
  protein_table: str,
44
  protein_column: str,
45
  e_value: float = 0.0001,
46
  iterations: int = 1,
47
+ search_type: str = "alphafold2",
48
+ output_alignment_formats: str = "a3m",
49
+ databases: str = "Uniref30_2302,colabfold_envdb_202108",
 
 
 
50
  ):
51
  bundle = bundle.copy()
52
+ bundle.dfs[protein_table]["alignments"] = None
53
+
54
+ formats = [format.strip() for format in output_alignment_formats.split(",")]
55
+ dbs = [db.strip() for db in databases.split(",")]
56
+
57
+ for idx, protein_sequence in enumerate(bundle.dfs[protein_table][protein_column]):
58
+ print(f"Processing protein {idx + 1}/{len(bundle.dfs[protein_table])}")
59
+
60
+ response = await query_bionemo_nim(
61
+ url="https://health.api.nvidia.com/v1/biology/colabfold/msa-search/predict",
62
+ payload={
63
+ "sequence": protein_sequence,
64
+ "e_value": e_value,
65
+ "iterations": iterations,
66
+ "search_type": search_type,
67
+ "output_alignment_formats": formats,
68
+ "databases": dbs,
69
+ },
70
+ )
71
+
72
+ bundle.dfs[protein_table].at[idx, "alignments"] = response["alignments"]
73
+
74
  return bundle
75
 
76
 
77
  @op("Query OpenFold2")
78
  @mem.cache
79
+ async def query_openfold2(
80
  bundle: Bundle,
81
  *,
82
  protein_table: str,
83
  protein_column: str,
84
  alignment_table: str,
85
  alignment_column: str,
86
+ selected_models: str = "1,2",
87
+ relax_prediction: bool = False,
 
88
  ):
89
  bundle = bundle.copy()
90
+
91
+ bundle.dfs[protein_table]["folded_protein"] = None
92
+ selected_models_list = [int(model) for model in selected_models.split(",")]
93
+
94
+ for idx in range(len(bundle.dfs[protein_table])):
95
+ print(f"Processing protein {idx + 1}/{len(bundle.dfs[protein_table])}")
96
+
97
+ protein = bundle.dfs[protein_table][protein_column].iloc[idx]
98
+ alignments = bundle.dfs[alignment_table][alignment_column].iloc[idx]
99
+
100
+ response = await query_bionemo_nim(
101
+ url="https://health.api.nvidia.com/v1/biology/openfold/openfold2/predict-structure-from-msa-and-template",
102
+ payload={
103
+ "sequence": protein,
104
+ "alignments": alignments,
105
+ "selected_models": selected_models_list,
106
+ "relax_prediction": relax_prediction,
107
+ },
108
+ )
109
+
110
+ folded_protein = response["structures_in_ranked_order"].pop(0)["structure"]
111
+ bundle.dfs[protein_table].at[idx, "folded_protein"] = folded_protein
112
+
113
+ bundle.dfs["openfold"] = pd.DataFrame()
114
+
115
  return bundle
116
 
117
 
118
+ @op("View molecule", view="molecule")
119
+ def view_molecule(
120
  bundle: Bundle,
121
  *,
122
  molecule_table: str,
123
  molecule_column: str,
124
+ row_index: int = 0,
125
  ):
126
+ molecule_data = bundle.dfs[molecule_table][molecule_column].iloc[row_index]
127
+
128
  return {
129
+ "data": molecule_data,
130
+ "format": "pdb"
131
+ if molecule_data.startswith("ATOM")
132
+ else "sdf"
133
+ if molecule_data.startswith("CTfile")
134
+ else "smiles",
 
 
 
 
 
 
 
 
 
 
135
  }
136
 
137
 
 
 
 
 
 
138
  @op("Query GenMol")
139
  @mem.cache
140
+ async def query_genmol(
141
  bundle: Bundle,
142
  *,
143
  molecule_table: str,
 
149
  scoring: str = "QED",
150
  ):
151
  bundle = bundle.copy()
152
+
153
+ response = await query_bionemo_nim(
154
+ url="https://health.api.nvidia.com/v1/biology/nvidia/genmol/generate",
155
+ payload={
156
+ "smiles": bundle.dfs[molecule_table][molecule_column].iloc[0],
157
+ "num_molecules": num_molecules,
158
+ "temperature": temperature,
159
+ "noise": noise,
160
+ "step_size": step_size,
161
+ "scoring": scoring,
162
+ },
163
+ )
164
+ generated_ligands = "\n".join([v["smiles"] for v in response["molecules"]])
165
+ bundle.dfs[molecule_table]["ligands"] = generated_ligands
166
  return bundle
167
 
168
 
169
  @op("Query DiffDock")
170
  @mem.cache
171
+ async def query_diffdock(
172
  proteins: Bundle,
173
  ligands: Bundle,
174
  *,
 
176
  protein_column: str,
177
  ligand_table: str,
178
  ligand_column: str,
179
+ ligand_file_type: str = "txt",
180
  num_poses=10,
181
  time_divisions=20,
182
  num_steps=18,
183
  ):
184
+ response = await query_bionemo_nim(
185
+ url="https://health.api.nvidia.com/v1/biology/mit/diffdock",
186
+ payload={
187
+ "protein": proteins.dfs[protein_table][protein_column].iloc[0],
188
+ "ligand": ligands.dfs[ligand_table][ligand_column].iloc[0],
189
+ "ligand_file_type": ligand_file_type,
190
+ "num_poses": num_poses,
191
+ "time_divisions": time_divisions,
192
+ "num_steps": num_steps,
193
+ },
194
+ )
195
+ bundle = Bundle()
196
+ bundle.dfs["diffdock_table"] = pd.DataFrame()
197
+ bundle.dfs["diffdock_table"]["protein"] = [response["protein"]] * len(response["status"])
198
+ bundle.dfs["diffdock_table"]["ligand"] = [response["ligand"]] * len(response["status"])
199
+ bundle.dfs["diffdock_table"]["trajectory"] = response["trajectory"]
200
+ bundle.dfs["diffdock_table"]["ligand_positions"] = response["ligand_positions"]
201
+ bundle.dfs["diffdock_table"]["position_confidence"] = response["position_confidence"]
202
+ bundle.dfs["diffdock_table"]["status"] = response["status"]
203
+
204
+ return bundle
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -180,6 +180,7 @@ class Op(BaseConfig):
180
  "table_view",
181
  "graph_creation_view",
182
  "image",
 
183
  ]:
184
  # If the operation is some kind of visualization, we use the output as the
185
  # value to display by default.
 
180
  "table_view",
181
  "graph_creation_view",
182
  "image",
183
+ "molecule",
184
  ]:
185
  # If the operation is some kind of visualization, we use the output as the
186
  # value to display by default.