ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.java Source code

Java tutorial

Introduction

Here is the source code for ai.grakn.graql.internal.reasoner.utils.ReasonerUtils.java

Source

/*
 * Grakn - A Distributed Semantic Database
 * Copyright (C) 2016  Grakn Labs Limited
 *
 * Grakn is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Grakn 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

package ai.grakn.graql.internal.reasoner.utils;

import ai.grakn.GraknGraph;
import ai.grakn.concept.RelationType;
import ai.grakn.concept.RoleType;
import ai.grakn.concept.Rule;
import ai.grakn.concept.Type;
import ai.grakn.concept.TypeLabel;
import ai.grakn.exception.GraqlQueryException;
import ai.grakn.graql.Graql;
import ai.grakn.graql.Pattern;
import ai.grakn.graql.Var;
import ai.grakn.graql.VarPattern;
import ai.grakn.graql.admin.ReasonerQuery;
import ai.grakn.graql.admin.Unifier;
import ai.grakn.graql.admin.VarPatternAdmin;
import ai.grakn.graql.internal.pattern.Patterns;
import ai.grakn.graql.internal.pattern.property.IdProperty;
import ai.grakn.graql.internal.pattern.property.LabelProperty;
import ai.grakn.graql.internal.pattern.property.ValueProperty;
import ai.grakn.graql.internal.reasoner.UnifierImpl;
import ai.grakn.graql.internal.reasoner.atom.predicate.IdPredicate;
import ai.grakn.graql.internal.reasoner.atom.predicate.ValuePredicate;
import ai.grakn.graql.internal.reasoner.utils.conversion.TypeConverter;
import ai.grakn.util.Schema;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import javafx.util.Pair;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Stream;

import static ai.grakn.graql.Graql.var;
import static ai.grakn.graql.internal.reasoner.atom.predicate.ValuePredicate.createValueVar;
import static java.util.stream.Collectors.toSet;

/**
 *
 * <p>
 * Utiliy class providing useful functionalities.
 * </p>
 *
 * @author Kasper Piskorski
 *
 */
public class ReasonerUtils {

    private static final String CAPTURE_MARK = "captured-";

    /**
     *
     * @param graph to be checked against
     * @return set of inference rule contained in the graph
     */
    public static Set<Rule> getRules(GraknGraph graph) {
        return new HashSet<>(graph.admin().getMetaRuleInference().instances());
    }

    /**
     *
     * @param graph to be checked against
     * @return true if at least one inference rule is present in the graph
     */
    public static boolean hasRules(GraknGraph graph) {
        TypeLabel inferenceRule = Schema.MetaSchema.INFERENCE_RULE.getLabel();
        return graph.graql().infer(false).match(var("x").isa(Graql.label(inferenceRule))).ask().execute();
    }

    /**
     * Capture a variable name, by prepending a constant to the name
     * @param var the variable name to capture
     * @return the captured variable
     */
    public static Var capture(Var var) {
        return var.map(CAPTURE_MARK::concat);
    }

    /**
     * Uncapture a variable name, by removing a prepended constant
     * @param var the variable name to uncapture
     * @return the uncaptured variable
     */
    public static Var uncapture(Var var) {
        // TODO: This could cause bugs if a user has a variable including the word "capture"
        return var.map(name -> name.replace(CAPTURE_MARK, ""));
    }

    /**
     * Check if a variable has been captured
     * @param var the variable to check
     * @return if the variable has been captured
     */
    public static boolean isCaptured(Var var) {
        // TODO: This could cause bugs if a user has a variable including the word "capture"
        return var.getValue().contains(CAPTURE_MARK);
    }

    /**
     * looks for an appropriate var property with a specified name among the vars and maps it to an IdPredicate,
     * covers the case when specified variable name is user defined
     * @param typeVariable variable name of interest
     * @param vars VarAdmins to look for properties
     * @param parent reasoner query the mapped predicate should belong to
     * @return mapped IdPredicate
     */
    public static IdPredicate getUserDefinedIdPredicate(Var typeVariable, Set<VarPatternAdmin> vars,
            ReasonerQuery parent) {
        return vars.stream().filter(v -> v.getVarName().equals(typeVariable))
                .flatMap(v -> v.hasProperty(LabelProperty.class)
                        ? v.getProperties(LabelProperty.class).map(np -> new IdPredicate(typeVariable, np, parent))
                        : v.getProperties(IdProperty.class).map(np -> new IdPredicate(typeVariable, np, parent)))
                .findFirst().orElse(null);
    }

