tgraphx 0.4.1__tar.gz → 0.4.2__tar.gz

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 (217) hide show
  1. {tgraphx-0.4.1 → tgraphx-0.4.2}/PKG-INFO +1 -1
  2. {tgraphx-0.4.1 → tgraphx-0.4.2}/pyproject.toml +1 -1
  3. tgraphx-0.4.2/tests/test_mining_centrality.py +215 -0
  4. tgraphx-0.4.2/tests/test_mining_generators_spectral.py +259 -0
  5. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/__init__.py +1 -1
  6. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/dashboard/app.py +50 -0
  7. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/dashboard/static/dashboard.css +75 -0
  8. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/dashboard/static/dashboard.js +256 -0
  9. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/__init__.py +120 -0
  10. tgraphx-0.4.2/tgraphx/mining/api.py +234 -0
  11. tgraphx-0.4.2/tgraphx/mining/centrality.py +608 -0
  12. tgraphx-0.4.2/tgraphx/mining/embeddings.py +207 -0
  13. tgraphx-0.4.2/tgraphx/mining/generators.py +468 -0
  14. tgraphx-0.4.2/tgraphx/mining/label_prop.py +194 -0
  15. tgraphx-0.4.2/tgraphx/mining/spectral.py +340 -0
  16. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/PKG-INFO +1 -1
  17. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/SOURCES.txt +8 -0
  18. {tgraphx-0.4.1 → tgraphx-0.4.2}/LICENSE +0 -0
  19. {tgraphx-0.4.1 → tgraphx-0.4.2}/README.md +0 -0
  20. {tgraphx-0.4.1 → tgraphx-0.4.2}/setup.cfg +0 -0
  21. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_3d_support.py +0 -0
  22. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_algorithms.py +0 -0
  23. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_amp_compile.py +0 -0
  24. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_backward_compatibility.py +0 -0
  25. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_benchmark_smoke.py +0 -0
  26. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_chunking.py +0 -0
  27. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dashboard.py +0 -0
  28. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dashboard_metadata.py +0 -0
  29. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dataset_cache.py +0 -0
  30. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dataset_docs_claims.py +0 -0
  31. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dataset_download_mocked.py +0 -0
  32. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_dataset_registry.py +0 -0
  33. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_datasets_base.py +0 -0
  34. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_devices.py +0 -0
  35. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_distributed_compat.py +0 -0
  36. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_documentation_claims.py +0 -0
  37. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_edge_features.py +0 -0
  38. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_edge_weight.py +0 -0
  39. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_experiments.py +0 -0
  40. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_explainability.py +0 -0
  41. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_factories.py +0 -0
  42. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_folder_datasets.py +0 -0
  43. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_gat_chunking.py +0 -0
  44. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_gnn_families.py +0 -0
  45. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_gradients.py +0 -0
  46. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_graph.py +0 -0
  47. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_graph_api.py +0 -0
  48. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_graph_builders.py +0 -0
  49. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_graph_transformer_v027.py +0 -0
  50. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_graph_utils.py +0 -0
  51. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_hard_negative_sampling.py +0 -0
  52. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_hetero_batch.py +0 -0
  53. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_hetero_layers.py +0 -0
  54. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_hetero_sampling.py +0 -0
  55. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_imports.py +0 -0
  56. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_layers.py +0 -0
  57. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_math.py +0 -0
  58. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_math_invariants_v030.py +0 -0
  59. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_metrics.py +0 -0
  60. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_mining_core.py +0 -0
  61. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_mining_structural.py +0 -0
  62. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_model_zoo.py +0 -0
  63. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_models.py +0 -0
  64. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_negative_sampling.py +0 -0
  65. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_neural_mining.py +0 -0
  66. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_neural_mining_batched.py +0 -0
  67. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_packaging.py +0 -0
  68. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_performance_smoke.py +0 -0
  69. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_plotting.py +0 -0
  70. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_public_benchmarks.py +0 -0
  71. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_pyg_dgl_ogb_wrappers.py +0 -0
  72. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_random_walk_sample.py +0 -0
  73. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_release_validation_v030.py +0 -0
  74. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_reproducibility.py +0 -0
  75. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_sampling.py +0 -0
  76. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_sampling_loaders.py +0 -0
  77. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_synthetic_datasets.py +0 -0
  78. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_temporal_sampling.py +0 -0
  79. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_temporal_v025.py +0 -0
  80. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_time_encoding.py +0 -0
  81. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_tiny_overfit_v030.py +0 -0
  82. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_torchvision_wrappers.py +0 -0
  83. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_tracking.py +0 -0
  84. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_training.py +0 -0
  85. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_transforms.py +0 -0
  86. {tgraphx-0.4.1 → tgraphx-0.4.2}/tests/test_v024_features.py +0 -0
  87. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/algorithms/__init__.py +0 -0
  88. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/algorithms/connectivity.py +0 -0
  89. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/algorithms/structural.py +0 -0
  90. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/algorithms/traversal.py +0 -0
  91. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/__init__.py +0 -0
  92. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/dataloader.py +0 -0
  93. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/graph.py +0 -0
  94. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/graph_utils.py +0 -0
  95. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/hetero_batch.py +0 -0
  96. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/hetero_graph.py +0 -0
  97. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/temporal.py +0 -0
  98. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/temporal_batch.py +0 -0
  99. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/core/utils.py +0 -0
  100. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/dashboard/__init__.py +0 -0
  101. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/dashboard/__main__.py +0 -0
  102. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/__init__.py +0 -0
  103. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/base.py +0 -0
  104. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/cache.py +0 -0
  105. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/converters.py +0 -0
  106. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/dgl_wrappers.py +0 -0
  107. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/download.py +0 -0
  108. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/errors.py +0 -0
  109. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/folder.py +0 -0
  110. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/metadata.py +0 -0
  111. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/ogb_wrappers.py +0 -0
  112. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/pyg_wrappers.py +0 -0
  113. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/registry.py +0 -0
  114. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/synthetic.py +0 -0
  115. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/datasets/torchvision_wrappers.py +0 -0
  116. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/distributed.py +0 -0
  117. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/__init__.py +0 -0
  118. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/callbacks.py +0 -0
  119. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/checkpoints.py +0 -0
  120. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/cli.py +0 -0
  121. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/config.py +0 -0
  122. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/grid.py +0 -0
  123. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/runner.py +0 -0
  124. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/experiments/summarize.py +0 -0
  125. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/__init__.py +0 -0
  126. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/attention.py +0 -0
  127. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/edge_attribution.py +0 -0
  128. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/export.py +0 -0
  129. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/integrated_gradients.py +0 -0
  130. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/patch_heatmap.py +0 -0
  131. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/saliency.py +0 -0
  132. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/explain/utils.py +0 -0
  133. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/graph_builders.py +0 -0
  134. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/hetero_sampling.py +0 -0
  135. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/interop.py +0 -0
  136. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/__init__.py +0 -0
  137. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/_dim.py +0 -0
  138. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/_scatter.py +0 -0
  139. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/aggregator.py +0 -0
  140. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/appnp.py +0 -0
  141. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/attention_message.py +0 -0
  142. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/base.py +0 -0
  143. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/conv_message.py +0 -0
  144. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/factory.py +0 -0
  145. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/gat.py +0 -0
  146. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/gatv2.py +0 -0
  147. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/gin.py +0 -0
  148. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/graph_transformer.py +0 -0
  149. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/hetero.py +0 -0
  150. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/hetero_readout.py +0 -0
  151. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/pooling.py +0 -0
  152. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/safe_pool.py +0 -0
  153. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/sage.py +0 -0
  154. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/temporal_readout.py +0 -0
  155. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/transformer_encodings.py +0 -0
  156. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/layers/vector_gcn.py +0 -0
  157. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/learned_graph.py +0 -0
  158. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/__init__.py +0 -0
  159. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/classification.py +0 -0
  160. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/link_prediction.py +0 -0
  161. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/ogb.py +0 -0
  162. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/ranking.py +0 -0
  163. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/regression.py +0 -0
  164. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/metrics/reports.py +0 -0
  165. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/anomaly.py +0 -0
  166. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/communities.py +0 -0
  167. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/frequent.py +0 -0
  168. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/hetero.py +0 -0
  169. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/kernels.py +0 -0
  170. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/link_prediction.py +0 -0
  171. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/motifs.py +0 -0
  172. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/neural.py +0 -0
  173. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/patterns.py +0 -0
  174. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/prototype.py +0 -0
  175. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/random_walk.py +0 -0
  176. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/reports.py +0 -0
  177. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/similarity.py +0 -0
  178. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/structural.py +0 -0
  179. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/mining/temporal.py +0 -0
  180. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/__init__.py +0 -0
  181. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/cnn_encoder.py +0 -0
  182. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/cnn_gnn_model.py +0 -0
  183. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/edge_predictor.py +0 -0
  184. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/factory.py +0 -0
  185. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/graph_classifier.py +0 -0
  186. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/hetero_models.py +0 -0
  187. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/model_zoo.py +0 -0
  188. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/node_classifier.py +0 -0
  189. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/pre_encoder.py +0 -0
  190. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/regressors.py +0 -0
  191. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/models/temporal_models.py +0 -0
  192. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/performance.py +0 -0
  193. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/plotting/__init__.py +0 -0
  194. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/plotting/graph.py +0 -0
  195. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/plotting/layouts.py +0 -0
  196. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/plotting/mining.py +0 -0
  197. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/plotting/utils.py +0 -0
  198. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/reproducibility.py +0 -0
  199. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/sampling.py +0 -0
  200. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/sampling_loaders.py +0 -0
  201. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/sampling_negative.py +0 -0
  202. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/temporal/__init__.py +0 -0
  203. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/temporal/time_encoding.py +0 -0
  204. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/temporal_sampling.py +0 -0
  205. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/tracking.py +0 -0
  206. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/training.py +0 -0
  207. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/__init__.py +0 -0
  208. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/compose.py +0 -0
  209. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/features.py +0 -0
  210. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/graph.py +0 -0
  211. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/patch.py +0 -0
  212. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/positional.py +0 -0
  213. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx/transforms/splits.py +0 -0
  214. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/dependency_links.txt +0 -0
  215. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/entry_points.txt +0 -0
  216. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/requires.txt +0 -0
  217. {tgraphx-0.4.1 → tgraphx-0.4.2}/tgraphx.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tgraphx
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Tensor-aware graph neural networks preserving spatial node feature layouts
5
5
  Author-email: Arash Sajjadi <arash.sajjadi@usask.ca>
