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
Example #2
0
    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)
Example #4
0
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
Example #5
0
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