grakn.core.graql.reasoner.cache.SemanticDifference.java Source code

Java tutorial

Introduction

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

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import grakn.core.concept.Concept;
import grakn.core.concept.answer.ConceptMap;
import grakn.core.concept.thing.Relation;
import grakn.core.concept.type.Role;
import grakn.core.concept.type.Type;
import grakn.core.graql.executor.property.ValueExecutor;
import grakn.core.graql.reasoner.atom.predicate.ValuePredicate;
import grakn.core.graql.reasoner.unifier.Unifier;
import grakn.core.graql.reasoner.unifier.UnifierType;
import grakn.core.server.kb.concept.ConceptUtils;
import graql.lang.statement.Variable;

import javax.annotation.CheckReturnValue;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Quantifies semantic difference between two queries provided they are in a subsumption relation, i. e. there exists
 * a {@link Unifier} of {@link UnifierType#SUBSUMPTIVE} between them.
 * Semantic difference between query C and P defines a specialisation operation
 * required to transform query P into a query equivalent to C.
 * In that way we can check whether answers to the parent (more generic) query are also answers
 * to the child query (more specific).
 */
public class SemanticDifference {

    final private ImmutableSet<VariableDefinition> definition;

    public SemanticDifference(Set<VariableDefinition> definition) {
        this.definition = ImmutableSet.copyOf(definition.stream().filter(vd -> !vd.isTrivial()).iterator());
    }

    @Override
    public String toString() {
        return definition.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || this.getClass() != obj.getClass())
            return false;
        if (obj == this)
            return true;
        SemanticDifference that = (SemanticDifference) obj;
        return this.definition.equals(that.definition);
    }

    @Override
    public int hashCode() {
        return definition.hashCode();
    }

    private Set<Relation> rolesToRels(Variable var, Set<Role> roles, ConceptMap answer) {
        if (!answer.containsVar(var))
            return new HashSet<>();
        Set<Role> roleAndTheirSubs = roles.stream().flatMap(Role::subs).collect(Collectors.toSet());
        return answer.get(var).asThing().relations(roleAndTheirSubs.toArray(new Role[0]))
                .collect(Collectors.toSet());
    }

    public boolean satisfiedBy(ConceptMap answer) {
        if (isEmpty())
            return true;

        Map<Variable, Set<Role>> roleRequirements = this.definition.stream()
                .filter(vd -> !vd.playedRoles().isEmpty())
                .collect(Collectors.toMap(VariableDefinition::var, VariableDefinition::playedRoles));

        //check for role compatibility
        Iterator<Map.Entry<Variable, Set<Role>>> reqIterator = roleRequirements.entrySet().iterator();
        Set<Relation> relations;
        if (reqIterator.hasNext()) {
            Map.Entry<Variable, Set<Role>> req = reqIterator.next();
            relations = rolesToRels(req.getKey(), req.getValue(), answer);
        } else {
            relations = new HashSet<>();
        }
        while (!relations.isEmpty() && reqIterator.hasNext()) {
            Map.Entry<Variable, Set<Role>> req = reqIterator.next();
            relations = Sets.intersection(relations, rolesToRels(req.getKey(), req.getValue(), answer));
        }
        if (relations.isEmpty() && !roleRequirements.isEmpty())
            return false;

        return definition.stream().allMatch(vd -> {
            Variable var = vd.var();
            Concept concept = answer.get(var);
            if (concept == null)
                return false;
            Type type = vd.type();
            Role role = vd.role();
            Set<ValuePredicate> vps = vd.valuePredicates();
            return (type == null || type.subs().anyMatch(t -> t.equals(concept.asThing().type())))
                    && (role == null || role.subs().anyMatch(r -> r.equals(concept.asRole())))
                    && (vps.isEmpty() || vps.stream().allMatch(vp -> ValueExecutor.Operation.of(vp.getPredicate())
                            .test(concept.asAttribute().value())));
        });
    }

    public SemanticDifference merge(SemanticDifference diff) {
        Map<Variable, VariableDefinition> mergedDefinition = definition.stream()
                .collect(Collectors.toMap(VariableDefinition::var, vd -> vd));
        diff.definition.forEach(varDefToMerge -> {
            Variable var = varDefToMerge.var();
            VariableDefinition varDef = mergedDefinition.get(var);
            mergedDefinition.put(var, varDef != null ? varDef.merge(varDefToMerge) : varDefToMerge);
        });
        return new SemanticDifference(new HashSet<>(mergedDefinition.values()));
    }

    /**
     * @param answer to project
     * @param partialSub partial child substitution that needs to be incorporated
     * @param vars       child vars
     * @param unifier    parent-child unifier
     * @return projected answer (empty if semantic difference not satisfied)
     */
    @CheckReturnValue
    public ConceptMap applyToAnswer(ConceptMap answer, ConceptMap partialSub, Set<Variable> vars, Unifier unifier) {
        ConceptMap unified = unifier.apply(answer);
        if (unified.isEmpty())
            return unified;
        Set<Variable> varsToRetain = Sets.difference(unified.vars(), partialSub.vars());
        return !this.satisfiedBy(unified) ? new ConceptMap()
                : ConceptUtils.mergeAnswers(unified.project(varsToRetain), partialSub).project(vars);
    }

    boolean isEmpty() {
        return definition.stream().allMatch(VariableDefinition::isTrivial);
    }

}