k3-node 1.0.0__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.
Files changed (459) hide show
  1. k3_node/__init__.py +122 -0
  2. k3_node/applications/__init__.py +17 -0
  3. k3_node/applications/bio/__init__.py +21 -0
  4. k3_node/applications/chemistry/__init__.py +155 -0
  5. k3_node/applications/materials/__init__.py +127 -0
  6. k3_node/applications/materials/basis.py +449 -0
  7. k3_node/applications/materials/chgnet.py +360 -0
  8. k3_node/applications/materials/core.py +351 -0
  9. k3_node/applications/materials/grace.py +246 -0
  10. k3_node/applications/materials/io.py +230 -0
  11. k3_node/applications/materials/m3gnet.py +462 -0
  12. k3_node/applications/materials/megnet.py +395 -0
  13. k3_node/applications/materials/qet.py +220 -0
  14. k3_node/applications/materials/readout.py +235 -0
  15. k3_node/applications/materials/so3net.py +234 -0
  16. k3_node/applications/materials/tensornet.py +381 -0
  17. k3_node/applications/materials/test_materials.py +167 -0
  18. k3_node/applications/materials/wrappers.py +95 -0
  19. k3_node/data/__init__.py +47 -0
  20. k3_node/data/batch.py +102 -0
  21. k3_node/data/collate.py +282 -0
  22. k3_node/data/data.py +532 -0
  23. k3_node/data/database.py +154 -0
  24. k3_node/data/dataset.py +182 -0
  25. k3_node/data/download.py +49 -0
  26. k3_node/data/extract.py +45 -0
  27. k3_node/data/feature_store.py +70 -0
  28. k3_node/data/graph_store.py +92 -0
  29. k3_node/data/hetero_data.py +374 -0
  30. k3_node/data/hypergraph_data.py +59 -0
  31. k3_node/data/in_memory_dataset.py +177 -0
  32. k3_node/data/makedirs.py +7 -0
  33. k3_node/data/on_disk_dataset.py +77 -0
  34. k3_node/data/separate.py +115 -0
  35. k3_node/data/storage.py +593 -0
  36. k3_node/data/temporal.py +154 -0
  37. k3_node/data/test_batch.py +67 -0
  38. k3_node/data/test_data.py +68 -0
  39. k3_node/data/test_dataset_and_stores.py +111 -0
  40. k3_node/data/test_hetero_data.py +33 -0
  41. k3_node/data/test_temporal_and_hyper.py +32 -0
  42. k3_node/data/view.py +43 -0
  43. k3_node/datasets/__init__.py +88 -0
  44. k3_node/datasets/actor.py +101 -0
  45. k3_node/datasets/airports.py +84 -0
  46. k3_node/datasets/amazon.py +66 -0
  47. k3_node/datasets/ba2motif_dataset.py +73 -0
  48. k3_node/datasets/ba_shapes.py +81 -0
  49. k3_node/datasets/bitcoin_otc.py +77 -0
  50. k3_node/datasets/citation_full.py +81 -0
  51. k3_node/datasets/coauthor.py +66 -0
  52. k3_node/datasets/dblp.py +106 -0
  53. k3_node/datasets/digits.py +63 -0
  54. k3_node/datasets/email_eu_core.py +60 -0
  55. k3_node/datasets/entities.py +158 -0
  56. k3_node/datasets/explainer_dataset.py +101 -0
  57. k3_node/datasets/facebook.py +51 -0
  58. k3_node/datasets/fake.py +256 -0
  59. k3_node/datasets/freebase.py +90 -0
  60. k3_node/datasets/geometric_shapes.py +69 -0
  61. k3_node/datasets/github.py +51 -0
  62. k3_node/datasets/graph_generator/__init__.py +6 -0
  63. k3_node/datasets/graph_generator/ba_graph.py +20 -0
  64. k3_node/datasets/graph_generator/base.py +29 -0
  65. k3_node/datasets/graph_generator/er_graph.py +21 -0
  66. k3_node/datasets/icews.py +58 -0
  67. k3_node/datasets/imdb.py +96 -0
  68. k3_node/datasets/jodie.py +56 -0
  69. k3_node/datasets/karate.py +56 -0
  70. k3_node/datasets/lastfm_asia.py +51 -0
  71. k3_node/datasets/mesh_correspondence.py +50 -0
  72. k3_node/datasets/molecule_net.py +148 -0
  73. k3_node/datasets/motif_generator/__init__.py +7 -0
  74. k3_node/datasets/motif_generator/base.py +29 -0
  75. k3_node/datasets/motif_generator/custom.py +17 -0
  76. k3_node/datasets/motif_generator/cycle.py +25 -0
  77. k3_node/datasets/motif_generator/house.py +27 -0
  78. k3_node/datasets/movielens.py +55 -0
  79. k3_node/datasets/planetoid.py +137 -0
  80. k3_node/datasets/polblogs.py +63 -0
  81. k3_node/datasets/ppi.py +189 -0
  82. k3_node/datasets/qm7.py +65 -0
  83. k3_node/datasets/qm9.py +132 -0
  84. k3_node/datasets/reddit.py +121 -0
  85. k3_node/datasets/sbm_dataset.py +165 -0
  86. k3_node/datasets/seal.py +74 -0
  87. k3_node/datasets/shape_scenes.py +92 -0
  88. k3_node/datasets/test_datasets.py +322 -0
  89. k3_node/datasets/tu_dataset.py +131 -0
  90. k3_node/datasets/twitch.py +66 -0
  91. k3_node/datasets/webkb.py +102 -0
  92. k3_node/datasets/wikics.py +85 -0
  93. k3_node/datasets/word_net.py +184 -0
  94. k3_node/etl/__init__.py +37 -0
  95. k3_node/etl/encoders.py +248 -0
  96. k3_node/etl/graph_builders.py +270 -0
  97. k3_node/etl/relational_to_graph.py +201 -0
  98. k3_node/etl/table_to_graph.py +244 -0
  99. k3_node/etl/test_etl.py +318 -0
  100. k3_node/export/__init__.py +15 -0
  101. k3_node/export/cross_backend.py +172 -0
  102. k3_node/export/onnx_exporter.py +190 -0
  103. k3_node/export/runtime.py +254 -0
  104. k3_node/export/tensorrt_exporter.py +201 -0
  105. k3_node/export/test_export.py +337 -0
  106. k3_node/export/tflite_exporter.py +112 -0
  107. k3_node/hub/__init__.py +29 -0
  108. k3_node/hub/dataset_hub.py +242 -0
  109. k3_node/hub/hub_mixin.py +599 -0
  110. k3_node/hub/model_card.py +133 -0
  111. k3_node/hub/test_hub.py +419 -0
  112. k3_node/io/__init__.py +22 -0
  113. k3_node/io/fs.py +117 -0
  114. k3_node/io/npz.py +45 -0
  115. k3_node/io/off.py +29 -0
  116. k3_node/io/planetoid.py +98 -0
  117. k3_node/io/tu.py +137 -0
  118. k3_node/io/txt_array.py +58 -0
  119. k3_node/layers/__init__.py +14 -0
  120. k3_node/layers/aggr/__init__.py +70 -0
  121. k3_node/layers/aggr/attention.py +77 -0
  122. k3_node/layers/aggr/base.py +403 -0
  123. k3_node/layers/aggr/basic.py +412 -0
  124. k3_node/layers/aggr/deep_sets.py +65 -0
  125. k3_node/layers/aggr/deepsets.py +29 -0
  126. k3_node/layers/aggr/equilibrium.py +107 -0
  127. k3_node/layers/aggr/fused.py +43 -0
  128. k3_node/layers/aggr/gmt.py +89 -0
  129. k3_node/layers/aggr/gru.py +58 -0
  130. k3_node/layers/aggr/lcm.py +143 -0
  131. k3_node/layers/aggr/lstm.py +58 -0
  132. k3_node/layers/aggr/mlp.py +75 -0
  133. k3_node/layers/aggr/multi.py +154 -0
  134. k3_node/layers/aggr/patch_transformer.py +137 -0
  135. k3_node/layers/aggr/quantile.py +125 -0
  136. k3_node/layers/aggr/resolver.py +68 -0
  137. k3_node/layers/aggr/scaler.py +133 -0
  138. k3_node/layers/aggr/set2set.py +87 -0
  139. k3_node/layers/aggr/set_transformer.py +107 -0
  140. k3_node/layers/aggr/sort.py +68 -0
  141. k3_node/layers/aggr/test_aggr.py +337 -0
  142. k3_node/layers/aggr/utils.py +210 -0
  143. k3_node/layers/aggr/variance_preserving.py +54 -0
  144. k3_node/layers/attention/__init__.py +5 -0
  145. k3_node/layers/attention/pair_attention.py +448 -0
  146. k3_node/layers/attention/performer.py +187 -0
  147. k3_node/layers/attention/polynormer.py +160 -0
  148. k3_node/layers/attention/qformer.py +143 -0
  149. k3_node/layers/attention/sgformer.py +106 -0
  150. k3_node/layers/attention/test_attention.py +68 -0
  151. k3_node/layers/attention/test_pair_attention.py +91 -0
  152. k3_node/layers/conv/__init__.py +149 -0
  153. k3_node/layers/conv/agnn_conv.py +120 -0
  154. k3_node/layers/conv/antisymmetric_conv.py +94 -0
  155. k3_node/layers/conv/appnp.py +105 -0
  156. k3_node/layers/conv/appnp_conv.py +157 -0
  157. k3_node/layers/conv/arma_conv.py +231 -0
  158. k3_node/layers/conv/cg_conv.py +92 -0
  159. k3_node/layers/conv/cheb_conv.py +137 -0
  160. k3_node/layers/conv/cluster_gcn_conv.py +102 -0
  161. k3_node/layers/conv/conv.py +100 -0
  162. k3_node/layers/conv/crystal_conv.py +140 -0
  163. k3_node/layers/conv/cugraph.py +84 -0
  164. k3_node/layers/conv/diffusion_conv.py +144 -0
  165. k3_node/layers/conv/dir_gnn_conv.py +93 -0
  166. k3_node/layers/conv/dna_conv.py +192 -0
  167. k3_node/layers/conv/edge_conv.py +107 -0
  168. k3_node/layers/conv/eg_conv.py +155 -0
  169. k3_node/layers/conv/fa_conv.py +107 -0
  170. k3_node/layers/conv/feast_conv.py +126 -0
  171. k3_node/layers/conv/film_conv.py +143 -0
  172. k3_node/layers/conv/gat_conv.py +244 -0
  173. k3_node/layers/conv/gated_graph_conv.py +136 -0
  174. k3_node/layers/conv/gatv2_conv.py +205 -0
  175. k3_node/layers/conv/gcn.py +144 -0
  176. k3_node/layers/conv/gcn2_conv.py +126 -0
  177. k3_node/layers/conv/gcn_conv.py +135 -0
  178. k3_node/layers/conv/gen_conv.py +163 -0
  179. k3_node/layers/conv/general_conv.py +218 -0
  180. k3_node/layers/conv/gin_conv.py +218 -0
  181. k3_node/layers/conv/gmm_conv.py +172 -0
  182. k3_node/layers/conv/gps_conv.py +153 -0
  183. k3_node/layers/conv/graph_attention.py +262 -0
  184. k3_node/layers/conv/graph_conv.py +84 -0
  185. k3_node/layers/conv/gravnet_conv.py +93 -0
  186. k3_node/layers/conv/han_conv.py +175 -0
  187. k3_node/layers/conv/heat_conv.py +131 -0
  188. k3_node/layers/conv/hetero_conv.py +128 -0
  189. k3_node/layers/conv/hgt_conv.py +218 -0
  190. k3_node/layers/conv/hypergraph_conv.py +182 -0
  191. k3_node/layers/conv/le_conv.py +81 -0
  192. k3_node/layers/conv/lg_conv.py +58 -0
  193. k3_node/layers/conv/meshcnn_conv.py +84 -0
  194. k3_node/layers/conv/message_passing.py +451 -0
  195. k3_node/layers/conv/mf_conv.py +95 -0
  196. k3_node/layers/conv/mixhop_conv.py +108 -0
  197. k3_node/layers/conv/nn_conv.py +110 -0
  198. k3_node/layers/conv/pan_conv.py +100 -0
  199. k3_node/layers/conv/pdn_conv.py +109 -0
  200. k3_node/layers/conv/pna_conv.py +177 -0
  201. k3_node/layers/conv/point_conv.py +101 -0
  202. k3_node/layers/conv/point_gnn_conv.py +90 -0
  203. k3_node/layers/conv/point_transformer_conv.py +132 -0
  204. k3_node/layers/conv/ppf_conv.py +135 -0
  205. k3_node/layers/conv/ppnp.py +89 -0
  206. k3_node/layers/conv/res_gated_graph_conv.py +126 -0
  207. k3_node/layers/conv/rgat_conv.py +251 -0
  208. k3_node/layers/conv/rgcn_conv.py +321 -0
  209. k3_node/layers/conv/sage_conv.py +154 -0
  210. k3_node/layers/conv/sg_conv.py +96 -0
  211. k3_node/layers/conv/signed_conv.py +100 -0
  212. k3_node/layers/conv/simple_conv.py +75 -0
  213. k3_node/layers/conv/spline_conv.py +182 -0
  214. k3_node/layers/conv/ssg_conv.py +101 -0
  215. k3_node/layers/conv/supergat_conv.py +195 -0
  216. k3_node/layers/conv/tag_conv.py +98 -0
  217. k3_node/layers/conv/test_backend_consistency.py +164 -0
  218. k3_node/layers/conv/test_conv.py +176 -0
  219. k3_node/layers/conv/test_conv_pyg.py +566 -0
  220. k3_node/layers/conv/transformer_conv.py +168 -0
  221. k3_node/layers/conv/utils.py +403 -0
  222. k3_node/layers/conv/wl_conv.py +151 -0
  223. k3_node/layers/conv/x_conv.py +187 -0
  224. k3_node/layers/dense/__init__.py +40 -0
  225. k3_node/layers/dense/dense_gat_conv.py +149 -0
  226. k3_node/layers/dense/dense_gcn_conv.py +117 -0
  227. k3_node/layers/dense/dense_gin_conv.py +88 -0
  228. k3_node/layers/dense/dense_graph_conv.py +95 -0
  229. k3_node/layers/dense/dense_sage_conv.py +85 -0
  230. k3_node/layers/dense/diff_pool.py +76 -0
  231. k3_node/layers/dense/dmon_pool.py +223 -0
  232. k3_node/layers/dense/linear.py +327 -0
  233. k3_node/layers/dense/mincut_pool.py +92 -0
  234. k3_node/layers/dense/test_dense.py +377 -0
  235. k3_node/layers/functional/__init__.py +13 -0
  236. k3_node/layers/functional/bro.py +49 -0
  237. k3_node/layers/functional/edge_dropout.py +55 -0
  238. k3_node/layers/functional/gini.py +44 -0
  239. k3_node/layers/functional/test_functional.py +34 -0
  240. k3_node/layers/kge/__init__.py +17 -0
  241. k3_node/layers/kge/base.py +255 -0
  242. k3_node/layers/kge/complex.py +98 -0
  243. k3_node/layers/kge/distmult.py +79 -0
  244. k3_node/layers/kge/loader.py +50 -0
  245. k3_node/layers/kge/rotate.py +103 -0
  246. k3_node/layers/kge/test_kge.py +76 -0
  247. k3_node/layers/kge/transe.py +96 -0
  248. k3_node/layers/norm/__init__.py +23 -0
  249. k3_node/layers/norm/batch_norm.py +328 -0
  250. k3_node/layers/norm/diff_group_norm.py +141 -0
  251. k3_node/layers/norm/graph_norm.py +105 -0
  252. k3_node/layers/norm/graph_size_norm.py +57 -0
  253. k3_node/layers/norm/instance_norm.py +163 -0
  254. k3_node/layers/norm/layer_norm.py +245 -0
  255. k3_node/layers/norm/mean_subtraction_norm.py +57 -0
  256. k3_node/layers/norm/msg_norm.py +58 -0
  257. k3_node/layers/norm/pair_norm.py +94 -0
  258. k3_node/layers/norm/test_norm.py +275 -0
  259. k3_node/layers/pool/__init__.py +83 -0
  260. k3_node/layers/pool/approx_knn.py +101 -0
  261. k3_node/layers/pool/asap.py +173 -0
  262. k3_node/layers/pool/avg_pool.py +165 -0
  263. k3_node/layers/pool/cluster_pool.py +168 -0
  264. k3_node/layers/pool/connect/__init__.py +10 -0
  265. k3_node/layers/pool/connect/base.py +103 -0
  266. k3_node/layers/pool/connect/filter_edges.py +113 -0
  267. k3_node/layers/pool/consecutive.py +30 -0
  268. k3_node/layers/pool/decimation.py +48 -0
  269. k3_node/layers/pool/edge_pool.py +189 -0
  270. k3_node/layers/pool/glob.py +139 -0
  271. k3_node/layers/pool/graclus.py +66 -0
  272. k3_node/layers/pool/knn.py +253 -0
  273. k3_node/layers/pool/max_pool.py +159 -0
  274. k3_node/layers/pool/mem_pool.py +145 -0
  275. k3_node/layers/pool/pan_pool.py +144 -0
  276. k3_node/layers/pool/point_cloud.py +212 -0
  277. k3_node/layers/pool/pool.py +119 -0
  278. k3_node/layers/pool/sag_pool.py +174 -0
  279. k3_node/layers/pool/select/__init__.py +10 -0
  280. k3_node/layers/pool/select/base.py +112 -0
  281. k3_node/layers/pool/select/topk.py +206 -0
  282. k3_node/layers/pool/test_pool.py +456 -0
  283. k3_node/layers/pool/topk_pool.py +103 -0
  284. k3_node/layers/pool/voxel_grid.py +70 -0
  285. k3_node/layers/unpool/__init__.py +9 -0
  286. k3_node/layers/unpool/knn_interpolate.py +57 -0
  287. k3_node/layers/unpool/test_unpool.py +31 -0
  288. k3_node/loader/__init__.py +62 -0
  289. k3_node/loader/base.py +69 -0
  290. k3_node/loader/cache.py +68 -0
  291. k3_node/loader/cluster.py +127 -0
  292. k3_node/loader/data_list_loader.py +45 -0
  293. k3_node/loader/dataloader.py +117 -0
  294. k3_node/loader/dense_data_loader.py +62 -0
  295. k3_node/loader/dynamic_batch_sampler.py +93 -0
  296. k3_node/loader/graph_saint.py +188 -0
  297. k3_node/loader/hgt_loader.py +90 -0
  298. k3_node/loader/imbalanced_sampler.py +87 -0
  299. k3_node/loader/keras_dataset.py +334 -0
  300. k3_node/loader/link_loader.py +179 -0
  301. k3_node/loader/link_neighbor_loader.py +202 -0
  302. k3_node/loader/mixin.py +190 -0
  303. k3_node/loader/neighbor_loader.py +159 -0
  304. k3_node/loader/neighbor_sampler.py +167 -0
  305. k3_node/loader/node_loader.py +185 -0
  306. k3_node/loader/prefetch.py +115 -0
  307. k3_node/loader/random_node_loader.py +89 -0
  308. k3_node/loader/sampler_utils.py +499 -0
  309. k3_node/loader/shadow.py +115 -0
  310. k3_node/loader/temporal_dataloader.py +98 -0
  311. k3_node/loader/test_dataloader.py +113 -0
  312. k3_node/loader/test_keras_dataset.py +221 -0
  313. k3_node/loader/test_neighbor_loader.py +122 -0
  314. k3_node/loader/test_sampler_utils.py +82 -0
  315. k3_node/loader/test_samplers.py +96 -0
  316. k3_node/loader/test_subgraph_loaders.py +89 -0
  317. k3_node/loader/utils.py +232 -0
  318. k3_node/loader/zip_loader.py +88 -0
  319. k3_node/metrics.py +94 -0
  320. k3_node/models/__init__.py +424 -0
  321. k3_node/models/attentive_fp.py +232 -0
  322. k3_node/models/attract_repel.py +108 -0
  323. k3_node/models/autoencoder.py +318 -0
  324. k3_node/models/basic_gnn.py +443 -0
  325. k3_node/models/bio/__init__.py +4 -0
  326. k3_node/models/captum.py +52 -0
  327. k3_node/models/chemistry/__init__.py +4 -0
  328. k3_node/models/correct_and_smooth.py +146 -0
  329. k3_node/models/deep_graph_infomax.py +113 -0
  330. k3_node/models/deepgcn.py +121 -0
  331. k3_node/models/dimenet.py +737 -0
  332. k3_node/models/dimenet_utils.py +153 -0
  333. k3_node/models/gnnff.py +263 -0
  334. k3_node/models/gps_model.py +1122 -0
  335. k3_node/models/gpse.py +638 -0
  336. k3_node/models/graph_unet.py +199 -0
  337. k3_node/models/graphmae2.py +954 -0
  338. k3_node/models/graphormer.py +1258 -0
  339. k3_node/models/graphormer_3d.py +868 -0
  340. k3_node/models/grover.py +1066 -0
  341. k3_node/models/jumping_knowledge.py +200 -0
  342. k3_node/models/label_prop.py +110 -0
  343. k3_node/models/lightgcn.py +171 -0
  344. k3_node/models/linkx.py +181 -0
  345. k3_node/models/lpformer.py +404 -0
  346. k3_node/models/mask_label.py +114 -0
  347. k3_node/models/materials/__init__.py +33 -0
  348. k3_node/models/meta.py +133 -0
  349. k3_node/models/metapath2vec.py +234 -0
  350. k3_node/models/mlp.py +264 -0
  351. k3_node/models/mole_bert.py +379 -0
  352. k3_node/models/neural_fingerprint.py +95 -0
  353. k3_node/models/node2vec.py +213 -0
  354. k3_node/models/pmlp.py +157 -0
  355. k3_node/models/polynormer.py +229 -0
  356. k3_node/models/rect.py +93 -0
  357. k3_node/models/renet.py +221 -0
  358. k3_node/models/rev_gnn.py +128 -0
  359. k3_node/models/schnet.py +484 -0
  360. k3_node/models/sgformer.py +195 -0
  361. k3_node/models/signed_gcn.py +185 -0
  362. k3_node/models/test_attentive_fp.py +32 -0
  363. k3_node/models/test_attract_repel.py +33 -0
  364. k3_node/models/test_autoencoder.py +119 -0
  365. k3_node/models/test_basic_gnn.py +102 -0
  366. k3_node/models/test_correct_and_smooth.py +40 -0
  367. k3_node/models/test_deep_graph_infomax.py +68 -0
  368. k3_node/models/test_deepgcn.py +21 -0
  369. k3_node/models/test_dimenet.py +86 -0
  370. k3_node/models/test_domain_apis.py +138 -0
  371. k3_node/models/test_gnnff.py +24 -0
  372. k3_node/models/test_gps_model.py +271 -0
  373. k3_node/models/test_gpse.py +34 -0
  374. k3_node/models/test_graph_unet.py +26 -0
  375. k3_node/models/test_graphmae2.py +226 -0
  376. k3_node/models/test_graphormer.py +233 -0
  377. k3_node/models/test_graphormer3d.py +163 -0
  378. k3_node/models/test_grover.py +287 -0
  379. k3_node/models/test_jumping_knowledge.py +129 -0
  380. k3_node/models/test_label_prop.py +37 -0
  381. k3_node/models/test_lightgcn.py +38 -0
  382. k3_node/models/test_linkx.py +31 -0
  383. k3_node/models/test_lpformer.py +22 -0
  384. k3_node/models/test_mask_label.py +90 -0
  385. k3_node/models/test_meta.py +159 -0
  386. k3_node/models/test_metapath2vec.py +45 -0
  387. k3_node/models/test_mlp.py +62 -0
  388. k3_node/models/test_mole_bert.py +164 -0
  389. k3_node/models/test_neural_fingerprint.py +13 -0
  390. k3_node/models/test_node2vec.py +57 -0
  391. k3_node/models/test_pmlp.py +81 -0
  392. k3_node/models/test_polynormer.py +104 -0
  393. k3_node/models/test_rect.py +23 -0
  394. k3_node/models/test_renet.py +32 -0
  395. k3_node/models/test_rev_gnn.py +24 -0
  396. k3_node/models/test_schnet.py +43 -0
  397. k3_node/models/test_sgformer.py +48 -0
  398. k3_node/models/test_signed_gcn.py +28 -0
  399. k3_node/models/test_tgn.py +77 -0
  400. k3_node/models/test_unimol.py +179 -0
  401. k3_node/models/test_unimol2.py +114 -0
  402. k3_node/models/test_unimol_plus.py +131 -0
  403. k3_node/models/test_visnet.py +44 -0
  404. k3_node/models/tgn.py +382 -0
  405. k3_node/models/unimol.py +1156 -0
  406. k3_node/models/unimol2.py +616 -0
  407. k3_node/models/unimol_docking_v2.py +301 -0
  408. k3_node/models/unimol_plus.py +456 -0
  409. k3_node/models/utils.py +97 -0
  410. k3_node/models/visnet.py +759 -0
  411. k3_node/ops/__init__.py +4 -0
  412. k3_node/ops/conv.py +56 -0
  413. k3_node/ops/creation.py +43 -0
  414. k3_node/ops/graph.py +27 -0
  415. k3_node/ops/host.py +41 -0
  416. k3_node/ops/matmul.py +49 -0
  417. k3_node/ops/numpy.py +24 -0
  418. k3_node/ops/segment.py +54 -0
  419. k3_node/ops/sparse.py +51 -0
  420. k3_node/rag/__init__.py +49 -0
  421. k3_node/rag/encoders.py +312 -0
  422. k3_node/rag/pipeline.py +192 -0
  423. k3_node/rag/projector.py +184 -0
  424. k3_node/rag/subgraph.py +270 -0
  425. k3_node/rag/test_rag.py +347 -0
  426. k3_node/rag/verbalizer.py +162 -0
  427. k3_node/tasks/__init__.py +19 -0
  428. k3_node/tasks/backbone_resolver.py +125 -0
  429. k3_node/tasks/base.py +67 -0
  430. k3_node/tasks/graph_classification.py +270 -0
  431. k3_node/tasks/graph_regression.py +228 -0
  432. k3_node/tasks/link_prediction.py +306 -0
  433. k3_node/tasks/node_classification.py +194 -0
  434. k3_node/tasks/node_regression.py +138 -0
  435. k3_node/tasks/test_tasks.py +319 -0
  436. k3_node/test_docstring_examples.py +106 -0
  437. k3_node/test_training_forwarding.py +116 -0
  438. k3_node/training.py +115 -0
  439. k3_node/transforms/__init__.py +166 -0
  440. k3_node/transforms/base_transform.py +32 -0
  441. k3_node/transforms/compose.py +58 -0
  442. k3_node/transforms/general.py +676 -0
  443. k3_node/transforms/graph.py +1070 -0
  444. k3_node/transforms/spatial.py +797 -0
  445. k3_node/transforms/test_random_link_split.py +45 -0
  446. k3_node/transforms/test_spatial_transforms.py +65 -0
  447. k3_node/transforms/test_transforms.py +253 -0
  448. k3_node/transforms/utils.py +102 -0
  449. k3_node/utils/__init__.py +5 -0
  450. k3_node/utils/backend_import.py +12 -0
  451. k3_node/utils/graph.py +286 -0
  452. k3_node/utils/keras.py +94 -0
  453. k3_node/utils/random.py +103 -0
  454. k3_node/utils/smiles.py +235 -0
  455. k3_node-1.0.0.dist-info/METADATA +284 -0
  456. k3_node-1.0.0.dist-info/RECORD +459 -0
  457. k3_node-1.0.0.dist-info/WHEEL +5 -0
  458. k3_node-1.0.0.dist-info/licenses/LICENSE +21 -0
  459. k3_node-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,189 @@
