relationalai 0.12.2__py3-none-any.whl → 0.12.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.
@@ -96,8 +96,12 @@ class DependencyInfo():
96
96
  # start with the cluster dependencies, because cluster represents the task we
97
97
  # care about
98
98
  queue.extend(cluster.dependencies)
99
+ seen = set()
99
100
  while queue:
100
101
  cluster = queue.pop()
102
+ if cluster.id in seen:
103
+ continue
104
+ seen.add(cluster.id)
101
105
  deps.update(cluster.content)
102
106
  queue.extend(cluster.dependencies)
103
107
 
@@ -625,13 +629,17 @@ class BindingAnalysis(visitor.Visitor):
625
629
  # TODO: this is similar to what's done below in visit_lookup, modularize
626
630
  if builtins.is_eq(child.relation):
627
631
  x, y = child.args[0], child.args[1]
632
+ # Compute input/output vars of the equality
628
633
  if isinstance(x, ir.Var) and not isinstance(y, ir.Var):
629
- grounds.add(x)
634
+ # Variable x is potentially grounded by other expressions at
635
+ # level in the Logical. If it is, then we should mark it as
636
+ # input (which is done later).
637
+ potentially_grounded.add((child, x, x))
630
638
  elif not isinstance(x, ir.Var) and isinstance(y, ir.Var):
631
- grounds.add(y)
639
+ potentially_grounded.add((child, y, y))
632
640
  elif isinstance(x, ir.Var) and isinstance(y, ir.Var):
633
641
  # mark as potentially grounded, if any is grounded in other atoms then we later ground both
634
- potentially_grounded.add((x, y))
642
+ potentially_grounded.add((child, x, y))
635
643
  else:
636
644
  # grounds only outputs
637
645
  for idx, f in enumerate(child.relation.fields):
@@ -654,23 +662,33 @@ class BindingAnalysis(visitor.Visitor):
654
662
  # grounds the output var
655
663
  grounds.add(child.id_var)
656
664
 
665
+ # add child hoisted vars to grounded so that they can be picked up by the children
666
+ for child in node.body:
667
+ if isinstance(child, helpers.COMPOSITES):
668
+ grounds.update(helpers.hoisted_vars(child.hoisted))
669
+
670
+ # equalities where both sides are already grounded mean that both sides are input
671
+ for child, x, y in potentially_grounded:
672
+ if x in grounds and y in grounds:
673
+ self.input(child, x)
674
+ self.input(child, y)
675
+
657
676
  # deal with potentially grounded vars up to a fixpoint
658
677
  changed = True
659
678
  while changed:
660
679
  changed = False
661
- for x, y in potentially_grounded:
680
+ for child, x, y in potentially_grounded:
662
681
  if x in grounds and y not in grounds:
682
+ self.input(child, x)
683
+ self.output(child, y)
663
684
  grounds.add(y)
664
685
  changed = True
665
686
  elif y in grounds and x not in grounds:
687
+ self.input(child, y)
688
+ self.output(child, x)
666
689
  grounds.add(x)
667
690
  changed = True
668
691
 
669
- # add child hoisted vars to grounded so that they can be picked up by the children
670
- for child in node.body:
671
- if isinstance(child, helpers.COMPOSITES):
672
- grounds.update(helpers.hoisted_vars(child.hoisted))
673
-
674
692
  # now visit the children
675
693
  self._grounded.append(grounds)
676
694
  super().visit_logical(node, parent)
@@ -765,20 +783,25 @@ class BindingAnalysis(visitor.Visitor):
765
783
  self.output(node, arg)
766
784
 
767
785
  if builtins.is_eq(node.relation):
768
- # special case eq because it can be input or output
769
- x, y = node.args[0], node.args[1]
770
- if isinstance(x, ir.Var) and not isinstance(y, ir.Var):
771
- self.output(node, x)
772
- elif not isinstance(x, ir.Var) and isinstance(y, ir.Var):
773
- self.output(node, y)
774
- elif isinstance(x, ir.Var) and isinstance(y, ir.Var):
775
- # in this case it's possible that both are outputs (if both are grounded), else both are inputs
786
+ # Most cases are covered already at the parent level if the equality is part of
787
+ # a Logical. The remaining cases are when the equality is a child of a
788
+ # non-Logical, or if its variables are not ground elsewhere in the Logical.
789
+ if self.info.task_inputs(node) or self.info.task_outputs(node):
790
+ # already covered
791
+ pass
792
+ else:
793
+ x, y = node.args[0], node.args[1]
776
794
  grounds = self._grounded[-1] if self._grounded else ordered_set()
777
- for var in (x, y):
778
- if var in grounds:
779
- self.output(node, var)
795
+ if isinstance(x, ir.Var):
796
+ if x in grounds:
797
+ self.input(node, x)
798
+ else:
799
+ self.output(node, x)
800
+ if isinstance(y, ir.Var):
801
+ if y in grounds:
802
+ self.input(node, y)
780
803
  else:
781
- self.input(node, var)
804
+ self.output(node, y)
782
805
  else:
783
806
  # register variables depending on the input flag of the relation bound to the lookup
784
807
  for idx, f in enumerate(node.relation.fields):
@@ -348,12 +348,13 @@ def clone_task(task: T) -> T:
348
348
  # if no childrean were stacked, we rewrote all fields of curr, so we can pop it and rewrite it
349
349
  if not stacked_children:
350
350
  stack.pop()
351
- children = []
352
- for f in curr_fields:
353
- children.append(from_cache(getattr(curr, f.name)))
354
- # create a new prev_node with the cloned children
355
- prev_node = curr.__class__(*children)
356
- cache[curr.id] = prev_node
351
+ if curr.id not in cache:
352
+ children = []
353
+ for f in curr_fields:
354
+ children.append(from_cache(getattr(curr, f.name)))
355
+ # create a new prev_node with the cloned children
356
+ prev_node = curr.__class__(*children)
357
+ cache[curr.id] = prev_node
357
358
 
358
359
  # the last node we processed is the rewritten original node
359
360
  assert(isinstance(prev_node, type(task)))
@@ -58,10 +58,7 @@ class LogicalExtractor(Rewriter):
58
58
  # compute the vars to be exposed by the extracted logical; those are keys (what
59
59
  # makes the values unique) + the values (the hoisted variables)
60
60
  exposed_vars = ordered_set()
61
- # start with all the inputs to this logical as keys
62
- # TODO - in the future we can analyze better these inputs to see if we can drop
63
- # some and have narrower intermediate relations
64
- exposed_vars.update(self.info.task_inputs(logical))
61
+
65
62
  # if there are aggregations, make sure we don't expose the projected and input vars,
66
63
  # but expose groupbys
67
64
  for child in node.body:
@@ -3,7 +3,7 @@ from dataclasses import dataclass
3
3
  from typing import cast, Optional, TypeVar
4
4
  from typing import Tuple
5
5
 
6
- from relationalai.semantics.metamodel import builtins, ir, factory as f, helpers, types, visitor
6
+ from relationalai.semantics.metamodel import builtins, ir, factory as f, helpers, types
7
7
  from relationalai.semantics.metamodel.compiler import Pass, group_tasks
8
8
  from relationalai.semantics.metamodel.util import OrderedSet, ordered_set, NameCache
9
9
  from relationalai.semantics.metamodel import dependency
@@ -611,18 +611,6 @@ def set_union(s1: Optional[OrderedSet[T]], s2: Optional[OrderedSet[T]]) -> list:
611
611
  return s2.get_list()
612
612
  return []
613
613
 
614
- def extractable(t: ir.Task):
615
- """
616
- Whether this task is a Logical that will be extracted as a top level by this
617
- pass, because it has an aggregation, effects, match, union, etc.
618
- """
619
- extractable_types = (ir.Update, ir.Aggregate, ir.Match, ir.Union, ir.Rank)
620
- return isinstance(t, ir.Logical) and len(visitor.collect_by_type(extractable_types, t)) > 0
621
-
622
- def extractables(composites: OrderedSet[ir.Task]):
623
- """ Filter the set of composites, keeping only the extractable ones. """
624
- return list(filter(extractable, composites))
625
-
626
614
  def negate(lookup: ir.Lookup, values: int):
627
615
  """
628
616
  Return a negation of this reference, where the last `values` arguments are to