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,797 @@
1
+ """Transforms that use node positions (``data.pos``), meshes (``data.face``) or images.
2
+
3
+ They run on the host with NumPy and return tensors of the same kind as their inputs, so they work
4
+ with every Keras backend.
5
+ """
6
+ import math
7
+ import random
8
+ import re
9
+ from typing import Any, List, Optional, Sequence, Tuple, Union
10
+
11
+ import keras
12
+ import numpy as np
13
+
14
+ from k3_node.data import Data, HeteroData
15
+ from k3_node.transforms.base_transform import BaseTransform, functional_transform
16
+ from k3_node.transforms.utils import match_tensor, to_numpy, to_undirected
17
+
18
+
19
+ def _np(x):
20
+ return None if x is None else to_numpy(x)
21
+
22
+
23
+ def _out(value, like, dtype=None):
24
+ """Returns the NumPy result ``value`` as the same kind of tensor (and dtype) as ``like``."""
25
+ if dtype is None:
26
+ dtype = keras.backend.standardize_dtype(like.dtype) if like is not None else None
27
+ value = np.asarray(value)
28
+ if dtype is not None:
29
+ value = value.astype(dtype)
30
+ if isinstance(like, np.ndarray):
31
+ return value
32
+ return match_tensor(value, like, dtype=dtype)
33
+
34
+
35
+ def _is_tensor(x) -> bool:
36
+ return hasattr(x, "shape") and hasattr(x, "dtype") and len(x.shape) > 0
37
+
38
+
39
+ def _normalize(x, axis=-1):
40
+ return x / np.maximum(np.linalg.norm(x, axis=axis, keepdims=True), 1e-12)
41
+
42
+
43
+ def _set_edge_attr(data, new, cat: bool, like):
44
+ """Stores ``new`` edge features, appended to the existing ones if ``cat``."""
45
+ pseudo = data.edge_attr
46
+ if pseudo is not None and cat:
47
+ p = _np(pseudo)
48
+ p = p.reshape(-1, 1) if p.ndim == 1 else p
49
+ data.edge_attr = _out(np.concatenate([p, new.astype(p.dtype)], axis=-1), pseudo)
50
+ else:
51
+ data.edge_attr = _out(new, like)
52
+
53
+
54
+ def _get_angle(v1, v2):
55
+ cross = np.cross(v1, v2)
56
+ cross_norm = np.linalg.norm(cross, axis=-1) if cross.ndim > 1 else np.abs(cross)
57
+ return np.arctan2(cross_norm, (v1 * v2).sum(-1))
58
+
59
+
60
+ def _point_pair_features(pos_i, pos_j, norm_i, norm_j):
61
+ pseudo = pos_j - pos_i
62
+ return np.stack([
63
+ np.linalg.norm(pseudo, axis=-1),
64
+ _get_angle(norm_i, pseudo),
65
+ _get_angle(norm_j, pseudo),
66
+ _get_angle(norm_i, norm_j),
67
+ ], axis=-1)
68
+
69
+
70
+ def _scatter_sum(src, index, dim_size):
71
+ out = np.zeros((dim_size,) + src.shape[1:], dtype=src.dtype)
72
+ np.add.at(out, index, src)
73
+ return out
74
+
75
+
76
+ def _scatter_mean(src, index, dim_size):
77
+ count = np.bincount(index, minlength=dim_size).reshape((-1,) + (1,) * (src.ndim - 1))
78
+ return _scatter_sum(src, index, dim_size) / np.maximum(count, 1)
79
+
80
+
81
+ def _scatter_max(src, index, dim_size):
82
+ out = np.full((dim_size,) + src.shape[1:], -np.inf, dtype=src.dtype)
83
+ np.maximum.at(out, index, src)
84
+ return np.where(np.isinf(out), 0, out)
85
+
86
+
87
+ def _edges(data):
88
+ edge_index = _np(data.edge_index)
89
+ return edge_index[0], edge_index[1], _np(data.pos)
90
+
91
+
92
+ @functional_transform('distance')
93
+ class Distance(BaseTransform):
94
+ r"""Saves the relative Euclidean distance of linked nodes in edge attributes
95
+ (functional name: :obj:`distance`).
96
+
97
+ Args:
98
+ norm (bool, optional): If set to :obj:`False`, output will not be
99
+ normalized to the specified interval. (default: :obj:`True`)
100
+ max_value (float, optional): If set and :obj:`norm=True`, normalization
101
+ will be performed based on this value instead of maximum distance.
102
+ (default: :obj:`None`)
103
+ cat (bool, optional): If set to :obj:`False`, all existing edge
104
+ attributes will be replaced. (default: :obj:`True`)
105
+ interval (tuple, optional): A tuple specifying the lower and upper
106
+ bound for normalization. (default: :obj:`(0.0, 1.0)`)
107
+ """
108
+ def __init__(
109
+ self,
110
+ norm: bool = True,
111
+ max_value: Optional[float] = None,
112
+ cat: bool = True,
113
+ interval: Tuple[float, float] = (0.0, 1.0),
114
+ ) -> None:
115
+ self.norm = norm
116
+ self.max = max_value
117
+ self.cat = cat
118
+ self.interval = interval
119
+
120
+ def forward(self, data: Data) -> Data:
121
+ assert data.pos is not None
122
+ assert data.edge_index is not None
123
+ row, col, pos = _edges(data)
124
+
125
+ dist = np.linalg.norm(pos[col] - pos[row], axis=-1).reshape(-1, 1)
126
+
127
+ if self.norm and dist.size > 0:
128
+ max_val = dist.max() if self.max is None else self.max
129
+ if max_val > 0:
130
+ dist = dist / max_val
131
+ length = self.interval[1] - self.interval[0]
132
+ dist = length * dist + self.interval[0]
133
+
134
+ _set_edge_attr(data, dist, self.cat, data.pos)
135
+ return data
136
+
137
+ def __repr__(self) -> str:
138
+ return (f'{self.__class__.__name__}(norm={self.norm}, '
139
+ f'max_value={self.max})')
140
+
141
+
142
+ @functional_transform('cartesian')
143
+ class Cartesian(BaseTransform):
144
+ r"""Saves the relative Cartesian coordinates of linked nodes in edge
145
+ attributes (functional name: :obj:`cartesian`).
146
+
147
+ Args:
148
+ norm (bool, optional): If set to :obj:`False`, the output will not be
149
+ normalized to the specified interval. (default: :obj:`True`)
150
+ max_value (float, optional): If set and :obj:`norm=True`, normalization
151
+ will be performed based on this value instead of maximum coordinate.
152
+ (default: :obj:`None`)
153
+ cat (bool, optional): If set to :obj:`False`, all existing edge
154
+ attributes will be replaced. (default: :obj:`True`)
155
+ interval (tuple, optional): A tuple specifying the lower and upper
156
+ bound for normalization. (default: :obj:`(0.0, 1.0)`)
157
+ """
158
+ def __init__(
159
+ self,
160
+ norm: bool = True,
161
+ max_value: Optional[float] = None,
162
+ cat: bool = True,
163
+ interval: Tuple[float, float] = (0.0, 1.0),
164
+ ) -> None:
165
+ self.norm = norm
166
+ self.max = max_value
167
+ self.cat = cat
168
+ self.interval = interval
169
+
170
+ def forward(self, data: Data) -> Data:
171
+ assert data.pos is not None
172
+ assert data.edge_index is not None
173
+ row, col, pos = _edges(data)
174
+
175
+ cart = pos[row] - pos[col]
176
+ cart = cart.reshape(-1, 1) if cart.ndim == 1 else cart
177
+
178
+ if self.norm and cart.size > 0:
179
+ max_val = np.abs(cart).max() if self.max is None else self.max
180
+ length = self.interval[1] - self.interval[0]
181
+ center = (self.interval[0] + self.interval[1]) / 2
182
+ if max_val > 0:
183
+ cart = length * (cart / (2 * max_val)) + center
184
+ else:
185
+ cart = cart + center
186
+
187
+ _set_edge_attr(data, cart, self.cat, data.pos)
188
+ return data
189
+
190
+ def __repr__(self) -> str:
191
+ return (f'{self.__class__.__name__}(norm={self.norm}, '
192
+ f'max_value={self.max})')
193
+
194
+
195
+ @functional_transform('local_cartesian')
196
+ class LocalCartesian(BaseTransform):
197
+ r"""Saves relative Cartesian coordinates, normalized per neighborhood to an interval
198
+ (functional name: :obj:`local_cartesian`)."""
199
+ def __init__(
200
+ self,
201
+ norm: bool = True,
202
+ cat: bool = True,
203
+ interval: Tuple[float, float] = (0.0, 1.0),
204
+ ) -> None:
205
+ self.norm = norm
206
+ self.cat = cat
207
+ self.interval = interval
208
+
209
+ def forward(self, data: Data) -> Data:
210
+ assert data.pos is not None
211
+ assert data.edge_index is not None
212
+ row, col, pos = _edges(data)
213
+
214
+ cart = pos[row] - pos[col]
215
+ cart = cart.reshape(-1, 1) if cart.ndim == 1 else cart
216
+
217
+ if self.norm and cart.size > 0:
218
+ max_value = _scatter_max(np.abs(cart), col, pos.shape[0]).max(axis=-1, keepdims=True)
219
+ length = self.interval[1] - self.interval[0]
220
+ center = (self.interval[0] + self.interval[1]) / 2
221
+ denom = 2 * max_value[col]
222
+ denom = np.where(denom == 0, 1.0, denom)
223
+ cart = length * cart / denom + center
224
+
225
+ _set_edge_attr(data, cart, self.cat, data.pos)
226
+ return data
227
+
228
+
229
+ @functional_transform('polar')
230
+ class Polar(BaseTransform):
231
+ r"""Saves the polar coordinates of linked nodes in its edge attributes
232
+ (functional name: :obj:`polar`)."""
233
+ def __init__(
234
+ self,
235
+ norm: bool = True,
236
+ max_value: Optional[float] = None,
237
+ cat: bool = True,
238
+ ) -> None:
239
+ self.norm = norm
240
+ self.max = max_value
241
+ self.cat = cat
242
+
243
+ def forward(self, data: Data) -> Data:
244
+ assert data.pos is not None
245
+ assert data.edge_index is not None
246
+ row, col, pos = _edges(data)
247
+ assert pos.ndim == 2 and pos.shape[1] == 2
248
+
249
+ cart = pos[col] - pos[row]
250
+ rho = np.linalg.norm(cart, axis=-1).reshape(-1, 1)
251
+ theta = np.arctan2(cart[..., 1], cart[..., 0]).reshape(-1, 1)
252
+ theta = theta + (theta < 0) * (2 * math.pi)
253
+
254
+ if self.norm:
255
+ max_val = rho.max() if self.max is None else self.max
256
+ if max_val > 0:
257
+ rho = rho / max_val
258
+ theta = theta / (2 * math.pi)
259
+
260
+ _set_edge_attr(data, np.concatenate([rho, theta], axis=-1), self.cat, data.pos)
261
+ return data
262
+
263
+ def __repr__(self) -> str:
264
+ return (f'{self.__class__.__name__}(norm={self.norm}, '
265
+ f'max_value={self.max})')
266
+
267
+
268
+ @functional_transform('spherical')
269
+ class Spherical(BaseTransform):
270
+ r"""Saves the spherical coordinates of linked nodes in its edge attributes
271
+ (functional name: :obj:`spherical`)."""
272
+ def __init__(
273
+ self,
274
+ norm: bool = True,
275
+ max_value: Optional[float] = None,
276
+ cat: bool = True,
277
+ ) -> None:
278
+ self.norm = norm
279
+ self.max = max_value
280
+ self.cat = cat
281
+
282
+ def forward(self, data: Data) -> Data:
283
+ assert data.pos is not None
284
+ assert data.edge_index is not None
285
+ row, col, pos = _edges(data)
286
+ assert pos.ndim == 2 and pos.shape[1] == 3
287
+
288
+ cart = pos[col] - pos[row]
289
+ rho = np.linalg.norm(cart, axis=-1).reshape(-1, 1)
290
+ theta = np.arctan2(cart[..., 1], cart[..., 0]).reshape(-1, 1)
291
+ theta = theta + (theta < 0) * (2 * math.pi)
292
+ denom = np.where(rho[:, 0] == 0, 1.0, rho[:, 0])
293
+ phi = np.arccos(np.clip(cart[..., 2] / denom, -1.0, 1.0)).reshape(-1, 1)
294
+
295
+ if self.norm:
296
+ max_val = rho.max() if self.max is None else self.max
297
+ if max_val > 0:
298
+ rho = rho / max_val
299
+ theta = theta / (2 * math.pi)
300
+ phi = phi / math.pi
301
+
302
+ _set_edge_attr(data, np.concatenate([rho, theta, phi], axis=-1), self.cat, data.pos)
303
+ return data
304
+
305
+ def __repr__(self) -> str:
306
+ return (f'{self.__class__.__name__}(norm={self.norm}, '
307
+ f'max_value={self.max})')
308
+
309
+
310
+ @functional_transform('point_pair_features')
311
+ class PointPairFeatures(BaseTransform):
312
+ r"""Computes rotation-invariant Point Pair Features in edge attributes
313
+ (functional name: :obj:`point_pair_features`)."""
314
+ def __init__(self, cat: bool = True) -> None:
315
+ self.cat = cat
316
+
317
+ def forward(self, data: Data) -> Data:
318
+ assert data.edge_index is not None
319
+ assert data.pos is not None and data.norm is not None
320
+ row, col, pos = _edges(data)
321
+ norm = _np(data.norm)
322
+ assert pos.shape[-1] == 3 and pos.shape == norm.shape
323
+
324
+ ppf = _point_pair_features(pos[row], pos[col], norm[row], norm[col])
325
+ _set_edge_attr(data, ppf, self.cat, data.pos)
326
+ return data
327
+
328
+
329
+ @functional_transform('center')
330
+ class Center(BaseTransform):
331
+ r"""Centers node positions :obj:`data.pos` around the origin
332
+ (functional name: :obj:`center`).
333
+ """
334
+ def forward(
335
+ self,
336
+ data: Union[Data, HeteroData],
337
+ ) -> Union[Data, HeteroData]:
338
+ for store in data.node_stores:
339
+ if hasattr(store, 'pos') and store.pos is not None:
340
+ pos = _np(store.pos)
341
+ store.pos = _out(pos - pos.mean(axis=-2, keepdims=True), store.pos)
342
+ return data
343
+
344
+
345
+ @functional_transform('normalize_rotation')
346
+ class NormalizeRotation(BaseTransform):
347
+ r"""Rotates all points according to the eigenvectors of the point cloud
348
+ (functional name: :obj:`normalize_rotation`)."""
349
+ def __init__(self, max_points: int = -1, sort: bool = False) -> None:
350
+ self.max_points = max_points
351
+ self.sort = sort
352
+
353
+ def forward(self, data: Data) -> Data:
354
+ assert data.pos is not None
355
+ pos_all = _np(data.pos)
356
+ pos = pos_all
357
+
358
+ if self.max_points > 0 and pos.shape[0] > self.max_points:
359
+ pos = pos[np.random.permutation(pos.shape[0])[:self.max_points]]
360
+
361
+ pos = pos - pos.mean(axis=0, keepdims=True)
362
+ e, v = np.linalg.eigh(pos.T @ pos)
363
+ if self.sort:
364
+ v = v[:, np.argsort(-e)]
365
+
366
+ data.pos = _out(pos_all @ v, data.pos)
367
+ if 'normal' in data and data.normal is not None:
368
+ data.normal = _out(_normalize(_np(data.normal) @ v), data.normal)
369
+ return data
370
+
371
+
372
+ @functional_transform('normalize_scale')
373
+ class NormalizeScale(BaseTransform):
374
+ r"""Centers and normalizes node positions to the interval :math:`(-1, 1)`
375
+ (functional name: :obj:`normalize_scale`)."""
376
+ def __init__(self) -> None:
377
+ self.center = Center()
378
+
379
+ def forward(self, data: Data) -> Data:
380
+ data = self.center(data)
381
+ assert data.pos is not None
382
+ pos = _np(data.pos)
383
+ data.pos = _out(pos * ((1.0 / np.abs(pos).max()) * 0.999999), data.pos)
384
+ return data
385
+
386
+
387
+ @functional_transform('random_jitter')
388
+ class RandomJitter(BaseTransform):
389
+ r"""Translates node positions by randomly sampled translation values within a given interval
390
+ (functional name: :obj:`random_jitter`)."""
391
+ def __init__(
392
+ self,
393
+ translate: Union[float, int, Sequence[Union[float, int]]],
394
+ ) -> None:
395
+ self.translate = translate
396
+
397
+ def forward(self, data: Data) -> Data:
398
+ assert data.pos is not None
399
+ pos = _np(data.pos)
400
+ dim = pos.shape[-1]
401
+
402
+ if isinstance(self.translate, (int, float)):
403
+ translate = [self.translate] * dim
404
+ else:
405
+ assert len(self.translate) == dim
406
+ translate = self.translate
407
+
408
+ bound = np.abs(np.asarray(translate, dtype=np.float64))
409
+ data.pos = _out(pos + np.random.uniform(-bound, bound, size=pos.shape), data.pos)
410
+ return data
411
+
412
+ def __repr__(self) -> str:
413
+ return f'{self.__class__.__name__}({self.translate})'
414
+
415
+
416
+ @functional_transform('random_flip')
417
+ class RandomFlip(BaseTransform):
418
+ """Flips node positions along a given axis randomly with a given probability
419
+ (functional name: :obj:`random_flip`)."""
420
+ def __init__(self, axis: int, p: float = 0.5) -> None:
421
+ self.axis = axis
422
+ self.p = p
423
+
424
+ def forward(self, data: Data) -> Data:
425
+ assert data.pos is not None
426
+ if random.random() < self.p:
427
+ pos = _np(data.pos).copy()
428
+ pos[..., self.axis] = -pos[..., self.axis]
429
+ data.pos = _out(pos, data.pos)
430
+ return data
431
+
432
+ def __repr__(self) -> str:
433
+ return f'{self.__class__.__name__}(axis={self.axis}, p={self.p})'
434
+
435
+
436
+ @functional_transform('linear_transformation')
437
+ class LinearTransformation(BaseTransform):
438
+ r"""Transforms node positions :obj:`data.pos` with a square transformation matrix
439
+ (functional name: :obj:`linear_transformation`)."""
440
+ def __init__(self, matrix: Any):
441
+ matrix = np.asarray(_np(matrix))
442
+ assert matrix.ndim == 2, 'Transformation matrix should be two-dimensional.'
443
+ assert matrix.shape[0] == matrix.shape[1], (
444
+ f'Transformation matrix should be square (got {matrix.shape})')
445
+ self.matrix = matrix.T
446
+
447
+ def forward(
448
+ self,
449
+ data: Union[Data, HeteroData],
450
+ ) -> Union[Data, HeteroData]:
451
+ for store in data.node_stores:
452
+ if not hasattr(store, 'pos') or store.pos is None:
453
+ continue
454
+ pos = _np(store.pos)
455
+ pos = pos.reshape(-1, 1) if pos.ndim == 1 else pos
456
+ assert pos.shape[-1] == self.matrix.shape[-2], (
457
+ 'Node position matrix and transformation matrix have incompatible shape')
458
+ store.pos = _out(pos @ self.matrix.astype(pos.dtype), store.pos)
459
+ return data
460
+
461
+ def __repr__(self) -> str:
462
+ return f'{self.__class__.__name__}(\n{self.matrix}\n)'
463
+
464
+
465
+ @functional_transform('random_scale')
466
+ class RandomScale(BaseTransform):
467
+ r"""Scales node positions by a randomly sampled factor within a given interval
468
+ (functional name: :obj:`random_scale`)."""
469
+ def __init__(self, scales: Tuple[float, float]) -> None:
470
+ assert isinstance(scales, (tuple, list)) and len(scales) == 2
471
+ self.scales = scales
472
+
473
+ def forward(self, data: Data) -> Data:
474
+ assert data.pos is not None
475
+ data.pos = _out(_np(data.pos) * random.uniform(*self.scales), data.pos)
476
+ return data
477
+
478
+ def __repr__(self) -> str:
479
+ return f'{self.__class__.__name__}({self.scales})'
480
+
481
+
482
+ @functional_transform('random_rotate')
483
+ class RandomRotate(BaseTransform):
484
+ r"""Rotates node positions around a specific axis by a randomly sampled angle
485
+ (functional name: :obj:`random_rotate`)."""
486
+ def __init__(
487
+ self,
488
+ degrees: Union[Tuple[float, float], float],
489
+ axis: int = 0,
490
+ ) -> None:
491
+ if isinstance(degrees, (int, float)):
492
+ degrees = (-abs(degrees), abs(degrees))
493
+ assert isinstance(degrees, (tuple, list)) and len(degrees) == 2
494
+ self.degrees = degrees
495
+ self.axis = axis
496
+
497
+ def forward(self, data: Data) -> Data:
498
+ assert data.pos is not None
499
+
500
+ degree = math.pi * random.uniform(*self.degrees) / 180.0
501
+ sin, cos = math.sin(degree), math.cos(degree)
502
+
503
+ if data.pos.shape[-1] == 2:
504
+ matrix = [[cos, sin], [-sin, cos]]
505
+ elif self.axis == 0:
506
+ matrix = [[1, 0, 0], [0, cos, sin], [0, -sin, cos]]
507
+ elif self.axis == 1:
508
+ matrix = [[cos, 0, -sin], [0, 1, 0], [sin, 0, cos]]
509
+ else:
510
+ matrix = [[cos, sin, 0], [-sin, cos, 0], [0, 0, 1]]
511
+
512
+ return LinearTransformation(np.array(matrix, dtype=np.float32))(data)
513
+
514
+ def __repr__(self) -> str:
515
+ return (f'{self.__class__.__name__}({self.degrees}, '
516
+ f'axis={self.axis})')
517
+
518
+
519
+ @functional_transform('random_shear')
520
+ class RandomShear(BaseTransform):
521
+ r"""Shears node positions by randomly sampled factors within a given interval
522
+ (functional name: :obj:`random_shear`)."""
523
+ def __init__(self, shear: Union[float, int]) -> None:
524
+ self.shear = abs(shear)
525
+
526
+ def forward(self, data: Data) -> Data:
527
+ assert data.pos is not None
528
+ dim = data.pos.shape[-1]
529
+ matrix = np.random.uniform(-self.shear, self.shear, size=(dim, dim))
530
+ np.fill_diagonal(matrix, 1)
531
+ return LinearTransformation(matrix)(data)
532
+
533
+ def __repr__(self) -> str:
534
+ return f'{self.__class__.__name__}({self.shear})'
535
+
536
+
537
+ @functional_transform('face_to_edge')
538
+ class FaceToEdge(BaseTransform):
539
+ r"""Converts mesh faces of shape :obj:`[3, num_faces]` or :obj:`[4, num_faces]`
540
+ to edge indices of shape :obj:`[2, num_edges]` (functional name: :obj:`face_to_edge`).
541
+ """
542
+ def __init__(self, remove_faces: bool = True) -> None:
543
+ self.remove_faces = remove_faces
544
+
545
+ def forward(self, data: Data) -> Data:
546
+ if hasattr(data, 'face') and data.face is not None:
547
+ face = _np(data.face)
548
+
549
+ if face.shape[0] not in [3, 4]:
550
+ raise RuntimeError(f"Expected 'face' tensor with shape "
551
+ f"[3, num_faces] or [4, num_faces] "
552
+ f"(got {list(face.shape)})")
553
+
554
+ if face.shape[0] == 3:
555
+ edge_index = np.concatenate([face[:2], face[1:], face[::2]], axis=1)
556
+ else:
557
+ edge_index = np.concatenate(
558
+ [face[:2], face[1:3], face[2:4], face[::2], face[1::2], face[::3]], axis=1)
559
+
560
+ edge_index = _np(to_undirected(edge_index, num_nodes=data.num_nodes))
561
+ data.edge_index = _out(edge_index, data.face)
562
+ if self.remove_faces:
563
+ data.face = None
564
+
565
+ return data
566
+
567
+
568
+ @functional_transform('sample_points')
569
+ class SamplePoints(BaseTransform):
570
+ r"""Uniformly samples a fixed number of points on the mesh surfaces
571
+ (functional name: :obj:`sample_points`)."""
572
+ def __init__(
573
+ self,
574
+ num: int,
575
+ remove_faces: bool = True,
576
+ include_normals: bool = False,
577
+ ) -> None:
578
+ self.num = num
579
+ self.remove_faces = remove_faces
580
+ self.include_normals = include_normals
581
+
582
+ def forward(self, data: Data) -> Data:
583
+ assert data.pos is not None
584
+ assert data.face is not None
585
+ like = data.pos
586
+ pos, face = _np(data.pos).astype(np.float64), _np(data.face)
587
+ assert pos.shape[1] == 3 and face.shape[0] == 3
588
+
589
+ pos_max = np.abs(pos).max()
590
+ pos = pos / pos_max
591
+
592
+ area = np.linalg.norm(np.cross(pos[face[1]] - pos[face[0]], pos[face[2]] - pos[face[0]]), axis=1) / 2
593
+ sample = np.random.choice(face.shape[1], self.num, replace=True, p=area / area.sum())
594
+ face = face[:, sample]
595
+
596
+ frac = np.random.rand(self.num, 2)
597
+ mask = frac.sum(axis=-1) > 1
598
+ frac[mask] = 1 - frac[mask]
599
+
600
+ vec1 = pos[face[1]] - pos[face[0]]
601
+ vec2 = pos[face[2]] - pos[face[0]]
602
+
603
+ if self.include_normals:
604
+ data.normal = _out(_normalize(np.cross(vec1, vec2)), like)
605
+
606
+ pos_sampled = pos[face[0]] + frac[:, :1] * vec1 + frac[:, 1:] * vec2
607
+ data.pos = _out(pos_sampled * pos_max, like)
608
+
609
+ if self.remove_faces:
610
+ data.face = None
611
+
612
+ return data
613
+
614
+ def __repr__(self) -> str:
615
+ return f'{self.__class__.__name__}({self.num})'
616
+
617
+
618
+ @functional_transform('fixed_points')
619
+ class FixedPoints(BaseTransform):
620
+ r"""Samples a fixed number of points and features from a point cloud
621
+ (functional name: :obj:`fixed_points`)."""
622
+ def __init__(
623
+ self,
624
+ num: int,
625
+ replace: bool = True,
626
+ allow_duplicates: bool = False,
627
+ ) -> None:
628
+ self.num = num
629
+ self.replace = replace
630
+ self.allow_duplicates = allow_duplicates
631
+
632
+ def forward(self, data: Data) -> Data:
633
+ num_nodes = data.num_nodes
634
+ assert num_nodes is not None
635
+
636
+ if self.replace:
637
+ choice = np.random.choice(num_nodes, self.num, replace=True)
638
+ elif not self.allow_duplicates:
639
+ choice = np.random.permutation(num_nodes)[:self.num]
640
+ else:
641
+ choice = np.concatenate([
642
+ np.random.permutation(num_nodes)
643
+ for _ in range(math.ceil(self.num / num_nodes))
644
+ ])[:self.num]
645
+
646
+ for key, value in list(data.items()):
647
+ if key == 'num_nodes':
648
+ data.num_nodes = len(choice)
649
+ elif bool(re.search('edge', key)):
650
+ continue
651
+ elif _is_tensor(value) and value.shape[0] == num_nodes and value.shape[0] != 1:
652
+ data[key] = _out(_np(value)[choice], value)
653
+
654
+ return data
655
+
656
+ def __repr__(self) -> str:
657
+ return f'{self.__class__.__name__}({self.num}, replace={self.replace})'
658
+
659
+
660
+ @functional_transform('generate_mesh_normals')
661
+ class GenerateMeshNormals(BaseTransform):
662
+ r"""Generate normal vectors for each mesh node based on neighboring faces
663
+ (functional name: :obj:`generate_mesh_normals`)."""
664
+ def forward(self, data: Data) -> Data:
665
+ assert data.pos is not None
666
+ assert data.face is not None
667
+ pos, face = _np(data.pos), _np(data.face)
668
+
669
+ face_norm = _normalize(np.cross(pos[face[1]] - pos[face[0]], pos[face[2]] - pos[face[0]]))
670
+ norm = _scatter_sum(np.tile(face_norm, (3, 1)), face.reshape(-1), pos.shape[0])
671
+ data.norm = _out(_normalize(norm), data.pos)
672
+ return data
673
+
674
+
675
+ @functional_transform('delaunay')
676
+ class Delaunay(BaseTransform):
677
+ r"""Computes the delaunay triangulation of a set of points
678
+ (functional name: :obj:`delaunay`)."""
679
+ def forward(self, data: Data) -> Data:
680
+ assert data.pos is not None
681
+ num_points = data.pos.shape[0]
682
+
683
+ if num_points < 2:
684
+ data.edge_index = _out(np.zeros((2, 0)), None, dtype="int64")
685
+ elif num_points == 2:
686
+ data.edge_index = _out(np.array([[0, 1], [1, 0]]), None, dtype="int64")
687
+ elif num_points == 3:
688
+ data.face = _out(np.array([[0], [1], [2]]), None, dtype="int64")
689
+ else:
690
+ try:
691
+ import scipy.spatial
692
+ tri = scipy.spatial.Delaunay(_np(data.pos), qhull_options='QJ')
693
+ except Exception as e:
694
+ raise RuntimeError(f"Delaunay triangulation failed: {e}")
695
+ data.face = _out(tri.simplices.T, None, dtype="int64")
696
+
697
+ return data
698
+
699
+
700
+ @functional_transform('to_slic')
701
+ class ToSLIC(BaseTransform):
702
+ r"""Converts an image of shape ``[channels, height, width]`` to a superpixel graph using SLIC
703
+ (functional name: :obj:`to_slic`)."""
704
+ def __init__(
705
+ self,
706
+ add_seg: bool = False,
707
+ add_img: bool = False,
708
+ **kwargs: Any,
709
+ ) -> None:
710
+ self.add_seg = add_seg
711
+ self.add_img = add_img
712
+ self.kwargs = kwargs
713
+
714
+ def forward(self, img: Any) -> Data:
715
+ try:
716
+ from skimage.segmentation import slic
717
+ except ImportError:
718
+ raise ImportError("ToSLIC requires scikit-image to be installed.")
719
+
720
+ img_t = np.transpose(_np(img), (1, 2, 0))
721
+ h, w, c = img_t.shape
722
+
723
+ seg = slic(img_t.astype(np.float64), start_label=0, **self.kwargs)
724
+ num_segments = int(seg.max()) + 1
725
+ x = _scatter_mean(img_t.reshape(h * w, c), seg.reshape(-1), num_segments)
726
+
727
+ pos_y, pos_x = np.meshgrid(np.arange(h, dtype=np.float32), np.arange(w, dtype=np.float32), indexing="ij")
728
+ pos = _scatter_mean(np.stack([pos_x.reshape(-1), pos_y.reshape(-1)], axis=-1), seg.reshape(-1), num_segments)
729
+
730
+ data = Data(x=_out(x, img), pos=_out(pos, img, dtype="float32"))
731
+ if self.add_seg:
732
+ data.seg = _out(seg.reshape(1, h, w), None, dtype="int64")
733
+ if self.add_img:
734
+ data.img = _out(np.transpose(img_t, (2, 0, 1)).reshape(1, c, h, w), img)
735
+ return data
736
+
737
+
738
+ @functional_transform('grid_sampling')
739
+ class GridSampling(BaseTransform):
740
+ r"""Clusters points into fixed-sized voxels (functional name: :obj:`grid_sampling`)."""
741
+ def __init__(
742
+ self,
743
+ size: Union[float, List[float], Any],
744
+ start: Optional[Union[float, List[float], Any]] = None,
745
+ end: Optional[Union[float, List[float], Any]] = None,
746
+ ) -> None:
747
+ self.size = size
748
+ self.start = start
749
+ self.end = end
750
+
751
+ def forward(self, data: Data) -> Data:
752
+ num_nodes = data.num_nodes
753
+ assert data.pos is not None
754
+
755
+ pos = _np(data.pos)
756
+ if pos.ndim == 1:
757
+ pos = pos[:, None]
758
+ dim = pos.shape[-1]
759
+
760
+ size = np.broadcast_to(np.asarray(_np(self.size), dtype=pos.dtype), (dim,))
761
+ start = pos.min(axis=0) if self.start is None else np.broadcast_to(
762
+ np.asarray(_np(self.start), dtype=pos.dtype), (dim,))
763
+
764
+ coord = np.floor((pos - start) / size).astype(np.int64)
765
+ if getattr(data, 'batch', None) is not None:
766
+ coord = np.concatenate([coord, _np(data.batch).reshape(-1, 1)], axis=-1)
767
+
768
+ unique, c = np.unique(coord, axis=0, return_inverse=True)
769
+ c = c.reshape(-1)
770
+ num_clusters = unique.shape[0]
771
+ perm = np.zeros(num_clusters, dtype=np.int64)
772
+ perm[c] = np.arange(len(c))
773
+
774
+ for key, item in list(data.items()):
775
+ if bool(re.search('edge', key)):
776
+ raise ValueError(f"'{self.__class__.__name__}' does not support coarsening of edges")
777
+
778
+ if _is_tensor(item) and item.shape[0] == num_nodes:
779
+ value = _np(item)
780
+ if key == 'y':
781
+ one_hot = np.eye(int(value.max()) + 1, dtype=np.float32)[value]
782
+ data[key] = _out(_scatter_sum(one_hot, c, num_clusters).argmax(axis=-1), item)
783
+ elif key == 'batch':
784
+ data[key] = _out(value[perm], item)
785
+ else:
786
+ data[key] = _out(_scatter_mean(value, c, num_clusters), item)
787
+
788
+ data.num_nodes = num_clusters
789
+ return data
790
+
791
+ def __repr__(self) -> str:
792
+ return f'{self.__class__.__name__}(size={self.size})'
793
+
794
+
795
+ class RandomTranslate(RandomJitter):
796
+ r"""Alias for RandomJitter."""
797
+ pass