All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mindmaps.graql.Reasoner Maven / Gradle / Ivy

/*
 * MindmapsDB - A Distributed Semantic Database
 * Copyright (C) 2016  Mindmaps Research Ltd
 *
 * MindmapsDB 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.
 *
 * MindmapsDB 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 MindmapsDB. If not, see .
 */

package io.mindmaps.graql;

import com.google.common.collect.Lists;
import io.mindmaps.MindmapsGraph;
import io.mindmaps.concept.Concept;
import io.mindmaps.concept.RoleType;
import io.mindmaps.concept.Rule;
import io.mindmaps.concept.Type;
import io.mindmaps.graql.internal.reasoner.query.AtomicMatchQuery;
import io.mindmaps.graql.internal.reasoner.query.AtomicQuery;
import io.mindmaps.graql.internal.reasoner.query.QueryAnswers;
import io.mindmaps.graql.internal.reasoner.query.ReasonerMatchQuery;
import io.mindmaps.graql.internal.reasoner.rule.InferenceRule;
import io.mindmaps.graql.internal.reasoner.predicate.Atomic;
import io.mindmaps.graql.internal.reasoner.query.Query;
import javafx.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static io.mindmaps.graql.internal.reasoner.query.QueryAnswers.getUnifiedAnswers;

public class Reasoner {

    private final MindmapsGraph graph;
    private final QueryBuilder qb;
    private final Logger LOG = LoggerFactory.getLogger(Reasoner.class);

    private final Map workingMemory = new HashMap<>();

    public Reasoner(MindmapsGraph graph) {
        this.graph = graph;
        qb = Graql.withGraph(graph);
        linkConceptTypes();
    }

    private boolean checkRuleApplicableToAtom(Atomic parentAtom, InferenceRule child) {
        boolean relRelevant = true;
        Query parent = parentAtom.getParentQuery();
        Atomic childAtom = child.getRuleConclusionAtom();

        if (parentAtom.isRelation()) {
            Map> childRoleVarTypeMap = childAtom.getRoleVarTypeMap();
            //Check for role compatibility
            Map> parentRoleVarTypeMap = parentAtom.getRoleVarTypeMap();
            for (Map.Entry> entry : parentRoleVarTypeMap.entrySet()) {
                RoleType role = entry.getKey();
                Type pType = entry.getValue().getValue();
                if (pType != null) {
                    //vars can be matched by role types
                    if (childRoleVarTypeMap.containsKey(role)) {
                        Type chType = childRoleVarTypeMap.get(role).getValue();
                        //check type compatibility
                        if (chType != null) {
                            relRelevant &= pType.equals(chType) || chType.subTypes().contains(pType);

                            //Check for any constraints on the variables
                            String chVar = childRoleVarTypeMap.get(role).getKey();
                            String pVar = entry.getValue().getKey();
                            String chId = child.getBody().getSubstitution(chVar);
                            String pId = parent.getSubstitution(pVar);
                            if (!chId.isEmpty() && !pId.isEmpty())
                                relRelevant &= chId.equals(pId);
                        }
                    }
                }
            }
        }
        else if (parentAtom.isResource()) {
            String childVal = child.getHead().getValuePredicate(childAtom.getVal());
            String parentVal = parent.getValuePredicate(parentAtom.getVal());
            relRelevant = parentVal.isEmpty() || parentVal.equals(childVal);
        }
        return relRelevant;
    }

    private Set getApplicableRules(Atomic atom) {
        Set children = new HashSet<>();
        Type type = atom.getType();
        if (type == null) return children;

        Collection rulesFromType = type.getRulesOfConclusion();
        rulesFromType.forEach( rule -> {
            InferenceRule child = workingMemory.get(rule.getId());
            boolean ruleRelevant = checkRuleApplicableToAtom(atom, child);
            if (ruleRelevant) children.add(rule);
        });
        return children;
    }

    private void linkConceptTypes(Rule rule) {
        LOG.debug("Linking rule " + rule.getId() + "...");
        MatchQuery qLHS = qb.match(qb.parsePatterns(rule.getLHS()));
        MatchQuery qRHS = qb.match(qb.parsePatterns(rule.getRHS()));

        Set hypothesisConceptTypes = qLHS.admin().getTypes();
        Set conclusionConceptTypes = qRHS.admin().getTypes();

        hypothesisConceptTypes.forEach(rule::addHypothesis);
        conclusionConceptTypes.forEach(rule::addConclusion);

        LOG.debug("Rule " + rule.getId() + " linked");
    }