1
+ from typing import Callable, List, NamedTuple, Optional, Tuple
2
+ from keras import layers, ops
3
+ import numpy as np
4
+ from k3_node.ops.segment import segment_max, segment_sum
5
+
6
+
7
+ class UnpoolInfo(NamedTuple):
8
+ edge_index: any
9
+ cluster: any
10
+ batch: any
11
+ new_edge_score: any
12
+
13
+
14
+ class EdgePooling(layers.Layer):
15
+ r"""The edge pooling operator from the `"Towards Graph Pooling by Edge
16
+ Contraction" <https://graphreason.github.io/papers/17.pdf>`__ and
17
+ `"Edge Contraction Pooling for Graph Neural Networks"
18
+ <https://arxiv.org/abs/1905.10990>`__ papers.
19
+
20
+ Example:
21
+ ```python
22
+ import numpy as np
23
+ from k3_node.layers import EdgePooling
24
+
25
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
26
+ edge_index = np.random.randint(0, 10, size=(2, 30)) # 30 random edges
27
+ batch = np.repeat([0, 1], 5) # nodes 0-4 belong to graph 0, nodes 5-9 to graph 1
28
+
29
+ layer = EdgePooling(in_channels=8)
30
+ x_pool, edge_index_pool, batch_pool, unpool_info = layer(x, edge_index, batch)
31
+ # The number of clusters depends on the learned edge scores
32
+ print(x_pool.shape[0] <= 10, x_pool.shape[1]) # True 8: fewer nodes, same features
33
+ ```
34
+ """
35
+ def __init__(
36
+ self,
37
+ in_channels: int,
38
+ edge_score_method: Optional[Callable] = None,
39
+ dropout: float = 0.0,
40
+ add_to_edge_score: float = 0.5,
41
+ **kwargs,
42
+ ):
43
+ super().__init__(**kwargs)
44
+ self.in_channels = in_channels
45
+ if edge_score_method is None:
46
+ edge_score_method = self.compute_edge_score_softmax
47
+ self.compute_edge_score = edge_score_method
48
+ self.add_to_edge_score = add_to_edge_score
49
+ self.dropout_rate = dropout
50
+ self.drop = layers.Dropout(dropout) if dropout > 0.0 else None
51
+
52
+ self.lin = layers.Dense(1, use_bias=True, name="lin")
53
+
54
+ def build(self, input_shape=None):
55
+ self.lin.build((None, 2 * self.in_channels))
56
+ super().build(input_shape)
57
+
58
+ def reset_parameters(self):
59
+ self.lin.reset_parameters()
60
+
61
+ @staticmethod
62
+ def compute_edge_score_softmax(raw_edge_score, edge_index, num_nodes: int):
63
+ col = ops.cast(edge_index[1], dtype="int32")
64
+ max_score = segment_max(raw_edge_score, col, num_segments=num_nodes)
65
+ max_exp = ops.take(max_score, col, axis=0)
66
+ exp_score = ops.exp(raw_edge_score - max_exp)
67
+ sum_exp = segment_sum(exp_score, col, num_segments=num_nodes)
68
+ sum_exp_taken = ops.take(sum_exp, col, axis=0)
69
+ return exp_score / (sum_exp_taken + 1e-12)
70
+
71
+ @staticmethod
72
+ def compute_edge_score_tanh(raw_edge_score, edge_index=None, num_nodes=None):
73
+ return ops.tanh(raw_edge_score)
74
+
75
+ @staticmethod
76
+ def compute_edge_score_sigmoid(raw_edge_score, edge_index=None, num_nodes=None):
77
+ return ops.sigmoid(raw_edge_score)
78
+
79
+ def call(
80
+ self,
81
+ x,
82
+ edge_index,
83
+ batch,
84
+ training: bool = False,
85
+ ) -> Tuple[any, any, any, UnpoolInfo]:
86
+ r"""Forward pass."""
87
+ row = ops.cast(edge_index[0], dtype="int32")
88
+ col = ops.cast(edge_index[1], dtype="int32")
89
+
90
+ e = ops.concatenate([ops.take(x, row, axis=0), ops.take(x, col, axis=0)], axis=-1)
91
+ e = ops.reshape(self.lin(e), (-1,))
92
+ if self.drop is not None:
93
+ e = self.drop(e, training=training)
94
+
95
+ num_nodes = ops.shape(x)[0]
96
+ e = self.compute_edge_score(e, edge_index, num_nodes)
97
+ e = e + self.add_to_edge_score
98
+
99
+ return self._merge_edges(x, edge_index, batch, e)
100
+
101
+ def _merge_edges(
102
+ self,
103
+ x,
104
+ edge_index,
105
+ batch,
106
+ edge_score,
107
+ ) -> Tuple[any, any, any, UnpoolInfo]:
108
+ from k3_node.layers.conv.utils import eager_only_placeholder, host_callback
109
+ if eager_only_placeholder("EdgePooling", x, edge_index, edge_score):
110
+ num_nodes = ops.shape(x)[0]
111
+ unpool_info = UnpoolInfo(edge_index, ops.arange(num_nodes, dtype="int32"), batch, ops.ones((num_nodes,), dtype=x.dtype))
112
+ return x, edge_index, batch, unpool_info
113
+
114
+ num_nodes = int(ops.shape(x)[0])
115
+ num_edges = int(ops.shape(edge_index)[1])
116
+
117
+ def contract(edge_index_np, edge_score_np, batch_np):
118
+ # Greedy contraction on the host; outputs are padded to fixed sizes (N clusters, E edges).
119
+ cluster_np = np.zeros(num_nodes, dtype=np.int64)
120
+ cluster_edge = np.full(num_nodes, -1, dtype=np.int64) # edge merged into each cluster
121
+ mask = np.ones(num_nodes, dtype=bool)
122
+ i = 0
123
+ for edge_idx in np.argsort(-edge_score_np, kind="stable"):
124
+ source, target = edge_index_np[0, edge_idx], edge_index_np[1, edge_idx]
125
+ if not mask[source] or not mask[target]:
126
+ continue
127
+ cluster_edge[i] = edge_idx
128
+ cluster_np[source] = i
129
+ mask[source] = False
130
+ if source != target:
131
+ cluster_np[target] = i
132
+ mask[target] = False
133
+ i += 1
134
+ remaining = np.where(mask)[0]
135
+ cluster_np[remaining] = np.arange(i, i + len(remaining))
136
+ num_clusters = i + len(remaining)
137
+
138
+ unique_edges = np.unique(cluster_np[edge_index_np.astype(np.int64)], axis=1)
139
+ edges_pad = np.zeros((2, num_edges), dtype=np.int64)
140
+ edges_pad[:, : unique_edges.shape[1]] = unique_edges
141
+
142
+ batch_pad = np.zeros(num_nodes, dtype=np.int64)
143
+ batch_pad[cluster_np] = batch_np
144
+ return cluster_np, cluster_edge, num_clusters, edges_pad, unique_edges.shape[1], batch_pad
145
+
146
+ cluster, cluster_edge, num_clusters, edges_pad, num_new_edges, batch_pad = host_callback(
147
+ contract,
148
+ [((num_nodes,), "int32"), ((num_nodes,), "int32"), ((), "int32"),
149
+ ((2, num_edges), "int32"), ((), "int32"), ((num_nodes,), "int32")],
150
+ edge_index, edge_score, batch,
151
+ )
152
+ num_clusters, num_new_edges = int(num_clusters), int(num_new_edges)
153
+
154
+ new_x = segment_sum(x, cluster, num_segments=num_clusters)
155
+
156
+ # Score of the edge merged into each cluster (1 for unmatched nodes), gathered from the score
157
+ # tensor so gradients reach the scoring layer.
158
+ cluster_edge = cluster_edge[:num_clusters]
159
+ if num_edges > 0:
160
+ gathered = ops.take(ops.cast(edge_score, x.dtype), ops.maximum(cluster_edge, 0), axis=0)
161
+ new_edge_score = ops.where(cluster_edge >= 0, gathered, ops.ones_like(gathered))
162
+ else:
163
+ new_edge_score = ops.ones((num_clusters,), dtype=x.dtype)
164
+ new_x = new_x * ops.reshape(new_edge_score, (-1, 1))
165
+
166
+ new_edge_index = ops.cast(edges_pad[:, :num_new_edges], edge_index.dtype)
167
+ new_batch = ops.cast(batch_pad[:num_clusters], batch.dtype)
168
+
169
+ unpool_info = UnpoolInfo(
170
+ edge_index=edge_index,
171
+ cluster=cluster,
172
+ batch=batch,
173
+ new_edge_score=new_edge_score,
174
+ )
175
+
176
+ return new_x, new_edge_index, new_batch, unpool_info
177
+
178
+ def unpool(
179
+ self,
180
+ x,
181
+ unpool_info: UnpoolInfo,
182
+ ) -> Tuple[any, any, any]:
183
+ r"""Unpools a previous edge pooling step."""
184
+ new_x = x / ops.reshape(unpool_info.new_edge_score, (-1, 1))
185
+ new_x = ops.take(new_x, ops.cast(unpool_info.cluster, dtype="int32"), axis=0)
186
+ return new_x, unpool_info.edge_index, unpool_info.batch
187
+
188
+ def __repr__(self) -> str:
189
+ return f"{self.__class__.__name__}({self.in_channels})"
@@ -0,0 +1,139 @@
1
+ from typing import Optional
2
+ from keras import ops
3
+
4
+
5
+ from k3_node.layers.conv.utils import is_tracing
6
+ from k3_node.ops.segment import segment_max, segment_sum
7
+ from k3_node.ops.host import to_numpy
8
+
9
+
10
+ def _infer_size(batch, size=None):
11
+ if size is not None:
12
+ return size
13
+ import keras
14
+ from keras.src.backend.common.symbolic_scope import in_symbolic_scope
15
+
16
+ if in_symbolic_scope() or (hasattr(batch, "is_meta") and batch.is_meta):
17
+ # Keras shape inference on placeholder values: any size gives the right output rank.
18
+ return 1 if keras.config.backend() == "jax" else None
19
+ if is_tracing(batch):
20
+ if keras.config.backend() == "jax":
21
+ # The number of graphs depends on the values in `batch`, which are unknown inside
22
+ # jax.jit; guessing would silently merge every graph into one.
23
+ raise ValueError(
24
+ "Cannot infer the number of graphs from `batch` inside a compiled JAX function. "
25
+ "Pass `size=` to the pooling function (or `batch_size=` to the model)."
26
+ )
27
+ # TensorFlow graph mode supports a dynamic number of segments.
28
+ return ops.cast(ops.max(batch), "int32") + 1
29
+ # Use the maximum (as PyG does), not the last entry: pooling layers such as EdgePooling
30
+ # return batch vectors that are not sorted by graph.
31
+ try:
32
+ if ops.shape(batch)[0] == 0:
33
+ return 0
34
+ return int(to_numpy(batch).max()) + 1
35
+ except Exception:
36
+ pass
37
+ try:
38
+ return ops.cast(ops.max(batch), "int32") + 1
39
+ except Exception:
40
+ return None
41
+
42
+
43
+ def global_add_pool(x, batch: Optional[any] = None, size: Optional[int] = None):
44
+ r"""Returns batch-wise graph-level-outputs by adding node features
45
+ across the node dimension.
46
+
47
+ Example:
48
+ ```python
49
+ import numpy as np
50
+ from k3_node.layers import global_add_pool
51
+
52
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
53
+ batch = np.repeat([0, 1], 5) # nodes 0-4 belong to graph 0, nodes 5-9 to graph 1
54
+
55
+ out = global_add_pool(x, batch) # one row per graph
56
+ print(tuple(out.shape)) # (2, 8)
57
+ ```
58
+ """
59
+ if batch is None:
60
+ dim = -1 if len(ops.shape(x)) == 1 else 0
61
+ keepdims = len(ops.shape(x)) <= 2
62
+ out = ops.sum(x, axis=dim, keepdims=keepdims)
63
+ if len(ops.shape(x)) == 1:
64
+ out = ops.reshape(out, (1, 1))
65
+ return out
66
+
67
+ batch = ops.cast(batch, dtype="int32")
68
+ if batch.shape[0] is not None and batch.shape[0] == 0:
69
+ num_seg = size if size is not None else 0
70
+ return ops.zeros((num_seg,) + tuple(ops.shape(x)[1:]), dtype=x.dtype)
71
+ size = _infer_size(batch, size)
72
+ return segment_sum(x, batch, num_segments=size)
73
+
74
+
75
+ def global_mean_pool(x, batch: Optional[any] = None, size: Optional[int] = None):
76
+ r"""Returns batch-wise graph-level-outputs by averaging node features
77
+ across the node dimension.
78
+
79
+ Example:
80
+ ```python
81
+ import numpy as np
82
+ from k3_node.layers import global_mean_pool
83
+
84
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
85
+ batch = np.repeat([0, 1], 5) # nodes 0-4 belong to graph 0, nodes 5-9 to graph 1
86
+
87
+ out = global_mean_pool(x, batch) # one row per graph
88
+ print(tuple(out.shape)) # (2, 8)
89
+ ```
90
+ """
91
+ if batch is None:
92
+ dim = -1 if len(ops.shape(x)) == 1 else 0
93
+ keepdims = len(ops.shape(x)) <= 2
94
+ out = ops.mean(x, axis=dim, keepdims=keepdims)
95
+ if len(ops.shape(x)) == 1:
96
+ out = ops.reshape(out, (1, 1))
97
+ return out
98
+
99
+ batch = ops.cast(batch, dtype="int32")
100
+ if batch.shape[0] is not None and batch.shape[0] == 0:
101
+ num_seg = size if size is not None else 0
102
+ return ops.zeros((num_seg,) + tuple(ops.shape(x)[1:]), dtype=x.dtype)
103
+ size = _infer_size(batch, size)
104
+ sum_val = segment_sum(x, batch, num_segments=size)
105
+ ones = ops.ones_like(x)
106
+ count = segment_sum(ones, batch, num_segments=size)
107
+ return sum_val / ops.maximum(count, 1.0)
108
+
109
+
110
+ def global_max_pool(x, batch: Optional[any] = None, size: Optional[int] = None):
111
+ r"""Returns batch-wise graph-level-outputs by taking the channel-wise
112
+ maximum across the node dimension.
113
+
114
+ Example:
115
+ ```python
116
+ import numpy as np
117
+ from k3_node.layers import global_max_pool
118
+
119
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
120
+ batch = np.repeat([0, 1], 5) # nodes 0-4 belong to graph 0, nodes 5-9 to graph 1
121
+
122
+ out = global_max_pool(x, batch) # one row per graph
123
+ print(tuple(out.shape)) # (2, 8)
124
+ ```
125
+ """
126
+ if batch is None:
127
+ dim = -1 if len(ops.shape(x)) == 1 else 0
128
+ keepdims = len(ops.shape(x)) <= 2
129
+ out = ops.max(x, axis=dim, keepdims=keepdims)
130
+ if len(ops.shape(x)) == 1:
131
+ out = ops.reshape(out, (1, 1))
132
+ return out
133
+
134
+ batch = ops.cast(batch, dtype="int32")
135
+ if batch.shape[0] is not None and batch.shape[0] == 0:
136
+ num_seg = size if size is not None else 0
137
+ return ops.zeros((num_seg,) + tuple(ops.shape(x)[1:]), dtype=x.dtype)
138
+ size = _infer_size(batch, size)
139
+ return segment_max(x, batch, num_segments=size)
@@ -0,0 +1,66 @@
1
+ from typing import Optional
2
+ from keras import ops
3
+ import numpy as np
4
+ from k3_node.ops.host import to_numpy
5
+
6
+
7
+ def graclus(
8
+ edge_index,
9
+ weight: Optional[any] = None,
10
+ num_nodes: Optional[int] = None,
11
+ ):
12
+ r"""A greedy clustering algorithm of picking an unmarked vertex and matching
13
+ it with one of its unmarked neighbors that maximizes its edge weight.
14
+
15
+ Example:
16
+ ```python
17
+ import numpy as np
18
+ from k3_node.layers import graclus
19
+
20
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
21
+ edge_index = np.random.randint(0, 10, size=(2, 30)) # 30 random edges
22
+
23
+ cluster = graclus(edge_index, num_nodes=10) # greedy matching of neighboring nodes
24
+ print(tuple(cluster.shape)) # (10,): a cluster id for every node
25
+ ```
26
+ """
27
+ edge_index_np = to_numpy(edge_index).astype(np.int64)
28
+ if num_nodes is None:
29
+ num_nodes = int(np.max(edge_index_np)) + 1 if edge_index_np.size > 0 else 0
30
+
31
+ if weight is not None:
32
+ weight_np = to_numpy(weight)
33
+ else:
34
+ weight_np = np.ones(edge_index_np.shape[1], dtype=np.float32)
35
+
36
+ # Build adjacency list
37
+ adj = [[] for _ in range(num_nodes)]
38
+ for idx in range(edge_index_np.shape[1]):
39
+ u = edge_index_np[0, idx]
40
+ v = edge_index_np[1, idx]
41
+ w = weight_np[idx]
42
+ adj[u].append((v, w))
43
+
44
+ cluster_np = np.full(num_nodes, -1, dtype=np.int64)
45
+ c = 0
46
+ for u in range(num_nodes):
47
+ if cluster_np[u] != -1:
48
+ continue
49
+
50
+ best_v = None
51
+ best_w = -float("inf")
52
+ for v, w in adj[u]:
53
+ if v != u and cluster_np[v] == -1:
54
+ if w > best_w:
55
+ best_w = w
56
+ best_v = v
57
+
58
+ if best_v is not None:
59
+ cluster_np[u] = c
60
+ cluster_np[best_v] = c
61
+ else:
62
+ cluster_np[u] = c
63
+ c += 1
64
+
65
+ return ops.convert_to_tensor(cluster_np, dtype="int64")
66
+
@@ -0,0 +1,253 @@
1
+ from typing import NamedTuple, Optional
2
+ from keras import ops
3
+ import numpy as np
4
+ from k3_node.ops.creation import repeat
5
+
6
+
7
+ class KNNOutput(NamedTuple):
8
+ score: any
9
+ index: any
10
+
11
+
12
+ class KNNIndex:
13
+ r"""A base class to perform k-nearest neighbor search."""
14
+ def __init__(
15
+ self,
16
+ index_factory: Optional[str] = None,
17
+ emb: Optional[any] = None,
18
+ reserve: Optional[int] = None,
19
+ ):
20
+ self.index_factory = index_factory
21
+ self.reserve = reserve
22
+ self.emb = None
23
+ if emb is not None:
24
+ self.add(emb)
25
+
26
+ @property
27
+ def numel(self) -> int:
28
+ if self.emb is None:
29
+ return 0
30
+ return ops.shape(self.emb)[0]
31
+
32
+ def add(self, emb):
33
+ if self.emb is None:
34
+ self.emb = emb
35
+ else:
36
+ self.emb = ops.concatenate([self.emb, emb], axis=0)
37
+
38
+ def get_emb(self):
39
+ return self.emb
40
+
41
+ def search(self, query, k: int) -> KNNOutput:
42
+ raise NotImplementedError
43
+
44
+
45
+ class L2KNNIndex(KNNIndex):
46
+ r"""k-NN search using squared Euclidean (L2) distance."""
47
+ def __init__(self, emb: Optional[any] = None, **kwargs):
48
+ super().__init__(index_factory="IndexFlatL2", emb=emb, **kwargs)
49
+
50
+ def search(self, query, k: int) -> KNNOutput:
51
+ # query: [M, F], emb: [N, F]
52
+ q_exp = ops.expand_dims(query, axis=1) # [M, 1, F]
53
+ e_exp = ops.expand_dims(self.emb, axis=0) # [1, N, F]
54
+ dist = ops.sum(ops.power(q_exp - e_exp, 2), axis=-1) # [M, N]
55
+
56
+ top_k_neg_score, top_k_idx = ops.top_k(-dist, k=k, sorted=True)
57
+ return KNNOutput(score=-top_k_neg_score, index=top_k_idx)
58
+
59
+
60
+ class MIPSKNNIndex(KNNIndex):
61
+ r"""k-NN search using Maximum Inner Product Search (MIPS)."""
62
+ def __init__(self, emb: Optional[any] = None, **kwargs):
63
+ super().__init__(index_factory="IndexFlatIP", emb=emb, **kwargs)
64
+
65
+ def search(self, query, k: int) -> KNNOutput:
66
+ # query: [M, F], emb: [N, F]
67
+ score_mat = ops.matmul(query, ops.transpose(self.emb)) # [M, N]
68
+ top_k_score, top_k_idx = ops.top_k(score_mat, k=k, sorted=True)
69
+ return KNNOutput(score=top_k_score, index=top_k_idx)
70
+
71
+
72
+ class ApproxL2KNNIndex(L2KNNIndex):
73
+ pass
74
+
75
+
76
+ class ApproxMIPSKNNIndex(MIPSKNNIndex):
77
+ pass
78
+
79
+
80
+ def knn(
81
+ x,
82
+ y,
83
+ k: int,
84
+ batch_x: Optional[any] = None,
85
+ batch_y: Optional[any] = None,
86
+ cosine: bool = False,
87
+ num_workers: int = 1,
88
+ batch_size: Optional[int] = None,
89
+ ):
90
+ r"""Finds for each element in `y` the `k` nearest points in `x`.
91
+
92
+ Example:
93
+ ```python
94
+ import numpy as np
95
+ from k3_node.layers import knn
96
+
97
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
98
+ pos = np.random.rand(10, 3).astype("float32") # 3D positions
99
+ query = np.random.rand(4, 3).astype("float32") # 4 query points
100
+
101
+ assign = knn(pos, query, k=3) # 3 nearest points in `pos` for every query point
102
+ print(tuple(assign.shape)) # (2, 12): [2, num_query * k]: (query index, point index)
103
+ ```
104
+ """
105
+ x = ops.convert_to_tensor(x)
106
+ y = ops.convert_to_tensor(y)
107
+ if len(ops.shape(x)) == 1:
108
+ x = ops.expand_dims(x, axis=-1)
109
+ if len(ops.shape(y)) == 1:
110
+ y = ops.expand_dims(y, axis=-1)
111
+
112
+ col = _knn_indices(x, y, k, batch_x, batch_y, cosine=cosine) # [M, k] indices into x
113
+ M = ops.shape(y)[0]
114
+ row = repeat(ops.arange(0, M, dtype="int64"), k)
115
+ return ops.stack([row, ops.reshape(ops.cast(col, "int64"), (-1,))], axis=0)
116
+
117
+
118
+ def _pairwise(y, x, cosine):
119
+ """Squared distances (or cosine distances) between the rows of ``y`` and ``x``, without
120
+ materializing ``[M, N, features]`` differences."""
121
+ if cosine:
122
+ y = y / ops.maximum(ops.norm(y, axis=-1, keepdims=True), 1e-12)
123
+ x = x / ops.maximum(ops.norm(x, axis=-1, keepdims=True), 1e-12)
124
+ return 1.0 - ops.matmul(y, ops.swapaxes(x, -1, -2))
125
+ y_sq = ops.sum(y * y, axis=-1, keepdims=True)
126
+ x_sq = ops.expand_dims(ops.sum(x * x, axis=-1), -2)
127
+ return ops.maximum(y_sq + x_sq - 2.0 * ops.matmul(y, ops.swapaxes(x, -1, -2)), 0.0)
128
+
129
+
130
+ def _knn_indices(x, y, k, batch_x=None, batch_y=None, cosine=False, exclude_self=False):
131
+ """Indices ``[M, k]`` into ``x`` of the ``k`` nearest points of every row of ``y`` within the
132
+ same example. Eagerly, distances are computed per example (``[examples, n, n]``); when compiled,
133
+ in chunks of query rows masked by example."""
134
+ from k3_node.layers.aggr.base import to_dense_batch
135
+ from k3_node.layers.conv.utils import is_tracing
136
+
137
+ N, M = ops.shape(x)[0], ops.shape(y)[0]
138
+ if isinstance(M, int) and isinstance(N, int) and (M == 0 or N == 0): # sizes are tensors while tracing
139
+ return ops.zeros((M, k), dtype="int32")
140
+ ids = [b for b in (batch_x, batch_y) if b is not None]
141
+ if not any(is_tracing(t) for t in [x, y] + ids) and isinstance(N, int) and isinstance(M, int):
142
+ try:
143
+ bx = np.zeros(N, np.int64) if batch_x is None else np.asarray(ops.convert_to_numpy(batch_x)).astype(np.int64)
144
+ by = np.zeros(M, np.int64) if batch_y is None else np.asarray(ops.convert_to_numpy(batch_y)).astype(np.int64)
145
+ num_examples = int(max(bx.max(initial=-1), by.max(initial=-1))) + 1
146
+ x_dense, x_mask = to_dense_batch(x, bx, dim_size=num_examples)
147
+ y_dense, _ = to_dense_batch(y, by, dim_size=num_examples)
148
+ dist = _pairwise(y_dense, x_dense, cosine) # [examples, max_y, max_x]
149
+ dist = ops.where(ops.expand_dims(x_mask, 1), dist, 1e9)
150
+ x_start = np.concatenate([[0], np.cumsum(np.bincount(bx, minlength=num_examples))])[:-1]
151
+ y_start = np.concatenate([[0], np.cumsum(np.bincount(by, minlength=num_examples))])[:-1]
152
+ y_local = np.arange(M) - y_start[by]
153
+ if exclude_self: # x and y are the same points
154
+ dist = ops.where(ops.expand_dims(ops.eye(dist.shape[1], dist.shape[2]), 0) > 0, 1e9, dist)
155
+ kk = min(k, int(dist.shape[2]))
156
+ _, idx = ops.top_k(-dist, k=kk, sorted=True) # local indices [examples, max_y, kk]
157
+ flat = ops.reshape(idx, (-1, kk))
158
+ local = ops.take(flat, by * int(dist.shape[1]) + y_local, axis=0) # [M, kk]
159
+ out = local + ops.convert_to_tensor(x_start[by][:, None].astype(np.int64), dtype=local.dtype)
160
+ if kk < k: # fewer candidates than k: repeat the last one
161
+ out = ops.concatenate([out] + [out[:, -1:]] * (k - kk), axis=1)
162
+ return out
163
+ except (TypeError, ValueError, NotImplementedError, RuntimeError):
164
+ pass
165
+
166
+ if not isinstance(N, int) and y is x and batch_x is not None and batch_y is batch_x:
167
+ # compiled knn_graph with a point count known only at run time (TensorFlow): per example,
168
+ # since the chunks below would all be the full [N, N] distance matrix
169
+ return _knn_graph_indices_traced(x, k, ops.cast(batch_x, "int32"), cosine, exclude_self)
170
+
171
+ # Compiled: masked distances for chunks of query rows ([chunk, N] memory at a time)
172
+ bx = ops.zeros((N,), "int32") if batch_x is None else ops.cast(batch_x, "int32")
173
+ by = ops.zeros((M,), "int32") if batch_y is None else ops.cast(batch_y, "int32")
174
+ chunk = M if not isinstance(M, int) or not isinstance(N, int) else max(1, min(M, (1 << 24) // max(N, 1)))
175
+ outs = []
176
+ for start in range(0, M, chunk) if isinstance(M, int) else [0]:
177
+ stop = start + chunk if isinstance(M, int) else None
178
+ dist = _pairwise(y[start:stop], x, cosine)
179
+ mask = ops.expand_dims(by[start:stop], 1) != ops.expand_dims(bx, 0)
180
+ if exclude_self:
181
+ rows = ops.arange(ops.shape(dist)[0], dtype="int32") + start
182
+ mask = ops.logical_or(mask, ops.expand_dims(rows, 1) == ops.expand_dims(ops.arange(N, dtype="int32"), 0))
183
+ kk = min(k, N) if isinstance(N, int) else k
184
+ _, idx = ops.top_k(-ops.where(mask, 1e9, dist), k=kk, sorted=True)
185
+ if kk < k: # fewer candidates than k: repeat the last one, as in the eager path
186
+ idx = ops.concatenate([idx] + [idx[:, -1:]] * (k - kk), axis=1)
187
+ outs.append(idx)
188
+ return outs[0] if len(outs) == 1 else ops.concatenate(outs, axis=0)
189
+
190
+
191
+ def _knn_graph_indices_traced(x, k, batch, cosine, exclude_self):
192
+ """``_knn_indices(x, x, ...)`` for sizes only known at run time: distances within each example
193
+ (``[examples, max_points, max_points]``), like the eager path. Needs ``k`` <= points per example."""
194
+ from k3_node.layers.aggr.base import to_dense_batch
195
+ from k3_node.ops.segment import segment_sum
196
+
197
+ num_points = ops.shape(x)[0]
198
+ x_dense, mask = to_dense_batch(x, batch)
199
+ n = ops.shape(x_dense)[1]
200
+ dist = ops.where(ops.expand_dims(mask, 1), _pairwise(x_dense, x_dense, cosine), 1e9)
201
+ if exclude_self:
202
+ same = ops.expand_dims(ops.arange(n), 1) == ops.expand_dims(ops.arange(n), 0)
203
+ dist = ops.where(ops.expand_dims(same, 0), 1e9, dist)
204
+ kk = min(k, x_dense.shape[1]) if isinstance(x_dense.shape[1], int) else k
205
+ _, idx = ops.top_k(-dist, k=kk, sorted=True) # local indices [examples, max_points, kk]
206
+ if kk < k: # fewer candidates than k (e.g. Keras' shape inference): repeat the last one
207
+ idx = ops.concatenate([idx] + [idx[..., -1:]] * (k - kk), axis=-1)
208
+ counts = segment_sum(ops.ones_like(batch), batch, num_segments=num_points)
209
+ starts = ops.take(ops.cumsum(counts) - counts, batch, axis=0) # first point of each point's example
210
+ local = ops.arange(num_points, dtype="int32") - starts
211
+ rows = ops.take(ops.reshape(idx, (-1, k)), batch * ops.cast(n, "int32") + local, axis=0)
212
+ return rows + ops.cast(ops.expand_dims(starts, 1), rows.dtype)
213
+
214
+
215
+ def knn_graph(
216
+ x,
217
+ k: int,
218
+ batch: Optional[any] = None,
219
+ loop: bool = False,
220
+ flow: str = "source_to_target",
221
+ cosine: bool = False,
222
+ num_workers: int = 1,
223
+ batch_size: Optional[int] = None,
224
+ ):
225
+ r"""Computes graph edges to the nearest `k` points.
226
+
227
+ Example:
228
+ ```python
229
+ import numpy as np
230
+ from k3_node.layers import knn_graph
231
+
232
+ x = np.random.rand(10, 8).astype("float32") # 10 nodes with 8 features each
233
+ pos = np.random.rand(10, 3).astype("float32") # 3D positions
234
+
235
+ edge_index = knn_graph(pos, k=3) # connect every point to its 3 nearest neighbors
236
+ print(tuple(edge_index.shape)) # (2, 30)
237
+ ```
238
+ """
239
+ assert flow in ["source_to_target", "target_to_source"]
240
+ x = ops.convert_to_tensor(x)
241
+ if len(ops.shape(x)) == 1:
242
+ x = ops.expand_dims(x, axis=-1)
243
+
244
+ N = ops.shape(x)[0]
245
+ col_indices = _knn_indices(x, x, k, batch, batch, cosine=cosine, exclude_self=not loop)
246
+ row = repeat(ops.arange(0, N, dtype="int64"), k)
247
+ col = ops.reshape(ops.cast(col_indices, "int64"), (-1,))
248
+
249
+ if flow == "source_to_target":
250
+ return ops.stack([col, row], axis=0)
251
+ else:
252
+ return ops.stack([row, col], axis=0)
253
+