relationalai 0.11.4__py3-none-any.whl → 0.12.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.
- relationalai/clients/config.py +7 -0
- relationalai/clients/direct_access_client.py +113 -0
- relationalai/clients/snowflake.py +35 -106
- relationalai/early_access/metamodel/rewrite/__init__.py +5 -3
- relationalai/early_access/rel/rewrite/__init__.py +1 -1
- relationalai/errors.py +24 -3
- relationalai/semantics/internal/annotations.py +1 -0
- relationalai/semantics/lqp/builtins.py +1 -0
- relationalai/semantics/lqp/passes.py +3 -4
- relationalai/semantics/{rel → lqp}/rewrite/__init__.py +6 -0
- relationalai/semantics/metamodel/builtins.py +12 -1
- relationalai/semantics/metamodel/rewrite/__init__.py +3 -9
- relationalai/semantics/reasoners/graph/core.py +221 -71
- relationalai/semantics/rel/builtins.py +5 -1
- relationalai/semantics/rel/compiler.py +3 -3
- relationalai/semantics/sql/compiler.py +2 -3
- relationalai/semantics/sql/executor/duck_db.py +8 -4
- relationalai/tools/cli.py +11 -4
- {relationalai-0.11.4.dist-info → relationalai-0.12.0.dist-info}/METADATA +5 -4
- {relationalai-0.11.4.dist-info → relationalai-0.12.0.dist-info}/RECORD +29 -30
- relationalai/semantics/metamodel/rewrite/gc_nodes.py +0 -58
- relationalai/semantics/metamodel/rewrite/list_types.py +0 -109
- /relationalai/semantics/{rel → lqp}/rewrite/cdc.py +0 -0
- /relationalai/semantics/{rel → lqp}/rewrite/extract_common.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/extract_keys.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/fd_constraints.py +0 -0
- /relationalai/semantics/{rel → lqp}/rewrite/quantify_vars.py +0 -0
- /relationalai/semantics/{metamodel → lqp}/rewrite/splinter.py +0 -0
- {relationalai-0.11.4.dist-info → relationalai-0.12.0.dist-info}/WHEEL +0 -0
- {relationalai-0.11.4.dist-info → relationalai-0.12.0.dist-info}/entry_points.txt +0 -0
- {relationalai-0.11.4.dist-info → relationalai-0.12.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,6 +20,7 @@ from relationalai.semantics import (
|
|
|
20
20
|
count, sum, avg,
|
|
21
21
|
)
|
|
22
22
|
from relationalai.docutils import include_in_docs
|
|
23
|
+
from relationalai.semantics.internal import annotations
|
|
23
24
|
from relationalai.semantics.std.math import abs, isnan, isinf, maximum, natural_log, sqrt
|
|
24
25
|
|
|
25
26
|
Numeric = Union[int, float, Decimal]
|
|
@@ -1250,6 +1251,8 @@ class Graph():
|
|
|
1250
1251
|
def _num_nodes(self):
|
|
1251
1252
|
"""Lazily define and cache the self._num_nodes relationship."""
|
|
1252
1253
|
_num_nodes_rel = self._model.Relationship("The graph has {num_nodes:Integer} nodes")
|
|
1254
|
+
_num_nodes_rel.annotate(annotations.track("graphs", "num_nodes"))
|
|
1255
|
+
|
|
1253
1256
|
define(_num_nodes_rel(count(self.Node) | 0))
|
|
1254
1257
|
return _num_nodes_rel
|
|
1255
1258
|
|
|
@@ -1316,6 +1319,7 @@ class Graph():
|
|
|
1316
1319
|
def _num_edges(self):
|
|
1317
1320
|
"""Lazily define and cache the self._num_edges relationship."""
|
|
1318
1321
|
_num_edges_rel = self._model.Relationship("The graph has {num_edges:Integer} edges")
|
|
1322
|
+
_num_edges_rel.annotate(annotations.track("graphs", "num_edges"))
|
|
1319
1323
|
|
|
1320
1324
|
src, dst = self.Node.ref(), self.Node.ref()
|
|
1321
1325
|
if self.directed:
|
|
@@ -1468,7 +1472,9 @@ class Graph():
|
|
|
1468
1472
|
@cached_property
|
|
1469
1473
|
def _neighbor(self):
|
|
1470
1474
|
"""Lazily define and cache the self._neighbor relationship."""
|
|
1471
|
-
|
|
1475
|
+
_neighbor_rel = self._create_neighbor_relationship(nodes_subset=None)
|
|
1476
|
+
_neighbor_rel.annotate(annotations.track("graphs", "neighbor"))
|
|
1477
|
+
return _neighbor_rel
|
|
1472
1478
|
|
|
1473
1479
|
def _neighbor_of(self, nodes_subset: Relationship):
|
|
1474
1480
|
"""
|
|
@@ -1476,7 +1482,9 @@ class Graph():
|
|
|
1476
1482
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
1477
1483
|
specific to the callsite.
|
|
1478
1484
|
"""
|
|
1479
|
-
|
|
1485
|
+
_neighbor_rel = self._create_neighbor_relationship(nodes_subset=nodes_subset)
|
|
1486
|
+
_neighbor_rel.annotate(annotations.track("graphs", "neighbor_of"))
|
|
1487
|
+
return _neighbor_rel
|
|
1480
1488
|
|
|
1481
1489
|
def _create_neighbor_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
1482
1490
|
_neighbor_rel = self._model.Relationship(f"{{src:{self._NodeConceptStr}}} has neighbor {{dst:{self._NodeConceptStr}}}")
|
|
@@ -1626,7 +1634,9 @@ class Graph():
|
|
|
1626
1634
|
@cached_property
|
|
1627
1635
|
def _inneighbor(self):
|
|
1628
1636
|
"""Lazily define and cache the self._inneighbor relationship."""
|
|
1629
|
-
|
|
1637
|
+
_inneighbor_rel = self._create_inneighbor_relationship(nodes_subset=None)
|
|
1638
|
+
_inneighbor_rel.annotate(annotations.track("graphs", "inneighbor"))
|
|
1639
|
+
return _inneighbor_rel
|
|
1630
1640
|
|
|
1631
1641
|
def _inneighbor_of(self, nodes_subset: Relationship):
|
|
1632
1642
|
"""
|
|
@@ -1634,7 +1644,9 @@ class Graph():
|
|
|
1634
1644
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
1635
1645
|
specific to the callsite.
|
|
1636
1646
|
"""
|
|
1637
|
-
|
|
1647
|
+
_inneighbor_rel = self._create_inneighbor_relationship(nodes_subset=nodes_subset)
|
|
1648
|
+
_inneighbor_rel.annotate(annotations.track("graphs", "inneighbor_of"))
|
|
1649
|
+
return _inneighbor_rel
|
|
1638
1650
|
|
|
1639
1651
|
def _create_inneighbor_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
1640
1652
|
_inneighbor_rel = self._model.Relationship(f"{{dst:{self._NodeConceptStr}}} has inneighbor {{src:{self._NodeConceptStr}}}")
|
|
@@ -1777,7 +1789,9 @@ class Graph():
|
|
|
1777
1789
|
@cached_property
|
|
1778
1790
|
def _outneighbor(self):
|
|
1779
1791
|
"""Lazily define and cache the self._outneighbor relationship."""
|
|
1780
|
-
|
|
1792
|
+
_outneighbor_rel = self._create_outneighbor_relationship(nodes_subset=None)
|
|
1793
|
+
_outneighbor_rel.annotate(annotations.track("graphs", "outneighbor"))
|
|
1794
|
+
return _outneighbor_rel
|
|
1781
1795
|
|
|
1782
1796
|
def _outneighbor_of(self, nodes_subset: Relationship):
|
|
1783
1797
|
"""
|
|
@@ -1785,7 +1799,9 @@ class Graph():
|
|
|
1785
1799
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
1786
1800
|
specific to the callsite.
|
|
1787
1801
|
"""
|
|
1788
|
-
|
|
1802
|
+
_outneighbor_rel = self._create_outneighbor_relationship(nodes_subset=nodes_subset)
|
|
1803
|
+
_outneighbor_rel.annotate(annotations.track("graphs", "outneighbor_of"))
|
|
1804
|
+
return _outneighbor_rel
|
|
1789
1805
|
|
|
1790
1806
|
def _create_outneighbor_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
1791
1807
|
_outneighbor_rel = self._model.Relationship(f"{{src:{self._NodeConceptStr}}} has outneighbor {{dst:{self._NodeConceptStr}}}")
|
|
@@ -1914,6 +1930,8 @@ class Graph():
|
|
|
1914
1930
|
def _common_neighbor(self):
|
|
1915
1931
|
"""Lazily define and cache the self._common_neighbor relationship."""
|
|
1916
1932
|
_common_neighbor_rel = self._model.Relationship(f"{{node_a:{self._NodeConceptStr}}} and {{node_b:{self._NodeConceptStr}}} have common neighbor {{node_c:{self._NodeConceptStr}}}")
|
|
1933
|
+
_common_neighbor_rel.annotate(annotations.track("graphs", "common_neighbor"))
|
|
1934
|
+
|
|
1917
1935
|
node_a, node_b, node_c = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
1918
1936
|
where(self._neighbor(node_a, node_c), self._neighbor(node_b, node_c)).define(_common_neighbor_rel(node_a, node_b, node_c))
|
|
1919
1937
|
return _common_neighbor_rel
|
|
@@ -2072,7 +2090,9 @@ class Graph():
|
|
|
2072
2090
|
@cached_property
|
|
2073
2091
|
def _degree(self):
|
|
2074
2092
|
"""Lazily define and cache the self._degree relationship."""
|
|
2075
|
-
|
|
2093
|
+
_degree_rel = self._create_degree_relationship(nodes_subset=None)
|
|
2094
|
+
_degree_rel.annotate(annotations.track("graphs", "degree"))
|
|
2095
|
+
return _degree_rel
|
|
2076
2096
|
|
|
2077
2097
|
def _degree_of(self, nodes_subset: Relationship):
|
|
2078
2098
|
"""
|
|
@@ -2080,7 +2100,9 @@ class Graph():
|
|
|
2080
2100
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2081
2101
|
specific to the callsite.
|
|
2082
2102
|
"""
|
|
2083
|
-
|
|
2103
|
+
_degree_rel = self._create_degree_relationship(nodes_subset=nodes_subset)
|
|
2104
|
+
_degree_rel.annotate(annotations.track("graphs", "degree_of"))
|
|
2105
|
+
return _degree_rel
|
|
2084
2106
|
|
|
2085
2107
|
def _create_degree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2086
2108
|
_degree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has degree {{count:Integer}}")
|
|
@@ -2263,7 +2285,9 @@ class Graph():
|
|
|
2263
2285
|
@cached_property
|
|
2264
2286
|
def _indegree(self):
|
|
2265
2287
|
"""Lazily define and cache the self._indegree relationship."""
|
|
2266
|
-
|
|
2288
|
+
_indegree_rel = self._create_indegree_relationship(nodes_subset=None)
|
|
2289
|
+
_indegree_rel.annotate(annotations.track("graphs", "indegree"))
|
|
2290
|
+
return _indegree_rel
|
|
2267
2291
|
|
|
2268
2292
|
def _indegree_of(self, nodes_subset: Relationship):
|
|
2269
2293
|
"""
|
|
@@ -2271,7 +2295,9 @@ class Graph():
|
|
|
2271
2295
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2272
2296
|
specific to the callsite.
|
|
2273
2297
|
"""
|
|
2274
|
-
|
|
2298
|
+
_indegree_rel = self._create_indegree_relationship(nodes_subset=nodes_subset)
|
|
2299
|
+
_indegree_rel.annotate(annotations.track("graphs", "indegree_of"))
|
|
2300
|
+
return _indegree_rel
|
|
2275
2301
|
|
|
2276
2302
|
def _create_indegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2277
2303
|
_indegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has indegree {{count:Integer}}")
|
|
@@ -2443,7 +2469,9 @@ class Graph():
|
|
|
2443
2469
|
@cached_property
|
|
2444
2470
|
def _outdegree(self):
|
|
2445
2471
|
"""Lazily define and cache the self._outdegree relationship."""
|
|
2446
|
-
|
|
2472
|
+
_outdegree_rel = self._create_outdegree_relationship(nodes_subset=None)
|
|
2473
|
+
_outdegree_rel.annotate(annotations.track("graphs", "outdegree"))
|
|
2474
|
+
return _outdegree_rel
|
|
2447
2475
|
|
|
2448
2476
|
def _outdegree_of(self, nodes_subset: Relationship):
|
|
2449
2477
|
"""
|
|
@@ -2451,7 +2479,9 @@ class Graph():
|
|
|
2451
2479
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2452
2480
|
specific to the callsite.
|
|
2453
2481
|
"""
|
|
2454
|
-
|
|
2482
|
+
_outdegree_rel = self._create_outdegree_relationship(nodes_subset=nodes_subset)
|
|
2483
|
+
_outdegree_rel.annotate(annotations.track("graphs", "outdegree_of"))
|
|
2484
|
+
return _outdegree_rel
|
|
2455
2485
|
|
|
2456
2486
|
def _create_outdegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2457
2487
|
_outdegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has outdegree {{count:Integer}}")
|
|
@@ -2588,7 +2618,9 @@ class Graph():
|
|
|
2588
2618
|
@cached_property
|
|
2589
2619
|
def _weighted_degree(self):
|
|
2590
2620
|
"""Lazily define and cache the self._weighted_degree relationship."""
|
|
2591
|
-
|
|
2621
|
+
_weighted_degree_rel = self._create_weighted_degree_relationship(nodes_subset=None)
|
|
2622
|
+
_weighted_degree_rel.annotate(annotations.track("graphs", "weighted_degree"))
|
|
2623
|
+
return _weighted_degree_rel
|
|
2592
2624
|
|
|
2593
2625
|
def _weighted_degree_of(self, nodes_subset: Relationship):
|
|
2594
2626
|
"""
|
|
@@ -2596,7 +2628,9 @@ class Graph():
|
|
|
2596
2628
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2597
2629
|
specific to the callsite.
|
|
2598
2630
|
"""
|
|
2599
|
-
|
|
2631
|
+
_weighted_degree_rel = self._create_weighted_degree_relationship(nodes_subset=nodes_subset)
|
|
2632
|
+
_weighted_degree_rel.annotate(annotations.track("graphs", "weighted_degree_of"))
|
|
2633
|
+
return _weighted_degree_rel
|
|
2600
2634
|
|
|
2601
2635
|
def _create_weighted_degree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2602
2636
|
_weighted_degree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted degree {{weight:Float}}")
|
|
@@ -2744,7 +2778,9 @@ class Graph():
|
|
|
2744
2778
|
@cached_property
|
|
2745
2779
|
def _weighted_indegree(self):
|
|
2746
2780
|
"""Lazily define and cache the self._weighted_indegree relationship."""
|
|
2747
|
-
|
|
2781
|
+
_weighted_indegree_rel = self._create_weighted_indegree_relationship(nodes_subset=None)
|
|
2782
|
+
_weighted_indegree_rel.annotate(annotations.track("graphs", "weighted_indegree"))
|
|
2783
|
+
return _weighted_indegree_rel
|
|
2748
2784
|
|
|
2749
2785
|
def _weighted_indegree_of(self, nodes_subset: Relationship):
|
|
2750
2786
|
"""
|
|
@@ -2752,7 +2788,9 @@ class Graph():
|
|
|
2752
2788
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2753
2789
|
specific to the callsite.
|
|
2754
2790
|
"""
|
|
2755
|
-
|
|
2791
|
+
_weighted_indegree_rel = self._create_weighted_indegree_relationship(nodes_subset=nodes_subset)
|
|
2792
|
+
_weighted_indegree_rel.annotate(annotations.track("graphs", "weighted_indegree_of"))
|
|
2793
|
+
return _weighted_indegree_rel
|
|
2756
2794
|
|
|
2757
2795
|
def _create_weighted_indegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2758
2796
|
_weighted_indegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted indegree {{weight:Float}}")
|
|
@@ -2892,7 +2930,9 @@ class Graph():
|
|
|
2892
2930
|
@cached_property
|
|
2893
2931
|
def _weighted_outdegree(self):
|
|
2894
2932
|
"""Lazily define and cache the self._weighted_outdegree relationship."""
|
|
2895
|
-
|
|
2933
|
+
_weighted_outdegree_rel = self._create_weighted_outdegree_relationship(nodes_subset=None)
|
|
2934
|
+
_weighted_outdegree_rel.annotate(annotations.track("graphs", "weighted_outdegree"))
|
|
2935
|
+
return _weighted_outdegree_rel
|
|
2896
2936
|
|
|
2897
2937
|
def _weighted_outdegree_of(self, nodes_subset: Relationship):
|
|
2898
2938
|
"""
|
|
@@ -2900,7 +2940,9 @@ class Graph():
|
|
|
2900
2940
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2901
2941
|
specific to the callsite.
|
|
2902
2942
|
"""
|
|
2903
|
-
|
|
2943
|
+
_weighted_outdegree_rel = self._create_weighted_outdegree_relationship(nodes_subset=nodes_subset)
|
|
2944
|
+
_weighted_outdegree_rel.annotate(annotations.track("graphs", "weighted_outdegree_of"))
|
|
2945
|
+
return _weighted_outdegree_rel
|
|
2904
2946
|
|
|
2905
2947
|
def _create_weighted_outdegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2906
2948
|
_weighted_outdegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted outdegree {{weight:Float}}")
|
|
@@ -3067,7 +3109,9 @@ class Graph():
|
|
|
3067
3109
|
@cached_property
|
|
3068
3110
|
def _degree_centrality(self):
|
|
3069
3111
|
"""Lazily define and cache the self._degree_centrality relationship."""
|
|
3070
|
-
|
|
3112
|
+
_degree_centrality_rel = self._create_degree_centrality_relationship(nodes_subset=None)
|
|
3113
|
+
_degree_centrality_rel.annotate(annotations.track("graphs", "degree_centrality"))
|
|
3114
|
+
return _degree_centrality_rel
|
|
3071
3115
|
|
|
3072
3116
|
def _degree_centrality_of(self, nodes_subset: Relationship):
|
|
3073
3117
|
"""
|
|
@@ -3075,7 +3119,9 @@ class Graph():
|
|
|
3075
3119
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
3076
3120
|
specific to the callsite.
|
|
3077
3121
|
"""
|
|
3078
|
-
|
|
3122
|
+
_degree_centrality_rel = self._create_degree_centrality_relationship(nodes_subset=nodes_subset)
|
|
3123
|
+
_degree_centrality_rel.annotate(annotations.track("graphs", "degree_centrality_of"))
|
|
3124
|
+
return _degree_centrality_rel
|
|
3079
3125
|
|
|
3080
3126
|
def _create_degree_centrality_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
3081
3127
|
"""Create a degree centrality relationship, optionally constrained to a subset of nodes."""
|
|
@@ -3572,6 +3618,7 @@ class Graph():
|
|
|
3572
3618
|
def _triangle(self):
|
|
3573
3619
|
"""Lazily define and cache the self._triangle relationship."""
|
|
3574
3620
|
_triangle_rel = self._model.Relationship(f"{{node_a:{self._NodeConceptStr}}} and {{node_b:{self._NodeConceptStr}}} and {{node_c:{self._NodeConceptStr}}} form a triangle")
|
|
3621
|
+
_triangle_rel.annotate(annotations.track("graphs", "triangle"))
|
|
3575
3622
|
|
|
3576
3623
|
a, b, c = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
3577
3624
|
|
|
@@ -3714,6 +3761,7 @@ class Graph():
|
|
|
3714
3761
|
def _unique_triangle(self):
|
|
3715
3762
|
"""Lazily define and cache the self._unique_triangle relationship."""
|
|
3716
3763
|
_unique_triangle_rel = self._model.Relationship(f"{{node_a:{self._NodeConceptStr}}} and {{node_b:{self._NodeConceptStr}}} and {{node_c:{self._NodeConceptStr}}} form unique triangle")
|
|
3764
|
+
_unique_triangle_rel.annotate(annotations.track("graphs", "unique_triangle"))
|
|
3717
3765
|
|
|
3718
3766
|
node_a, node_b, node_c = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
3719
3767
|
|
|
@@ -3849,6 +3897,7 @@ class Graph():
|
|
|
3849
3897
|
def _num_triangles(self):
|
|
3850
3898
|
"""Lazily define and cache the self._num_triangles relationship."""
|
|
3851
3899
|
_num_triangles_rel = self._model.Relationship("The graph has {num_triangles:Integer} triangles")
|
|
3900
|
+
_num_triangles_rel.annotate(annotations.track("graphs", "num_triangles"))
|
|
3852
3901
|
|
|
3853
3902
|
_num_triangles = Integer.ref()
|
|
3854
3903
|
node_a, node_b, node_c = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
@@ -3973,7 +4022,9 @@ class Graph():
|
|
|
3973
4022
|
@cached_property
|
|
3974
4023
|
def _triangle_count(self):
|
|
3975
4024
|
"""Lazily define and cache the self._triangle_count relationship."""
|
|
3976
|
-
|
|
4025
|
+
_triangle_count_rel = self._create_triangle_count_relationship(nodes_subset=None)
|
|
4026
|
+
_triangle_count_rel.annotate(annotations.track("graphs", "triangle_count"))
|
|
4027
|
+
return _triangle_count_rel
|
|
3977
4028
|
|
|
3978
4029
|
def _triangle_count_of(self, nodes_subset: Relationship):
|
|
3979
4030
|
"""
|
|
@@ -3981,7 +4032,9 @@ class Graph():
|
|
|
3981
4032
|
in `nodes_subset`. Note this relationship is not cached; it is
|
|
3982
4033
|
specific to the callsite.
|
|
3983
4034
|
"""
|
|
3984
|
-
|
|
4035
|
+
_triangle_count_rel = self._create_triangle_count_relationship(nodes_subset=nodes_subset)
|
|
4036
|
+
_triangle_count_rel.annotate(annotations.track("graphs", "triangle_count_of"))
|
|
4037
|
+
return _triangle_count_rel
|
|
3985
4038
|
|
|
3986
4039
|
def _create_triangle_count_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
3987
4040
|
"""Create a triangle count relationship, optionally constrained to a subset of nodes."""
|
|
@@ -4113,7 +4166,7 @@ class Graph():
|
|
|
4113
4166
|
|
|
4114
4167
|
|
|
4115
4168
|
@include_in_docs
|
|
4116
|
-
def local_clustering_coefficient(self):
|
|
4169
|
+
def local_clustering_coefficient(self, *, of: Optional[Relationship] = None):
|
|
4117
4170
|
"""Returns a binary relationship containing the local clustering coefficient of each node.
|
|
4118
4171
|
|
|
4119
4172
|
The local clustering coefficient quantifies how close a node's neighbors
|
|
@@ -4122,6 +4175,14 @@ class Graph():
|
|
|
4122
4175
|
directly connecting them, and 1.0 indicates all neighbors have edges
|
|
4123
4176
|
directly connecting them.
|
|
4124
4177
|
|
|
4178
|
+
Parameters
|
|
4179
|
+
----------
|
|
4180
|
+
of : Relationship, optional
|
|
4181
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
4182
|
+
provided, constrains the domain of the local clustering coefficient
|
|
4183
|
+
computation: only coefficients of nodes in this relationship are
|
|
4184
|
+
computed and returned.
|
|
4185
|
+
|
|
4125
4186
|
Returns
|
|
4126
4187
|
-------
|
|
4127
4188
|
Relationship
|
|
@@ -4148,17 +4209,6 @@ class Graph():
|
|
|
4148
4209
|
| Directed | No | Undirected only. |
|
|
4149
4210
|
| Weighted | Yes | Weights are ignored. |
|
|
4150
4211
|
|
|
4151
|
-
Notes
|
|
4152
|
-
-----
|
|
4153
|
-
The formal definition of the local clustering coefficient (`C`) for a
|
|
4154
|
-
node (`v`) can be given as::
|
|
4155
|
-
|
|
4156
|
-
C(v) = (2 * num_edges) / (degree(v) * (degree(v) - 1))
|
|
4157
|
-
|
|
4158
|
-
Here, `num_edges` represents the number of edges between the
|
|
4159
|
-
neighbors of node `v`, and `degree(v)` represents the degree of the
|
|
4160
|
-
node, i.e., the number of edges connected to the node.
|
|
4161
|
-
|
|
4162
4212
|
Examples
|
|
4163
4213
|
--------
|
|
4164
4214
|
>>> from relationalai.semantics import Model, define, select, Float
|
|
@@ -4194,6 +4244,41 @@ class Graph():
|
|
|
4194
4244
|
3 4 0.333333
|
|
4195
4245
|
4 5 0.000000
|
|
4196
4246
|
|
|
4247
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute local clustering coefficients of
|
|
4248
|
+
>>> # Define a subset containing only nodes 1 and 3
|
|
4249
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
4250
|
+
>>> node = Node.ref()
|
|
4251
|
+
>>> where(union(node.id == 1, node.id == 3)).define(subset(node))
|
|
4252
|
+
>>>
|
|
4253
|
+
>>> # Get local clustering coefficients only of nodes in the subset
|
|
4254
|
+
>>> constrained_lcc = graph.local_clustering_coefficient(of=subset)
|
|
4255
|
+
>>> select(node.id, coeff).where(constrained_lcc(node, coeff)).inspect()
|
|
4256
|
+
▰▰▰▰ Setup complete
|
|
4257
|
+
id coeff
|
|
4258
|
+
0 1 1.000000
|
|
4259
|
+
1 3 0.666667
|
|
4260
|
+
|
|
4261
|
+
Notes
|
|
4262
|
+
-----
|
|
4263
|
+
The local clustering coefficient for node `v` is::
|
|
4264
|
+
|
|
4265
|
+
(2 * num_neighbor_edges(v)) / (ext_degree(v) * (ext_degree(v) - 1))
|
|
4266
|
+
|
|
4267
|
+
where `num_neighbor_edges(v)` is the number of edges between
|
|
4268
|
+
the neighbors of node `v`, and `ext_degree(v)` is the degree of the
|
|
4269
|
+
node excluding self-loops. If `ext_degree(v)` is less than 2,
|
|
4270
|
+
the local clustering coefficient is 0.0.
|
|
4271
|
+
|
|
4272
|
+
The ``local_clustering_coefficient()`` method, called with no parameters, computes
|
|
4273
|
+
and caches the full local clustering coefficient relationship, providing efficient
|
|
4274
|
+
reuse across multiple calls to ``local_clustering_coefficient()``. In contrast,
|
|
4275
|
+
``local_clustering_coefficient(of=subset)`` computes a constrained relationship
|
|
4276
|
+
specific to the passed-in ``subset`` and that call site. When a significant fraction
|
|
4277
|
+
of the local clustering coefficient relation is needed across a program,
|
|
4278
|
+
``local_clustering_coefficient()`` is typically more efficient; this is the typical
|
|
4279
|
+
case. Use ``local_clustering_coefficient(of=subset)`` only when small subsets of the
|
|
4280
|
+
local clustering coefficient relationship are needed collectively across the program.
|
|
4281
|
+
|
|
4197
4282
|
|
|
4198
4283
|
See Also
|
|
4199
4284
|
--------
|
|
@@ -4206,29 +4291,51 @@ class Graph():
|
|
|
4206
4291
|
raise NotImplementedError(
|
|
4207
4292
|
"`local_clustering_coefficient` is not applicable to directed graphs"
|
|
4208
4293
|
)
|
|
4294
|
+
|
|
4295
|
+
if of is not None:
|
|
4296
|
+
self._validate_node_subset_parameter(of)
|
|
4297
|
+
return self._local_clustering_coefficient_of(of)
|
|
4209
4298
|
return self._local_clustering_coefficient
|
|
4210
4299
|
|
|
4211
4300
|
@cached_property
|
|
4212
4301
|
def _local_clustering_coefficient(self):
|
|
4302
|
+
"""Lazily define and cache the self._local_clustering_coefficient relationship."""
|
|
4303
|
+
_local_clustering_coefficient_rel = self._create_local_clustering_coefficient_relationship(nodes_subset=None)
|
|
4304
|
+
_local_clustering_coefficient_rel.annotate(annotations.track("graphs", "local_clustering_coefficient"))
|
|
4305
|
+
return _local_clustering_coefficient_rel
|
|
4306
|
+
|
|
4307
|
+
def _local_clustering_coefficient_of(self, nodes_subset: Relationship):
|
|
4213
4308
|
"""
|
|
4214
|
-
|
|
4215
|
-
|
|
4309
|
+
Create a local clustering coefficient relationship constrained to the subset of nodes
|
|
4310
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
4311
|
+
specific to the callsite.
|
|
4216
4312
|
"""
|
|
4217
|
-
_local_clustering_coefficient_rel = self.
|
|
4313
|
+
_local_clustering_coefficient_rel = self._create_local_clustering_coefficient_relationship(nodes_subset=nodes_subset)
|
|
4314
|
+
_local_clustering_coefficient_rel.annotate(annotations.track("graphs", "local_clustering_coefficient_of"))
|
|
4315
|
+
return _local_clustering_coefficient_rel
|
|
4218
4316
|
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
)
|
|
4317
|
+
def _create_local_clustering_coefficient_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
4318
|
+
"""Create a local clustering coefficient relationship, optionally constrained to a subset of nodes."""
|
|
4319
|
+
_local_clustering_coefficient_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has local clustering coefficient {{coefficient:Float}}")
|
|
4223
4320
|
|
|
4224
4321
|
node = self.Node.ref()
|
|
4322
|
+
|
|
4323
|
+
if nodes_subset is None:
|
|
4324
|
+
degree_no_self_rel = self._degree_no_self
|
|
4325
|
+
triangle_count_rel = self._triangle_count
|
|
4326
|
+
node_constraint = node # No constraint on nodes.
|
|
4327
|
+
else:
|
|
4328
|
+
degree_no_self_rel = self._degree_no_self_of(nodes_subset)
|
|
4329
|
+
triangle_count_rel = self._triangle_count_of(nodes_subset)
|
|
4330
|
+
node_constraint = nodes_subset(node) # Nodes constrained to given subset.
|
|
4331
|
+
|
|
4225
4332
|
degree_no_self = Integer.ref()
|
|
4226
4333
|
triangle_count = Integer.ref()
|
|
4227
4334
|
where(
|
|
4228
|
-
|
|
4335
|
+
node_constraint,
|
|
4229
4336
|
_lcc := where(
|
|
4230
|
-
|
|
4231
|
-
|
|
4337
|
+
degree_no_self_rel(node, degree_no_self),
|
|
4338
|
+
triangle_count_rel(node, triangle_count),
|
|
4232
4339
|
degree_no_self > 1
|
|
4233
4340
|
).select(
|
|
4234
4341
|
2.0 * triangle_count / (degree_no_self * (degree_no_self - 1.0))
|
|
@@ -4243,11 +4350,32 @@ class Graph():
|
|
|
4243
4350
|
Lazily define and cache the self._degree_no_self relationship,
|
|
4244
4351
|
a non-public helper for local_clustering_coefficient.
|
|
4245
4352
|
"""
|
|
4353
|
+
return self._create_degree_no_self_relationship(nodes_subset=None)
|
|
4354
|
+
|
|
4355
|
+
def _degree_no_self_of(self, nodes_subset: Relationship):
|
|
4356
|
+
"""
|
|
4357
|
+
Create a self-loop-exclusive degree relationship constrained to
|
|
4358
|
+
the subset of nodes in `nodes_subset`. Note this relationship
|
|
4359
|
+
is not cached; it is specific to the callsite.
|
|
4360
|
+
"""
|
|
4361
|
+
return self._create_degree_no_self_relationship(nodes_subset=nodes_subset)
|
|
4362
|
+
|
|
4363
|
+
def _create_degree_no_self_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
4364
|
+
"""
|
|
4365
|
+
Create a self-loop-exclusive degree relationship,
|
|
4366
|
+
optionally constrained to a subset of nodes.
|
|
4367
|
+
"""
|
|
4246
4368
|
_degree_no_self_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has degree excluding self loops {{num:Integer}}")
|
|
4247
4369
|
|
|
4248
4370
|
node, neighbor = self.Node.ref(), self.Node.ref()
|
|
4371
|
+
|
|
4372
|
+
if nodes_subset is None:
|
|
4373
|
+
node_constraint = node # No constraint on nodes.
|
|
4374
|
+
else:
|
|
4375
|
+
node_constraint = nodes_subset(node) # Nodes constrained to given subset.
|
|
4376
|
+
|
|
4249
4377
|
where(
|
|
4250
|
-
|
|
4378
|
+
node_constraint,
|
|
4251
4379
|
_dns := count(neighbor).per(node).where(self._no_loop_edge(node, neighbor)) | 0,
|
|
4252
4380
|
).define(_degree_no_self_rel(node, _dns))
|
|
4253
4381
|
|
|
@@ -4331,6 +4459,7 @@ class Graph():
|
|
|
4331
4459
|
which only applies to undirected graphs.
|
|
4332
4460
|
"""
|
|
4333
4461
|
_average_clustering_coefficient_rel = self._model.Relationship("The graph has average clustering coefficient {{coefficient:Float}}")
|
|
4462
|
+
_average_clustering_coefficient_rel.annotate(annotations.track("graphs", "average_clustering_coefficient"))
|
|
4334
4463
|
|
|
4335
4464
|
if self.directed:
|
|
4336
4465
|
raise NotImplementedError(
|
|
@@ -4471,6 +4600,7 @@ class Graph():
|
|
|
4471
4600
|
def _reachable_from(self):
|
|
4472
4601
|
"""Lazily define and cache the self._reachable_from relationship."""
|
|
4473
4602
|
_reachable_from_rel = self._model.Relationship(f"{{node_a:{self._NodeConceptStr}}} reaches {{node_b:{self._NodeConceptStr}}}")
|
|
4603
|
+
_reachable_from_rel.annotate(annotations.track("graphs", "reachable_from"))
|
|
4474
4604
|
|
|
4475
4605
|
node_a, node_b, node_c = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
4476
4606
|
define(_reachable_from_rel(node_a, node_a))
|
|
@@ -4613,9 +4743,12 @@ class Graph():
|
|
|
4613
4743
|
def _distance(self):
|
|
4614
4744
|
"""Lazily define and cache the self._distance relationship."""
|
|
4615
4745
|
if not self.weighted:
|
|
4616
|
-
|
|
4746
|
+
_distance_rel = self._distance_non_weighted
|
|
4617
4747
|
else:
|
|
4618
|
-
|
|
4748
|
+
_distance_rel = self._distance_weighted
|
|
4749
|
+
|
|
4750
|
+
_distance_rel.annotate(annotations.track("graphs", "distance"))
|
|
4751
|
+
return _distance_rel
|
|
4619
4752
|
|
|
4620
4753
|
@cached_property
|
|
4621
4754
|
def _distance_weighted(self):
|
|
@@ -4741,6 +4874,7 @@ class Graph():
|
|
|
4741
4874
|
def _weakly_connected_component(self):
|
|
4742
4875
|
"""Lazily define and cache the self._weakly_connected_component relationship."""
|
|
4743
4876
|
_weakly_connected_component_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} is in the connected component {{id:{self._NodeConceptStr}}}")
|
|
4877
|
+
_weakly_connected_component_rel.annotate(annotations.track("graphs", "weakly_connected_component"))
|
|
4744
4878
|
|
|
4745
4879
|
node, node_v, component = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
4746
4880
|
node, component = union(
|
|
@@ -4864,6 +4998,8 @@ class Graph():
|
|
|
4864
4998
|
"""
|
|
4865
4999
|
_diameter_range_min_rel = self._model.Relationship("The graph has a min diameter range of {value:Integer}")
|
|
4866
5000
|
_diameter_range_max_rel = self._model.Relationship("The graph has a max diameter range of {value:Integer}")
|
|
5001
|
+
_diameter_range_min_rel.annotate(annotations.track("graphs", "diameter_range_min"))
|
|
5002
|
+
_diameter_range_max_rel.annotate(annotations.track("graphs", "diameter_range_max"))
|
|
4867
5003
|
|
|
4868
5004
|
component_node_pairs = self._model.Relationship(f"component id {{cid:{self._NodeConceptStr}}} has node id {{nid:{self._NodeConceptStr}}}")
|
|
4869
5005
|
nodeid, cid, degreevalue = self.Node.ref(), self.Node.ref(), Integer.ref()
|
|
@@ -4924,16 +5060,22 @@ class Graph():
|
|
|
4924
5060
|
|
|
4925
5061
|
@include_in_docs
|
|
4926
5062
|
def is_connected(self):
|
|
4927
|
-
"""Returns a
|
|
5063
|
+
"""Returns a unary relationship containing whether the graph is connected.
|
|
4928
5064
|
|
|
4929
5065
|
A graph is considered connected if every node is reachable from every
|
|
4930
5066
|
other node in the underlying undirected graph.
|
|
4931
5067
|
|
|
4932
5068
|
Returns
|
|
4933
5069
|
-------
|
|
4934
|
-
|
|
4935
|
-
A
|
|
4936
|
-
|
|
5070
|
+
Relationship
|
|
5071
|
+
A unary relationship containing a boolean indicator of whether the graph
|
|
5072
|
+
is connected.
|
|
5073
|
+
|
|
5074
|
+
Relationship Schema
|
|
5075
|
+
-------------------
|
|
5076
|
+
``is_connected(connected)``
|
|
5077
|
+
|
|
5078
|
+
* **connected** (*Boolean*): Whether the graph is connected.
|
|
4937
5079
|
|
|
4938
5080
|
Supported Graph Types
|
|
4939
5081
|
---------------------
|
|
@@ -4951,8 +5093,6 @@ class Graph():
|
|
|
4951
5093
|
--------
|
|
4952
5094
|
**Connected Graph Example**
|
|
4953
5095
|
|
|
4954
|
-
The following query will produce a result because the graph is connected.
|
|
4955
|
-
|
|
4956
5096
|
>>> from relationalai.semantics import Model, define, select
|
|
4957
5097
|
>>> from relationalai.semantics.reasoners.graph import Graph
|
|
4958
5098
|
>>>
|
|
@@ -4970,17 +5110,14 @@ class Graph():
|
|
|
4970
5110
|
... Edge.new(src=n4, dst=n3),
|
|
4971
5111
|
... )
|
|
4972
5112
|
>>>
|
|
4973
|
-
>>> # 3.
|
|
4974
|
-
>>> select(
|
|
5113
|
+
>>> # 3. Select and inspect the relation
|
|
5114
|
+
>>> select(graph.is_connected()).inspect()
|
|
4975
5115
|
▰▰▰▰ Setup complete
|
|
4976
|
-
|
|
4977
|
-
0
|
|
5116
|
+
is_connected
|
|
5117
|
+
0 True
|
|
4978
5118
|
|
|
4979
5119
|
**Disconnected Graph Example**
|
|
4980
5120
|
|
|
4981
|
-
The following query will produce no results because the graph is not
|
|
4982
|
-
connected.
|
|
4983
|
-
|
|
4984
5121
|
>>> from relationalai.semantics import Model, define, select
|
|
4985
5122
|
>>> from relationalai.semantics.reasoners.graph import Graph
|
|
4986
5123
|
>>>
|
|
@@ -4998,22 +5135,31 @@ class Graph():
|
|
|
4998
5135
|
... Edge.new(src=n4, dst=n5), # This edge creates a separate component
|
|
4999
5136
|
... )
|
|
5000
5137
|
>>>
|
|
5001
|
-
>>> # 3.
|
|
5002
|
-
>>> select(
|
|
5138
|
+
>>> # 3. Select and inspect the relation
|
|
5139
|
+
>>> select(graph.is_connected()).inspect()
|
|
5003
5140
|
▰▰▰▰ Setup complete
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
Index: []
|
|
5141
|
+
is_connected
|
|
5142
|
+
0 False
|
|
5007
5143
|
|
|
5008
5144
|
"""
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5145
|
+
return self._is_connected
|
|
5146
|
+
|
|
5147
|
+
@cached_property
|
|
5148
|
+
def _is_connected(self):
|
|
5149
|
+
"""Lazily define and cache the self._is_connected relationship."""
|
|
5150
|
+
_is_connected_rel = self._model.Relationship("'The graph is connected' is {is_connected:Boolean}")
|
|
5151
|
+
_is_connected_rel.annotate(annotations.track("graphs", "is_connected"))
|
|
5152
|
+
|
|
5153
|
+
where(
|
|
5014
5154
|
self._num_nodes(0) |
|
|
5015
5155
|
count(self._reachable_from_min_node(self.Node.ref())) == self._num_nodes(Integer.ref())
|
|
5016
|
-
)
|
|
5156
|
+
).define(_is_connected_rel(True))
|
|
5157
|
+
|
|
5158
|
+
where(
|
|
5159
|
+
not_(_is_connected_rel(True))
|
|
5160
|
+
).define(_is_connected_rel(False))
|
|
5161
|
+
|
|
5162
|
+
return _is_connected_rel
|
|
5017
5163
|
|
|
5018
5164
|
|
|
5019
5165
|
@include_in_docs
|
|
@@ -5179,6 +5325,7 @@ class Graph():
|
|
|
5179
5325
|
def _jaccard_similarity(self):
|
|
5180
5326
|
"""Lazily define and cache the self._jaccard_similarity relationship."""
|
|
5181
5327
|
_jaccard_similarity_rel = self._model.Relationship(f"{{node_u:{self._NodeConceptStr}}} has a similarity to {{node_v:{self._NodeConceptStr}}} of {{similarity:Float}}")
|
|
5328
|
+
_jaccard_similarity_rel.annotate(annotations.track("graphs", "jaccard_similarity"))
|
|
5182
5329
|
|
|
5183
5330
|
if not self.weighted:
|
|
5184
5331
|
node_u, node_v = self.Node.ref(), self.Node.ref()
|
|
@@ -5430,6 +5577,7 @@ class Graph():
|
|
|
5430
5577
|
def _cosine_similarity(self):
|
|
5431
5578
|
"""Lazily define and cache the self._cosine_similarity relationship."""
|
|
5432
5579
|
_cosine_similarity_rel = self._model.Relationship(f"{{node_u:{self._NodeConceptStr}}} has a cosine similarity to {{node_v:{self._NodeConceptStr}}} of {{score:Float}}")
|
|
5580
|
+
_cosine_similarity_rel.annotate(annotations.track("graphs", "cosine_similarity"))
|
|
5433
5581
|
|
|
5434
5582
|
if not self.weighted:
|
|
5435
5583
|
node_u, node_v = self.Node.ref(), self.Node.ref()
|
|
@@ -5550,6 +5698,7 @@ class Graph():
|
|
|
5550
5698
|
def _adamic_adar(self):
|
|
5551
5699
|
"""Lazily define and cache the self._adamic_adar relationship."""
|
|
5552
5700
|
_adamic_adar_rel = self._model.Relationship(f"{{node_u:{self._NodeConceptStr}}} and {{node_v:{self._NodeConceptStr}}} have adamic adar score {{score:Float}}")
|
|
5701
|
+
_adamic_adar_rel.annotate(annotations.track("graphs", "adamic_adar"))
|
|
5553
5702
|
|
|
5554
5703
|
node_u, node_v, common_neighbor = self.Node.ref(), self.Node.ref(), self.Node.ref()
|
|
5555
5704
|
neighbor_count = Integer.ref()
|
|
@@ -5648,6 +5797,7 @@ class Graph():
|
|
|
5648
5797
|
def _preferential_attachment(self):
|
|
5649
5798
|
"""Lazily define and cache the self._preferential_attachment relationship."""
|
|
5650
5799
|
_preferential_attachment_rel = self._model.Relationship(f"{{node_u:{self._NodeConceptStr}}} and {{node_v:{self._NodeConceptStr}}} have preferential attachment score {{score:Integer}}")
|
|
5800
|
+
_preferential_attachment_rel.annotate(annotations.track("graphs", "preferential_attachment"))
|
|
5651
5801
|
|
|
5652
5802
|
node_u, node_v = self.Node.ref(), self.Node.ref()
|
|
5653
5803
|
count_u, count_v = Integer.ref(), Integer.ref()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from relationalai.semantics.metamodel import types, factory as f
|
|
4
4
|
from relationalai.semantics.metamodel.util import OrderedSet
|
|
5
|
+
from relationalai.semantics.metamodel import builtins
|
|
5
6
|
|
|
6
7
|
# Rel Annotations as IR Relations (to be used in IR Annotations)
|
|
7
8
|
|
|
@@ -28,7 +29,10 @@ inner_loop_non_stratified_annotation = f.annotation(inner_loop_non_stratified, [
|
|
|
28
29
|
|
|
29
30
|
# collect all supported builtin rel annotations
|
|
30
31
|
builtin_annotations = OrderedSet.from_iterable([
|
|
31
|
-
arrow, no_diagnostics, no_inline, function, inner_loop, inner_loop_non_stratified
|
|
32
|
+
arrow, no_diagnostics, no_inline, function, inner_loop, inner_loop_non_stratified,
|
|
33
|
+
# track annotations on relations do not currently propagate into Rel
|
|
34
|
+
# TODO: from Thiago, ensure annotation goes from the Logical into the proper declaration
|
|
35
|
+
builtins.track,
|
|
32
36
|
])
|
|
33
37
|
|
|
34
38
|
builtin_annotation_names = OrderedSet.from_iterable([a.name for a in builtin_annotations])
|