    public Set getRules() {
        Set rules = new HashSet<>();
        MatchQuery sq = qb.parse("match $x isa inference-rule;");
        List> results = Lists.newArrayList(sq);
        for (Map result : results) {
            for (Map.Entry entry : result.entrySet()) {
                Concept concept = entry.getValue();
                rules.add((Rule) concept);
            }
        }
        return rules;
    }

    /**
     * Link all unlinked rules in the rule base to their matching types
     */
    public void linkConceptTypes() {
        Set rules = getRules();
        LOG.debug(rules.size() + " rules initialized...");

        for (Rule rule : rules) {
            workingMemory.putIfAbsent(rule.getId(), new InferenceRule(rule, graph));
            if (rule.getHypothesisTypes().isEmpty() && rule.getConclusionTypes().isEmpty()) {
                linkConceptTypes(rule);
            }
        }
    }

    private void propagateAnswers(Map matAnswers){
        matAnswers.keySet().forEach(aq -> {
            if (aq.getParent() == null) aq.propagateAnswers(matAnswers);
        });
    }

    private void recordAnswers(AtomicQuery atomicQuery, Map matAnswers) {
        AtomicQuery equivalentQuery = matAnswers.get(atomicQuery);
        if (equivalentQuery != null) {
            QueryAnswers unifiedAnswers = getUnifiedAnswers(equivalentQuery, atomicQuery, atomicQuery.getAnswers());
            matAnswers.get(atomicQuery).getAnswers().addAll(unifiedAnswers);
        }
        else
            matAnswers.put(atomicQuery, atomicQuery);
    }

    private QueryAnswers propagateHeadSubstitutions(Query atomicQuery, Query ruleHead, QueryAnswers answers){
        QueryAnswers newAnswers = new QueryAnswers();
        if(answers.isEmpty()) return newAnswers;

        Set queryVars = atomicQuery.getSelectedNames();
        Set headVars = ruleHead.getSelectedNames();
        Set extraSubs = new HashSet<>();
        if(queryVars.size() > headVars.size()){
            extraSubs.addAll(ruleHead.getSubstitutions()
                    .stream().filter(sub -> queryVars.contains(sub.getVarName()))
                    .collect(Collectors.toSet()));
        }

        answers.forEach( map -> {
            Map newAns = new HashMap<>(map);
            extraSubs.forEach(sub -> newAns.put(sub.getVarName(), graph.getInstance(sub.getVal())) );
            newAnswers.add(newAns);
        });

        return newAnswers;
    }

    private QueryAnswers answerWM(AtomicQuery atomicQuery, Set subGoals){
        boolean queryAdmissible = !subGoals.contains(atomicQuery);
        atomicQuery.DBlookup();

        if(queryAdmissible) {
            Atomic atom = atomicQuery.getAtom();
            Set rules = getApplicableRules(atom);
            for (Rule rl : rules) {
                InferenceRule rule = new InferenceRule(rl, graph);
                rule.unify(atom);
                Query ruleBody = rule.getBody();
                AtomicQuery ruleHead = rule.getHead();

                Set atoms = ruleBody.selectAtoms();
                Iterator atIt = atoms.iterator();

                subGoals.add(atomicQuery);
                AtomicQuery childAtomicQuery = new AtomicMatchQuery(atIt.next());
                atomicQuery.establishRelation(childAtomicQuery);
                QueryAnswers subs = answerWM(childAtomicQuery, subGoals);
                while(atIt.hasNext()){
                    childAtomicQuery = new AtomicMatchQuery(atIt.next());
                    atomicQuery.establishRelation(childAtomicQuery);
                    QueryAnswers localSubs = answerWM(childAtomicQuery, subGoals);
                    subs = subs.join(localSubs);
                }
                QueryAnswers answers = propagateHeadSubstitutions(atomicQuery, ruleHead, subs)
                        .filterVars(atomicQuery.getSelectedNames());
                QueryAnswers newAnswers = new QueryAnswers();
                newAnswers.addAll(new AtomicMatchQuery(ruleHead, answers).materialise());
                if (!newAnswers.isEmpty()) answers = newAnswers;
                QueryAnswers filteredAnswers = answers.filterInComplete(atomicQuery.getSelectedNames());
                atomicQuery.getAnswers().addAll(filteredAnswers);
            }
        }
        return atomicQuery.getAnswers();
    }

