relationalai 0.11.2__py3-none-any.whl → 0.11.4__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/snowflake.py +44 -15
- relationalai/clients/types.py +1 -0
- relationalai/clients/use_index_poller.py +446 -178
- relationalai/early_access/builder/std/__init__.py +1 -1
- relationalai/early_access/dsl/bindings/csv.py +4 -4
- relationalai/semantics/internal/internal.py +22 -4
- relationalai/semantics/lqp/executor.py +69 -18
- relationalai/semantics/lqp/intrinsics.py +23 -0
- relationalai/semantics/lqp/model2lqp.py +16 -6
- relationalai/semantics/lqp/passes.py +3 -4
- relationalai/semantics/lqp/primitives.py +38 -14
- relationalai/semantics/metamodel/builtins.py +152 -11
- relationalai/semantics/metamodel/factory.py +3 -2
- relationalai/semantics/metamodel/helpers.py +78 -2
- relationalai/semantics/reasoners/graph/core.py +343 -40
- relationalai/semantics/reasoners/optimization/solvers_dev.py +20 -1
- relationalai/semantics/reasoners/optimization/solvers_pb.py +24 -3
- relationalai/semantics/rel/compiler.py +5 -17
- relationalai/semantics/rel/executor.py +2 -2
- relationalai/semantics/rel/rel.py +6 -0
- relationalai/semantics/rel/rel_utils.py +37 -1
- relationalai/semantics/rel/rewrite/extract_common.py +153 -242
- relationalai/semantics/sql/compiler.py +540 -202
- relationalai/semantics/sql/executor/duck_db.py +21 -0
- relationalai/semantics/sql/executor/result_helpers.py +7 -0
- relationalai/semantics/sql/executor/snowflake.py +9 -2
- relationalai/semantics/sql/rewrite/denormalize.py +4 -6
- relationalai/semantics/sql/rewrite/recursive_union.py +23 -3
- relationalai/semantics/sql/sql.py +120 -46
- relationalai/semantics/std/__init__.py +9 -4
- relationalai/semantics/std/datetime.py +363 -0
- relationalai/semantics/std/math.py +77 -0
- relationalai/semantics/std/re.py +83 -0
- relationalai/semantics/std/strings.py +1 -1
- relationalai/tools/cli_controls.py +445 -60
- relationalai/util/format.py +78 -1
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/METADATA +3 -2
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/RECORD +41 -39
- relationalai/semantics/std/dates.py +0 -213
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/WHEEL +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/entry_points.txt +0 -0
- {relationalai-0.11.2.dist-info → relationalai-0.11.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -2476,7 +2476,7 @@ class Graph():
|
|
|
2476
2476
|
|
|
2477
2477
|
|
|
2478
2478
|
@include_in_docs
|
|
2479
|
-
def weighted_degree(self):
|
|
2479
|
+
def weighted_degree(self, *, of: Optional[Relationship] = None):
|
|
2480
2480
|
"""Returns a binary relationship containing the weighted degree of each node.
|
|
2481
2481
|
|
|
2482
2482
|
A node's weighted degree is the sum of the weights of all edges
|
|
@@ -2484,6 +2484,13 @@ class Graph():
|
|
|
2484
2484
|
of both incoming and outgoing edges. For unweighted graphs, all edge
|
|
2485
2485
|
weights are considered to be 1.0.
|
|
2486
2486
|
|
|
2487
|
+
Parameters
|
|
2488
|
+
----------
|
|
2489
|
+
of : Relationship, optional
|
|
2490
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
2491
|
+
provided, constrains the domain of the weighted degree computation: only
|
|
2492
|
+
weighted degrees of nodes in this relationship are computed and returned.
|
|
2493
|
+
|
|
2487
2494
|
Returns
|
|
2488
2495
|
-------
|
|
2489
2496
|
Relationship
|
|
@@ -2508,7 +2515,7 @@ class Graph():
|
|
|
2508
2515
|
|
|
2509
2516
|
Examples
|
|
2510
2517
|
--------
|
|
2511
|
-
>>> from relationalai.semantics import Model, define, select, Float
|
|
2518
|
+
>>> from relationalai.semantics import Model, define, select, where, union, Float
|
|
2512
2519
|
>>> from relationalai.semantics.reasoners.graph import Graph
|
|
2513
2520
|
>>>
|
|
2514
2521
|
>>> # 1. Set up a directed, weighted graph
|
|
@@ -2528,12 +2535,42 @@ class Graph():
|
|
|
2528
2535
|
>>> # 3. Select the weighted degree of each node and inspect
|
|
2529
2536
|
>>> node, node_weighted_degree = Node.ref("node"), Float.ref("node_weighted_degree")
|
|
2530
2537
|
>>> weighted_degree = graph.weighted_degree()
|
|
2531
|
-
>>> select(
|
|
2538
|
+
>>> select(
|
|
2539
|
+
... node.id, node_weighted_degree
|
|
2540
|
+
... ).where(
|
|
2541
|
+
... weighted_degree(node, node_weighted_degree)
|
|
2542
|
+
... ).inspect()
|
|
2532
2543
|
▰▰▰▰ Setup complete
|
|
2533
2544
|
id node_weighted_degree
|
|
2534
2545
|
0 1 0.0
|
|
2535
2546
|
1 2 1.0
|
|
2536
2547
|
2 3 1.0
|
|
2548
|
+
>>>
|
|
2549
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted degree of
|
|
2550
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
2551
|
+
>>> node = Node.ref()
|
|
2552
|
+
>>> where(union(node.id == 2, node.id == 3)).define(subset(node))
|
|
2553
|
+
>>> constrained_weighted_degree = graph.weighted_degree(of=subset)
|
|
2554
|
+
>>> select(
|
|
2555
|
+
... node.id, node_weighted_degree
|
|
2556
|
+
... ).where(
|
|
2557
|
+
... constrained_weighted_degree(node, node_weighted_degree)
|
|
2558
|
+
... ).inspect()
|
|
2559
|
+
▰▰▰▰ Setup complete
|
|
2560
|
+
id node_weighted_degree
|
|
2561
|
+
0 2 1.0
|
|
2562
|
+
1 3 1.0
|
|
2563
|
+
|
|
2564
|
+
Notes
|
|
2565
|
+
-----
|
|
2566
|
+
The ``weighted_degree()`` method, called with no parameters, computes and caches
|
|
2567
|
+
the full weighted degree relationship, providing efficient reuse across multiple
|
|
2568
|
+
calls to ``weighted_degree()``. In contrast, ``weighted_degree(of=subset)`` computes a
|
|
2569
|
+
constrained relationship specific to the passed-in ``subset`` and that
|
|
2570
|
+
call site. When a significant fraction of the weighted degree relation is needed
|
|
2571
|
+
across a program, ``weighted_degree()`` is typically more efficient; this is the
|
|
2572
|
+
typical case. Use ``weighted_degree(of=subset)`` only when small subsets of the
|
|
2573
|
+
weighted degree relationship are needed collectively across the program.
|
|
2537
2574
|
|
|
2538
2575
|
See Also
|
|
2539
2576
|
--------
|
|
@@ -2541,23 +2578,55 @@ class Graph():
|
|
|
2541
2578
|
weighted_outdegree
|
|
2542
2579
|
|
|
2543
2580
|
"""
|
|
2544
|
-
|
|
2581
|
+
if of is None:
|
|
2582
|
+
return self._weighted_degree
|
|
2583
|
+
else:
|
|
2584
|
+
# Validate the 'of' parameter
|
|
2585
|
+
self._validate_node_subset_parameter(of)
|
|
2586
|
+
return self._weighted_degree_of(of)
|
|
2545
2587
|
|
|
2546
2588
|
@cached_property
|
|
2547
2589
|
def _weighted_degree(self):
|
|
2548
2590
|
"""Lazily define and cache the self._weighted_degree relationship."""
|
|
2591
|
+
return self._create_weighted_degree_relationship(nodes_subset=None)
|
|
2592
|
+
|
|
2593
|
+
def _weighted_degree_of(self, nodes_subset: Relationship):
|
|
2594
|
+
"""
|
|
2595
|
+
Create a weighted degree relationship constrained to the subset of nodes
|
|
2596
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2597
|
+
specific to the callsite.
|
|
2598
|
+
"""
|
|
2599
|
+
return self._create_weighted_degree_relationship(nodes_subset=nodes_subset)
|
|
2600
|
+
|
|
2601
|
+
def _create_weighted_degree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2549
2602
|
_weighted_degree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted degree {{weight:Float}}")
|
|
2550
2603
|
|
|
2551
2604
|
if self.directed:
|
|
2605
|
+
# For directed graphs, weighted degree is the sum of weighted indegree and weighted outdegree.
|
|
2606
|
+
if nodes_subset is None:
|
|
2607
|
+
weighted_indegree_rel = self._weighted_indegree
|
|
2608
|
+
weighted_outdegree_rel = self._weighted_outdegree
|
|
2609
|
+
else:
|
|
2610
|
+
weighted_indegree_rel = self._weighted_indegree_of(nodes_subset)
|
|
2611
|
+
weighted_outdegree_rel = self._weighted_outdegree_of(nodes_subset)
|
|
2612
|
+
|
|
2552
2613
|
inweight, outweight = Float.ref(), Float.ref()
|
|
2553
2614
|
where(
|
|
2554
|
-
|
|
2555
|
-
|
|
2615
|
+
weighted_indegree_rel(self.Node, inweight),
|
|
2616
|
+
weighted_outdegree_rel(self.Node, outweight),
|
|
2556
2617
|
).define(_weighted_degree_rel(self.Node, inweight + outweight))
|
|
2557
2618
|
elif not self.directed:
|
|
2619
|
+
# Choose the appropriate node set
|
|
2620
|
+
if nodes_subset is None:
|
|
2621
|
+
# No constraint - use all nodes
|
|
2622
|
+
node_set = self.Node
|
|
2623
|
+
else:
|
|
2624
|
+
# Constrained to nodes in the subset
|
|
2625
|
+
node_set = nodes_subset
|
|
2626
|
+
|
|
2558
2627
|
dst, weight = self.Node.ref(), Float.ref()
|
|
2559
2628
|
where(
|
|
2560
|
-
self.Node,
|
|
2629
|
+
node_set(self.Node),
|
|
2561
2630
|
_weighted_degree := sum(dst, weight).per(self.Node).where(self._weight(self.Node, dst, weight)) | 0.0,
|
|
2562
2631
|
).define(_weighted_degree_rel(self.Node, _weighted_degree))
|
|
2563
2632
|
|
|
@@ -2565,13 +2634,20 @@ class Graph():
|
|
|
2565
2634
|
|
|
2566
2635
|
|
|
2567
2636
|
@include_in_docs
|
|
2568
|
-
def weighted_indegree(self):
|
|
2637
|
+
def weighted_indegree(self, *, of: Optional[Relationship] = None):
|
|
2569
2638
|
"""Returns a binary relationship containing the weighted indegree of each node.
|
|
2570
2639
|
|
|
2571
2640
|
A node's weighted indegree is the sum of the weights of all incoming
|
|
2572
2641
|
edges. For undirected graphs, this is identical to `weighted_degree`.
|
|
2573
2642
|
For unweighted graphs, all edge weights are considered to be 1.0.
|
|
2574
2643
|
|
|
2644
|
+
Parameters
|
|
2645
|
+
----------
|
|
2646
|
+
of : Relationship, optional
|
|
2647
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
2648
|
+
provided, constrains the domain of the weighted indegree computation: only
|
|
2649
|
+
weighted indegrees of nodes in this relationship are computed and returned.
|
|
2650
|
+
|
|
2575
2651
|
Returns
|
|
2576
2652
|
-------
|
|
2577
2653
|
Relationship
|
|
@@ -2596,7 +2672,7 @@ class Graph():
|
|
|
2596
2672
|
|
|
2597
2673
|
Examples
|
|
2598
2674
|
--------
|
|
2599
|
-
>>> from relationalai.semantics import Model, define, select, Float
|
|
2675
|
+
>>> from relationalai.semantics import Model, define, select, where, union, Float
|
|
2600
2676
|
>>> from relationalai.semantics.reasoners.graph import Graph
|
|
2601
2677
|
>>>
|
|
2602
2678
|
>>> # 1. Set up a directed, weighted graph
|
|
@@ -2626,6 +2702,28 @@ class Graph():
|
|
|
2626
2702
|
0 1 -1.0
|
|
2627
2703
|
1 2 1.0
|
|
2628
2704
|
2 3 1.0
|
|
2705
|
+
>>>
|
|
2706
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted indegree of
|
|
2707
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
2708
|
+
>>> node = Node.ref()
|
|
2709
|
+
>>> where(union(node.id == 2, node.id == 3)).define(subset(node))
|
|
2710
|
+
>>> constrained_weighted_indegree = graph.weighted_indegree(of=subset)
|
|
2711
|
+
>>> select(node.id, node_weighted_indegree).where(constrained_weighted_indegree(node, node_weighted_indegree)).inspect()
|
|
2712
|
+
▰▰▰▰ Setup complete
|
|
2713
|
+
id node_weighted_indegree
|
|
2714
|
+
0 2 1.0
|
|
2715
|
+
1 3 1.0
|
|
2716
|
+
|
|
2717
|
+
Notes
|
|
2718
|
+
-----
|
|
2719
|
+
The ``weighted_indegree()`` method, called with no parameters, computes and caches
|
|
2720
|
+
the full weighted indegree relationship, providing efficient reuse across multiple
|
|
2721
|
+
calls to ``weighted_indegree()``. In contrast, ``weighted_indegree(of=subset)`` computes a
|
|
2722
|
+
constrained relationship specific to the passed-in ``subset`` and that
|
|
2723
|
+
call site. When a significant fraction of the weighted indegree relation is needed
|
|
2724
|
+
across a program, ``weighted_indegree()`` is typically more efficient; this is the
|
|
2725
|
+
typical case. Use ``weighted_indegree(of=subset)`` only when small subsets of the
|
|
2726
|
+
weighted indegree relationship are needed collectively across the program.
|
|
2629
2727
|
|
|
2630
2728
|
See Also
|
|
2631
2729
|
--------
|
|
@@ -2633,16 +2731,49 @@ class Graph():
|
|
|
2633
2731
|
weighted_outdegree
|
|
2634
2732
|
|
|
2635
2733
|
"""
|
|
2636
|
-
|
|
2734
|
+
# TODO: It looks like the weights in the example in the docstring above
|
|
2735
|
+
# are holdovers from a version of the library that did not disallow
|
|
2736
|
+
# negative weights. Need to update the example to use only non-negative weights.
|
|
2737
|
+
if of is None:
|
|
2738
|
+
return self._weighted_indegree
|
|
2739
|
+
else:
|
|
2740
|
+
# Validate the 'of' parameter
|
|
2741
|
+
self._validate_node_subset_parameter(of)
|
|
2742
|
+
return self._weighted_indegree_of(of)
|
|
2637
2743
|
|
|
2638
2744
|
@cached_property
|
|
2639
2745
|
def _weighted_indegree(self):
|
|
2640
2746
|
"""Lazily define and cache the self._weighted_indegree relationship."""
|
|
2747
|
+
return self._create_weighted_indegree_relationship(nodes_subset=None)
|
|
2748
|
+
|
|
2749
|
+
def _weighted_indegree_of(self, nodes_subset: Relationship):
|
|
2750
|
+
"""
|
|
2751
|
+
Create a weighted indegree relationship constrained to the subset of nodes
|
|
2752
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2753
|
+
specific to the callsite.
|
|
2754
|
+
"""
|
|
2755
|
+
return self._create_weighted_indegree_relationship(nodes_subset=nodes_subset)
|
|
2756
|
+
|
|
2757
|
+
def _create_weighted_indegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2641
2758
|
_weighted_indegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted indegree {{weight:Float}}")
|
|
2642
2759
|
|
|
2760
|
+
# Choose the appropriate node set
|
|
2761
|
+
if nodes_subset is None:
|
|
2762
|
+
# No constraint - use all nodes
|
|
2763
|
+
node_set = self.Node
|
|
2764
|
+
else:
|
|
2765
|
+
# Constrained to nodes in the subset
|
|
2766
|
+
node_set = nodes_subset
|
|
2767
|
+
# TODO: In a future cleanup pass, replace `node_set` with a `node_constraint`
|
|
2768
|
+
# that replaces the `node_set(self.Node)` in the where clause below,
|
|
2769
|
+
# and generates only `self.Node` (rather than `self.Node(self.Node)`)
|
|
2770
|
+
# in the `subset is None` case. This applies to a couple other
|
|
2771
|
+
# degree-of type relations as well.
|
|
2772
|
+
|
|
2773
|
+
# Apply the weighted indegree logic for both cases
|
|
2643
2774
|
src, inweight = self.Node.ref(), Float.ref()
|
|
2644
2775
|
where(
|
|
2645
|
-
self.Node,
|
|
2776
|
+
node_set(self.Node),
|
|
2646
2777
|
_weighted_indegree := sum(src, inweight).per(self.Node).where(self._weight(src, self.Node, inweight)) | 0.0,
|
|
2647
2778
|
).define(_weighted_indegree_rel(self.Node, _weighted_indegree))
|
|
2648
2779
|
|
|
@@ -2650,13 +2781,20 @@ class Graph():
|
|
|
2650
2781
|
|
|
2651
2782
|
|
|
2652
2783
|
@include_in_docs
|
|
2653
|
-
def weighted_outdegree(self):
|
|
2784
|
+
def weighted_outdegree(self, *, of: Optional[Relationship] = None):
|
|
2654
2785
|
"""Returns a binary relationship containing the weighted outdegree of each node.
|
|
2655
2786
|
|
|
2656
2787
|
A node's weighted outdegree is the sum of the weights of all outgoing
|
|
2657
2788
|
edges. For undirected graphs, this is identical to `weighted_degree`.
|
|
2658
2789
|
For unweighted graphs, all edge weights are considered to be 1.0.
|
|
2659
2790
|
|
|
2791
|
+
Parameters
|
|
2792
|
+
----------
|
|
2793
|
+
of : Relationship, optional
|
|
2794
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
2795
|
+
provided, constrains the domain of the weighted outdegree computation: only
|
|
2796
|
+
weighted outdegrees of nodes in this relationship are computed and returned.
|
|
2797
|
+
|
|
2660
2798
|
Returns
|
|
2661
2799
|
-------
|
|
2662
2800
|
Relationship
|
|
@@ -2681,7 +2819,7 @@ class Graph():
|
|
|
2681
2819
|
|
|
2682
2820
|
Examples
|
|
2683
2821
|
--------
|
|
2684
|
-
>>> from relationalai.semantics import Model, define, select, Float
|
|
2822
|
+
>>> from relationalai.semantics import Model, define, select, where, union, Float
|
|
2685
2823
|
>>> from relationalai.semantics.reasoners.graph import Graph
|
|
2686
2824
|
>>>
|
|
2687
2825
|
>>> # 1. Set up a directed, weighted graph
|
|
@@ -2711,6 +2849,32 @@ class Graph():
|
|
|
2711
2849
|
0 1 1.0
|
|
2712
2850
|
1 2 0.0
|
|
2713
2851
|
2 3 0.0
|
|
2852
|
+
>>>
|
|
2853
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute weighted outdegree of
|
|
2854
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
2855
|
+
>>> node = Node.ref()
|
|
2856
|
+
>>> where(union(node.id == 1, node.id == 2)).define(subset(node))
|
|
2857
|
+
>>> constrained_weighted_outdegree = graph.weighted_outdegree(of=subset)
|
|
2858
|
+
>>> select(
|
|
2859
|
+
... node.id, node_weighted_outdegree
|
|
2860
|
+
... ).where(
|
|
2861
|
+
... constrained_weighted_outdegree(node, node_weighted_outdegree)
|
|
2862
|
+
... ).inspect()
|
|
2863
|
+
▰▰▰▰ Setup complete
|
|
2864
|
+
id node_weighted_outdegree
|
|
2865
|
+
0 1 1.0
|
|
2866
|
+
1 2 0.0
|
|
2867
|
+
|
|
2868
|
+
Notes
|
|
2869
|
+
-----
|
|
2870
|
+
The ``weighted_outdegree()`` method, called with no parameters, computes and caches
|
|
2871
|
+
the full weighted outdegree relationship, providing efficient reuse across multiple
|
|
2872
|
+
calls to ``weighted_outdegree()``. In contrast, ``weighted_outdegree(of=subset)`` computes a
|
|
2873
|
+
constrained relationship specific to the passed-in ``subset`` and that
|
|
2874
|
+
call site. When a significant fraction of the weighted outdegree relation is needed
|
|
2875
|
+
across a program, ``weighted_outdegree()`` is typically more efficient; this is the
|
|
2876
|
+
typical case. Use ``weighted_outdegree(of=subset)`` only when small subsets of the
|
|
2877
|
+
weighted outdegree relationship are needed collectively across the program.
|
|
2714
2878
|
|
|
2715
2879
|
See Also
|
|
2716
2880
|
--------
|
|
@@ -2718,16 +2882,41 @@ class Graph():
|
|
|
2718
2882
|
weighted_indegree
|
|
2719
2883
|
|
|
2720
2884
|
"""
|
|
2721
|
-
|
|
2885
|
+
if of is None:
|
|
2886
|
+
return self._weighted_outdegree
|
|
2887
|
+
else:
|
|
2888
|
+
# Validate the 'of' parameter
|
|
2889
|
+
self._validate_node_subset_parameter(of)
|
|
2890
|
+
return self._weighted_outdegree_of(of)
|
|
2722
2891
|
|
|
2723
2892
|
@cached_property
|
|
2724
2893
|
def _weighted_outdegree(self):
|
|
2725
2894
|
"""Lazily define and cache the self._weighted_outdegree relationship."""
|
|
2895
|
+
return self._create_weighted_outdegree_relationship(nodes_subset=None)
|
|
2896
|
+
|
|
2897
|
+
def _weighted_outdegree_of(self, nodes_subset: Relationship):
|
|
2898
|
+
"""
|
|
2899
|
+
Create a weighted outdegree relationship constrained to the subset of nodes
|
|
2900
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
2901
|
+
specific to the callsite.
|
|
2902
|
+
"""
|
|
2903
|
+
return self._create_weighted_outdegree_relationship(nodes_subset=nodes_subset)
|
|
2904
|
+
|
|
2905
|
+
def _create_weighted_outdegree_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
2726
2906
|
_weighted_outdegree_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has weighted outdegree {{weight:Float}}")
|
|
2727
2907
|
|
|
2908
|
+
# Choose the appropriate node set
|
|
2909
|
+
if nodes_subset is None:
|
|
2910
|
+
# No constraint - use all nodes
|
|
2911
|
+
node_set = self.Node
|
|
2912
|
+
else:
|
|
2913
|
+
# Constrained to nodes in the subset
|
|
2914
|
+
node_set = nodes_subset
|
|
2915
|
+
|
|
2916
|
+
# Apply the weighted outdegree logic for both cases
|
|
2728
2917
|
dst, outweight = self.Node.ref(), Float.ref()
|
|
2729
2918
|
where(
|
|
2730
|
-
self.Node,
|
|
2919
|
+
node_set(self.Node),
|
|
2731
2920
|
_weighted_outdegree := sum(dst, outweight).per(self.Node).where(self._weight(self.Node, dst, outweight)) | 0.0,
|
|
2732
2921
|
).define(_weighted_outdegree_rel(self.Node, _weighted_outdegree))
|
|
2733
2922
|
|
|
@@ -2735,14 +2924,24 @@ class Graph():
|
|
|
2735
2924
|
|
|
2736
2925
|
|
|
2737
2926
|
@include_in_docs
|
|
2738
|
-
def degree_centrality(self):
|
|
2927
|
+
def degree_centrality(self, *, of: Optional[Relationship] = None):
|
|
2739
2928
|
"""Returns a binary relationship containing the degree centrality of each node.
|
|
2740
2929
|
|
|
2741
2930
|
Degree centrality is a measure of a node's importance, defined as its
|
|
2742
2931
|
degree (or weighted degree for weighted graphs) divided by the number
|
|
2743
|
-
of other nodes in the graph.
|
|
2744
|
-
|
|
2745
|
-
|
|
2932
|
+
of other nodes in the graph.
|
|
2933
|
+
|
|
2934
|
+
For unewighted graphs without self-loops, this value will be at most 1.0;
|
|
2935
|
+
unweighted graphs with self-loops might have nodes with a degree centrality
|
|
2936
|
+
greater than 1.0. Weighted graphs may have degree centralities
|
|
2937
|
+
greater than 1.0 as well.
|
|
2938
|
+
|
|
2939
|
+
Parameters
|
|
2940
|
+
----------
|
|
2941
|
+
of : Relationship, optional
|
|
2942
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
2943
|
+
provided, constrains the domain of the degree centrality computation: only
|
|
2944
|
+
degree centralities of nodes in this relationship are computed and returned.
|
|
2746
2945
|
|
|
2747
2946
|
Returns
|
|
2748
2947
|
-------
|
|
@@ -2798,6 +2997,20 @@ class Graph():
|
|
|
2798
2997
|
2 3 1.000000
|
|
2799
2998
|
3 4 0.666667
|
|
2800
2999
|
|
|
3000
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute degree centrality of
|
|
3001
|
+
>>> # Define a subset containing only nodes 2 and 3
|
|
3002
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
3003
|
+
>>> node = Node.ref()
|
|
3004
|
+
>>> where(union(node.id == 2, node.id == 3)).define(subset(node))
|
|
3005
|
+
>>>
|
|
3006
|
+
>>> # Get degree centralities only of nodes in the subset
|
|
3007
|
+
>>> constrained_degree_centrality = graph.degree_centrality(of=subset)
|
|
3008
|
+
>>> select(node.id, centrality).where(constrained_degree_centrality(node, centrality)).inspect()
|
|
3009
|
+
▰▰▰▰ Setup complete
|
|
3010
|
+
id centrality
|
|
3011
|
+
0 2 1.0
|
|
3012
|
+
1 3 1.0
|
|
3013
|
+
|
|
2801
3014
|
**Weighted Graph Example**
|
|
2802
3015
|
|
|
2803
3016
|
>>> from relationalai.semantics import Model, define, select, Float
|
|
@@ -2827,52 +3040,90 @@ class Graph():
|
|
|
2827
3040
|
1 2 1.75
|
|
2828
3041
|
2 3 1.00
|
|
2829
3042
|
|
|
3043
|
+
Notes
|
|
3044
|
+
-----
|
|
3045
|
+
The ``degree_centrality()`` method, called with no parameters, computes and caches
|
|
3046
|
+
the full degree centrality relationship, providing efficient reuse across multiple
|
|
3047
|
+
calls to ``degree_centrality()``. In contrast, ``degree_centrality(of=subset)`` computes a
|
|
3048
|
+
constrained relationship specific to the passed-in ``subset`` and that
|
|
3049
|
+
call site. When a significant fraction of the degree centrality relation is needed
|
|
3050
|
+
across a program, ``degree_centrality()`` is typically more efficient; this is the
|
|
3051
|
+
typical case. Use ``degree_centrality(of=subset)`` only when small subsets of the
|
|
3052
|
+
degree centrality relationship are needed collectively across the program.
|
|
3053
|
+
|
|
2830
3054
|
See Also
|
|
2831
3055
|
--------
|
|
2832
3056
|
degree
|
|
2833
3057
|
weighted_degree
|
|
2834
3058
|
|
|
2835
3059
|
"""
|
|
2836
|
-
|
|
3060
|
+
if of is None:
|
|
3061
|
+
return self._degree_centrality
|
|
3062
|
+
else:
|
|
3063
|
+
# Validate the 'of' parameter
|
|
3064
|
+
self._validate_node_subset_parameter(of)
|
|
3065
|
+
return self._degree_centrality_of(of)
|
|
2837
3066
|
|
|
2838
3067
|
@cached_property
|
|
2839
3068
|
def _degree_centrality(self):
|
|
2840
3069
|
"""Lazily define and cache the self._degree_centrality relationship."""
|
|
3070
|
+
return self._create_degree_centrality_relationship(nodes_subset=None)
|
|
3071
|
+
|
|
3072
|
+
def _degree_centrality_of(self, nodes_subset: Relationship):
|
|
3073
|
+
"""
|
|
3074
|
+
Create a degree centrality relationship constrained to the subset of nodes
|
|
3075
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
3076
|
+
specific to the callsite.
|
|
3077
|
+
"""
|
|
3078
|
+
return self._create_degree_centrality_relationship(nodes_subset=nodes_subset)
|
|
3079
|
+
|
|
3080
|
+
def _create_degree_centrality_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
3081
|
+
"""Create a degree centrality relationship, optionally constrained to a subset of nodes."""
|
|
2841
3082
|
_degree_centrality_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} has {{degree_centrality:Float}}")
|
|
2842
3083
|
|
|
2843
|
-
|
|
2844
|
-
|
|
3084
|
+
if nodes_subset is None:
|
|
3085
|
+
degree_rel = self._degree
|
|
3086
|
+
node_constraint = [] # No constraint on nodes.
|
|
3087
|
+
else:
|
|
3088
|
+
degree_rel = self._degree_of(nodes_subset)
|
|
3089
|
+
node_constraint = [nodes_subset(self.Node)] # Nodes constrained to given subset.
|
|
2845
3090
|
|
|
2846
|
-
|
|
3091
|
+
degree = Integer.ref()
|
|
2847
3092
|
|
|
2848
3093
|
# A single isolated node has degree centrality zero.
|
|
2849
3094
|
where(
|
|
2850
3095
|
self._num_nodes(1),
|
|
2851
|
-
|
|
3096
|
+
*node_constraint,
|
|
3097
|
+
degree_rel(self.Node, 0)
|
|
2852
3098
|
).define(_degree_centrality_rel(self.Node, 0.0))
|
|
2853
3099
|
|
|
2854
3100
|
# A single non-isolated node has degree centrality one.
|
|
2855
3101
|
where(
|
|
2856
3102
|
self._num_nodes(1),
|
|
2857
|
-
|
|
3103
|
+
*node_constraint,
|
|
3104
|
+
degree_rel(self.Node, degree),
|
|
2858
3105
|
degree > 0
|
|
2859
3106
|
).define(_degree_centrality_rel(self.Node, 1.0))
|
|
2860
3107
|
|
|
2861
3108
|
# General case, i.e. with more than one node.
|
|
2862
|
-
num_nodes = Integer.ref()
|
|
2863
3109
|
if self.weighted:
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
3110
|
+
maybe_weighted_degree = Float.ref()
|
|
3111
|
+
if nodes_subset is None:
|
|
3112
|
+
maybe_weighted_degree_rel = self._weighted_degree
|
|
3113
|
+
else:
|
|
3114
|
+
maybe_weighted_degree_rel = self._weighted_degree_of(nodes_subset)
|
|
3115
|
+
else: # not self.weighted
|
|
3116
|
+
maybe_weighted_degree = Integer.ref()
|
|
3117
|
+
maybe_weighted_degree_rel = degree_rel
|
|
3118
|
+
|
|
3119
|
+
num_nodes = Integer.ref()
|
|
3120
|
+
|
|
3121
|
+
where(
|
|
3122
|
+
self._num_nodes(num_nodes),
|
|
3123
|
+
num_nodes > 1,
|
|
3124
|
+
*node_constraint,
|
|
3125
|
+
maybe_weighted_degree_rel(self.Node, maybe_weighted_degree)
|
|
3126
|
+
).define(_degree_centrality_rel(self.Node, maybe_weighted_degree / (num_nodes - 1.0)))
|
|
2876
3127
|
|
|
2877
3128
|
return _degree_centrality_rel
|
|
2878
3129
|
|
|
@@ -3614,12 +3865,19 @@ class Graph():
|
|
|
3614
3865
|
|
|
3615
3866
|
|
|
3616
3867
|
@include_in_docs
|
|
3617
|
-
def triangle_count(self):
|
|
3868
|
+
def triangle_count(self, *, of: Optional[Relationship] = None):
|
|
3618
3869
|
"""Returns a binary relationship containing the number of unique triangles each node belongs to.
|
|
3619
3870
|
|
|
3620
3871
|
A triangle is a set of three nodes where each node has a directed
|
|
3621
3872
|
or undirected edge to the other two nodes, forming a 3-cycle.
|
|
3622
3873
|
|
|
3874
|
+
Parameters
|
|
3875
|
+
----------
|
|
3876
|
+
of : Relationship, optional
|
|
3877
|
+
A unary relationship containing a subset of the graph's nodes. When
|
|
3878
|
+
provided, constrains the domain of the triangle count computation: only
|
|
3879
|
+
triangle counts of nodes in this relationship are computed and returned.
|
|
3880
|
+
|
|
3623
3881
|
Returns
|
|
3624
3882
|
-------
|
|
3625
3883
|
Relationship
|
|
@@ -3675,6 +3933,31 @@ class Graph():
|
|
|
3675
3933
|
3 4 0
|
|
3676
3934
|
4 5 0
|
|
3677
3935
|
|
|
3936
|
+
>>> # 4. Use 'of' parameter to constrain the set of nodes to compute triangle counts of
|
|
3937
|
+
>>> # Define a subset containing only nodes 1 and 3
|
|
3938
|
+
>>> subset = model.Relationship(f"{{node:{Node}}} is in subset")
|
|
3939
|
+
>>> node = Node.ref()
|
|
3940
|
+
>>> where(union(node.id == 1, node.id == 3)).define(subset(node))
|
|
3941
|
+
>>>
|
|
3942
|
+
>>> # Get triangle counts only of nodes in the subset
|
|
3943
|
+
>>> constrained_triangle_count = graph.triangle_count(of=subset)
|
|
3944
|
+
>>> select(node.id, count).where(constrained_triangle_count(node, count)).inspect()
|
|
3945
|
+
▰▰▰▰ Setup complete
|
|
3946
|
+
id count
|
|
3947
|
+
0 1 1
|
|
3948
|
+
1 3 1
|
|
3949
|
+
|
|
3950
|
+
Notes
|
|
3951
|
+
-----
|
|
3952
|
+
The ``triangle_count()`` method, called with no parameters, computes and caches
|
|
3953
|
+
the full triangle count relationship, providing efficient reuse across multiple
|
|
3954
|
+
calls to ``triangle_count()``. In contrast, ``triangle_count(of=subset)`` computes a
|
|
3955
|
+
constrained relationship specific to the passed-in ``subset`` and that
|
|
3956
|
+
call site. When a significant fraction of the triangle count relation is needed
|
|
3957
|
+
across a program, ``triangle_count()`` is typically more efficient; this is the
|
|
3958
|
+
typical case. Use ``triangle_count(of=subset)`` only when small subsets of the
|
|
3959
|
+
triangle count relationship are needed collectively across the program.
|
|
3960
|
+
|
|
3678
3961
|
See Also
|
|
3679
3962
|
--------
|
|
3680
3963
|
triangle
|
|
@@ -3682,15 +3965,35 @@ class Graph():
|
|
|
3682
3965
|
num_triangles
|
|
3683
3966
|
|
|
3684
3967
|
"""
|
|
3968
|
+
if of is not None:
|
|
3969
|
+
self._validate_node_subset_parameter(of)
|
|
3970
|
+
return self._triangle_count_of(of)
|
|
3685
3971
|
return self._triangle_count
|
|
3686
3972
|
|
|
3687
3973
|
@cached_property
|
|
3688
3974
|
def _triangle_count(self):
|
|
3689
3975
|
"""Lazily define and cache the self._triangle_count relationship."""
|
|
3976
|
+
return self._create_triangle_count_relationship(nodes_subset=None)
|
|
3977
|
+
|
|
3978
|
+
def _triangle_count_of(self, nodes_subset: Relationship):
|
|
3979
|
+
"""
|
|
3980
|
+
Create a triangle count relationship constrained to the subset of nodes
|
|
3981
|
+
in `nodes_subset`. Note this relationship is not cached; it is
|
|
3982
|
+
specific to the callsite.
|
|
3983
|
+
"""
|
|
3984
|
+
return self._create_triangle_count_relationship(nodes_subset=nodes_subset)
|
|
3985
|
+
|
|
3986
|
+
def _create_triangle_count_relationship(self, *, nodes_subset: Optional[Relationship]):
|
|
3987
|
+
"""Create a triangle count relationship, optionally constrained to a subset of nodes."""
|
|
3690
3988
|
_triangle_count_rel = self._model.Relationship(f"{{node:{self._NodeConceptStr}}} belongs to {{count:Integer}} triangles")
|
|
3691
3989
|
|
|
3990
|
+
if nodes_subset is None:
|
|
3991
|
+
node_constraint = self.Node # No constraint on nodes.
|
|
3992
|
+
else:
|
|
3993
|
+
node_constraint = nodes_subset(self.Node) # Nodes constrained to given subset.
|
|
3994
|
+
|
|
3692
3995
|
where(
|
|
3693
|
-
|
|
3996
|
+
node_constraint,
|
|
3694
3997
|
_count := self._nonzero_triangle_count_fragment(self.Node) | 0
|
|
3695
3998
|
).define(_triangle_count_rel(self.Node, _count))
|
|
3696
3999
|
|
|
@@ -2,12 +2,15 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Union
|
|
3
3
|
import textwrap
|
|
4
4
|
import uuid
|
|
5
|
+
import time
|
|
5
6
|
|
|
6
7
|
from relationalai.semantics.snowflake import Table
|
|
7
8
|
from relationalai.semantics import std
|
|
8
9
|
from relationalai.semantics.internal import internal as b # TODO(coey) change b name or remove b.?
|
|
9
10
|
from relationalai.semantics.rel.executor import RelExecutor
|
|
10
11
|
from relationalai.semantics.lqp.executor import LQPExecutor
|
|
12
|
+
from relationalai.tools.constants import DEFAULT_QUERY_TIMEOUT_MINS
|
|
13
|
+
from relationalai.util.timeout import calc_remaining_timeout_minutes
|
|
11
14
|
|
|
12
15
|
from .common import make_name
|
|
13
16
|
from relationalai.experimental.solvers import Solver
|
|
@@ -243,6 +246,17 @@ class SolverModelDev:
|
|
|
243
246
|
app_name = resources.get_app_name()
|
|
244
247
|
print(app_name)
|
|
245
248
|
|
|
249
|
+
# Note: currently the query timeout is not propagated to the steps 'export model
|
|
250
|
+
# relations', and 'import result relations'. For those steps the default query
|
|
251
|
+
# timeout value defined in the config will apply.
|
|
252
|
+
# TODO: propagate the query timeout to those steps as well.
|
|
253
|
+
query_timeout_mins = kwargs.get("query_timeout_mins", None)
|
|
254
|
+
config = self._model._config
|
|
255
|
+
if query_timeout_mins is None and (timeout_value := config.get("query_timeout_mins", DEFAULT_QUERY_TIMEOUT_MINS)) is not None:
|
|
256
|
+
query_timeout_mins = int(timeout_value)
|
|
257
|
+
config_file_path = getattr(config, 'file_path', None)
|
|
258
|
+
start_time = time.monotonic()
|
|
259
|
+
|
|
246
260
|
# 1. export model relations
|
|
247
261
|
print("export model relations")
|
|
248
262
|
# TODO(coey) perf: only export the relations that are actually used in the model
|
|
@@ -266,6 +280,9 @@ class SolverModelDev:
|
|
|
266
280
|
b.select(*rel._field_refs).where(rel(*rel._field_refs)).into(table)
|
|
267
281
|
|
|
268
282
|
# 2. execute solver job and wait for completion
|
|
283
|
+
remaining_timeout_minutes = calc_remaining_timeout_minutes(
|
|
284
|
+
start_time, query_timeout_mins, config_file_path=config_file_path,
|
|
285
|
+
)
|
|
269
286
|
print("execute solver job")
|
|
270
287
|
payload = {
|
|
271
288
|
"solver": solver.solver_name.lower(),
|
|
@@ -273,7 +290,9 @@ class SolverModelDev:
|
|
|
273
290
|
"input_id": input_id,
|
|
274
291
|
"data_type": self._data_type
|
|
275
292
|
}
|
|
276
|
-
job_id = solver._exec_job(
|
|
293
|
+
job_id = solver._exec_job(
|
|
294
|
+
payload, log_to_console=log_to_console, query_timeout_mins=remaining_timeout_minutes,
|
|
295
|
+
)
|
|
277
296
|
print(f"job id: {job_id}") # TODO(coey) maybe job_id is not useful
|
|
278
297
|
|
|
279
298
|
# 3. import result relations
|