package edu.stanford.nlp.sempre.freebase;
import edu.stanford.nlp.sempre.*;
import fig.basic.*;
import fig.exec.Execution;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
/**
* Converts Luke Zettlemoyer's lambda calculus data format into our example files.
* @author Percy Liang
* @author Ziang Xie
*/
public class LambdaCalculusConverter implements Runnable {
public static class Options {
@Option(gloss = "Input path (lambda calculus)")
public String inPath = "overnight/geo/geosents280-typed.ccg.test.new";
@Option(gloss = "Specification of translations")
public String specPath = "overnight/geo/geo.spec";
@Option(gloss = "Specification of variable names")
public String varPath = "overnight/geo/geo.vars";
@Option(gloss = "Specification of primitive types")
public String primPath = "overnight/geo/geo.primitives";
@Option(gloss = "Specification of formula replacements")
public String replacePath = "overnight/geo/geo.replace";
@Option(gloss = "Specification of manual conversions")
public String manualConversionsPath = "overnight/geo/geo.manual_conversions";
@Option(gloss = "Output path (examples)")
public String outPath = "overnight/geo/geo.out.json";
@Option(gloss = "Specific example to parse and run")
public int runInd = -1;
@Option(gloss = "Output path for lexicon grammar")
public String lexiconPath = "overnight/geo/geo.out.grammar";
@Option(gloss = "Verbose output (for debugging)")
public boolean verbose = false; // TODO Currently unused
}
public static Options opts = new Options();
// Mapping between predicates
Map<String, Formula> predicatesMap = new HashMap<String, Formula>();
// Mapping from variables in the input (e.g. "$1") to our variables (e.g. "x")
Map<String, Formula> varMap = new HashMap<String, Formula>();
// Mapping from types (e.g. "i" in "population:i") to semparse primitive types
Map<String, String> primitiveMap = new HashMap<String, String>();
// Hardcoded replacements for mis-specified formulas in the input
Map<String, String> replaceMap = new HashMap<String, String>();
// Hardcoded conversions for where the converter fails
Map<String, String> manualConversionsMap = new HashMap<String, String>();
// Colorize output
Colorizer color = new Colorizer();
// Examples to be converted
List<Example> examples = new ArrayList<Example>();
// Indices of examples that were executed without error
List<Integer> validExampleIds = new ArrayList<Integer>();
List<Integer> failedExampleIds = new ArrayList<Integer>();
// Use this to later sort the valid examples
List<Integer> validExampleLengths = new ArrayList<Integer>();
public void run() {
readPrereqs();
convertExamples();
executeExamples();
if (opts.runInd < 0) {
writeExamples();
printSummary();
}
}
public void readPrereqs() {
LogInfo.begin_track("Reading prereq");
readSpec();
readPrimitives();
readVars();
readStringMap(opts.replacePath, replaceMap);
readStringMap(opts.manualConversionsPath, manualConversionsMap);
LogInfo.end_track();
}
void readSpec() {
for (String line : IOUtils.readLinesHard(opts.specPath)) {
if (line.startsWith("#")) continue;
if (line.equals("")) continue;
String[] tokens = line.split(" ", 2);
predicatesMap.put(tokens[0], Formula.fromString(tokens[1]));
}
}
void readPrimitives() {
for (String line : IOUtils.readLinesHard(opts.primPath)) {
if (line.startsWith("#")) continue;
if (line.equals("")) continue;
String[] tokens = line.split(" ", 2);
primitiveMap.put(tokens[0], tokens[1]);
}
}
void readVars() {
for (String line : IOUtils.readLinesHard(opts.varPath)) {
if (line.startsWith("#")) continue;
if (line.equals("")) continue;
String[] tokens = line.split(" ", 2);
varMap.put(tokens[0], Formula.fromString(tokens[1]));
}
}
void readStringMap(String path, Map<String, String> map) {
boolean readOriginal = false;
String original = "";
for (String line : IOUtils.readLinesHard(path)) {
if (line.startsWith("#")) continue;
if (line.equals("")) continue;
if (!readOriginal) {
readOriginal = true;
original = line;
} else {
readOriginal = false;
map.put(original, line);
}
}
}
Formula toPredicate(String func) {
boolean reverse = false;
while (func.startsWith("!")) {
LogInfo.log(func);
reverse = !reverse;
func = func.substring(1);
}
if (!predicatesMap.containsKey(func))
func = removeGeoType(func);
if (func.startsWith("$"))
return Formula.fromString(String.format("(var %s)", toVariable(func).toString()));
if (!predicatesMap.containsKey(func)) {
throw new RuntimeException("Unknown predicate: " + func);
}
Formula form = predicatesMap.get(func);
if (reverse) {
return new ReverseFormula(form);
} else {
return form;
}
}
Formula toLambdaVar(String var) {
return toVariable(var);
}
boolean isVar(String var) {
return var != null && var.startsWith("$");
}
Formula toVariable(String var) {
var = removeGeoType(var);
if (!varMap.containsKey(var)) {
throw new RuntimeException("Unknown variable: " + var);
}
return varMap.get(var);
}
// FIXME Not general to converters
String removeGeoType(String pred) {
return pred.split(":", 2)[0];
}
String getGeoType(String pred) {
String[] parts = pred.split(":", 2);
if (parts.length > 0)
return parts[1];
return "";
}
Formula toJoin(String func, Formula arg) {
return new JoinFormula(toPredicate(func), arg);
}
Formula toLambda(String var, Formula body) {
return new LambdaFormula(toLambdaVar(var).toString(), body);
}
Formula toMark(String var, Formula body) {
return new MarkFormula(toPredicate(var).toString(), body);
}
Formula toAndFormula(List<LispTree> clauses, boolean[] hit, String headVar, List<String> existsVars) {
// (and (river:t $1) (loc:t $1 $0))
Formula formula = null;
for (int i = 0; i < clauses.size(); i++) {
if (hit[i]) {
LogInfo.log("hit " + i);
continue;
}
// if (!hasHeadVar(clauses.get(i), headVar)) continue;
hit[i] = true;
LispTree tree = clauses.get(i);
Formula newFormula = toFormula(tree, headVar, existsVars);
if (formula == null) formula = newFormula;
else {
formula = new MergeFormula(MergeFormula.Mode.and, formula, newFormula);
}
}
return formula;
}
Formula toOrFormula(List<LispTree> clauses, boolean[] hit, String headVar, List<String> existsVars) {
// (or (town:t $1) (city:t $1))
LogInfo.log("# or clauses: " + clauses.size());
Formula formula = null;
for (int i = 0; i < clauses.size(); i++) {
if (hit[i]) {
LogInfo.log("hit " + i);
continue;
}
// if (!hasHeadVar(clauses.get(i), headVar)) continue;
hit[i] = true;
LispTree tree = clauses.get(i);
Formula newFormula = toFormula(tree, headVar, existsVars);
if (formula == null) formula = newFormula;
else {
formula = new MergeFormula(MergeFormula.Mode.or, formula, newFormula);
}
}
return formula;
}
boolean validVar(String var, String headVar, List<String> existsVars) {
if (var == null)
return false;
if (!var.startsWith("$"))
return true;
boolean valid = false;
if (var.equals(headVar))
valid = true;
if (existsVars != null && existsVars.contains(var))
valid = true;
return valid;
}
Formula toPrimitive(String s) {
String[] parts = s.split(":", 2);
if (parts.length < 2)
return null;
String type = parts[1];
String primitive = primitiveMap.get(type);
if (primitive == null)
return null;
// if (primitive.equals("string"))
// parts[0] = capitalizeWords(parts[0]);
// return Formula.fromString(String.format("(%s %s)", primitive, parts[0]));
if (primitive.equals("string"))
return Formula.fromString(String.format("(!fb:type.object.name fb:en.%s)", parts[0]));
return Formula.fromString(String.format("(%s %s)", primitive, parts[0]));
}
Formula handleExists(LispTree tree, String headVar, List<String> existsVars) {
// (exists $1 (and (river:t $1) (loc:t $1 $0)))
String eVar = tree.child(1).value;
existsVars.add(eVar);
// FIXME Currently assuming format where exists contains and statement with
// n clauses where first n - 1 clauses describe exists var and last clause
// relates exists var to the head var
List<LispTree> andTreeList = tree.child(2).children;
// FIXME Don't assume and tree
LispTree newAndTree = LispTree.proto.newList();
int predTreeInd = -1;
int numPredTrees = 0;
for (int k = 0; k < andTreeList.size(); k++) {
String childStr = andTreeList.get(k).toString();
if (childStr.contains(headVar)) {
predTreeInd = k;
numPredTrees++;
}
}
String func = tree.child(2).child(0).value;
if (numPredTrees == 0 || func.equals("exists")) {
return toLambda(eVar, toFormula(tree.child(2), eVar, existsVars));
}
newAndTree.children = new ArrayList<LispTree>(andTreeList.subList(0, andTreeList.size()));
LispTree predTree = newAndTree.children.remove(predTreeInd);
if (newAndTree.children.size() == 2)
newAndTree = newAndTree.child(1);
LispTree newPredTree = LispTree.proto.parseFromString(predTree.toString().replace(eVar, newAndTree.toString()));
Formula form = toFormula(newPredTree, eVar, existsVars);
existsVars.remove(eVar);
return form;
}
// hit: ignore these clauses in (and ...) constructions
public Formula toFormula(LispTree tree, String headVar, List<String> existsVars) {
if (tree.isLeaf()) {
Formula s = toPrimitive(tree.toString());
if (s != null)
return s;
return toPredicate(tree.toString());
}
String func = tree.child(0).value;
if (func.equals("lambda")) {
// (lambda $0 e (and (river:t $0) (loc:t $0 arkansas:s)))
return toFormula(tree.child(3), tree.child(1).value, existsVars);
} else if (func.equals("count")) {
// (count $0 (and (river:t $0) (loc:t $0 washington:s)))
String countVar = tree.child(1).value;
if (headVar == null)
headVar = countVar;
if (!countVar.equals(headVar) && !existsVars.contains(countVar)) {
return toLambda(countVar, new AggregateFormula(AggregateFormula.Mode.count, toFormula(tree.child(2), countVar, existsVars)));
} else
return new AggregateFormula(AggregateFormula.Mode.count, toFormula(tree.child(2), countVar, existsVars));
} else if (func.equals("sum")) {
// (sum $0 (and (state:t $0) (next_to:t $0 texas:s)) (population:i $0))
String numFunc = tree.child(3).child(0).value;
return new AggregateFormula(AggregateFormula.Mode.sum,
toJoin(numFunc, toFormula(tree.child(2), tree.child(1).value, existsVars)));
} else if (func.equals("argmax") || func.equals("argmin")) {
// (argmax $0 (state:t $0) (density:i $0))
// (argmin $1 (river:t $1) (len:i $1))
SuperlativeFormula.Mode mode = SuperlativeFormula.Mode.argmax;
if (func.equals("argmin"))
mode = SuperlativeFormula.Mode.argmin;
String superVar = tree.child(1).value;
Formula headFormula = toFormula(tree.child(2), superVar, existsVars);
Formula degreeFormula = toFormula(tree.child(3), superVar, existsVars);
NumberValue one = new NumberValue(1.0);
Formula rankFormula = new ValueFormula(one);
Formula countFormula = new ValueFormula(one);
// FIXME Hack to handle "most"/"least" expressions
if (tree.child(3).child(0).value.equals("count"))
degreeFormula = new ReverseFormula(degreeFormula);
// FIXME Currently assumes 1 1
return new SuperlativeFormula(mode, rankFormula, countFormula, headFormula, degreeFormula);
} else if (func.equals("exists")) {
if (headVar == null)
headVar = tree.child(1).value;
return handleExists(tree, headVar, existsVars);
} else if (func.equals("not")) {
return new NotFormula(toFormula(tree.child(1), headVar, existsVars));
} else if (func.equals("and")) {
return toAndFormula(tree.children.subList(1, tree.children.size()), new boolean[tree.children.size() - 1], headVar, existsVars);
} else if (func.equals("or")) {
return toOrFormula(tree.children.subList(1, tree.children.size()), new boolean[tree.children.size() - 1], headVar, existsVars);
} else { // Predicate
// Find head var
if (tree.children.size() == 2) { // Unary
if (isVar(tree.child(1).value)) {
// FIXME HACK
if (!getGeoType(func).equals("t"))
return toPredicate("!" + func);
return toPredicate(func);
} else
return toJoin(func, toFormula(tree.child(1), headVar, existsVars));
} else if (tree.children.size() == 3) { // Binary
// FIXME Move elsewhere, there's both a binary and unary "capital:t"
if (func.equals("capital:t"))
func = "is_capital:t";
Formula form1 = toFormula(tree.child(1), headVar, existsVars);
Formula form2 = toFormula(tree.child(2), headVar, existsVars);
String first = tree.child(1).value;
String second = tree.child(2).value;
// FIXME So ugly
boolean secondIsHead = isVar(second) && !existsVars.contains(second);
boolean firstIsHead = isVar(first) && (!existsVars.contains(first) || !secondIsHead);
if (firstIsHead)
return toJoin(func, form2);
else // secondIsHead
return toJoin("!" + func, form1);
// else
// return toJoin(func, form1);
} else {
throw new RuntimeException("Bad arity: " + tree);
}
}
}
void countTokens(LispTree tree, Map<String, Integer> counts) {
if (tree.isLeaf())
MapUtils.incr(counts, tree.value, 1);
else {
for (LispTree child : tree.children)
countTokens(child, counts);
}
}
String preprocessPredicates(String lispLine, String utterance) {
// Replacements
boolean replaced = false;
if (replaceMap.containsKey(lispLine)) {
lispLine = replaceMap.get(lispLine);
replaced = true;
}
// FIXME Assumes that only one "major" in the line
// FIXME Specific to geoquery
if (utterance.contains("major river"))
lispLine = lispLine.replace("major", "major_river");
else if (utterance.contains("major lake"))
lispLine = lispLine.replace("major", "major_lake");
else
lispLine = lispLine.replace("major", "major_city");
if (utterance.contains("river"))
if (!replaced)
lispLine = lispLine.replace("loc:t", "river_loc:t");
lispLine = lispLine.replace("new_york:s", "new_york_state:s");
return lispLine;
}
void convertExamples() {
String line;
int runInd = opts.runInd;
try {
Example.Builder ex = null;
BufferedReader in = IOUtils.openIn(opts.inPath);
boolean gotUtterance = false;
int newId = 0;
String utterance = "";
while ((line = in.readLine()) != null) {
if (line.equals("") || line.startsWith("//")) continue;
if (ex == null) {
newId++;
ex = new Example.Builder();
ex.setId("" + newId);
gotUtterance = false;
}
if (!gotUtterance) {
ex.setUtterance(line);
utterance = line;
gotUtterance = true;
} else {
if (runInd > 0 && newId != runInd) {
ex = null;
continue;
}
LispTree tree = LispTree.proto.parseFromString(preprocessPredicates(line, utterance));
LogInfo.logs(color.colorize("IN [%d]: %s", "blue"), newId, utterance);
LogInfo.logs(color.colorize("IN [%d]: %s", "purple"), newId, tree);
ArrayList<String> existsVars = new ArrayList<String>();
if (manualConversionsMap.containsKey(line)) {
LogInfo.logs("MANUAL CONVERSION=%s", line);
ex.setTargetFormula(Formula.fromString(manualConversionsMap.get(line)));
} else {
LogInfo.log("AUTOMATIC CONVERSION");
ex.setTargetFormula(toFormula(tree, null, existsVars));
}
LogInfo.logs(color.colorize("OUT [%d]: %s", "yellow"), newId, ex.createExample().targetFormula.toLispTree());
examples.add(ex.createExample());
ex = null;
}
}
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void executeExamples() {
int runInd = opts.runInd;
// FIXME Should be passed in
SparqlExecutor.opts.endpointUrl = "http://localhost:3094/sparql";
SparqlExecutor.opts.cachePath = "SparqlExecutor.cache";
SparqlExecutor.opts.lambdaAllowDiagonals = false; //jonathan
SparqlExecutor executor = new SparqlExecutor();
int exInd = 1;
if (runInd > 0)
exInd = runInd;
for (Example ex : examples) {
// Useful for just testing specific portion of examples
if (runInd > 0 && exInd != runInd) {
break;
}
LogInfo.logs(color.colorize("[%d] %s", "blue"), exInd, ex.utterance);
LogInfo.logs(color.colorize("[%d] %s", "yellow"), exInd, ex.targetFormula.toString());
try {
Executor.Response response = executor.execute(ex.targetFormula, null);
LogInfo.logs("\t\t [%d] %s", exInd, response.value.toString());
validExampleIds.add(exInd);
validExampleLengths.add(ex.utterance.length());
} catch (RuntimeException e) {
LogInfo.error(e);
failedExampleIds.add(exInd);
}
exInd++;
}
}
void writeExamples() {
// Sort validExampleIds by the length of the utterance of the example
// Collections.sort(validExampleIds, new Comparator<Integer>() {
// public int compare(Integer left, Integer right) {
// return Integer.compare(validExampleLengths.get(validExampleIds.indexOf(left)),
// validExampleLengths.get(validExampleIds.indexOf(right)));
// }
// });
PrintWriter out = IOUtils.openOutHard(opts.outPath);
out.println("["); // Print out as a list
String indent = " ";
for (int k = 0; k < validExampleIds.size(); k++) {
int j = validExampleIds.get(k) - 1;
Example ex = examples.get(j);
if (k < validExampleIds.size() - 1)
out.println(indent + ex.toJson() + ",");
else
out.println(indent + ex.toJson());
}
out.println("]"); // Print out as a list
out.close();
}
void printSummary() {
LogInfo.logs("%d input examples", examples.size());
LogInfo.logs("%d successful executions", validExampleIds.size());
LogInfo.logs("Failed executions (%d):", failedExampleIds.size());
for (int k : failedExampleIds) {
Example ex = examples.get(k - 1);
LogInfo.log(ex.toJson());
}
}
public static void main(String[] args) throws InterruptedException {
Execution.run(args, new LambdaCalculusConverter(), "lcc", LambdaCalculusConverter.opts);
}
}