    private QueryAnswers answer(AtomicQuery atomicQuery, Set subGoals, Map matAnswers){
        boolean queryAdmissible = !subGoals.contains(atomicQuery);
        boolean queryVisited = matAnswers.containsKey(atomicQuery);

        if(queryAdmissible) {
            if (!queryVisited){
                atomicQuery.DBlookup();
                recordAnswers(atomicQuery, matAnswers);
            }
            else
                atomicQuery.memoryLookup(matAnswers);

            Atomic atom = atomicQuery.getAtom();
            Set rules = getApplicableRules(atom);
            for (Rule rl : rules) {
                InferenceRule rule = new InferenceRule(rl, graph);
                rule.unify(atom);
                Query ruleBody = rule.getBody();
                AtomicQuery ruleHead = rule.getHead();

                Set atoms = ruleBody.selectAtoms();
                Iterator atIt = atoms.iterator();

                subGoals.add(atomicQuery);
                Atomic at = atIt.next();
                AtomicQuery childAtomicQuery = new AtomicMatchQuery(at);
                atomicQuery.establishRelation(childAtomicQuery);
                QueryAnswers subs = answer(childAtomicQuery, subGoals, matAnswers);
                while(atIt.hasNext()){
                    at = atIt.next();
                    childAtomicQuery = new AtomicMatchQuery(at);
                    atomicQuery.establishRelation(childAtomicQuery);
                    QueryAnswers localSubs = answer(childAtomicQuery, subGoals, matAnswers);
                    subs = subs.join(localSubs);
                }

                QueryAnswers answers = propagateHeadSubstitutions(atomicQuery, ruleHead, subs)
                        .filterVars(atomicQuery.getSelectedNames());
                QueryAnswers newAnswers = new QueryAnswers();
                if (atom.isResource())
                    newAnswers.addAll(new AtomicMatchQuery(ruleHead, answers).materialise());
                if (!newAnswers.isEmpty()) answers = newAnswers;

                QueryAnswers filteredAnswers = answers.filterInComplete(atomicQuery.getSelectedNames());
                atomicQuery.getAnswers().addAll(filteredAnswers);
                recordAnswers(atomicQuery, matAnswers);
            }
        }
        else
            atomicQuery.memoryLookup(matAnswers);

        return atomicQuery.getAnswers();
    }

    private void answer(AtomicQuery atomicQuery, Set subGoals, Map matAnswers,
                        boolean materialise){
        if(!materialise) {
            answer(atomicQuery, subGoals, matAnswers);
            propagateAnswers(matAnswers);
        }
        else
            answerWM(atomicQuery, subGoals);
    }

    private QueryAnswers resolveAtomicQuery(AtomicQuery atomicQuery, boolean materialise) {
        int dAns;
        int iter = 0;

        if (!atomicQuery.getAtom().isRuleResolvable()){
            atomicQuery.DBlookup();
            return atomicQuery.getAnswers();
        }
        else {
            Map matAnswers = new HashMap<>();
            if (!materialise) {
                atomicQuery.DBlookup();
                recordAnswers(atomicQuery, matAnswers);
                matAnswers.put(atomicQuery, atomicQuery);
            }
            do {
                Set subGoals = new HashSet<>();
                dAns = atomicQuery.getAnswers().size();
                answer(atomicQuery, subGoals, matAnswers, materialise);
                LOG.debug("iter: " + iter++ + " answers: " + atomicQuery.getAnswers().size());
                dAns = atomicQuery.getAnswers().size() - dAns;
            } while (dAns != 0);
            return atomicQuery.getAnswers();
        }
    }

    private QueryAnswers resolveQuery(Query query, boolean materialise) {
        Iterator atIt = query.selectAtoms().iterator();
        AtomicQuery atomicQuery = new AtomicMatchQuery(atIt.next().clone());
        QueryAnswers answers = resolveAtomicQuery(atomicQuery, materialise);
        while(atIt.hasNext()){
            atomicQuery = new AtomicMatchQuery(atIt.next());
            QueryAnswers subAnswers = resolveAtomicQuery(atomicQuery, materialise);
            answers = answers.join(subAnswers);
        }
        return answers.filterVars(query.getSelectedNames());
    }

    /**
     * Resolve a given query using the rule base
     * @param inputQuery the query string to be expanded
     * @return set of answers
     */
    public QueryAnswers resolve(MatchQuery inputQuery, boolean materialise) {
        Query query = new ReasonerMatchQuery(inputQuery, graph);
        return resolveQuery(query, materialise);
    }

    public QueryAnswers  resolve(MatchQuery inputQuery) { return resolve(inputQuery, false);}

    /**
     * Resolve a given query using the rule base
     * @param inputQuery the query string to be expanded
     * @return MatchQuery with answers
     */
    public MatchQuery resolveToQuery(MatchQuery inputQuery, boolean materialise) {
        Query query = new Query(inputQuery, graph);
        if (!query.isRuleResolvable()) return inputQuery;
        QueryAnswers answers = resolveQuery(query, materialise);
        return new ReasonerMatchQuery(inputQuery, graph, answers);
    }

    public MatchQuery resolveToQuery(MatchQuery inputQuery) { return resolveToQuery(inputQuery, false);}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy