grakn.core.graql.reasoner.query.ReasonerAtomicQuery.java Source code

Java tutorial

Introduction

Here is the source code for grakn.core.graql.reasoner.query.ReasonerAtomicQuery.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.query;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import grakn.core.concept.answer.ConceptMap;
import grakn.core.graql.reasoner.atom.Atom;
import grakn.core.graql.reasoner.atom.Atomic;
import grakn.core.graql.reasoner.atom.AtomicFactory;
import grakn.core.graql.reasoner.atom.binary.TypeAtom;
import grakn.core.graql.reasoner.atom.predicate.NeqPredicate;
import grakn.core.graql.reasoner.cache.SemanticDifference;
import grakn.core.graql.reasoner.rule.InferenceRule;
import grakn.core.graql.reasoner.rule.RuleUtils;
import grakn.core.graql.reasoner.state.AnswerState;
import grakn.core.graql.reasoner.state.AtomicState;
import grakn.core.graql.reasoner.state.AtomicStateProducer;
import grakn.core.graql.reasoner.state.CacheCompletionState;
import grakn.core.graql.reasoner.state.NeqComplementState;
import grakn.core.graql.reasoner.state.QueryStateBase;
import grakn.core.graql.reasoner.state.ResolutionState;
import grakn.core.graql.reasoner.unifier.MultiUnifier;
import grakn.core.graql.reasoner.unifier.MultiUnifierImpl;
import grakn.core.graql.reasoner.unifier.Unifier;
import grakn.core.graql.reasoner.unifier.UnifierType;
import grakn.core.graql.reasoner.utils.Pair;
import grakn.core.graql.reasoner.utils.ReasonerUtils;
import grakn.core.server.session.TransactionOLTP;
import graql.lang.pattern.Conjunction;
import graql.lang.statement.Statement;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Base reasoner atomic query. An atomic query is a query constrained to having at most one rule-resolvable atom
 * together with its accompanying constraints (predicates and types).
 */
@SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS")
public class ReasonerAtomicQuery extends ReasonerQueryImpl {

    private final Atom atom;

    ReasonerAtomicQuery(Conjunction<Statement> pattern, TransactionOLTP tx) {
        super(pattern, tx);
        this.atom = Iterables.getOnlyElement(selectAtoms()::iterator);
    }

    ReasonerAtomicQuery(ReasonerQueryImpl query) {
        super(query);
        this.atom = Iterables.getOnlyElement(selectAtoms()::iterator);
    }

    ReasonerAtomicQuery(Atom at) {
        super(at);
        this.atom = Iterables.getOnlyElement(selectAtoms()::iterator);
    }

    ReasonerAtomicQuery(Set<Atomic> atoms, TransactionOLTP tx) {
        super(atoms, tx);
        this.atom = Iterables.getOnlyElement(selectAtoms()::iterator);
    }

    @Override
    public ReasonerAtomicQuery copy() {
        return new ReasonerAtomicQuery(this);
    }

    @Override
    public ReasonerAtomicQuery withSubstitution(ConceptMap sub) {
        return new ReasonerAtomicQuery(Sets.union(this.getAtoms(), AtomicFactory.answerToPredicates(sub, this)),
                this.tx());
    }

    @Override
    public ReasonerAtomicQuery inferTypes() {
        return new ReasonerAtomicQuery(getAtoms().stream().map(Atomic::inferTypes).collect(Collectors.toSet()),
                tx());
    }

    @Override
    public String toString() {
        return getAtoms(Atom.class).map(Atomic::toString).collect(Collectors.joining(", "));
    }

    @Override
    public boolean isAtomic() {
        return true;
    }

    /**
     * Determines whether the subsumption relation between this (C) and provided query (P) holds,
     * i. e. determines if:
     *
     * C <= P
     *
     * is true meaning that P is more general than C and their respective answer sets meet:
     *
     * answers(C) subsetOf answers(P)
     *
     * i. e. the set of answers of C is a subset of the set of answers of P
     *
     * @param parent query to compare with
     * @return true if this query subsumes the provided query
     */
    public boolean subsumes(ReasonerAtomicQuery parent) {
        MultiUnifier multiUnifier = this.getMultiUnifier(parent, UnifierType.SUBSUMPTIVE);
        if (multiUnifier.isEmpty())
            return false;
        MultiUnifier inverse = multiUnifier.inverse();

        //check whether propagated answers would be complete
        boolean propagatedAnswersComplete = !inverse.isEmpty()
                && inverse.stream().allMatch(u -> u.values().containsAll(this.getVarNames()));
        return propagatedAnswersComplete && !parent.getAtoms(NeqPredicate.class).findFirst().isPresent()
                && !this.getAtoms(NeqPredicate.class).findFirst().isPresent();
    }

    /**
     * @return the atom constituting this atomic query
     */
    public Atom getAtom() {
        return atom;
    }

