package edu.stanford.nlp.sempre; import fig.basic.LispTree; import fig.basic.LogInfo; import fig.basic.Option; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Input: two children (a binary and a unary in some order). * JoinFn really serve two roles: * 1. binary is just a relation (e.g., !fb:people.person.place_of_birth), in * which case we do a join-project. * 2. binary is a lambda calculus expression (e.g., (lambda x (count (var * x)))), in which case we do (macro) function application. * * @author Percy Liang */ public class JoinFn extends SemanticFn { public static class Options { @Option(gloss = "Verbose") public int verbose = 0; @Option public boolean showTypeCheckFailures = false; @Option public boolean typeInference = true; // TODO(joberant): this flag is for backward compatibility. If we don't // need it for the new results, get rid of it. @Option public boolean specializedTypeCheck = false; } public static Options opts = new Options(); // The arguments to JoinFn can be either: // - binary unary // - unary binary private boolean unaryFirst = false; // A binary has two arguments, arg0 and arg1. // 1. arg0 = fb:en.barack_obama, binary = fb:people.person.place_of_birth, arg1 = fb:en.honolulu // From a relation point viewpoint, arg0 is the subject and arg1 is the object. // 2. arg0 = fb:en.barack_obama, binary = (lambda x (fb:people.person.place_of_birth (var x))), arg1 = fb:en.honolulu // From a function application viewpoint, arg1 is the argument and arg0 is the return type. // The unary can be placed into arg0 of the binary or arg1 of the binary. // When we write a join (binary unary), unary goes into arg1, so if we want the unary to go into arg0, we need to reverse the binary. private boolean unaryCanBeArg0 = false; private boolean unaryCanBeArg1 = false; // If we want to do a betaReduction rather than creating a JoinFormula. private boolean betaReduce = false; /** * There are four different ways binaries and unaries can be combined: * - where was unary[Obama] binary[born]? (unary,binary unaryCanBeArg0) * - unary[Spanish] binary[speaking] countries (unary,binary unaryCanBeArg1) * - binary[parents] of unary[Obama] (binary,unary unaryCanBeArg0) * - has binary[parents] unary[Obama] (binary,unary unaryCanBeArg1) */ // Optionally specify the first of the two arguments to the JoinFn, // in which case, this function should only be called on one argument. // Note: this is confusing - arg0 here refers to the arguments to JoinFn, not // the arg0 and arg1 of the binary. private ConstantFn arg0Fn = null; public ConstantFn getArg0Fn() { return arg0Fn; } public void init(LispTree tree) { super.init(tree); for (int j = 1; j < tree.children.size(); j++) { String arg = tree.child(j).value; if (tree.child(j).isLeaf()) { switch (arg) { case "binary,unary": unaryFirst = false; break; case "unary,binary": unaryFirst = true; break; case "unaryCanBeArg0": unaryCanBeArg0 = true; break; case "unaryCanBeArg1": unaryCanBeArg1 = true; break; case "forward": unaryFirst = false; unaryCanBeArg1 = true; break; case "backward": unaryFirst = true; unaryCanBeArg1 = true; break; case "betaReduce": betaReduce = true; break; default: throw new RuntimeException("Invalid argument: " + arg); } } else { if ("arg0".equals(tree.child(j).child(0).value)) { arg0Fn = new ConstantFn(); arg0Fn.init(tree.child(j)); } else { throw new RuntimeException("Invalid argument: " + tree.child(j)); } } } if (!unaryCanBeArg0 && !unaryCanBeArg1) throw new RuntimeException("At least one of unaryCanBeArg0 and unaryCanBeArg1 must be set"); } public DerivationStream call(Example ex, Callable c) { return new LazyJoinFnDerivs(ex, c); } public class LazyJoinFnDerivs extends MultipleDerivationStream { private int currIndex = 0; private List<Derivation> derivations = new ArrayList<>(); private Example ex; private Callable callable; Derivation unaryDeriv, binaryDeriv; public LazyJoinFnDerivs(Example ex, Callable c) { this.ex = ex; this.callable = c; Derivation child0, child1; // TODO(pliang): we can actually push most of this logic into createDerivation() // don't need to get the exact size if (arg0Fn != null) { if (c.getChildren().size() != 1) throw new RuntimeException("Expected one argument (already have " + arg0Fn + "), but got args: " + c.getChildren()); // This is just a virtual child which is not a derivation. DerivationStream ld = arg0Fn.call(ex, CallInfo.NULL_INFO); child0 = ld.next(); child1 = c.child(0); } else { if (c.getChildren().size() != 2) throw new RuntimeException("Expected two arguments, but got: " + c.getChildren()); child0 = c.child(0); child1 = c.child(1); } if (unaryFirst) { unaryDeriv = child0; binaryDeriv = child1; } else { binaryDeriv = child0; unaryDeriv = child1; } } @Override public int estimatedSize() { return 2; // This is an upper bound } @Override public Derivation createDerivation() { if (currIndex == 0) doJoins(binaryDeriv, unaryDeriv); if (currIndex == derivations.size()) return null; return derivations.get(currIndex++); } SemType specializedTypeCheck(SemType binaryType, SemType unaryType) { // Ugly special case for Free917/WebQuestions: when |that| is a // UnionSemType corresponding to an entity (e.g., // fb:en.the_washington_post) and we are joining with a relation (e.g., // fb:cvg.game_version.publisher), then we end up calling this function // with: // - that: (union fb:business.employer ...) // - argType: fb:cvg.cvg_publisher // The meet here is fb:cvg.cvg_publisher, but we actually want to return bottom (to be more stringent). SemType argType = binaryType.getArgType(); if (unaryType instanceof TopSemType) // Happens when we don't know the type of the unary return SemType.bottomType; if (unaryType instanceof AtomicSemType) // Make things uniform unaryType = new UnionSemType(unaryType); if (unaryType instanceof UnionSemType && argType instanceof AtomicSemType) { for (SemType t : ((UnionSemType) unaryType).baseTypes) if (t instanceof AtomicSemType && SemTypeHierarchy.singleton.getSupertypes(((AtomicSemType) t).name).contains(((AtomicSemType) argType).name)) return binaryType.getRetType(); return SemType.bottomType; } return binaryType.apply(unaryType); // Default } // Return null if unable to join. private Derivation doJoin(Derivation binaryDeriv, Formula binaryFormula, SemType binaryType, Derivation unaryDeriv, Formula unaryFormula, SemType unaryType, String featureDesc) { // Do a coarse type check. SemType type = opts.specializedTypeCheck ? specializedTypeCheck(binaryType, unaryType) : binaryType.apply(unaryType); if (!type.isValid()) { if (opts.showTypeCheckFailures) LogInfo.warnings("JoinFn: type check failed: [%s : %s] JOIN [%s : %s]", binaryFormula, binaryType, unaryFormula, unaryType); return null; } Formula f; if (betaReduce) { if (!(binaryFormula instanceof LambdaFormula)) throw new RuntimeException("Expected LambdaFormula as the binary, but got: " + binaryFormula + ", unary is " + unaryFormula); f = Formulas.lambdaApply((LambdaFormula) binaryFormula, unaryFormula); } else { f = new JoinFormula(binaryFormula, unaryFormula); } // Do full type inference. if (opts.typeInference) { SemType fullType = TypeInference.inferType(f); if (opts.verbose >= 2) LogInfo.logs("JoinFn.typeInference: %s => %s [coarse type = %s]", f, fullType, type); if (!fullType.isValid()) return null; // Rule out logical form type = fullType; // Use the more specific type } if (opts.verbose >= 3) { LogInfo.logs( "JoinFn: binary: %s [%s], unary: %s [%s], result: %s [%s]", binaryFormula, binaryType, unaryFormula, unaryType, f, type); } // Add features FeatureVector features = new FeatureVector(); if (FeatureExtractor.containsDomain("joinPos") && featureDesc != null) features.add("joinPos", featureDesc); // FbFormulasInfo.touchBinaryFormula(binaryFormula); Derivation newDeriv = new Derivation.Builder() .withCallable(callable) .formula(f) .type(type) .localFeatureVector(features) .createDerivation(); if (SemanticFn.opts.trackLocalChoices) { newDeriv.addLocalChoice( "JoinFn " + (binaryDeriv.start == -1 ? "-" : binaryDeriv.startEndString(ex.getTokens())) + " " + binaryDeriv.formula + " AND " + (unaryDeriv.start == -1 ? "-" : unaryDeriv.startEndString(ex.getTokens())) + " " + unaryDeriv.formula); } return newDeriv; } private void doJoins(Derivation binaryDeriv, Derivation unaryDeriv) { String binaryPos = ex.languageInfo.getCanonicalPos(binaryDeriv.start); String unaryPos = ex.languageInfo.getCanonicalPos(unaryDeriv.start); if (unaryCanBeArg0) { Derivation deriv = doJoin( binaryDeriv, Formulas.reverseFormula(binaryDeriv.formula), binaryDeriv.type.reverse(), unaryDeriv, unaryDeriv.formula, unaryDeriv.type, "binary=" + binaryPos + ",unary=" + unaryPos + "_reverse"); if (deriv != null) derivations.add(deriv); } if (unaryCanBeArg1) { Derivation deriv = doJoin( binaryDeriv, binaryDeriv.formula, binaryDeriv.type, unaryDeriv, unaryDeriv.formula, unaryDeriv.type, "binary=" + binaryPos + ",unary=" + unaryPos); if (deriv != null) derivations.add(deriv); } Collections.sort(derivations, Derivation.derivScoreComparator); } } }