    /**
     * looks for an appropriate var property with a specified name among the vars and maps it to an IdPredicate,
     * covers both the cases when variable is and isn't user defined
     * @param typeVariable variable name of interest
     * @param typeVar {@link VarPatternAdmin} to look for in case the variable name is not user defined
     * @param vars VarAdmins to look for properties
     * @param parent reasoner query the mapped predicate should belong to
     * @return mapped IdPredicate
     */
    public static IdPredicate getIdPredicate(Var typeVariable, VarPatternAdmin typeVar, Set<VarPatternAdmin> vars,
            ReasonerQuery parent) {
        IdPredicate predicate = null;
        //look for id predicate among vars
        if (typeVar.getVarName().isUserDefinedName()) {
            predicate = getUserDefinedIdPredicate(typeVariable, vars, parent);
        } else {
            LabelProperty nameProp = typeVar.getProperty(LabelProperty.class).orElse(null);
            if (nameProp != null)
                predicate = new IdPredicate(typeVariable, nameProp, parent);
        }
        return predicate;
    }

    /**
     * looks for appropriate var properties with a specified name among the vars and maps them to ValuePredicates,
     * covers both the case when variable is and isn't user defined
     * @param valueVariable variable name of interest
     * @param valueVar {@link VarPatternAdmin} to look for in case the variable name is not user defined
     * @param vars VarAdmins to look for properties
     * @param parent reasoner query the mapped predicate should belong to
     * @return set of mapped ValuePredicates
     */
    public static Set<ValuePredicate> getValuePredicates(Var valueVariable, VarPatternAdmin valueVar,
            Set<VarPatternAdmin> vars, ReasonerQuery parent) {
        Set<ValuePredicate> predicates = new HashSet<>();
        if (valueVar.getVarName().isUserDefinedName()) {
            vars.stream().filter(v -> v.getVarName().equals(valueVariable))
                    .flatMap(v -> v.getProperties(ValueProperty.class)
                            .map(vp -> new ValuePredicate(v.getVarName(), vp.getPredicate(), parent)))
                    .forEach(predicates::add);
        }
        //add value atom
        else {
            valueVar.getProperties(ValueProperty.class).forEach(vp -> predicates
                    .add(new ValuePredicate(createValueVar(valueVariable, vp.getPredicate()), parent)));
        }
        return predicates;
    }

    /**
     * get unifiers by comparing permutations with original variables
     * @param originalVars original ordered variables
     * @param permutations different permutations on the variables
     * @return set of unifiers
     */
    public static Set<Unifier> getUnifiersFromPermutations(List<Var> originalVars, List<List<Var>> permutations) {
        Set<Unifier> unifierSet = new HashSet<>();
        permutations.forEach(perm -> {
            Unifier unifier = new UnifierImpl();
            Iterator<Var> pIt = originalVars.iterator();
            Iterator<Var> cIt = perm.iterator();
            while (pIt.hasNext() && cIt.hasNext()) {
                Var pVar = pIt.next();
                Var chVar = cIt.next();
                if (!pVar.equals(chVar))
                    unifier.addMapping(pVar, chVar);
            }
            unifierSet.add(unifier);
        });
        return unifierSet;
    }

    /**
     * get all permutations of an entry list
     * @param entryList entry list to generate permutations of
     * @param <T> element type
     * @return set of all possible permutations
     */
    public static <T> List<List<T>> getListPermutations(List<T> entryList) {
        if (entryList.isEmpty()) {
            List<List<T>> result = new ArrayList<>();
            result.add(new ArrayList<>());
            return result;
        }
        List<T> list = new ArrayList<>(entryList);
        T firstElement = list.remove(0);
        List<List<T>> returnValue = new ArrayList<>();
        List<List<T>> permutations = getListPermutations(list);
        for (List<T> smallerPermuted : permutations) {
            for (int index = 0; index <= smallerPermuted.size(); index++) {
                List<T> temp = new ArrayList<>(smallerPermuted);
                temp.add(index, firstElement);
                returnValue.add(temp);
            }
        }
        return returnValue;
    }

    /**
     * @param type input type
     * @return set of all non-meta super types of the role
     */
    public static Set<Type> getSuperTypes(Type type) {
        Set<Type> superTypes = new HashSet<>();
        Type superType = type.superType();
        while (!Schema.MetaSchema.isMetaLabel(superType.getLabel())) {
            superTypes.add(superType);
            superType = superType.superType();
        }
        return superTypes;
    }

    /**
     *
     * @param type for which top type is to be found
     * @return non-meta top type of the type
     */
    public static Type getTopType(Type type) {
        Type superType = type;
        while (!Schema.MetaSchema.isMetaLabel(superType.getLabel())) {
            superType = superType.superType();
        }
        return superType;
    }

    /**
     * @param types entry type set
     * @return top non-meta types from within the provided set of role types
     */
    public static <T extends Type> Set<T> getTopTypes(Set<T> types) {
        return types.stream().filter(rt -> Sets.intersection(getSuperTypes(rt), types).isEmpty()).collect(toSet());
    }

