grakn.core.graql.reasoner.plan.GraqlTraversalPlanner.java Source code

Java tutorial

Introduction

Here is the source code for grakn.core.graql.reasoner.plan.GraqlTraversalPlanner.java

Source

/*
 * GRAKN.AI - THE KNOWLEDGE GRAPH
 * Copyright (C) 2018 Grakn Labs Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package grakn.core.graql.reasoner.plan;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import grakn.core.concept.ConceptId;
import grakn.core.graql.gremlin.GraqlTraversal;
import grakn.core.graql.gremlin.TraversalPlanner;
import grakn.core.graql.gremlin.fragment.Fragment;
import grakn.core.graql.reasoner.atom.Atom;
import grakn.core.graql.reasoner.atom.Atomic;
import grakn.core.graql.reasoner.atom.binary.OntologicalAtom;
import grakn.core.graql.reasoner.atom.predicate.IdPredicate;
import grakn.core.graql.reasoner.query.ReasonerQueryImpl;
import grakn.core.server.session.TransactionOLTP;
import graql.lang.Graql;
import graql.lang.pattern.Conjunction;
import graql.lang.pattern.Pattern;
import graql.lang.property.VarProperty;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Resolution planner using {@link TraversalPlanner} to establish optimal resolution order..
 */
public class GraqlTraversalPlanner {

    /**
     *
     * Refined plan procedure:
     * - establish a list of starting atom candidates based on their substitutions
     * - create a plan using {@link TraversalPlanner}
     * - if the graql plan picks an atom that is not a candidate
     *   - pick an optimal candidate
     *   - call the procedure on atoms with removed candidate
     * - otherwise return
     *
     * @param query for which the plan should be constructed
     * @return list of atoms in order they should be resolved using a refined {@link GraqlTraversal} procedure.
     */
    public static ImmutableList<Atom> plan(ReasonerQueryImpl query) {
        List<Atom> startCandidates = query.getAtoms(Atom.class).filter(Atomic::isSelectable)
                .collect(Collectors.toList());
        Set<IdPredicate> subs = query.getAtoms(IdPredicate.class).collect(Collectors.toSet());
        return ImmutableList.copyOf(refinePlan(query, startCandidates, subs));
    }

    @Nullable
    private static Atom optimalCandidate(List<Atom> candidates) {
        return candidates.stream().sorted(Comparator.comparing(at -> !at.isGround()))
                .sorted(Comparator.comparing(at -> -at.getPredicates().count())).findFirst().orElse(null);
    }

    private static String PLACEHOLDER_ID = "placeholderId";

    /**
     * @param query top level query for which the plan is constructed
     * @param atoms list of current atoms of interest
     * @param subs extra substitutions
     * @return an optimally ordered list of provided atoms
     */
    private static List<Atom> refinePlan(ReasonerQueryImpl query, List<Atom> atoms, Set<IdPredicate> subs) {
        List<Atom> candidates = subs.isEmpty() ? atoms
                : atoms.stream().filter(at -> at.getPredicates(IdPredicate.class).findFirst().isPresent())
                        .collect(Collectors.toList());

        ImmutableList<Atom> initialPlan = planFromTraversal(atoms, atomsToPattern(atoms, subs), query.tx());
        if (candidates.contains(initialPlan.get(0)) || candidates.isEmpty()) {
            return initialPlan;
        } else {
            Atom first = optimalCandidate(candidates);
            List<Atom> atomsToPlan = new ArrayList<>(atoms);

            if (first != null) {
                atomsToPlan.remove(first);

                Set<IdPredicate> extraSubs = first.getVarNames().stream()
                        .filter(v -> subs.stream().noneMatch(s -> s.getVarName().equals(v)))
                        .map(v -> IdPredicate.create(v, ConceptId.of(PLACEHOLDER_ID), query))
                        .collect(Collectors.toSet());

                return Stream
                        .concat(Stream.of(first),
                                refinePlan(query, atomsToPlan, Sets.union(subs, extraSubs)).stream())
                        .collect(Collectors.toList());
            } else {
                return refinePlan(query, atomsToPlan, subs);
            }
        }
    }

    /**
     * @param atoms of interest
     * @param subs extra substitutions in the form of id predicates
     * @return conjunctive pattern composed of atoms + their constraints + subs
     */
    public static Conjunction<Pattern> atomsToPattern(List<Atom> atoms, Set<IdPredicate> subs) {
        return Graql.and(Stream
                .concat(atoms.stream().flatMap(
                        at -> Stream.concat(Stream.of(at), at.getNonSelectableConstraints())), subs.stream())
                .map(Atomic::getCombinedPattern).flatMap(p -> p.statements().stream()).collect(Collectors.toSet()));
    }

    /**
     *
     * @param atoms list of current atoms of interest
     * @param queryPattern corresponding pattern
     * @return an optimally ordered list of provided atoms
     */
    private static ImmutableList<Atom> planFromTraversal(List<Atom> atoms, Conjunction<?> queryPattern,
            TransactionOLTP tx) {
        Multimap<VarProperty, Atom> propertyMap = HashMultimap.create();
        atoms.stream().filter(atom -> !(atom instanceof OntologicalAtom))
                .forEach(atom -> atom.getVarProperties().forEach(property -> propertyMap.put(property, atom)));
        Set<VarProperty> properties = propertyMap.keySet();

        GraqlTraversal graqlTraversal = TraversalPlanner.createTraversal(queryPattern, tx);
        ImmutableList<Fragment> fragments = Iterables.getOnlyElement(graqlTraversal.fragments());

        List<Atom> atomList = new ArrayList<>();

        atoms.stream().filter(atom -> atom instanceof OntologicalAtom).forEach(atomList::add);

        fragments.stream().map(Fragment::varProperty).filter(Objects::nonNull).filter(properties::contains)
                .distinct().flatMap(property -> propertyMap.get(property).stream()).distinct()
                .forEach(atomList::add);

        //add any unlinked items (disconnected and indexed for instance)
        propertyMap.values().stream().filter(at -> !atomList.contains(at)).forEach(atomList::add);
        return ImmutableList.copyOf(atomList);
    }
}