fr.boreal.forgetting.Forgetting Maven / Gradle / Ivy
Show all versions of integraal-forgetting Show documentation
package fr.boreal.forgetting;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import fr.boreal.backward_chaining.core.QueryCoreProcessorImpl;
import fr.boreal.backward_chaining.pure.PureRewriter;
import fr.boreal.model.formula.FOFormulas;
import fr.boreal.model.formula.api.FOFormula;
import fr.boreal.model.formula.factory.FOFormulaFactory;
import fr.boreal.model.kb.api.RuleBase;
import fr.boreal.model.kb.impl.RuleBaseImpl;
import fr.boreal.model.logicalElements.api.Atom;
import fr.boreal.model.logicalElements.api.Predicate;
import fr.boreal.model.logicalElements.api.Substitution;
import fr.boreal.model.logicalElements.api.Variable;
import fr.boreal.model.logicalElements.factory.api.TermFactory;
import fr.boreal.model.logicalElements.factory.impl.SameObjectTermFactory;
import fr.boreal.model.logicalElements.impl.SubstitutionImpl;
import fr.boreal.model.query.api.FOQuery;
import fr.boreal.model.query.factory.FOQueryFactory;
import fr.boreal.model.rule.api.FORule;
import fr.boreal.model.rule.impl.FORuleImpl;
import fr.boreal.redundancy.Redundancy;
import fr.boreal.unifier.QueryUnifier;
import fr.boreal.unifier.QueryUnifierAlgorithm;
/**
* The process of Forgetting is to remove some rules from the rulebase according to specific properties.
*
* The goal is to obtain an equivalent rulebase (in terms of end-inferences) by short-cutting some inferences,
* removing some predicates from the inferred vocabulary.
*
* This work is done for datalog rules.
*
* @author Florent Tornil
*
*/
public class Forgetting {
private static final TermFactory termFactory = SameObjectTermFactory.instance();
private static final FOFormulaFactory formulaFactory = FOFormulaFactory.instance();
private static final FOQueryFactory queryFactory = FOQueryFactory.instance();
private static final PureRewriter rw = new PureRewriter();
private static final QueryCoreProcessorImpl coreProcessor = new QueryCoreProcessorImpl();
/**
* Rewrite the bodies of all rules with using a query-rewriting algorithm
* @param rb a rule base
* @return a semantically equivalent superset of the original rule base
*/
public static RuleBase bodyUnfolding(RuleBase rb){
return new RuleBaseImpl(bodyUnfoldingWith(rb, rb.getRules()));
}
/**
* Rewrites the rules' bodies in the given {@code toRewrite} collection using the given RuleBase.
*
* The rules occurring only in {@code rb} will not be present in the returned rules.
* Each body rewriting results in a new rule, where the head is a specialization of the
* original one.
*
*
* Note that this algorithm only halts if the rewriting process halts (ie: the RuleBase is FUS)
*
*
* @param rb rules used to unfold bodies
* @param toRewrite rules that have to be body-unfolded
* @return a new stream of {@code FORule} with all rewritings of {@code toRewrite} rules
*/
public static Collection bodyUnfoldingWith(RuleBase rb, Collection toRewrite) {
Collection res = new HashSet<>();
for(FORule rule : toRewrite) {
FOQuery> q = queryFactory.createOrGetQuery(rule.getBody(), rule.getFrontier());
for(FOQuery> rew : rw.rewrite(q, rb).getQueries()) {
FOFormula body = rew.getFormula();
FOFormula head = FOFormulas.createImageWith(rule.getHead(), rew.getVariableEqualities().getAssociatedSubstitution(rew).orElseThrow());
res.add(new FORuleImpl(body, head));
}
}
return res;
}
/**
* Creates a new RuleBase equivalent to the given one where some predicates have been removed from the vocabulary
*
*
* The given RuleBase is not modified
*
* The given RuleBase must be FUS and all rules must be datalog
*
*
* @param rb RuleBase to apply the forgetting on
* @param toDrop condition on predicates to remove
* @return a new RuleBase equivalent to the given one with less vocabulary
*/
public static RuleBase naiveDatalogForget(RuleBase rb, java.util.function.Predicate toDrop){
Collection res = new HashSet<>();
for(FORule rule : bodyUnfolding(rb).getRules()) {
if(!satisfies(rule, toDrop)) {
res.add(rule);
}
}
return new RuleBaseImpl(res);
}
/**
* Creates a new RuleBase equivalent to the given one where some predicates have been removed from the vocabulary
*
*
* The given RuleBase is not modified
*
* The given RuleBase must be FUS and all rules must be datalog
*
*
* @param rb RuleBase to apply the forgetting on
* @param toDrop condition on predicates to remove
* @return a new RuleBase equivalent to the given one with less vocabulary
*/
public static RuleBase semiNaiveDatalogForget(RuleBase rb, java.util.function.Predicate toDrop){
HashSet toKeep = new HashSet<>();
HashSet toRewrite = new HashSet<>();
HashSet forRewriting = new HashSet<>();
for (FORule rule : rb.getRules()){
if (satisfies(rule.getHead(), toDrop)){
forRewriting.add(rule);
} else if (satisfies(rule.getBody(), toDrop)) {
toRewrite.add(rule);
} else {
toKeep.add(rule);
}
}
for(FORule rule : bodyUnfoldingWith(new RuleBaseImpl(forRewriting), toRewrite)) {
if(!satisfies(rule, toDrop)) {
toKeep.add(rule);
}
}
return new RuleBaseImpl(toKeep);
}
/**
* Creates a new RuleBase equivalent to the given one where some predicates have been removed from the vocabulary
*
*
* The given RuleBase is not modified
*
* The given RuleBase must be FUS
*
*
* @param rb RuleBase to apply the forgetting on
* @param toDrop condition on predicates to remove
* @return a new RuleBase equivalent to the given one with less vocabulary
*/
public static RuleBase forget(RuleBase rb, java.util.function.Predicate toDrop) {
Collection res = new HashSet<>();
for(FORule rule : closureByComposition(rb.getRules())) {
if(!satisfies(rule.getBody(), toDrop)) {
Collection filteredHead = new HashSet<>();
for (Atom atom : rule.getHead().asAtomSet()) {
if (!toDrop.test(atom.getPredicate())) {
filteredHead.add(atom);
}
}
FOFormula head = formulaFactory.createOrGetConjunction(filteredHead);
res.add(new FORuleImpl(rule.getBody(), head));
}
}
return new RuleBaseImpl(res);
}
/**
*
* @param r rule to check
* @param condition to satisfy
* @return true iff at least one predicate of the rule satisfies the condition
*/
private static boolean satisfies(FORule r, java.util.function.Predicate condition) {
return satisfies(r.getHead(), condition) || satisfies(r.getBody(), condition);
}
/**
*
* @param f formula to check
* @param condition to satisfy
* @return true iff at least one predicate of the formula satisfies the condition
*/
private static boolean satisfies(FOFormula f, java.util.function.Predicate condition) {
for(Predicate p : f.getPredicates()) {
if(condition.test(p)) {
return true;
}
}
return false;
}
/**
* Computes the closure of a given set of rules by the composition operator
*
*
* The given rules must be FUS
*
*
* @param rules to close by composition
* @return the closure of rules by the composition operator
*/
private static Collection closureByComposition(Collection rules) {
Collection closure = new HashSet<>();
Collection rulesToHandle = new HashSet<>(rules);
while(!rulesToHandle.isEmpty()) {
Collection newComposedRules = new HashSet<>();
for (FORule r1 : rulesToHandle) {
for (FORule r2 : closure) {
newComposedRules.addAll(ruleComposition(r1, r2));
}
for (FORule r2 : rulesToHandle) {
newComposedRules.addAll(ruleComposition(r1, r2));
}
}
for (FORule r1 : closure) {
for (FORule r2 : rulesToHandle) {
newComposedRules.addAll(ruleComposition(r1, r2));
}
}
closure.addAll(rulesToHandle);
rulesToHandle.clear();
for(FORule composedRule : newComposedRules) {
boolean isRedundant = false;
for(FORule rule : closure) {
isRedundant = isRedundant || Redundancy.ruleConsequence(new RuleBaseImpl(Set.of(rule)), composedRule);
}
for(FORule rule : rulesToHandle) {
isRedundant = isRedundant || Redundancy.ruleConsequence(new RuleBaseImpl(Set.of(rule)), composedRule);
}
if(!isRedundant) {
rulesToHandle.add(composedRule);
}
}
}
return closure;
}
/**
* Compose the two given rules
* @param r1 the first rule
* @param r2 the second rule
* @return the composed rules of r2 by r1
*/
private static Collection ruleComposition(FORule r1, FORule r2) {
Collection newRules = new HashSet<>();
Substitution s = new SubstitutionImpl();
for (Variable v : r1.getBody().getVariables()) {
if (r2.getExistentials().contains(v) || r2.getBody().getVariables().contains(v)) {
s.add(v, termFactory.createOrGetFreshVariable());
}
}
for (Variable v : r1.getExistentials()) {
s.add(v, termFactory.createOrGetFreshVariable());
}
FOQuery> toRewrite = queryFactory.createOrGetQuery(FOFormulas.createImageWith(r1.getBody(), s), List.of(), null);
Collection unifiers = new QueryUnifierAlgorithm().getMostGeneralSinglePieceUnifiers(toRewrite, r2);
for (QueryUnifier u : unifiers) {
Substitution s1 = u.getAssociatedSubstitution();
FOFormula newHead = formulaFactory.createOrGetConjunction(
FOFormulas.createImageWith(r1.getHead(), s),
FOFormulas.createImageWith(r2.getHead(), s1));
Collection newBody = new HashSet<>(toRewrite.getFormula().asAtomSet());
newBody.addAll(r2.getBody().asAtomSet());
newBody.removeAll(u.getUnifiedQueryPart().asAtomSet());
FOFormula q = FOFormulas.createImageWith(formulaFactory.createOrGetConjunction(newBody), s1);
Collection frontier = new HashSet<>(q.getVariables());
frontier.retainAll(newHead.getVariables());
newHead = coreProcessor.computeCore(queryFactory.createOrGetQuery(newHead, frontier, null)).getFormula();
newRules.add(new FORuleImpl(q, newHead));
}
return newRules;
}
}