    /**
     * Gets roletypes a given type can play in the provided relType relation type by performing
     * type intersection between type's playedRoles and relation's relates.
     * @param type for which we want to obtain compatible roles it plays
     * @param relRoles relation type of interest
     * @return set of role types the type can play in relType
     */
    public static Set<RoleType> getCompatibleRoleTypes(Type type, Set<RoleType> relRoles) {
        Collection<RoleType> typeRoles = type.plays();
        return relRoles.stream().filter(typeRoles::contains).collect(toSet());
    }

    /**
     * calculates map intersection by doing an intersection on key sets and accumulating the keys
     * @param m1 first operand
     * @param m2 second operand
     * @param <K> map key type
     * @param <V> map value type
     * @return map intersection
     */
    public static <K, V> Multimap<K, V> multimapIntersection(Multimap<K, V> m1, Multimap<K, V> m2) {
        Multimap<K, V> intersection = HashMultimap.create();
        Sets.SetView<K> keyIntersection = Sets.intersection(m1.keySet(), m2.keySet());
        Stream.concat(m1.entries().stream(), m2.entries().stream())
                .filter(e -> keyIntersection.contains(e.getKey()))
                .forEach(e -> intersection.put(e.getKey(), e.getValue()));
        return intersection;
    }

    /**
     * compute the map of compatible relation types for given types (intersection of allowed sets of relation types for each entry type)
     * and compatible role types
     * @param types for which the set of compatible relation types is to be computed
     //* @param typeMapper function mapping a type to the set of compatible relation types
     * @param <T> type generic
     * @return map of compatible relation types and their corresponding role types
     */
    public static <T extends Type> Multimap<RelationType, RoleType> getCompatibleRelationTypesWithRoles(
            Set<T> types, TypeConverter<T> typeConverter) {
        Multimap<RelationType, RoleType> compatibleTypes = HashMultimap.create();
        if (types.isEmpty())
            return compatibleTypes;
        Iterator<T> it = types.iterator();
        compatibleTypes.putAll(typeConverter.toRelationMultimap(it.next()));
        while (it.hasNext() && compatibleTypes.size() > 1) {
            compatibleTypes = multimapIntersection(compatibleTypes, typeConverter.toRelationMultimap(it.next()));
        }
        return compatibleTypes;
    }

    /**
     * compute all rolePlayer-roleType combinations complementing provided roleMap
     * @param vars set of rolePlayers
     * @param roles set of roleTypes
     * @param roleMap initial rolePlayer-roleType roleMap to be complemented
     * @param roleMaps output set containing possible role mappings complementing the roleMap configuration
     */
    public static void computeRoleCombinations(Set<Var> vars, Set<RoleType> roles, Map<Var, VarPattern> roleMap,
            Set<Map<Var, VarPattern>> roleMaps) {
        Set<Var> tempVars = Sets.newHashSet(vars);
        Set<RoleType> tempRoles = Sets.newHashSet(roles);
        Var var = vars.iterator().next();

        roles.forEach(role -> {
            tempVars.remove(var);
            tempRoles.remove(role);
            roleMap.put(var, var().label(role.getLabel()).admin());
            if (!tempVars.isEmpty() && !tempRoles.isEmpty()) {
                computeRoleCombinations(tempVars, tempRoles, roleMap, roleMaps);
            } else {
                if (!roleMap.isEmpty()) {
                    roleMaps.add(Maps.newHashMap(roleMap));
                }
                roleMap.remove(var);
            }
            tempVars.add(var);
            tempRoles.add(role);
        });
    }

    /**
     * create transitive rule R(from: X, to: Y) :- R(from: X,to: Z), R(from: Z, to: Y)
     * @param relType transitive relation type
     * @param fromRoleLabel  from directional role type type label
     * @param toRoleLabel to directional role type type label
     * @param graph graph for the rule to be inserted
     * @return rule instance
     */
    public static Rule createTransitiveRule(RelationType relType, TypeLabel fromRoleLabel, TypeLabel toRoleLabel,
            GraknGraph graph) {
        final int arity = relType.relates().size();
        if (arity != 2)
            throw GraqlQueryException.ruleCreationArityMismatch();

        VarPatternAdmin startVar = var().isa(Graql.label(relType.getLabel())).rel(Graql.label(fromRoleLabel), "x")
                .rel(Graql.label(toRoleLabel), "z").admin();
        VarPatternAdmin endVar = var().isa(Graql.label(relType.getLabel())).rel(Graql.label(fromRoleLabel), "z")
                .rel(Graql.label(toRoleLabel), "y").admin();
        VarPatternAdmin headVar = var().isa(Graql.label(relType.getLabel())).rel(Graql.label(fromRoleLabel), "x")
                .rel(Graql.label(toRoleLabel), "y").admin();
        Pattern body = Patterns.conjunction(Sets.newHashSet(startVar, endVar));
        return graph.admin().getMetaRuleInference().putRule(body, headVar);
    }

