def _adapt_query_plan(self, plan: QueryPlan, query: Query): # The given plan is assumed to be a canonical query plan. # Also, the transformation in plan should be the same as the transformation in query. This should be # guaranteed by construction of the query plan. # Create a fresh copy of the plan where the transformation is the actual subgraph. adapted_plan = plan.deepcopy().adapt(new_transformation=query.subgraph, mapping_old_to_new=query.mapping) equality_label = self._domain.get_equality_edge_label() if equality_label is None: return None # Propagate known values amongst the nodes with the equality edge influence: Dict[Node, Set[Node]] = collections.defaultdict(set) for k, v in adapted_plan.all_connections.m_node.items(): influence[v].add(k) seen = set() worklist = collections.deque(adapted_plan.transformation.iter_nodes()) while len(worklist) > 0: node = worklist.popleft() if node in seen: continue seen.add(node) if node.value is SYMBOLIC_VALUE: continue # Connected nodes inherit the value for n in influence[node]: if n.value is SYMBOLIC_VALUE: n.value = node.value worklist.append(n) # Equality edges with src and dst as node also propagate the values for unit in adapted_plan.units: for e in unit.iter_edges(src=node, label=equality_label): if e.dst.value is SYMBOLIC_VALUE: e.dst.value = node.value worklist.append(e.dst) for e in unit.iter_edges(dst=node, label=equality_label): if e.src.value is SYMBOLIC_VALUE: e.src.value = node.value worklist.append(e.src) # We may have wrecked the internal data-structures of the unit transformations by changing values directly. # Create shallow copies which force a rebuild adapted_plan.units = [Transformation.build_from_graph(Graph.from_nodes_and_edges(unit.get_all_nodes(), unit.get_all_edges()), unit.get_input_entities(), unit.get_output_entity()) for unit in adapted_plan.units] return adapted_plan
def get_strengthening_constraint(self, input_graph: Graph) -> Graph: common_tags = None common_edges = None common_tagged_edges = None for plan, partial_mappings in self._checks.items(): strengthening, s_mapping = plan.strengthenings[self.depth] s_edges = strengthening.get_all_edges() s_tagged_edges = set(strengthening.iter_tagged_edges()) plan_tags = set(strengthening.iter_tags()) plan_tagged_edges = None plan_edges = None for partial_mapping in partial_mappings: mapping_wrt_inp_graph = partial_mapping.apply_mapping( s_mapping, only_keys=True) for m in strengthening.get_subgraph_mappings( input_graph, partial_mapping=mapping_wrt_inp_graph): if plan_tagged_edges is None: plan_tagged_edges = { TaggedEdge(m.m_node[e.src], m.m_node[e.dst], e.tag) for e in s_tagged_edges } plan_edges = { Edge(m.m_node[e.src], m.m_node[e.dst], e.label) for e in s_edges } else: plan_tagged_edges.intersection_update( TaggedEdge(m.m_node[e.src], m.m_node[e.dst], e.tag) for e in s_tagged_edges) plan_edges.intersection_update( Edge(m.m_node[e.src], m.m_node[e.dst], e.label) for e in s_edges) if common_tags is None: common_tags = plan_tags or set() common_tagged_edges = plan_tagged_edges or set() common_edges = plan_edges or set() else: common_tags.intersection_update(plan_tags or set()) common_tagged_edges.intersection_update(plan_tagged_edges or set()) common_edges.intersection_update(plan_edges or set()) nodes = {e.src for e in common_tagged_edges} nodes.update(e.dst for e in common_tagged_edges) nodes.update(e.src for e in common_edges) nodes.update(e.dst for e in common_edges) result = Graph.from_nodes_and_edges(nodes=nodes, edges=common_edges) result.add_tagged_edges(common_tagged_edges) result.add_tags(common_tags) return result
def _extract_unit_plans(self, component_name: str, witness_entry: WitnessEntry): graph = witness_entry.graph input_entities = witness_entry.get_input_entities() output_entity = witness_entry.get_output_entity() placeholder_dict = {} # A placeholder node can represent any node belonging to an entity. # This helps coalesce equivalent query plans. for ent in itertools.chain(input_entities, [output_entity]): placeholder_dict[ent] = PlaceholderNode(entity=ent) path_dict: Dict[Entity, List[Path]] = extract_paths(graph, input_entities, output_entity) # Find queries by taking exactly one path, and placeholder nodes for the # input entities not present in the path. for path_ent, paths in path_dict.items(): remaining_entities = [ent for ent in input_entities if ent is not path_ent] for path in paths: path_nodes, path_edges = path nodes = list(path_nodes) + [placeholder_dict[ent] for ent in remaining_entities] edges = path_edges # Get the corresponding subgraph. subgraph = Graph.from_nodes_and_edges(nodes=set(nodes), edges=set(edges)) self._record_unit_meta_query_plan(component_name, subgraph, input_entities, output_entity) # Include empty transformations to help with evolution (the second stage). # An empty transformation plays the role of a *wildcard* plan, that is, any transformation is valid. # We add all empty transformations with input nodes spanning all distinct input node types # (including placeholders) and the output being the placeholder node. label_canonical_node: Dict[Entity, Dict[int, Node]] = collections.defaultdict(dict) for ent in input_entities: for node in graph.iter_nodes(entity=ent): label_canonical_node[ent][node.label] = node canonical_nodes: List[List[Node]] = [list(v.values()) for v in label_canonical_node.values()] canonical_nodes.append([placeholder_dict[output_entity]]) for subgraph_nodes in itertools.product(*canonical_nodes): subgraph = Graph.from_nodes_and_edges(nodes=subgraph_nodes, edges=[]) self._record_unit_meta_query_plan(component_name, subgraph, input_entities, output_entity, empty=True)
def create_symbolic_copy(graph: Graph) -> Tuple[Graph, GraphMapping]: mapping = GraphMapping() for entity in graph.iter_entities(): mapping.m_ent[entity] = Entity(value=SYMBOLIC_VALUE) for node in graph.iter_nodes(): mapping.m_node[node] = Node(label=node.label, entity=mapping.m_ent[node.entity], value=SYMBOLIC_VALUE) new_graph = Graph.from_nodes_and_edges(nodes=set(mapping.m_node.values()), edges={ Edge(src=mapping.m_node[e.src], dst=mapping.m_node[e.dst], label=e.label) for e in graph.iter_edges() }) return new_graph, mapping
def extract_queries( problem: SynthesisProblem) -> Dict[Transformation, List[Query]]: # Stage 1 : Get all paths from one of the input nodes to an output node without any input/output node in between. graph = problem.graph inputs = problem.inputs output = problem.output entities = list(graph.iter_entities()) input_entities = [ next(ent for ent in entities if ent.value is inp) for inp in inputs ] output_entity = next(ent for ent in entities if ent.value is output) arg_numbering = {ent: idx for idx, ent in enumerate(input_entities)} placeholder_dict = {} # A placeholder node can represent any node belonging to an entity. # This helps coalesce equivalent query plans. for ent in itertools.chain(input_entities, [output_entity]): placeholder_dict[ent] = PlaceholderNode(entity=ent) path_dict: Dict[Entity, List[Path]] = extract_paths(graph, input_entities, output_entity) canonical_transformations: Dict[Transformation, Transformation] = {} # Only keep one transformation for a set of nodes as satisfying that query means satisfying all the others. seen: Set[Tuple[Transformation, FrozenSet[Node]]] = set() queries: Dict[Transformation, List[Query]] = collections.defaultdict(list) set_input_entities = set(input_entities) # Find queries by taking exactly one path, and placeholder nodes for the # input entities not present in the path. for path_ent, paths in path_dict.items(): remaining_entities = [ ent for ent in set_input_entities if ent is not path_ent ] for path in paths: path_nodes, path_edges = path nodes = list(path_nodes) + [ placeholder_dict[ent] for ent in remaining_entities ] edges = path_edges # Get the corresponding subgraph. subgraph = Graph.from_nodes_and_edges(nodes=set(nodes), edges=set(edges)) subgraph_transformation = Transformation.build_from_graph( subgraph, input_entities=input_entities, output_entity=output_entity) # Compute the symbolic counter-part to group subgraphs together by the underlying transformation. symbolic_copy, mapping = create_symbolic_copy(subgraph) mapped_input_entities = [mapping.m_ent[i] for i in input_entities] mapped_output_entity = mapping.m_ent[output_entity] transformation = Transformation.build_from_graph( symbolic_copy, input_entities=mapped_input_entities, output_entity=mapped_output_entity) # Check if the transformation was seen before if transformation not in canonical_transformations: canonical_transformations[transformation] = transformation seen.add((transformation, frozenset(n for n in subgraph.iter_nodes() if n.entity in set_input_entities))) mapping = mapping.reverse() arg_number_mapping = ArgumentNumberMapping({ idx: arg_numbering[mapping.m_ent[ent]] for idx, ent in enumerate(mapped_input_entities) }) # We need a mapping from transformation to the subgraph. queries[transformation].append( Query(transformation=transformation, subgraph=subgraph_transformation, mapping=mapping, arg_number_mapping=arg_number_mapping)) else: canonical = canonical_transformations[transformation] key = (transformation, frozenset(n for n in subgraph.iter_nodes() if n.entity in set_input_entities)) # Check if the transformation was seen before with the same input nodes. If yes, continue. This is # because if a graph satisfies the already seen transformation for these input nodes, it will satisfy # this one as well. So no point in checking it. if key in seen: continue seen.add(key) # We need a mapping from the canonical transformation to the subgraph. mapping = next(canonical.get_subgraph_mappings( transformation)).apply_mapping(mapping.reverse()) arg_number_mapping = ArgumentNumberMapping({ idx: arg_numbering[mapping.m_ent[ent]] for idx, ent in enumerate(canonical.get_input_entities()) }) queries[canonical].append( Query(transformation=canonical, subgraph=subgraph_transformation, mapping=mapping, arg_number_mapping=arg_number_mapping)) return queries