    /**
     * @throws IllegalArgumentException if passed a {@link ReasonerQuery} that is not a {@link ReasonerAtomicQuery}.
     */
    @Override
    public MultiUnifier getMultiUnifier(ReasonerQuery p, UnifierType unifierType) {
        if (p == this)
            return MultiUnifierImpl.trivial();
        Preconditions.checkArgument(p instanceof ReasonerAtomicQuery);

        //NB: this is a defensive check and potentially expensive
        if (unifierType.equivalence() != null && !unifierType.equivalence().equivalent(p, this))
            return MultiUnifierImpl.nonExistent();

        ReasonerAtomicQuery parent = (ReasonerAtomicQuery) p;
        MultiUnifier multiUnifier = this.getAtom().getMultiUnifier(parent.getAtom(), unifierType);

        Set<TypeAtom> childTypes = this.getAtom().getTypeConstraints().collect(Collectors.toSet());
        if (multiUnifier.isEmpty() || childTypes.isEmpty())
            return multiUnifier;

        //get corresponding type unifiers
        Set<TypeAtom> parentTypes = parent.getAtom().getTypeConstraints().collect(Collectors.toSet());

        Set<Unifier> unifiers = multiUnifier.unifiers().stream()
                .map(unifier -> ReasonerUtils.typeUnifier(childTypes, parentTypes, unifier, unifierType))
                .collect(Collectors.toSet());
        return new MultiUnifierImpl(unifiers);
    }

    /**
     * Calculates:
     * - subsumptive unifier between this (child) and parent query
     * - semantic difference between this (child and parent query
     * @param parent parent query
     * @return pair of subsumptive unifier and semantic difference between this and parent query
     */
    public Set<Pair<Unifier, SemanticDifference>> getMultiUnifierWithSemanticDiff(ReasonerAtomicQuery parent) {
        return this.getMultiUnifier(parent, UnifierType.SUBSUMPTIVE).stream()
                .map(u -> new Pair<>(u, this.getAtom().semanticDifference(parent.getAtom(), u)))
                .collect(Collectors.toSet());
    }

    /**
     * materialise  this query with the accompanying answer - persist to kb
     * @param answer to be materialised
     * @return stream of materialised answers
     */
    public Stream<ConceptMap> materialise(ConceptMap answer) {
        return this.withSubstitution(answer).getAtom().materialise().map(ans -> ans.explain(answer.explanation()));
    }

    @Override
    public ResolutionState subGoal(ConceptMap sub, Unifier u, QueryStateBase parent,
            Set<ReasonerAtomicQuery> subGoals) {
        if (getAtom().getSchemaConcept() == null)
            return new AtomicStateProducer(this, sub, u, parent, subGoals);
        return isNeqPositive() ? new AtomicState(this, sub, u, parent, subGoals)
                : new NeqComplementState(this, sub, u, parent, subGoals);
    }

    @Override
    protected Stream<ReasonerQueryImpl> getQueryStream(ConceptMap sub) {
        return getAtom().atomOptions(sub).stream().map(ReasonerAtomicQuery::new);
    }

    @Override
    public Iterator<ResolutionState> queryStateIterator(QueryStateBase parent,
            Set<ReasonerAtomicQuery> visitedSubGoals) {
        Pair<Stream<ConceptMap>, MultiUnifier> cacheEntry = tx().queryCache().getAnswerStreamWithUnifier(this);
        Iterator<AnswerState> dbIterator = cacheEntry.getKey()
                .map(a -> a.explain(a.explanation().setPattern(this.getPattern())))
                .map(ans -> new AnswerState(ans, parent.getUnifier(), parent)).iterator();

        Iterator<ResolutionState> dbCompletionIterator = Iterators
                .singletonIterator(new CacheCompletionState(this, new ConceptMap(), null));

        Iterator<ResolutionState> subGoalIterator;
        boolean visited = visitedSubGoals.contains(this);
        //if this is ground and exists in the db then do not resolve further
        boolean doNotResolveFurther = visited || tx().queryCache().isComplete(this)
                || (this.isGround() && dbIterator.hasNext());
        if (doNotResolveFurther) {
            subGoalIterator = Collections.emptyIterator();
        } else {

            subGoalIterator = getRuleStream().map(rulePair -> rulePair.getKey().subGoal(this.getAtom(),
                    rulePair.getValue(), parent, visitedSubGoals)).iterator();
        }
        if (!visited)
            visitedSubGoals.add(this);
        return Iterators.concat(dbIterator, dbCompletionIterator, subGoalIterator);
    }

    private Stream<Pair<InferenceRule, Unifier>> getRuleStream() {
        return RuleUtils.stratifyRules(getAtom().getApplicableRules().collect(Collectors.toSet()))
                .flatMap(r -> r.getMultiUnifier(getAtom()).stream().map(unifier -> new Pair<>(r, unifier)));
    }
}