    /**
     * create reflexive rule R(from: X, to: X) :- R(from: X,to: Y)
     * @param relType reflexive relation type
     * @param fromRoleLabel from directional role type type label
     * @param toRoleLabel to directional role type type label
     * @param graph graph for the rule to be inserted
     * @return rule instance
     */
    public static Rule createReflexiveRule(RelationType relType, TypeLabel fromRoleLabel, TypeLabel toRoleLabel,
            GraknGraph graph) {
        final int arity = relType.relates().size();
        if (arity != 2)
            throw GraqlQueryException.ruleCreationArityMismatch();

        VarPattern body = var().isa(Graql.label(relType.getLabel())).rel(Graql.label(fromRoleLabel), "x")
                .rel(Graql.label(toRoleLabel), "y");
        VarPattern head = var().isa(Graql.label(relType.getLabel())).rel(Graql.label(fromRoleLabel), "x")
                .rel(Graql.label(toRoleLabel), "x");
        return graph.admin().getMetaRuleInference().putRule(body, head);
    }

    /**
     * creates rule parent :- child
     * @param parent relation type of parent
     * @param child relation type of child
     * @param roleMappings map of corresponding role type type names
     * @param graph graph for the rule to be inserted
     * @return rule instance
     */
    public static Rule createSubPropertyRule(RelationType parent, RelationType child,
            Map<TypeLabel, TypeLabel> roleMappings, GraknGraph graph) {
        final int parentArity = parent.relates().size();
        final int childArity = child.relates().size();
        if (parentArity != childArity || parentArity != roleMappings.size()) {
            throw GraqlQueryException.ruleCreationArityMismatch();
        }
        VarPattern parentVar = var().isa(Graql.label(parent.getLabel()));
        VarPattern childVar = var().isa(Graql.label(child.getLabel()));

        for (Map.Entry<TypeLabel, TypeLabel> entry : roleMappings.entrySet()) {
            Var varName = var().asUserDefined();
            parentVar = parentVar.rel(Graql.label(entry.getKey()), varName);
            childVar = childVar.rel(Graql.label(entry.getValue()), varName);
        }
        return graph.admin().getMetaRuleInference().putRule(childVar, parentVar);
    }

    /**
     * creates rule R(fromRole: x, toRole: xm) :- R1(fromRole: x, ...), , R2, ... , Rn(..., toRole: xm)
     * @param relation head relation
     * @param fromRoleLabel specifies the role directionality of the head relation
     * @param toRoleLabel specifies the role directionality of the head relation
     * @param chain map containing ordered relation with their corresponding role mappings
     * @param graph graph for the rule to be inserted
     * @return rule instance
     */
    public static Rule createPropertyChainRule(RelationType relation, TypeLabel fromRoleLabel,
            TypeLabel toRoleLabel, LinkedHashMap<RelationType, Pair<TypeLabel, TypeLabel>> chain,
            GraknGraph graph) {
        Stack<Var> varNames = new Stack<>();
        varNames.push(var("x"));
        Set<VarPatternAdmin> bodyVars = new HashSet<>();
        chain.forEach((relType, rolePair) -> {
            Var varName = var().asUserDefined();
            VarPatternAdmin var = var().isa(Graql.label(relType.getLabel()))
                    .rel(Graql.label(rolePair.getKey()), varNames.peek())
                    .rel(Graql.label(rolePair.getValue()), varName).admin();
            varNames.push(varName);
            bodyVars.add(var);
        });

        VarPattern headVar = var().isa(Graql.label(relation.getLabel())).rel(Graql.label(fromRoleLabel), "x")
                .rel(Graql.label(toRoleLabel), varNames.peek());
        return graph.admin().getMetaRuleInference().putRule(Patterns.conjunction(bodyVars), headVar);
    }

    /**
     * @param parent type
     * @param child type
     * @return true if child is a subtype of parent
     */
    public static boolean checkTypesCompatible(Type parent, Type child) {
        if (Schema.MetaSchema.isMetaLabel(parent.getLabel()))
            return true;
        Type superType = child;
        while (!Schema.MetaSchema.isMetaLabel(superType.getLabel())) {
            if (superType.equals(parent))
                return true;
            superType = superType.superType();
        }
        return false;
    }

    /**
     * @param parent type
     * @param child type
     * @return true if types do not belong to the same type hierarchy
     */
    public static boolean checkTypesDisjoint(Type parent, Type child) {
        return !checkTypesCompatible(parent, child) && !checkTypesCompatible(child, parent);
    }
}