package edu.stanford.nlp.sempre.tables.lambdadcs; import java.util.*; import edu.stanford.nlp.sempre.*; import edu.stanford.nlp.sempre.tables.ScopedFormula; import edu.stanford.nlp.sempre.tables.ScopedValue; import edu.stanford.nlp.sempre.tables.TableKnowledgeGraph; import edu.stanford.nlp.sempre.tables.lambdadcs.LambdaDCSException.Type; import fig.basic.*; /** * Execute a Formula on the given KnowledgeGraph instance. * * @author ppasupat */ public class LambdaDCSExecutor extends Executor { public static class Options { @Option(gloss = "Verbosity") public int verbose = 0; @Option(gloss = "Use caching") public boolean useCache = true; @Option(gloss = "Sort the resulting values (may slow down execution)") public boolean sortResults = true; @Option(gloss = "Allow the return value to be an implicit value") public boolean allowImplicitValues = true; @Option(gloss = "Allow the root formula to be a binary") public boolean executeBinary = false; @Option(gloss = "Generic DateValue: (date -1 5 -1) in formula also matches (date -1 5 12)") public boolean genericDateValue = false; @Option(gloss = "Return all ties on (argmax 1 1 ...) and (argmin 1 1 ...)") public boolean superlativesReturnAllTopTies = true; @Option(gloss = "Aggregates (sum, avg, max, min) throw an error on empty lists") public boolean aggregatesFailOnEmptyLists = false; @Option(gloss = "Superlatives (argmax, argmin) throw an error on empty lists") public boolean superlativesFailOnEmptyLists = false; @Option(gloss = "Arithmetics (+, -, *, /) throw an error on empty lists") public boolean arithmeticsFailOnEmptyLists = false; @Option(gloss = "Arithmetics (+, -, *, /) throw an error when both operants have > 1 values") public boolean arithmeticsFailOnMultipleElements = true; } public static Options opts = new Options(); public final Evaluation stats = new Evaluation(); @Override public Response execute(Formula formula, ContextValue context) { LambdaDCSCoreLogic logic; if (opts.verbose < 3) { logic = new LambdaDCSCoreLogic(context, stats); } else { logic = new LambdaDCSCoreLogicWithVerbosity(context, stats); } StopWatch stopWatch = new StopWatch(); stopWatch.start(); formula = Formulas.betaReduction(formula); Value answer = logic.execute(formula); stopWatch.stop(); stats.addCumulative("execTime", stopWatch.ms); if (stopWatch.ms >= 10 && opts.verbose >= 1) LogInfo.logs("long time (%d ms): %s => %s", stopWatch.ms, formula, answer); return new Response(answer); } public void summarize() { LogInfo.begin_track("LambdaDCSExecutor: summarize"); stats.logStats("LambdaDCSExecutor"); LogInfo.end_track(); } } // ============================================================ // Execution // ============================================================ /** * Main logic of Lambda DCS Executor. * * Find the denotation of a formula (logical form) with respect to the given knowledge graph. * * Assume that the denotation is either a unary or a binary, * and the final denotation is a unary. * * Both unaries and binaries are lists (not sets). * However, the following formula types will treat them as sets: * - and, or * - count (= count the number of distinct values) * * Note that (and (!weight (@type @row)) (@p.num (> (number 90)))) may give a wrong answer, * but it can be rewritten as (!weight (and (@type @row) (weight (@p.num (> (number 90)))))) * * @author ppasupat */ class LambdaDCSCoreLogic { // Note: STAR does not work well with type checking static final NameValue STAR = new NameValue("*"); final KnowledgeGraph graph; final Evaluation stats; ExecutorCache cache; public LambdaDCSCoreLogic(ContextValue context, Evaluation stats) { graph = context.graph; this.stats = stats; if (graph == null) throw new RuntimeException("Cannot call LambdaDCSExecutor when context graph is null"); if (graph instanceof TableKnowledgeGraph) cache = ((TableKnowledgeGraph) graph).executorCache; if (cache == null) cache = ExecutorCache.singleton; } public Value execute(Formula formula) { if (LambdaDCSExecutor.opts.verbose >= 2) LogInfo.logs("%s", formula); Value answer; // Special case: ScopedFormula if (formula instanceof ScopedFormula) { ScopedFormula scoped = (ScopedFormula) formula; try { // Head UnaryDenotation head = (UnaryDenotation) computeUnary(scoped.head, TypeHint.UNRESTRICTED_UNARY); if (head.size() == Integer.MAX_VALUE) throw new LambdaDCSException(Type.infiniteList, "Cannot have an infinite head: ", head); ListValue headValue = ((ListValue) head.toValue()).getUnique(); // Relation LambdaFormula lambdaRelation = (LambdaFormula) scoped.relation; List<Pair<Value, Value>> collapsedPairs = new ArrayList<>(); for (Value varValue : headValue.values) { UnaryDenotation results = (UnaryDenotation) computeUnary(lambdaRelation.body, TypeHint.UNRESTRICTED_UNARY.withVar(lambdaRelation.var, varValue)); if (LambdaDCSExecutor.opts.useCache) { cache.put(graph, new Pair<>(lambdaRelation.body, new Pair<>(lambdaRelation.var, varValue)), results); } if (!results.isEmpty()) collapsedPairs.add(new Pair<>(varValue, results.toValue())); } Value relationValue = new PairListValue(collapsedPairs); answer = new ScopedValue(headValue, relationValue); } catch (LambdaDCSException e) { answer = new ErrorValue(e.toString()); } } else { // Unaries and Binaries try { Unarylike denotation = computeUnary(formula, TypeHint.UNRESTRICTED_UNARY); if (LambdaDCSExecutor.opts.useCache) { cache.put(graph, formula, denotation); } answer = denotation.toValue(); } catch (LambdaDCSException e) { if (LambdaDCSExecutor.opts.executeBinary && e.type == Type.notUnary) { try { Binarylike denotation = computeBinary(formula, TypeHint.UNRESTRICTED_BINARY); answer = denotation.toValue(); } catch (LambdaDCSException e2) { answer = new ErrorValue(e2.toString()); } } else { answer = new ErrorValue(e.toString()); } } } if (LambdaDCSExecutor.opts.verbose >= 2) LogInfo.logs("=> %s", answer); return answer; } public Unarylike computeUnary(Formula formula, UnarylikeTypeHint typeHint) { assert typeHint != null; if (formula instanceof LambdaFormula) { throw new LambdaDCSException(Type.notUnary, "[Unary] Not a unary %s", formula); } if (LambdaDCSExecutor.opts.useCache) { Object object = cache.get(graph, formula); if (object != null && object instanceof Unarylike) { stats.addCumulative("normalCacheHit", true); stats.addCumulative("scopedCacheHit", false); return (Unarylike) object; } else if (typeHint.getIfSingleVar() != null) { object = cache.get(graph, new Pair<>(formula, typeHint.getIfSingleVar())); if (object != null && object instanceof Unarylike) { stats.addCumulative("normalCacheHit", false); stats.addCumulative("scopedCacheHit", true); return (Unarylike) object; } } stats.addCumulative("normalCacheHit", false); stats.addCumulative("scopedCacheHit", false); } if (formula instanceof ValueFormula) { // ============================================================ // ValueFormula // ============================================================ Value value = ((ValueFormula<?>) formula).value; if (value instanceof BooleanValue || value instanceof NumberValue || value instanceof StringValue || value instanceof DateValue || value instanceof NameValue) { // Special case: * if (STAR.equals(value)) return InfiniteUnaryDenotation.STAR_UNARY; // Special case: generic date if (LambdaDCSExecutor.opts.genericDateValue && value instanceof DateValue) return typeHint.applyBound(InfiniteUnaryDenotation.GenericDateUnaryDenotation.get((DateValue) value)); // Rule out binaries if (CanonicalNames.isBinary(value) && LambdaDCSExecutor.opts.executeBinary) throw new LambdaDCSException(Type.notUnary, "[Unary] Binary value %s", formula); if (value instanceof NameValue && graph instanceof TableKnowledgeGraph) value = ((TableKnowledgeGraph) graph).getNameValueWithOriginalString((NameValue) value); // Other cases return typeHint.applyBound(new ExplicitUnaryDenotation(value)); } } else if (formula instanceof VariableFormula) { // ============================================================ // Variable // ============================================================ String name = ((VariableFormula) formula).name; Value value = typeHint.get(name); if (value != null) return typeHint.applyBound(new ExplicitUnaryDenotation(value)); // Could be a mapping if (name.equals(typeHint.getFreeVar())) return typeHint.applyBound(new MappingDenotation<>(name, PredicatePairList.IDENTITY)); } else if (formula instanceof JoinFormula) { // ============================================================ // JoinFormula // ============================================================ JoinFormula join = (JoinFormula) formula; try { // Compute unary, then join binary Unarylike childD = computeUnary(join.child, typeHint.unrestrictedUnary()); Binarylike relationD = computeBinary(join.relation, typeHint.asFirstOfBinaryWithSecond(childD.range())); return typeHint.applyBound(DenotationUtils.genericJoin(relationD, childD)); } catch (LambdaDCSException e1) { try { // Compute binary, then join unary Binarylike relationD = computeBinary(join.relation, typeHint.asFirstOfBinary()); Unarylike childUpperBound = relationD.joinOnValue(typeHint.upperBound); Unarylike childD = computeUnary(join.child, typeHint.restrictedUnary(childUpperBound.range())); return typeHint.applyBound(DenotationUtils.genericJoin(relationD, childD)); } catch (LambdaDCSException e2) { Type errorType = (e1.type == e2.type) ? e1.type : Type.unknown; throw new LambdaDCSException(errorType, "Cannot join | %s | %s", e1, e2); } } } else if (formula instanceof MergeFormula) { // ============================================================ // Merge // ============================================================ MergeFormula merge = (MergeFormula) formula; try { Unarylike child1D = computeUnary(merge.child1, typeHint); Unarylike child2D = computeUnary(merge.child2, merge.mode == MergeFormula.Mode.and ? typeHint.restrict(child1D) : typeHint); return typeHint.applyBound(DenotationUtils.merge(child1D, child2D, merge.mode)); } catch (LambdaDCSException e1) { try { Unarylike child2D = computeUnary(merge.child2, typeHint); Unarylike child1D = computeUnary(merge.child1, merge.mode == MergeFormula.Mode.and ? typeHint.restrict(child2D) : typeHint); return typeHint.applyBound(DenotationUtils.merge(child2D, child1D, merge.mode)); } catch (LambdaDCSException e2) { Type errorType = (e1.type == e2.type) ? e1.type : Type.unknown; throw new LambdaDCSException(errorType, "Cannot merge | %s | %s", e1, e2); } } } else if (formula instanceof AggregateFormula) { // ============================================================ // Aggregate // ============================================================ AggregateFormula aggregate = (AggregateFormula) formula; Unarylike childD = computeUnary(aggregate.child, typeHint.unrestrictedUnary()); return typeHint.applyBound(childD.aggregate(aggregate.mode)); } else if (formula instanceof SuperlativeFormula) { // ============================================================ // Superlative // ============================================================ SuperlativeFormula superlative = (SuperlativeFormula) formula; int rank = DenotationUtils.getSinglePositiveInteger( computeUnary(superlative.rank, typeHint.unrestrictedUnary()).range()); int count = DenotationUtils.getSinglePositiveInteger( computeUnary(superlative.count, typeHint.unrestrictedUnary()).range()); Unarylike headD = computeUnary(superlative.head, typeHint); Binarylike relationD; if (superlative.relation instanceof ReverseFormula) { relationD = computeBinary(((ReverseFormula) superlative.relation).child, typeHint.restrictedBinary(null, headD.range())); } else { relationD = computeBinary(superlative.relation, typeHint.restrictedBinary(headD.range(), null)).reverse(); } return typeHint.applyBound(DenotationUtils.superlative(rank, count, headD, relationD, superlative.mode)); } else if (formula instanceof ArithmeticFormula) { // ============================================================ // Arithmetic // ============================================================ ArithmeticFormula arithmetic = (ArithmeticFormula) formula; Unarylike child1D = computeUnary(arithmetic.child1, typeHint.unrestrictedUnary()); Unarylike child2D = computeUnary(arithmetic.child2, typeHint.unrestrictedUnary()); return typeHint.applyBound(DenotationUtils.arithmetic(child1D, child2D, arithmetic.mode)); } else if (formula instanceof MarkFormula) { // ============================================================ // Mark // ============================================================ MarkFormula mark = (MarkFormula) formula; LambdaFormula lambda = new LambdaFormula(mark.var, new MergeFormula(MergeFormula.Mode.and, new VariableFormula(mark.var), mark.body)); Binarylike lambdaD = computeBinary(lambda, typeHint.asFirstAndSecondOfBinary()); return lambdaD.joinOnValue(InfiniteUnaryDenotation.STAR_UNARY); } else { throw new LambdaDCSException(Type.notUnary, "[Unary] Not a valid unary %s", formula); } // Catch-all error throw new LambdaDCSException(Type.unknown, "[Unary] Cannot handle formula %s", formula); } public Binarylike computeBinary(Formula formula, BinaryTypeHint typeHint) { assert typeHint != null; if (formula instanceof ValueFormula) { // ============================================================ // ValueFormula // ============================================================ Value value = ((ValueFormula<?>) formula).value; // Must be a binary if (CanonicalNames.isBinary(value)) return new BinaryDenotation<>(new PredicatePairList(value, graph)); else throw new LambdaDCSException(Type.notBinary, "[Binary] Unary value %s", formula); } else if (formula instanceof ReverseFormula) { // ============================================================ // Reverse // ============================================================ ReverseFormula reverse = (ReverseFormula) formula; Binarylike childD = computeBinary(reverse.child, typeHint.reverse()); return childD.reverse(); } else if (formula instanceof LambdaFormula) { // ============================================================ // Lambda // ============================================================ // Note: The variable's values become the SECOND argument of the binary pairs LambdaFormula lambda = (LambdaFormula) formula; String var = lambda.var; // Assuming that the type hint has enough information ... try { List<Pair<Value, Value>> pairs = new ArrayList<>(); for (Value varValue : typeHint.secondUpperBound) { Unarylike results = computeUnary(lambda.body, typeHint.first().withVar(var, varValue)); if (!(results instanceof UnaryDenotation)) throw new LambdaDCSException(Type.notUnary, "Not a unary denotation: %s", results); for (Value result : (UnaryDenotation) results) { pairs.add(new Pair<>(result, varValue)); } } return new BinaryDenotation<>(new ExplicitPairList(pairs)); } catch (LambdaDCSException e) { } // Try the reverse try { Formula reversed = Formulas.reverseFormula(lambda); if (reversed instanceof LambdaFormula && !reversed.equals(lambda)) { List<Pair<Value, Value>> pairs = new ArrayList<>(); for (Value varValue : typeHint.firstUpperBound) { Unarylike results = computeUnary(((LambdaFormula) reversed).body, typeHint.second().withVar(var, varValue)); if (!(results instanceof UnaryDenotation)) throw new LambdaDCSException(Type.notUnary, "Not a unary denotation: %s", results); for (Value result : (UnaryDenotation) results) { pairs.add(new Pair<>(varValue, result)); } } return new BinaryDenotation<>(new ExplicitPairList(pairs)); } else { throw new LambdaDCSException(Type.unknown, "Cannot compute reverse of %s", lambda); } } catch (LambdaDCSException e) { } // Try to execute using a mapping. if (LambdaDCSExecutor.opts.executeBinary) { try { Unarylike mapping = computeUnary(lambda.body, typeHint.asMapping(lambda.var)); if (mapping instanceof MappingDenotation) return ((MappingDenotation<?>) mapping).asBinary(); } catch (LambdaDCSException e) { } } } else { throw new LambdaDCSException(Type.notBinary, "[Binary] Not a valid binary %s", formula); } // Catch-all error throw new LambdaDCSException(Type.unknown, "[Binary] Cannot handle formula %s", formula); } } // ============================================================ // Debug Print // ============================================================ class LambdaDCSCoreLogicWithVerbosity extends LambdaDCSCoreLogic { public LambdaDCSCoreLogicWithVerbosity(ContextValue context, Evaluation stats) { super(context, stats); } @Override public Unarylike computeUnary(Formula formula, UnarylikeTypeHint typeHint) { LogInfo.begin_track("UNARY %s [%s]", formula, typeHint); try { Unarylike denotation = super.computeUnary(formula, typeHint); LogInfo.logs("%s", denotation); LogInfo.end_track(); return denotation; } catch (Exception e) { LogInfo.end_track(); throw e; } } @Override public Binarylike computeBinary(Formula formula, BinaryTypeHint typeHint) { LogInfo.begin_track("BINARY %s [%s]", formula, typeHint); try { Binarylike denotation = super.computeBinary(formula, typeHint); LogInfo.logs("%s", denotation); LogInfo.end_track(); return denotation; } catch (Exception e) { LogInfo.end_track(); throw e; } } }