package edu.stanford.nlp.sempre; import com.google.common.base.Function; import com.google.common.collect.Lists; import fig.basic.LispTree; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Utilities for working with Formulas. * * @author Percy Liang */ public abstract class Formulas { public static Formula fromLispTree(LispTree tree) { // Try to interpret as ValueFormula if (tree.isLeaf()) // Leaves are name values return new ValueFormula<NameValue>(new NameValue(tree.value, null)); Value value = Values.fromLispTreeOrNull(tree); // General case if (value != null) return new ValueFormula<Value>(value); String func = tree.child(0).value; if (func != null) { if (func.equals("var")) return new VariableFormula(tree.child(1).value); if (func.equals("lambda")) return new LambdaFormula(tree.child(1).value, fromLispTree(tree.child(2))); if (func.equals("mark")) return new MarkFormula(tree.child(1).value, fromLispTree(tree.child(2))); if (func.equals("not")) return new NotFormula(fromLispTree(tree.child(1))); if (func.equals("reverse")) return new ReverseFormula(fromLispTree(tree.child(1))); if (func.equals("call")) { Formula callFunc = fromLispTree(tree.child(1)); List<Formula> args = Lists.newArrayList(); for (int i = 2; i < tree.children.size(); i++) args.add(fromLispTree(tree.child(i))); return new CallFormula(callFunc, args); } } { // Merge: (and (fb:type.object.type fb:people.person) (fb:people.person.children fb:en.barack_obama)) MergeFormula.Mode mode = MergeFormula.parseMode(func); if (mode != null) return new MergeFormula(mode, fromLispTree(tree.child(1)), fromLispTree(tree.child(2))); } { // Aggregate: (count (fb:type.object.type fb:people.person)) AggregateFormula.Mode mode = AggregateFormula.parseMode(func); if (mode != null) return new AggregateFormula(mode, fromLispTree(tree.child(1))); } { // Superlative: (argmax 1 1 (fb:type.object.type fb:people.person) (lambda x (!fb:people.person.height_meters (var x)))) SuperlativeFormula.Mode mode = SuperlativeFormula.parseMode(func); if (mode != null) { Formula rank = parseIntToFormula(tree.child(1)); Formula count = parseIntToFormula(tree.child(2)); return new SuperlativeFormula( mode, rank, count, fromLispTree(tree.child(3)), fromLispTree(tree.child(4))); } } { // Arithmetic: (- (!fb:people.person.height_meters (var x)) (!fb:people.person.height_meters (var y))) ArithmeticFormula.Mode mode = ArithmeticFormula.parseMode(func); if (mode != null) return new ArithmeticFormula(mode, fromLispTree(tree.child(1)), fromLispTree(tree.child(2))); } { // ActionFormula ActionFormula.Mode mode = ActionFormula.parseMode(func); if (mode != null) { List<Formula> args = Lists.newArrayList(); for (int i = 1; i < tree.children.size(); i++) args.add(fromLispTree(tree.child(i))); return new ActionFormula(mode, args); } } // Default is join: (fb:type.object.type fb:people.person) if (tree.children.size() != 2) throw new RuntimeException("Invalid number of arguments for join (want 2): " + tree); return new JoinFormula(fromLispTree(tree.child(0)), fromLispTree(tree.child(1))); } // Special case to enable "argmax 1 1" rather than "argmax (number 1) (number 1)" private static Formula parseIntToFormula(LispTree tree) { try { int i = Integer.parseInt(tree.value); double d = i; NumberValue value = new NumberValue(d); return new ValueFormula(value); } catch (NumberFormatException e) { Formula formula = fromLispTree(tree); if (!(formula instanceof PrimitiveFormula)) throw new RuntimeException("Rank and count of argmax must be variables or numbers"); return formula; } } // Replace occurrences of the variable reference |var| with |formula|. public static Formula substituteVar(Formula formula, final String var, final Formula replaceFormula) { return formula.map( new Function<Formula, Formula>() { public Formula apply(Formula formula) { if (formula instanceof VariableFormula) { // Replace variable String name = ((VariableFormula) formula).name; return var.equals(name) ? replaceFormula : formula; } else if (formula instanceof LambdaFormula) { if (((LambdaFormula) formula).var.equals(var)) // |var| is bound, so don't substitute inside return formula; } return null; } }); } // Replace top-level occurrences of |searchFormula| inside |formula| with |replaceFormula|. public static Formula substituteFormula(Formula formula, final Formula searchFormula, final Formula replaceFormula) { return formula.map( new Function<Formula, Formula>() { public Formula apply(Formula formula) { if (formula.equals(searchFormula)) return replaceFormula; return null; } }); } // Beta-reduction. public static Formula lambdaApply(LambdaFormula func, Formula arg) { return substituteVar(func.body, func.var, arg); } // Apply all the nested LambdaFormula's. public static Formula betaReduction(Formula formula) { return formula.map( new Function<Formula, Formula>() { public Formula apply(Formula formula) { if (formula instanceof JoinFormula) { Formula relation = betaReduction(((JoinFormula) formula).relation); Formula child = ((JoinFormula) formula).child; if (relation instanceof LambdaFormula) return betaReduction(lambdaApply((LambdaFormula) relation, child)); } return null; } }); } // Return whether |formula| contains a free instance of |var|. public static boolean containsFreeVar(Formula formula, VariableFormula var) { if (formula instanceof PrimitiveFormula) return formula.equals(var); if (formula instanceof MergeFormula) { MergeFormula merge = (MergeFormula) formula; return containsFreeVar(merge.child1, var) || containsFreeVar(merge.child2, var); } if (formula instanceof JoinFormula) { JoinFormula join = (JoinFormula) formula; return containsFreeVar(join.relation, var) || containsFreeVar(join.child, var); } if (formula instanceof LambdaFormula) { LambdaFormula lambda = (LambdaFormula) formula; if (lambda.var.equals(var.name)) return false; // Blocked by bound variable return containsFreeVar(lambda.body, var); } if (formula instanceof MarkFormula) { MarkFormula mark = (MarkFormula) formula; // Note: marks are transparent, unlike lambdas return containsFreeVar(mark.body, var); } if (formula instanceof ReverseFormula) { return containsFreeVar(((ReverseFormula) formula).child, var); } if (formula instanceof AggregateFormula) { return containsFreeVar(((AggregateFormula) formula).child, var); } if (formula instanceof ArithmeticFormula) { return containsFreeVar(((ArithmeticFormula) formula).child1, var) || containsFreeVar(((ArithmeticFormula) formula).child2, var); } if (formula instanceof SuperlativeFormula) { SuperlativeFormula superlative = (SuperlativeFormula) formula; return containsFreeVar(superlative.rank, var) || containsFreeVar(superlative.count, var) || containsFreeVar(superlative.head, var) || containsFreeVar(superlative.relation, var); } if (formula instanceof NotFormula) { NotFormula notForm = (NotFormula) formula; return containsFreeVar(notForm.child, var); } throw new RuntimeException("Unhandled: " + formula); } // TODO(joberant): use Formula.map, and use CanonicalNames.isReverseProperty, etc. public static Set<String> extractAtomicFreebaseElements(Formula formula) { Set<String> res = new HashSet<>(); LispTree formulaTree = formula.toLispTree(); extractAtomicFreebaseElements(formulaTree, res); return res; } private static void extractAtomicFreebaseElements(LispTree formulaTree, Set<String> res) { if (formulaTree.isLeaf()) { // base if (formulaTree.value.startsWith("fb:")) res.add(formulaTree.value); else if (formulaTree.value.startsWith("!fb:")) res.add(formulaTree.value.substring(1)); } else { // recursion for (LispTree child : formulaTree.children) { extractAtomicFreebaseElements(child, res); } } } // TODO(jonathan): move to feature extractor (this function doesn't seem fundamental) public static boolean isCountFormula(Formula formula) { if (formula instanceof AggregateFormula) return ((AggregateFormula) formula).mode == AggregateFormula.Mode.count; if (formula instanceof JoinFormula) { Formula relation = ((JoinFormula) formula).relation; if (relation instanceof LambdaFormula) { Formula l = ((LambdaFormula) relation).body; if (l instanceof AggregateFormula) return ((AggregateFormula) l).mode == AggregateFormula.Mode.count; } } return false; } public static String getString(Formula formula) { if (formula instanceof ValueFormula) { Value value = ((ValueFormula) formula).value; if (value instanceof StringValue) return ((StringValue) value).value; if (value instanceof NameValue) return ((NameValue) value).id; if (value instanceof NumberValue) return ((NumberValue) value).value + ""; } else if (formula instanceof VariableFormula) { return ((VariableFormula) formula).name; } return null; } public static String getNameId(Formula formula) { if (formula instanceof ValueFormula) { Value value = ((ValueFormula) formula).value; if (value instanceof NameValue) return ((NameValue) value).id; } return null; } public static double getDouble(Formula formula) { if (formula instanceof ValueFormula) { Value value = ((ValueFormula) formula).value; if (value instanceof NumberValue) return ((NumberValue) value).value; } return Double.NaN; } public static int getInt(Formula formula) { return (int) getDouble(formula); } /** * If the formula represents a binary (e.g., fb:a.b.c or <=), * return the ID of the binary as a string. * If the formula represents a reversed binary (e.g., !fb:a.b.c or (reverse fb:a.b.c)), * return "!" + ID of the binary. * Otherwise, return null. */ public static String getBinaryId(Formula formula) { if (formula instanceof ReverseFormula) { String childId = getBinaryId(((ReverseFormula) formula).child); if (childId == null) return null; return CanonicalNames.reverseProperty(childId); } else if (formula instanceof ValueFormula) { Value v = ((ValueFormula<?>) formula).value; if (v instanceof NameValue) { return ((NameValue) v).id; } } return null; } public static ValueFormula<NameValue> newNameFormula(String id) { return new ValueFormula<NameValue>(new NameValue(id)); } /* * Extract all subformulas in a string format (to also have primitive values) * TODO(joberant): replace this with Formulas.map */ public static Set<String> extractSubparts(Formula f) { Set<String> res = new HashSet<String>(); extractSubpartsRecursive(f, res); return res; } private static void extractSubpartsRecursive(Formula f, Set<String> res) { // base res.add(f.toString()); // recurse if (f instanceof AggregateFormula) { AggregateFormula aggFormula = (AggregateFormula) f; extractSubpartsRecursive(aggFormula, res); } else if (f instanceof CallFormula) { CallFormula callFormula = (CallFormula) f; extractSubpartsRecursive(callFormula.func, res); for (Formula argFormula : callFormula.args) extractSubpartsRecursive(argFormula, res); } else if (f instanceof JoinFormula) { JoinFormula joinFormula = (JoinFormula) f; extractSubpartsRecursive(joinFormula.relation, res); extractSubpartsRecursive(joinFormula.child, res); } else if (f instanceof LambdaFormula) { LambdaFormula lambdaFormula = (LambdaFormula) f; extractSubpartsRecursive(lambdaFormula.body, res); } else if (f instanceof MarkFormula) { MarkFormula markFormula = (MarkFormula) f; extractSubpartsRecursive(markFormula.body, res); } else if (f instanceof MergeFormula) { MergeFormula mergeFormula = (MergeFormula) f; extractSubpartsRecursive(mergeFormula.child1, res); extractSubpartsRecursive(mergeFormula.child2, res); } else if (f instanceof NotFormula) { NotFormula notFormula = (NotFormula) f; extractSubpartsRecursive(notFormula.child, res); } else if (f instanceof ReverseFormula) { ReverseFormula revFormula = (ReverseFormula) f; extractSubpartsRecursive(revFormula.child, res); } else if (f instanceof SuperlativeFormula) { SuperlativeFormula superlativeFormula = (SuperlativeFormula) f; extractSubpartsRecursive(superlativeFormula.rank, res); extractSubpartsRecursive(superlativeFormula.count, res); extractSubpartsRecursive(superlativeFormula.head, res); extractSubpartsRecursive(superlativeFormula.relation, res); } } // Takes in a |rawFormula| which represents a function x => y and returns a // function y => x. public static Formula reverseFormula(Formula rawFormula) { if (rawFormula instanceof ValueFormula) { @SuppressWarnings({ "unchecked" }) ValueFormula<NameValue> vf = (ValueFormula<NameValue>) rawFormula; return reverseNameFormula(vf); } else if (rawFormula instanceof LambdaFormula) { // Convert (lambda x (relation1 (relation2 (var x)))) <=> (lambda x (!relation2 (!relation1 (var x)))) // Note: currently only handles chains. Make this more generic. LambdaFormula formula = (LambdaFormula) rawFormula; if (isChain(formula.body)) return new LambdaFormula(formula.var, reverseChain(formula.body, new VariableFormula(formula.var))); else return new ReverseFormula(formula); } else { return new ReverseFormula(rawFormula); // throw new RuntimeException("Not handled: " + rawFormula); } } // Helper function for reverseFormula(). // Check to see if formula has the form (a (b (c (var x)))) private static boolean isChain(Formula source) { if (source instanceof JoinFormula) { JoinFormula join = (JoinFormula) source; return isChain(join.child); } return source instanceof VariableFormula; } // Reverse the chain private static Formula reverseChain(Formula source, Formula result) { if (source instanceof JoinFormula) { JoinFormula join = (JoinFormula) source; return reverseChain(join.child, new JoinFormula(reverseFormula(join.relation), result)); } else if (source instanceof VariableFormula) { return result; } else { throw new RuntimeException("Not handled: " + source); } } // !fb:people.person.place_of_birth <=> fb:people.person.place_of_birth private static ValueFormula<NameValue> reverseNameFormula(ValueFormula<NameValue> formula) { String id = formula.value.id; return new ValueFormula<>(new NameValue(CanonicalNames.reverseProperty(id))); } // Try to simplify reverse subformulas within the specified formula public static Formula simplifyReverses(Formula formula) { return formula.map(new Function<Formula, Formula>() { public Formula apply(Formula formula) { if (formula instanceof ReverseFormula) return reverseFormula(((ReverseFormula) formula).child); return null; } }); } }