package org.aksw.jena_sparql_api.utils; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import org.apache.jena.ext.com.google.common.collect.HashMultimap; import org.apache.jena.graph.Node; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; import org.apache.jena.sparql.algebra.Algebra; import org.apache.jena.sparql.algebra.Op; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.expr.E_Equals; import org.apache.jena.sparql.expr.E_LogicalAnd; import org.apache.jena.sparql.expr.E_LogicalNot; import org.apache.jena.sparql.expr.E_LogicalOr; import org.apache.jena.sparql.expr.E_NotEquals; import org.apache.jena.sparql.expr.Expr; import org.apache.jena.sparql.expr.ExprFunction; import org.apache.jena.sparql.expr.ExprFunction2; import org.apache.jena.sparql.expr.ExprList; import org.apache.jena.sparql.expr.NodeValue; import com.google.common.collect.Multimap; // TODO There is already org.apache.jena.sparql.algebra.optimize.TransformFilterConjunction public class CnfUtils { @SuppressWarnings("unchecked") public static <T extends ExprFunction2> T normalize(T expr) { Expr a = expr.getArg1(); Expr b = expr.getArg2(); Expr result = a.isConstant() && b.isVariable() ? expr.copy(b, a) : expr ; return (T)result; } public static Entry<Var, Node> extractEquality(Collection<? extends Expr> clause) { Entry<Var, Node> result = null; if(clause.size() == 1) { Expr expr = clause.iterator().next(); if(expr instanceof E_Equals) { E_Equals eq = (E_Equals)expr; eq = normalize(eq); Expr a = eq.getArg1(); Expr b = eq.getArg2(); if(a.isVariable() && b.isConstant()) { Var v = a.asVar(); Node c = b.getConstant().getNode(); result = new SimpleEntry<>(v, c); } } } return result; } public static <E extends Expr, C extends Collection<? extends E>> Set<C> getEqualityClauses(Iterable<C> cnf) { Set<C> result = new HashSet<>(); for(C clause : cnf) { Entry<Var, Node> entry = extractEquality(clause); if(entry != null) { result.add(clause); } } return result; } /** * Extract from the CNF all mappings from a variable to constant, i.e. * if there is ?x = foo, then the result will contain the mapping ?x -> foo. * * * @param cnf * @return */ public static Map<Var, Node> getConstants(Iterable<? extends Collection <? extends Expr>> cnf) { Map<Var, Node> result = new HashMap<Var, Node>(); // Inconsistent variables are those mapping to different values Set<Var> inconsistent = new HashSet<Var>(); Multimap<Var, Iterable<? extends Collection <? extends Expr>>> varToClauses; for(Collection<? extends Expr> clause : cnf) { Entry<Var, Node> entry = extractEquality(clause); if(entry != null) { Var v = entry.getKey(); Node c = entry.getValue(); Node o = result.get(v); if(o != null && !o.equals(c) && !inconsistent.contains(v)) { inconsistent.add(v); //c = NodeValue.FALSE.getNode(); result.remove(v); } else { result.put(v, c); } } } return result; } public static Expr toExpr(Iterable<Set<Expr>> cnf) { ExprList exprList = toExprList(cnf); Expr result = ExprUtils.andifyBalanced(exprList); return result; } public static ExprList toExprList(Iterable<Set<Expr>> cnf) { ExprList result = new ExprList(); for(Set<Expr> clause : cnf) { if(!clause.equals(ClauseUtils.TRUE)) { Expr expr = ExprUtils.orifyBalanced(clause); result.add(expr); } } return result; } public static void main(String[] args) { //!a->b&(a|d&b) String sA = "Select * { ?s ?p ?o . Filter((!(!?a) || ?b) && (?a || ?d && ?b)) . }"; //String sA = "Select * { ?s ?p ?o . Filter(?o != <http://Person>). Optional { { ?x ?y ?z . Filter(?x != <http://x> && ?x = ?y) . } Union { ?x a ?y . Filter(?x = <http://x>) . } } . }"; //String sA = "Select * { ?s ?p ?o . Filter(!(?s = ?p || ?p = ?o && ?s = ?o || ?o = ?s)) . }"; //String sA = "Select * { ?s ?p ?o . Filter(!(?s = ?p || ?j = <http://x>)) . }"; Query qA = QueryFactory.create(sA); Op opA = Algebra.compile(qA); opA = Algebra.toQuadForm(opA); //System.out.println(opA); // How to deal with union? variables appearing in them //System.out.println(opA.getClass()); ExprList exprs = FilterUtils.collectExprs(opA, new ExprList()); Expr expr = ExprUtils.andifyBalanced(exprs); Expr x = eval(expr); System.out.println("HERE: " + x); Set<Set<Expr>> cnf = toSetCnf(expr); System.out.println("THERE: " + cnf); /* ExprList proc = eval(exprs); System.out.println(proc); List<ExprList> clauses = dnfToClauses(proc); System.out.println("Mentioned vars:" + proc.getVarsMentioned()); System.out.println("Clauses: " + clauses); */ } /** * Test if the candidate is subsumed by the reference. * This means that the candidate must contain at least all constraints of ref * * @param cand * @param ref * @return */ public static boolean isSubsumedBy(Set<Set<Expr>> cand, Set<Set<Expr>> ref) { boolean result = true; for(Set<Expr> clause : ref) { boolean isContained = cand.contains(clause); if(!isContained) { result = false; break; } } return result; } /** * Return a sub cnf that where each element of a clause contains all variables * * @param clauses * @param requiredVars * @return */ /* public static Set<Set<Expr>> filterByVars(Set<Set<Expr>> clauses, Set<Var> requiredVars) { Set<Set<Expr>> result = new HashSet<Set<Expr>>(); for(Set<Expr> clause : clauses) { Set<Expr> tmp = null; for(Expr expr : clause) { if(!expr.getVarsMentioned().containsAll(requiredVars)) { continue; } if(tmp == null) { tmp = new HashSet<Expr>(); } tmp.add(expr); } if(tmp != null) { result.add(tmp); } } return result; } */ public static Set<Set<Expr>> toSetCnf(ExprList exprs) { List<ExprList> clauses = toClauses(exprs); Set<Set<Expr>> cnf = FilterUtils.toSets(clauses); return cnf; } public static Set<Set<Expr>> toSetCnf(Expr expr) { Set<Set<Expr>> result; if(NodeValue.TRUE.equals(expr)) { result = new HashSet<>(); // Return a new set, as callers may want to extend the set//Collections.emptySet(); } else { List<ExprList> clauses = toClauses(expr); result = FilterUtils.toSets(clauses); } return result; } public static List<ExprList> toClauses(Expr expr) { Expr evaluated = eval(expr); return evaluated == null ? null : cnfToClauses(Collections.singleton(evaluated)); } public static List<ExprList> toClauses(ExprList exprs) { Expr evaluated = eval(ExprUtils.andifyBalanced(exprs)); return evaluated == null ? null : cnfToClauses(Collections.singleton(evaluated)); } /** * This method only words if the input expressions are in DNF, * otherwise you will likely get junk back. * * @param exprs * @return */ public static List<ExprList> cnfToClauses(Iterable<Expr> exprs) { List<ExprList> result = new ArrayList<ExprList>(); for(Expr expr : exprs) { collectAnd(expr, result); } return result; } public static void collectAnd(Expr expr, List<ExprList> list) { if(expr instanceof E_LogicalAnd) { E_LogicalAnd e = (E_LogicalAnd)expr; collectAnd(e.getArg1(), list); collectAnd(e.getArg2(), list); } else if(expr instanceof E_LogicalOr) { //List<Expr> ors = new ArrayList<Expr>(); ExprList ors = new ExprList(); collectOr(expr, ors); list.add(ors); } else { list.add(new ExprList(expr)); } } public static void collectOr(Expr expr, ExprList list) { if(expr instanceof E_LogicalOr) { E_LogicalOr e = (E_LogicalOr)expr; collectOr(e.getArg1(), list); collectOr(e.getArg2(), list); } else { list.add(expr); } } public static Expr eval(Expr expr) { if(expr instanceof ExprFunction) { return handle((ExprFunction)expr); } else { return expr; } } public static Expr handle(ExprFunction expr) { //System.out.println("Converting to KNF: [" + expr.getClass() + "]: " + expr); // not(and(A, B)) -> or(not A, not B) // not(or(A, B)) -> or(not A, not B) if(expr instanceof E_LogicalNot) { Expr tmp = ((E_LogicalNot)expr).getArg(); if (!(tmp instanceof ExprFunction)) { return expr; } ExprFunction child = (ExprFunction)tmp; Expr newExpr = expr; if (child instanceof E_LogicalAnd) { newExpr = new E_LogicalOr(eval(new E_LogicalNot(child.getArg(1))), eval(new E_LogicalNot(child.getArg(2)))); } else if (child instanceof E_LogicalOr) { newExpr = new E_LogicalAnd(eval(new E_LogicalNot(child.getArg(1))), eval(new E_LogicalNot(child.getArg(2)))); } else if (child instanceof E_LogicalNot) { // Remove double negation newExpr = eval(child.getArg(1)); } else { return expr; } return eval(newExpr); } else if (expr instanceof E_LogicalAnd) { //return expr; //return eval(expr); return new E_LogicalAnd(eval(expr.getArg(1)), eval(expr.getArg(2))); } /* Given: * (A or B) AND (C x D) becomes: * (A and (C x D)) OR (B and (c x D)) * * * (A or B) AND (C or D) * * Goal: * (A and C) OR (A and D) OR (B and C) OR (B and D) * * This method transforms any "or" children of an AND node. * other nodes are left untouched: * (A or B) AND (c x D) becomes: * (A and (c x D)) OR (B and (c x D)) */ else if (expr instanceof E_LogicalOr) { Expr aa = eval(expr.getArg(1)); Expr bb = eval(expr.getArg(2)); E_LogicalAnd a = null; Expr b = null; if (aa instanceof E_LogicalAnd) { a = (E_LogicalAnd)aa; b = bb; } else if(bb instanceof E_LogicalAnd) { a = (E_LogicalAnd)bb; b = aa; } if(a == null) { return new E_LogicalOr(aa, bb); } else { return new E_LogicalAnd(eval(new E_LogicalOr(a.getArg(1), b)), eval(new E_LogicalOr(a.getArg(2), b))); } } else if (expr instanceof E_NotEquals) { // Normalize (a != b) to !(a = b) --- this makes it easier to find "a and !a" cases return new E_LogicalNot(eval(new E_Equals(expr.getArg(1), expr.getArg(2)))); } return expr; } }