Java tutorial
/* * 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.rule; import ai.grakn.GraknGraph; import ai.grakn.concept.ConceptId; import ai.grakn.concept.Rule; import ai.grakn.concept.Type; import ai.grakn.exception.GraqlQueryException; import ai.grakn.graql.admin.Atomic; import ai.grakn.graql.admin.Conjunction; import ai.grakn.graql.admin.PatternAdmin; import ai.grakn.graql.admin.Unifier; import ai.grakn.graql.admin.VarPatternAdmin; import ai.grakn.graql.internal.pattern.Patterns; import ai.grakn.graql.internal.reasoner.UnifierImpl; import ai.grakn.graql.internal.reasoner.utils.ReasonerUtils; import ai.grakn.graql.internal.reasoner.atom.Atom; import ai.grakn.graql.internal.reasoner.atom.AtomicFactory; import ai.grakn.graql.internal.reasoner.atom.binary.Relation; import ai.grakn.graql.internal.reasoner.atom.binary.Resource; import ai.grakn.graql.internal.reasoner.atom.binary.TypeAtom; import ai.grakn.graql.internal.reasoner.atom.predicate.ValuePredicate; import ai.grakn.graql.internal.reasoner.query.ReasonerAtomicQuery; import ai.grakn.graql.internal.reasoner.query.ReasonerQueries; import ai.grakn.graql.internal.reasoner.query.ReasonerQueryImpl; import com.google.common.collect.Sets; import java.util.HashSet; import java.util.Objects; import java.util.Set; import static java.util.stream.Collectors.toSet; /** * * <p> * Class providing resolution and higher level facilities for {@link Rule} objects. * </p> * * @author Kasper Piskorski * */ public class InferenceRule { private final ConceptId ruleId; private final ReasonerQueryImpl body; private final ReasonerAtomicQuery head; private int priority = Integer.MAX_VALUE; public InferenceRule(Rule rule, GraknGraph graph) { ruleId = rule.getId(); //TODO simplify once changes propagated to rule objects body = ReasonerQueries.create(conjunction(rule.getLHS().admin()), graph); head = ReasonerQueries.atomic(conjunction(rule.getRHS().admin()), graph); //run time check for head atom validity if (!getHead().getAtom().isAllowedToFormRuleHead()) { throw GraqlQueryException.disallowedAtomInRuleHead(this.getHead().getAtom().toString(), this.toString()); } } public InferenceRule(InferenceRule r) { this.ruleId = r.getRuleId(); this.body = ReasonerQueries.create(r.getBody()); this.head = ReasonerQueries.atomic(r.getHead()); } @Override public String toString() { return "\n" + this.body.toString() + "->" + this.head.toString() + "[" + resolutionPriority() + "]"; } @Override public boolean equals(Object obj) { if (obj == null || this.getClass() != obj.getClass()) return false; InferenceRule rule = (InferenceRule) obj; return this.getBody().equals(rule.getBody()) && this.getHead().equals(rule.getHead()); } @Override public int hashCode() { int hashCode = 1; hashCode = hashCode * 37 + getBody().hashCode(); hashCode = hashCode * 37 + getHead().hashCode(); return hashCode; } /** * @return the priority with which the rule should be fired */ public int resolutionPriority() { if (priority == Integer.MAX_VALUE) { priority = getBody().resolutionPriority(); } return priority; } private static Conjunction<VarPatternAdmin> conjunction(PatternAdmin pattern) { Set<VarPatternAdmin> vars = pattern.getDisjunctiveNormalForm().getPatterns().stream() .flatMap(p -> p.getPatterns().stream()).collect(toSet()); return Patterns.conjunction(vars); } public ConceptId getRuleId() { return ruleId; } /** * @return true if head and body do not share any variables */ public boolean hasDisconnectedHead() { return Sets.intersection(body.getVarNames(), head.getVarNames()).isEmpty(); } /** * rule requires materialisation in the context of resolving parentatom * if parent atom requires materialisation, head atom requires materialisation or if the head contains only fresh variables * * @return true if the rule needs to be materialised */ public boolean requiresMaterialisation(Atom parentAtom) { return parentAtom.requiresMaterialisation() || getHead().getAtom().requiresMaterialisation() || hasDisconnectedHead(); } /** * @return body of the rule of the form head :- body */ public ReasonerQueryImpl getBody() { return body; } /** * @return head of the rule of the form head :- body */ public ReasonerAtomicQuery getHead() { return head; } /** * @return a conclusion atom which parent contains all atoms in the rule */ public Atom getRuleConclusionAtom() { ReasonerAtomicQuery ruleQuery = ReasonerQueries.atomic(head); Atom atom = ruleQuery.getAtom(); body.getAtoms().forEach(at -> ruleQuery.addAtomic(at.copy())); return atom; } /** * @param parentAtom atom containing constraints (parent) * @param unifier unifier unifying parent with the rule * @return rule with propagated constraints from parent */ public InferenceRule propagateConstraints(Atom parentAtom, Unifier unifier) { if (!parentAtom.isRelation() && !parentAtom.isResource()) return this; //only transfer value predicates if head has a user specified value variable Atom headAtom = head.getAtom(); if (headAtom.isResource() && ((Resource) headAtom).getMultiPredicate().isEmpty()) { Set<ValuePredicate> valuePredicates = parentAtom.getValuePredicates().stream() .flatMap(vp -> vp.unify(unifier).stream()).collect(toSet()); head.addAtomConstraints(valuePredicates); body.addAtomConstraints(valuePredicates); } Set<TypeAtom> unifiedTypes = parentAtom.getTypeConstraints().stream() .flatMap(type -> type.unify(unifier).stream()).collect(toSet()); //set rule body types to sub types of combined query+rule types Set<TypeAtom> ruleTypes = body.getTypeConstraints().stream().filter(t -> !t.isRelation()).collect(toSet()); Set<TypeAtom> allTypes = Sets.union(unifiedTypes, ruleTypes); Set<TypeAtom> types = allTypes.stream().filter(ta -> { Type type = ta.getType(); Type subType = allTypes.stream().map(Atom::getType).filter(Objects::nonNull) .filter(t -> ReasonerUtils.getSuperTypes(t).contains(type)).findFirst().orElse(null); return type == null || subType == null; }).collect(toSet()); ruleTypes.forEach(body::removeAtomic); body.addAtomConstraints(types); return this; } private InferenceRule rewriteHead() { Atom childAtom = head.getAtom(); Atom newAtom = childAtom.rewriteToUserDefined(); head.removeAtomic(childAtom); head.addAtomic(newAtom); return this; } private InferenceRule rewriteBody() { new HashSet<>(body.getAtoms()).stream().filter(Atomic::isAtom).map(at -> (Atom) at).filter(Atom::isRelation) .filter(at -> !at.isUserDefinedName()).filter(at -> Objects.nonNull(at.getType())) .filter(at -> at.getType().equals(head.getAtom().getType())).forEach(at -> { Atom rewrite = at.rewriteToUserDefined(); body.removeAtomic(at); body.addAtomic(rewrite); }); return this; } /** * rewrite the rule to a form with user defined variables * @param parentAtom reference parent atom * @return rewritten rule */ public InferenceRule rewriteToUserDefined(Atom parentAtom) { return parentAtom.isUserDefinedName() ? this.rewriteHead().rewriteBody() : this; } /** * @param parentAtom atom to unify the rule with * @return corresponding unifier */ public Unifier getUnifier(Atom parentAtom) { Atom childAtom = getRuleConclusionAtom(); Unifier unifier = new UnifierImpl(); if (parentAtom.getType() != null) { unifier.merge(childAtom.getUnifier(parentAtom)); } //case of match all relation atom else { Relation extendedParent = ((Relation) AtomicFactory.create(parentAtom, parentAtom.getParentQuery())) .addType(childAtom.getType()); unifier.merge(childAtom.getUnifier(extendedParent)); } return unifier; } }