6
6
  Maintainer-email: Arash Sajjadi <arash.sajjadi@usask.ca>
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "tgraphx"
7
7
  # Keep this in sync with tgraphx/__init__.py::__version__
8
- version = "0.4.1"
8
+ version = "0.4.2"
9
9
  description = "Tensor-aware graph neural networks preserving spatial node feature layouts"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -0,0 +1,215 @@
1
+ """Tests for tgraphx.mining.centrality — centrality algorithms."""
2
+ import math
3
+ import pytest
4
+ import torch
5
+ from tgraphx.mining import (
6
+ degree_centrality, pagerank, personalized_pagerank, hits,
7
+ katz_centrality, closeness_centrality, harmonic_centrality,
8
+ betweenness_centrality, eigenvector_centrality, k_core_numbers,
9
+ in_degree_centrality, out_degree_centrality,
10
+ )
11
+
12
+
13
+ def _chain(N=4):
14
+ src = list(range(N-1)) + list(range(1, N))
15
+ dst = list(range(1, N)) + list(range(N-1))
16
+ return torch.tensor([src, dst], dtype=torch.long), N
17
+
18
+
19
+ def _star(N=5):
20
+ src = [0]*(N-1) + list(range(1, N))
21
+ dst = list(range(1, N)) + [0]*(N-1)
22
+ return torch.tensor([src, dst], dtype=torch.long), N
23
+
24
+
25
+ def _triangle():
26
+ ei = torch.tensor([[0,1,2,1,2,0],[1,2,0,0,1,2]], dtype=torch.long)
27
+ return ei, 3
28
+
29
+
30
+ class TestDegreeCentrality:
31
+ def test_star_hub_highest(self):
32
+ ei, N = _star()
33
+ dc = degree_centrality(ei, N, directed=False)
34
+ assert float(dc[0]) == float(dc.max())
35
+
36
+ def test_range_zero_one(self):
37
+ ei, N = _chain()
38
+ dc = degree_centrality(ei, N)
39
+ assert (dc >= 0).all() and (dc <= 1).all()
40
+
41
+ def test_isolated_zero(self):
42
+ ei = torch.zeros((2, 0), dtype=torch.long)
43
+ dc = degree_centrality(ei, 5)
44
+ assert dc.sum() == 0.0
45
+
46
+ def test_complete_all_one(self):
47
+ from tgraphx.mining import complete_graph
48
+ ei, N = complete_graph(4)
49
+ dc = degree_centrality(ei, N, directed=False)
50
+ assert all(abs(float(v) - 1.0) < 1e-5 for v in dc.tolist())
51
+
52
+
53
+ class TestPageRank:
54
+ def test_sums_to_one(self):
55
+ ei, N = _chain()
56
+ pr = pagerank(ei, N)
57
+ assert abs(float(pr.sum()) - 1.0) < 1e-4
58
+
59
+ def test_non_negative(self):
60
+ ei, N = _chain()
61
+ pr = pagerank(ei, N)
62
+ assert (pr >= 0).all()
63
+
64
+ def test_uniform_on_symmetric_complete(self):
65
+ from tgraphx.mining import complete_graph
66
+ ei, N = complete_graph(4)
67
+ pr = pagerank(ei, N)
68
+ assert pr.std().item() < 0.01 # all equal for symmetric complete graph
69
+
70
+ def test_empty_graph(self):
71
+ ei = torch.zeros((2, 0), dtype=torch.long)
72
+ pr = pagerank(ei, 4)
73
+ # All equal: 1/N
74
+ assert pr.shape == (4,)
75
+ assert (pr >= 0).all()
76
+
77
+ def test_deterministic(self):
78
+ ei, N = _chain()
79
+ pr1 = pagerank(ei, N)
80
+ pr2 = pagerank(ei, N)
81
+ assert torch.equal(pr1, pr2)
82
+
83
+
84
+ class TestPersonalisedPageRank:
85
+ def test_teleport_to_seed(self):
86
+ """PPR from node 0 should assign higher weight to node 0's side."""
87
+ ei, N = _chain(6)
88
+ pers = torch.zeros(6)
89
+ pers[0] = 1.0
90
+ ppr = personalized_pagerank(ei, N, pers)
91
+ assert float(ppr[0]) >= float(ppr[N-1])
92
+
93
+ def test_sums_to_one(self):
94
+ ei, N = _chain()
95
+ pers = torch.ones(N)
96
+ ppr = personalized_pagerank(ei, N, pers)
97
+ assert abs(float(ppr.sum()) - 1.0) < 1e-4
98
+
99
+
100
+ class TestHITS:
101
+ def test_shapes(self):
102
+ ei, N = _chain()
103
+ h, a = hits(ei, N)
104
+ assert h.shape == (N,) and a.shape == (N,)
105
+
106
+ def test_non_negative(self):
107
+ ei, N = _chain()
108
+ h, a = hits(ei, N)
109
+ assert (h >= 0).all() and (a >= 0).all()
110
+
111
+ def test_empty_graph(self):
112
+ ei = torch.zeros((2, 0), dtype=torch.long)
113
+ h, a = hits(ei, 3)
114
+ assert h.shape == (3,) and a.shape == (3,)
115
+
116
+
117
+ class TestKatzCentrality:
118
+ def test_non_negative(self):
119
+ ei, N = _chain()
120
+ k = katz_centrality(ei, N)
121
+ assert (k >= 0).all()
122
+
123
+ def test_higher_degree_higher_katz(self):
124
+ # In a chain, middle nodes should have >= katz as leaves.
125
+ ei, N = _chain(5)
126
+ k = katz_centrality(ei, N)
127
+ assert float(k[0]) <= float(k[2]) # leaf <= middle
128
+
129
+
130
+ class TestClosenessCentrality:
131
+ def test_hand_computed_triangle(self):
132
+ """All K3 nodes have closeness = 1 (distance to each other node = 1)."""
133
+ ei, N = _triangle()
134
+ cc = closeness_centrality(ei, N)
135
+ for v in cc.tolist():
136
+ assert abs(v - 1.0) < 1e-5
137
+
138
+ def test_path_center_highest(self):
139
+ ei, N = _chain(5) # 0-1-2-3-4
140
+ cc = closeness_centrality(ei, N)
141
+ # Node 2 (center) has highest closeness.
142
+ assert int(cc.argmax().item()) == 2
143
+
144
+ def test_size_guard(self):
145
+ ei = torch.zeros((2, 0), dtype=torch.long)
146
+ with pytest.raises(ValueError, match="max_nodes_exact"):
147
+ closeness_centrality(ei, 2001, max_nodes_exact=2000)
148
+
149
+
150
+ class TestHarmonicCentrality:
151
+ def test_non_negative(self):
152
+ ei, N = _chain()
153
+ hc = harmonic_centrality(ei, N)
154
+ assert (hc >= 0).all()
155
+
156
+ def test_star_hub_highest(self):
157
+ ei, N = _star()
158
+ hc = harmonic_centrality(ei, N)
159
+ assert int(hc.argmax().item()) == 0
160
+
161
+
162
+ class TestBetweennessCentrality:
163
+ def test_star_hub_highest(self):
164
+ """Hub node 0 lies on all shortest paths between leaves."""
165
+ ei, N = _star()
166
+ bc = betweenness_centrality(ei, N)
167
+ assert int(bc.argmax().item()) == 0
168
+
169
+ def test_path_middle_highest(self):
170
+ ei, N = _chain(5)
171
+ bc = betweenness_centrality(ei, N)
172
+ # Middle node 2 has highest betweenness in a 5-node path.
173
+ assert int(bc.argmax().item()) == 2
174
+
175
+ def test_triangle_all_zero(self):
176
+ """In K3, no node lies strictly between two others (only direct edges)."""
177
+ ei, N = _triangle()
178
+ bc = betweenness_centrality(ei, N)
179
+ assert bc.max().item() < 1e-6
180
+
181
+ def test_size_guard(self):
182
+ ei = torch.zeros((2, 0), dtype=torch.long)
183
+ with pytest.raises(ValueError, match="max_nodes_exact"):
184
+ betweenness_centrality(ei, 501, max_nodes_exact=500)
185
+
186
+
187
+ class TestEigenvectorCentrality:
188
+ def test_non_negative(self):
189
+ ei, N = _chain()
190
+ ec = eigenvector_centrality(ei, N)
191
+ assert (ec >= 0).all()
192
+
193
+ def test_star_hub_highest(self):
194
+ ei, N = _star()
195
+ ec = eigenvector_centrality(ei, N)
196
+ assert int(ec.argmax().item()) == 0
197
+
198
+
199
+ class TestKCoreNumbers:
200
+ def test_path_core_one(self):
201
+ """In a path graph all nodes have k-core = 1 (or 0 for isolated)."""
202
+ ei, N = _chain()
203
+ kc = k_core_numbers(ei, N)
204
+ assert kc.max().item() == 1
205
+
206
+ def test_triangle_core_two(self):
207
+ """In K3 all nodes have k-core = 2."""
208
+ ei, N = _triangle()
209
+ kc = k_core_numbers(ei, N)
210
+ assert kc.min().item() == 2 and kc.max().item() == 2
211
+
212
+ def test_isolated_core_zero(self):
213
+ ei = torch.zeros((2, 0), dtype=torch.long)
214
+ kc = k_core_numbers(ei, 5)
215
+ assert kc.max().item() == 0
@@ -0,0 +1,259 @@
1
+ """Tests for graph generators, spectral analysis, label propagation, and embeddings."""
2
+ import pytest
3
+ import torch
4
+ from tgraphx.mining import (
5
+ erdos_renyi_graph, barabasi_albert_graph, stochastic_block_model_graph,
6
+ watts_strogatz_graph, complete_graph, cycle_graph, path_graph, star_graph,
7
+ karate_club_graph, synthetic_anomaly_graph, motif_injected_graph,
8
+ graph_laplacian, normalized_laplacian, laplacian_eigenvalues,
9
+ fiedler_vector, algebraic_connectivity, laplacian_eigvec_positional_encoding,
10
+ spectral_clustering, spectral_distance, dirichlet_energy,
11
+ label_propagation, LabelPropagationClassifier,
12
+ extract_node_embeddings, extract_graph_embeddings,
13
+ embedding_similarity_matrix, embedding_pairwise_distances,
14
+ embedding_nearest_neighbors,
15
+ analyze_graph,
16
+ )
17
+
18
+
19
+ class TestGenerators:
20
+ def test_erdos_renyi_deterministic(self):
21
+ ei1, N1 = erdos_renyi_graph(20, 0.3, seed=0)
22
+ ei2, N2 = erdos_renyi_graph(20, 0.3, seed=0)
23
+ assert torch.equal(ei1, ei2) and N1 == N2 == 20
24
+
25
+ def test_erdos_renyi_density(self):
26
+ """Dense ER graph should have more edges than sparse one."""
27
+ ei_dense, _ = erdos_renyi_graph(20, 0.8, seed=42)
28
+ ei_sparse, _ = erdos_renyi_graph(20, 0.1, seed=42)
29
+ assert ei_dense.size(1) > ei_sparse.size(1)
30
+
31
+ def test_barabasi_albert_connects(self):
32
+ ei, N = barabasi_albert_graph(20, m=2, seed=0)
33
+ assert N == 20
34
+ assert ei.size(1) > 0
35
+
36
+ def test_sbm_communities_larger_p_in(self):
37
+ """SBM with higher p_in should have more intra-community edges."""
38
+ ei, N, labels = stochastic_block_model_graph([5, 5], p_in=0.8, p_out=0.05, seed=0)
39
+ intra = sum(int(labels[ei[0,k]]) == int(labels[ei[1,k]]) for k in range(ei.size(1)))
40
+ inter = ei.size(1) - intra
41
+ assert intra > inter
42
+
43
+ def test_watts_strogatz_shape(self):
44
+ ei, N = watts_strogatz_graph(10, k=4, p=0.1, seed=0)
45
+ assert N == 10
46
+
47
+ def test_complete_graph_edges(self):
48
+ ei, N = complete_graph(4)
49
+ assert ei.size(1) == 4 * 3 # 12 directed edges in K4
50
+
51
+ def test_cycle_graph_edges(self):
52
+ ei, N = cycle_graph(5)
53
+ assert ei.size(1) == 10 # 5 undirected * 2 directions
54
+
55
+ def test_path_graph_edges(self):
56
+ ei, N = path_graph(5)
57
+ assert ei.size(1) == 8 # 4 undirected edges
58
+
59
+ def test_star_graph_hub_degree(self):
60
+ ei, N = star_graph(5)
61
+ deg = torch.zeros(N, dtype=torch.long)
62
+ ones = torch.ones(ei.size(1), dtype=torch.long)
63
+ deg.scatter_add_(0, ei[0], ones)
64
+ assert int(deg[0]) == 4 # hub has degree 4
65
+
66
+ def test_karate_club(self):
67
+ ei, N = karate_club_graph()
68
+ assert N == 34
69
+
70
+ def test_anomaly_graph_mask_matches(self):
71
+ ei, N, mask = synthetic_anomaly_graph(20, num_anomalous=3, seed=0)
72
+ assert int(mask.sum()) == 3
73
+
74
+ def test_motif_injected(self):
75
+ ei, N, tri_nodes = motif_injected_graph(20, num_triangles=2, seed=0)
76
+ assert tri_nodes.numel() == 6
77
+
78
+
79
+ class TestSpectral:
80
+ def test_laplacian_positive_semidefinite(self):
81
+ """Laplacian eigenvalues should be >= 0."""
82
+ ei, N = path_graph(5)
83
+ L = graph_laplacian(ei, N)
84
+ evals = torch.linalg.eigvalsh(L)
85
+ assert (evals >= -1e-6).all()
86
+
87
+ def test_laplacian_row_sums_zero(self):
88
+ """L * 1 = 0 (row sums are zero)."""
89
+ ei, N = complete_graph(4)
90
+ L = graph_laplacian(ei, N)
91
+ row_sums = L.sum(dim=1)
92
+ assert (row_sums.abs() < 1e-5).all()
93
+
94
+ def test_normalized_laplacian_spectrum_range(self):
95
+ """Normalised Laplacian eigenvalues should be in [0, 2]."""
96
+ ei, N = cycle_graph(6)
97
+ L_norm = normalized_laplacian(ei, N)
98
+ evals = torch.linalg.eigvalsh(L_norm)
99
+ assert float(evals.min()) >= -1e-6
100
+ assert float(evals.max()) <= 2.0 + 1e-6
101
+
102
+ def test_fiedler_vector_orthogonal_to_ones(self):
103
+ """Fiedler vector should be approximately orthogonal to the all-ones vector."""
104
+ ei, N = path_graph(6)
105
+ fv, lam2 = fiedler_vector(ei, N)
106
+ dot = float(fv.sum().abs().item())
107
+ assert dot < 0.1
108
+
109
+ def test_algebraic_connectivity_zero_disconnected(self):
110
+ ei = torch.tensor([[0,1],[1,0]], dtype=torch.long) # 2 isolated components
111
+ N = 4 # nodes 2,3 are isolated
112
+ lam2 = algebraic_connectivity(ei, N)
113
+ assert lam2 < 1e-6 # disconnected → λ₂ = 0
114
+
115
+ def test_algebraic_connectivity_positive_connected(self):
116
+ ei, N = complete_graph(4)
117
+ lam2 = algebraic_connectivity(ei, N)
118
+ assert lam2 > 0
119
+
120
+ def test_laplacian_pe_shape(self):
121
+ ei, N = path_graph(8)
122
+ enc = laplacian_eigvec_positional_encoding(ei, N, k=4)
123
+ assert enc.shape == (N, 4)
124
+ assert enc.dtype == torch.float32
125
+
126
+ def test_spectral_clustering_shape(self):
127
+ ei, N, labels = stochastic_block_model_graph([4, 4], p_in=0.8, p_out=0.05, seed=0)
128
+ cluster_labels = spectral_clustering(ei, N, num_clusters=2, seed=0)
129
+ assert cluster_labels.shape == (N,)
130
+ assert set(cluster_labels.tolist()) <= {0, 1}
131
+
132
+ def test_spectral_distance_zero_same_graph(self):
133
+ ei, N = path_graph(5)
134
+ d = spectral_distance(ei, N, ei, N)
135
+ assert d < 1e-5
136
+
137
+ def test_spectral_distance_positive_different(self):
138
+ ei1, N1 = path_graph(5)
139
+ ei2, N2 = complete_graph(5)
140
+ d = spectral_distance(ei1, N1, ei2, N2)
141
+ assert d > 0
142
+
143
+ def test_dirichlet_energy_smooth(self):
144
+ """Constant node features should have zero Dirichlet energy."""
145
+ ei, N = path_graph(5)
146
+ x = torch.ones(N, 4)
147
+ e = dirichlet_energy(x, ei, N)
148
+ assert abs(e) < 1e-5
149
+
150
+ def test_spectral_size_guard(self):
151
+ ei = torch.zeros((2, 0), dtype=torch.long)
152
+ with pytest.raises(ValueError, match="max_nodes"):
153
+ graph_laplacian(ei, 2001, max_nodes=2000)
154
+
155
+
156
+ class TestLabelPropagation:
157
+ def _two_clique_data(self):
158
+ """Two connected cliques + labels only on clique centers."""
159
+ # Clique 1: 0,1,2 Clique 2: 3,4,5
160
+ src = [0,0,1,3,3,4,0,1,2,3,4,5]
161
+ dst = [1,2,2,4,5,5,0,1,2,3,4,5]
162
+ ei = torch.tensor([src+dst, dst+src], dtype=torch.long)
163
+ ei = torch.unique(ei, dim=1)
164
+ N = 6
165
+ y = torch.tensor([0, 0, 0, 1, 1, 1], dtype=torch.long)
166
+ # Only reveal label for node 0 and node 3.
167
+ mask = torch.tensor([True, False, False, True, False, False])
168
+ return ei, N, y, mask
169
+
170
+ def test_propagates_correct_labels(self):
171
+ ei, N, y, mask = self._two_clique_data()
172
+ Z = label_propagation(ei, N, y, mask, num_classes=2)
173
+ preds = Z.argmax(dim=1)
174
+ # Nodes 1,2 should get class 0; nodes 4,5 should get class 1.
175
+ assert int(preds[1]) == 0 and int(preds[2]) == 0
176
+ assert int(preds[4]) == 1 and int(preds[5]) == 1
177
+
178
+ def test_output_shape(self):
179
+ ei, N, y, mask = self._two_clique_data()
180
+ Z = label_propagation(ei, N, y, mask, num_classes=2)
181
+ assert Z.shape == (N, 2)
182
+
183
+ def test_labeled_nodes_stay_close_to_seed(self):
184
+ """Labeled nodes should keep high probability for their own class."""
185
+ ei, N, y, mask = self._two_clique_data()
186
+ Z = label_propagation(ei, N, y, mask, num_classes=2, alpha=0.5)
187
+ # Node 0 labeled class 0 — still high probability class 0.
188
+ assert float(Z[0, 0]) >= float(Z[0, 1])
189
+
190
+ def test_classifier_api(self):
191
+ ei, N, y, mask = self._two_clique_data()
192
+ clf = LabelPropagationClassifier(alpha=0.9)
193
+ Z = clf.fit_predict(ei, N, y, mask, num_classes=2)
194
+ preds = clf.predict()
195
+ assert preds.shape == (N,)
196
+ assert Z.shape == (N, 2)
197
+
198
+ def test_deterministic(self):
199
+ ei, N, y, mask = self._two_clique_data()
200
+ Z1 = label_propagation(ei, N, y, mask, num_classes=2)
201
+ Z2 = label_propagation(ei, N, y, mask, num_classes=2)
202
+ assert torch.equal(Z1, Z2)
203
+
204
+
205
+ class TestEmbeddings:
206
+ def test_extract_node_embeddings_shape(self):
207
+ import torch.nn as nn
208
+ model = nn.Sequential(nn.Linear(4, 8), nn.ReLU())
209
+ # Model that ignores edge_index for simplicity.
210
+ class _SimpleModel(nn.Module):
211
+ def __init__(self): super().__init__(); self.lin = nn.Linear(4, 8)
212
+ def forward(self, x, ei): return self.lin(x)
213
+ m = _SimpleModel()
214
+ ei = torch.tensor([[0,1],[1,2]], dtype=torch.long)
215
+ x = torch.randn(3, 4)
216
+ emb = extract_node_embeddings(m, ei, x, no_grad=True)
217
+ assert emb.shape == (3, 8)
218
+ assert not emb.requires_grad
219
+
220
+ def test_extract_graph_embeddings_shape(self):
221
+ class _M(torch.nn.Module):
222
+ def __init__(self): super().__init__(); self.lin = torch.nn.Linear(4, 6)
223
+ def forward(self, x, ei): return self.lin(x)
224
+ m = _M()
225
+ graphs = [
226
+ {"node_features": torch.randn(3, 4), "edge_index": torch.tensor([[0],[1]], dtype=torch.long)},
227
+ {"node_features": torch.randn(4, 4), "edge_index": torch.tensor([[0,1],[1,2]], dtype=torch.long)},
228
+ ]
229
+ embs = extract_graph_embeddings(m, graphs, pooling="mean")
230
+ assert embs.shape == (2, 6)
231
+
232
+ def test_embedding_similarity_cosine_symmetric(self):
233
+ emb = torch.randn(5, 8)
234
+ S = embedding_similarity_matrix(emb, method="cosine")
235
+ assert S.shape == (5, 5)
236
+ assert torch.allclose(S, S.t(), atol=1e-5)
237
+ # Diagonal should be ~1.
238
+ assert (S.diagonal() - 1.0).abs().max() < 1e-4
239
+
240
+ def test_embedding_pairwise_distances_non_negative(self):
241
+ emb = torch.randn(4, 8)
242
+ D = embedding_pairwise_distances(emb)
243
+ assert (D >= 0).all()
244
+ assert (D.diagonal() < 1e-5).all()
245
+
246
+ def test_embedding_nearest_neighbors(self):
247
+ emb = torch.eye(5) # orthogonal — nearest is self
248
+ idx, scores = embedding_nearest_neighbors(emb, emb, k=2, method="cosine")
249
+ assert idx.shape == (5, 2)
250
+ # First nearest neighbour of each is itself.
251
+ assert (idx[:, 0] == torch.arange(5)).all()
252
+
253
+ def test_analyze_graph_runs(self):
254
+ ei, N = path_graph(8)
255
+ result = analyze_graph(ei, N, include_spectral=True)
256
+ assert "summary" in result
257
+ assert "motifs" in result
258
+ assert "centrality" in result
259
+ assert isinstance(result["summary"], dict)
@@ -10,7 +10,7 @@ Common one-liner imports::
10
10
  """
11
11
 
12
12
  # Keep this in sync with [project].version in pyproject.toml.
13
- __version__ = "0.4.1"
13
+ __version__ = "0.4.2"
14
14
 
15
15
  # ── Core data structures ──────────────────────────────────────────────────────
16
16
  from .core.graph import Graph, GraphBatch
@@ -84,6 +84,7 @@ _HTML = b"""<!DOCTYPE html>
84
84
  <section id="sec-overview" class="sec active" aria-labelledby="sec-overview-title"></section>
85
85
  <section id="sec-metrics" class="sec" aria-labelledby="sec-metrics-title"></section>
86
86
  <section id="sec-graph" class="sec" aria-labelledby="sec-graph-title"></section>
87
+ <section id="sec-mining" class="sec" aria-labelledby="sec-mining-title"></section>
87
88
  <section id="sec-hardware" class="sec" aria-labelledby="sec-hardware-title"></section>
88
89
  <section id="sec-logs" class="sec" aria-labelledby="sec-logs-title"></section>
89
90
  <section id="sec-config" class="sec" aria-labelledby="sec-config-title"></section>
@@ -617,6 +618,25 @@ class DashboardHandler(BaseHTTPRequestHandler):
617
618
  self._send_json(_list_runs(logdir))
618
619
  elif endpoint == "config":
619
620
  self._api_config()
621
+ # ── Mining artifact endpoints (v0.4.2+) ────────────────────────────
622
+ elif endpoint == "mining_summary":
623
+ self._api_json_file(logdir, "graph_mining_summary.json")
624
+ elif endpoint == "motif_summary":
625
+ self._api_json_file(logdir, "motif_summary.json")
626
+ elif endpoint == "anomaly_summary":
627
+ self._api_json_file_capped(logdir, "anomaly_summary.json", max_list_rows=50)
628
+ elif endpoint == "community_summary":
629
+ self._api_json_file(logdir, "community_summary.json")
630
+ elif endpoint == "prototype_membership":
631
+ self._api_json_file_capped(logdir, "prototype_membership_report.json", max_list_rows=30)
632
+ elif endpoint == "neural_mining":
633
+ self._api_json_file(logdir, "neural_mining_report.json")
634
+ elif endpoint == "reproducibility":
635
+ self._api_json_file(logdir, "reproducibility_report.json")
636
+ elif endpoint == "mining_benchmark":
637
+ self._api_json_file_capped(logdir, "mining_benchmark_results.json", max_list_rows=50)
638
+ elif endpoint == "link_prediction_summary":
639
+ self._api_json_file_capped(logdir, "link_prediction_summary.json", max_list_rows=30)
620
640
  else:
621
641
  self._send_json({"error": f"Unknown endpoint: {endpoint}"}, 404)
622
642
 
@@ -853,6 +873,35 @@ class DashboardHandler(BaseHTTPRequestHandler):
853
873
  except json.JSONDecodeError as exc:
854
874
  self._send_json({"error": f"JSON parse error in {filename}: {exc}"}, 500)
855
875
 
876
+ def _api_json_file_capped(
877
+ self,
878
+ logdir: str,
879
+ filename: str,
880
+ max_list_rows: int = 50,
881
+ ) -> None:
882
+ """Read a JSON file and cap any top-level lists to max_list_rows.
883
+
884
+ This prevents the dashboard from receiving unmanageably large
885
+ datasets (e.g. hundreds of top-k anomaly nodes). Adds
886
+ ``'_truncated': True`` and the original length to capped lists.
887
+ """
888
+ content = _read_logfile(logdir, filename)
889
+ if content is None:
890
+ self._send_json({"_available": False})
891
+ return
892
+ try:
893
+ data = json.loads(content)
894
+ except json.JSONDecodeError as exc:
895
+ self._send_json({"error": f"JSON parse error in {filename}: {exc}"}, 500)
896
+ return
897
+ if isinstance(data, dict):
898
+ for k, v in list(data.items()):
899
+ if isinstance(v, list) and len(v) > max_list_rows:
900
+ data[k] = v[:max_list_rows]
901
+ data[f"_{k}_total"] = len(v)
902
+ data[f"_{k}_truncated"] = True
903
+ self._send_json(data)
904
+
856
905
  def _api_graph(self, logdir: str) -> None:
857
906
  content = _read_logfile(logdir, "graph_metadata.json")
858
907
  if content is None:
@@ -1073,6 +1122,7 @@ def export_dashboard_html(logdir: str, out_path: str) -> None:
1073
1122
  <section id="sec-overview" class="sec active" aria-labelledby="sec-overview-title"></section>
1074
1123
  <section id="sec-metrics" class="sec" aria-labelledby="sec-metrics-title"></section>
1075
1124
  <section id="sec-graph" class="sec" aria-labelledby="sec-graph-title"></section>
1125
+ <section id="sec-mining" class="sec" aria-labelledby="sec-mining-title"></section>
1076
1126
  <section id="sec-hardware" class="sec" aria-labelledby="sec-hardware-title"></section>
1077
1127
  <section id="sec-logs" class="sec" aria-labelledby="sec-logs-title"></section>
1078
1128
  <section id="sec-config" class="sec" aria-labelledby="sec-config-title"></section>
@@ -857,3 +857,78 @@ select:focus-visible {
857
857
  .pw-bar-wrap { flex: 1; margin: 0 12px; height: 5px; background: var(--border);
858
858
  border-radius: 3px; overflow: hidden; }
859
859
  .pw-bar { height: 100%; border-radius: 3px; transition: width .4s; background: var(--amber); }
860
+
861
+ /* ── Mining panel styles (v0.4.2+) ─────────────────────────────────────── */
862
+ .mining-panel {
863
+ background: var(--card);
864
+ border: 1px solid var(--border);
865
+ border-radius: var(--radius);
866
+ padding: 16px 20px;
867
+ margin-bottom: 16px;
868
+ }
869
+ .mining-panel .panel-title {
870
+ font-size: 1rem;
871
+ font-weight: 600;
872
+ color: var(--text);
873
+ margin: 0 0 12px;
874
+ }
875
+ .mining-cols {
876
+ display: grid;
877
+ grid-template-columns: 1fr 1fr;
878
+ gap: 16px;
879
+ }
880
+ @media (max-width: 600px) {
881
+ .mining-cols { grid-template-columns: 1fr; }
882
+ }
883
+ .kv-table {
884
+ width: 100%;
885
+ border-collapse: collapse;
886
+ font-size: .85rem;
887
+ }
888
+ .kv-table thead th {
889
+ text-align: left;
890
+ padding: 4px 8px;
891
+ color: var(--text2);
892
+ border-bottom: 1px solid var(--border);
893
+ font-weight: 600;
894
+ }
895
+ .kv-table tbody tr:nth-child(even) { background: var(--bg-alt, rgba(0,0,0,.03)); }
896
+ .kv-key {
897
+ padding: 3px 8px;
898
+ color: var(--text2);
899
+ white-space: nowrap;
900
+ font-weight: 500;
901
+ }
902
+ .kv-val {
903
+ padding: 3px 8px;
904
+ color: var(--text);
905
+ font-variant-numeric: tabular-nums;
906
+ word-break: break-all;
907
+ }
908
+ .kv-caption {
909
+ caption-side: top;
910
+ text-align: left;
911
+ font-size: .78rem;
912
+ color: var(--text3);
913
+ padding-bottom: 4px;
914
+ }
915
+ .mining-chart-wrap {
916
+ padding: 4px 0;
917
+ }
918
+ .mining-chart-wrap .chart-label {
919
+ font-size: .78rem;
920
+ color: var(--text2);
921
+ margin: 0 0 4px;
922
+ }
923
+ .empty-panel {
924
+ background: var(--card);
925
+ border: 1px dashed var(--border);
926
+ border-radius: var(--radius);
927
+ padding: 24px;
928
+ text-align: center;
929
+ color: var(--text2);
930
+ }
931
+ .empty-note { color: var(--text3); font-size: .85rem; font-style: italic; }
932
+ .warn-note { color: var(--amber); font-size: .82rem; margin: 4px 0; }
933
+ .loading-note { color: var(--text2); font-size: .9rem; }
934
+ .help-note { font-size: .8rem; color: var(--text3); margin-top: 8px; }