package edu.stanford.nlp.sempre.interactive;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.beust.jcommander.internal.Lists;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import edu.stanford.nlp.sempre.ChartParserState;
import edu.stanford.nlp.sempre.Derivation;
import edu.stanford.nlp.sempre.DerivationStream;
import edu.stanford.nlp.sempre.Example;
import edu.stanford.nlp.sempre.Formula;
import edu.stanford.nlp.sempre.Json;
import edu.stanford.nlp.sempre.Params;
import edu.stanford.nlp.sempre.Parser;
import edu.stanford.nlp.sempre.ParserState;
import edu.stanford.nlp.sempre.Rule;
import edu.stanford.nlp.sempre.SemanticFn;
import edu.stanford.nlp.sempre.Trie;
import fig.basic.Evaluation;
import fig.basic.IOUtils;
import fig.basic.IntRef;
import fig.basic.LogInfo;
import fig.basic.Option;
import fig.basic.SetUtils;
import fig.basic.StopWatch;
import fig.basic.StopWatchSet;
import fig.exec.Execution;
/**
* A modified version of the BeamParser, with consideration for use in the interactive setting
*
* @author Percy Liang, sidaw
*/
public class InteractiveBeamParser extends Parser {
public static class Options {
@Option
public int maxNewTreesPerSpan = Integer.MAX_VALUE;
@Option
public FloatStrategy floatStrategy = FloatStrategy.Never;
@Option(gloss = "track these categories")
public List<String> trackedCats;
}
public enum FloatStrategy {
Always, Never, NoParse
};
public static Options opts = new Options();
Trie trie; // For non-cat-unary rules
// so that duplicated rules are never added
Set<Rule> allRules;
List<Rule> interactiveCatUnaryRules;
public InteractiveBeamParser(Spec spec) {
super(spec);
if (opts.trackedCats != null) {
opts.trackedCats = opts.trackedCats.stream().map(s -> "$" + s).collect(Collectors.toList());
LogInfo.logs("Mapped trackedCats to: %s", opts.trackedCats);
}
interactiveCatUnaryRules = new LinkedList<>(super.catUnaryRules);
allRules = new LinkedHashSet<>(super.catUnaryRules);
// Index the non-cat-unary rules
trie = new Trie();
for (Rule rule : grammar.getRules()) {
addRule(rule);
}
if (Parser.opts.visualizeChartFilling)
this.chartFillOut = IOUtils.openOutAppendEasy(Execution.getFile("chartfill"));
}
@Override
public synchronized void addRule(Rule rule) {
if (allRules.contains(rule))
return;
allRules.add(rule);
if (!rule.isCatUnary()) {
trie.add(rule);
} else {
interactiveCatUnaryRules.add(rule);
}
}
@Override
public List<Rule> getCatUnaryRules() {
return interactiveCatUnaryRules;
}
// for grammar induction, just need the formula, do not execute
public InteractiveBeamParserState parseWithoutExecuting(Params params, Example ex, boolean computeExpectedCounts) {
// Parse
StopWatch watch = new StopWatch();
watch.start();
InteractiveBeamParserState state = new InteractiveBeamParserState(this, params, ex);
state.infer();
watch.stop();
state.parseTime = watch.getCurrTimeLong();
ex.predDerivations = state.predDerivations;
Derivation.sortByScore(ex.predDerivations);
// Clean up temporary state used during parsing
return state;
}
@Override
public ParserState newParserState(Params params, Example ex, boolean computeExpectedCounts) {
InteractiveBeamParserState coarseState = null;
if (Parser.opts.coarsePrune) {
LogInfo.begin_track("Parser.coarsePrune");
coarseState = new InteractiveBeamParserState(this, params, ex, computeExpectedCounts,
InteractiveBeamParserState.Mode.bool, null);
coarseState.infer();
coarseState.keepTopDownReachable();
LogInfo.end_track();
}
return new InteractiveBeamParserState(this, params, ex, computeExpectedCounts, InteractiveBeamParserState.Mode.full,
coarseState);
}
}
/**
* Stores BeamFloatingParser information about parsing a particular example. The
* actual parsing code lives here.
*
* @author Percy Liang
* @author Roy Frostig
* @author sidaw
*/
class InteractiveBeamParserState extends ChartParserState {
public final Mode mode;
// Modes:
// 1) Bool: just check if cells (cat, start, end) are reachable (to prune
// chart)
// 2) Full: compute everything
public enum Mode {
bool, full
}
private final InteractiveBeamParser parser;
private final InteractiveBeamParserState coarseState; // Used to prune
private final boolean execute;
public List<Derivation> chartList;
public InteractiveBeamParserState(InteractiveBeamParser parser, Params params, Example ex) {
super(parser, params, ex, false);
this.parser = parser;
this.mode = Mode.full;
this.coarseState = null;
this.execute = false;
}
public InteractiveBeamParserState(InteractiveBeamParser parser, Params params, Example ex, boolean computeExpectedCounts,
Mode mode, InteractiveBeamParserState coarseState) {
super(parser, params, ex, computeExpectedCounts);
this.parser = parser;
this.mode = mode;
this.coarseState = coarseState;
this.execute = true;
}
@Override
public void infer() {
if (numTokens == 0)
return;
if (parser.verbose(2))
LogInfo.begin_track("ParserState.infer");
// Base case
for (Derivation deriv : gatherTokenAndPhraseDerivations()) {
featurizeAndScoreDerivation(deriv);
addToChart(deriv);
}
// Recursive case
for (int len = 1; len <= numTokens; len++)
for (int i = 0; i + len <= numTokens; i++)
build(i, i + len);
if (parser.verbose(2))
LogInfo.end_track();
// Visualize
if (parser.chartFillOut != null && Parser.opts.visualizeChartFilling && this.mode != Mode.bool) {
parser.chartFillOut.println(
Json.writeValueAsStringHard(new ChartFillingData(ex.id, chartFillingList, ex.utterance, ex.numTokens())));
parser.chartFillOut.flush();
}
setPredDerivations();
for (Derivation deriv : predDerivations) {
deriv.getAnchoredTokens();
}
this.chartList = this.collectChart();
boolean parseFloat = false;
if (InteractiveBeamParser.opts.floatStrategy == InteractiveBeamParser.FloatStrategy.Always)
parseFloat = true;
else if (InteractiveBeamParser.opts.floatStrategy == InteractiveBeamParser.FloatStrategy.NoParse)
parseFloat = predDerivations.size() == 0;
else
parseFloat = false;
if (mode == Mode.full) {
// Compute gradient with respect to the predicted derivations
if (this.execute)
ensureExecuted();
if (computeExpectedCounts) {
expectedCounts = new HashMap<>();
ParserState.computeExpectedCounts(predDerivations, expectedCounts);
}
}
/* If Beam Parser failed to find derivations, try a floating parser */
if (parseFloat) {
/*
* For every base span of the chart, add the derivations from nothing
* rules
*/
List<Rule> nothingRules = new ArrayList<Rule>();
for (Rule rule : parser.grammar.getRules())
if (rule.isFloating() && rule.rhs.size() == 1 && rule.isRhsTerminals())
nothingRules.add(rule);
for (int i = 0; i < numTokens; i++)
for (Rule rule : nothingRules)
applyRule(i, i + 1, rule, chart[i][i + 1].get("$TOKEN"));
/* Traverse the chart bottom up */
for (int len = 1; len <= numTokens; len++) {
for (int i = 0; i + len <= numTokens; i++) {
buildFloating(i, i + len);
}
}
/* Add unique derivations to predDerivations */
List<Derivation> rootDerivs = chart[0][numTokens].get("$FROOT");
if (rootDerivs == null)
rootDerivs = new ArrayList<Derivation>(Derivation.emptyList);
List<Derivation> actionDerivs = new ArrayList<Derivation>(Derivation.emptyList);
if (actionDerivs != null) {
Set<Formula> formulas = new HashSet<Formula>();
for (Derivation d : rootDerivs) {
Formula f = d.getFormula();
if (!formulas.contains(f)) {
formulas.add(f);
predDerivations.add(d);
}
}
}
}
}
private List<Derivation> collectChart() {
List<Derivation> chartList = Lists.newArrayList();
for (int len = 1; len <= numTokens; ++len) {
for (int i = 0; i + len <= numTokens; ++i) {
for (String cat : chart[i][i + len].keySet()) {
if (Rule.specialCats.contains(cat))
continue;
chartList.addAll(chart[i][i + len].get(cat));
}
}
}
return chartList;
}
// Create all the derivations for the span [start, end).
protected void build(int start, int end) {
applyNonCatUnaryRules(start, end, start, parser.trie, new ArrayList<Derivation>(), new IntRef(0));
Set<String> cellsPruned = new HashSet<>();
applyCatUnaryRules(start, end, cellsPruned);
for (Map.Entry<String, List<Derivation>> entry : chart[start][end].entrySet())
pruneCell(cellsPruned, entry.getKey(), start, end, entry.getValue());
}
private static String cellString(String cat, int start, int end) {
return cat + ":" + start + ":" + end;
}
// Return number of new derivations added
private int applyRule(int start, int end, Rule rule, List<Derivation> children) {
if (Parser.opts.verbose >= 5)
LogInfo.logs("applyRule %s %s %s %s", start, end, rule, children);
try {
if (mode == Mode.full) {
StopWatchSet.begin(rule.getSemRepn());
DerivationStream results = rule.sem.call(ex,
new SemanticFn.CallInfo(rule.lhs, start, end, rule, ImmutableList.copyOf(children)));
StopWatchSet.end();
while (results.hasNext()) {
Derivation newDeriv = results.next();
featurizeAndScoreDerivation(newDeriv);
addToChart(newDeriv);
}
return results.estimatedSize();
} else if (mode == Mode.bool) {
Derivation deriv = new Derivation.Builder().cat(rule.lhs).start(start).end(end).rule(rule)
.children(ImmutableList.copyOf(children)).formula(Formula.nullFormula).createDerivation();
addToChart(deriv);
return 1;
} else {
throw new RuntimeException("Invalid mode");
}
} catch (Exception e) {
LogInfo.errors("Composition failed: rule = %s, children = %s", rule, children);
e.printStackTrace();
throw new RuntimeException(e);
}
}
// Don't prune the same cell more than once.
protected void pruneCell(Set<String> cellsPruned, String cat, int start, int end, List<Derivation> derivations) {
String cell = cellString(cat, start, end);
if (cellsPruned.contains(cell))
return;
cellsPruned.add(cell);
pruneCell(cell, derivations);
}
private boolean canBeRoot(int start, int end) {return start==0 && end==numTokens;};
// Apply all unary rules with RHS category.
// Before applying each unary rule (rule.lhs -> rhsCat), we can prune the cell
// of rhsCat
// because we assume acyclicity, so rhsCat's cell will never grow.
private void applyCatUnaryRules(int start, int end, Set<String> cellsPruned) {
for (Rule rule : parser.getCatUnaryRules()) {
if (!coarseAllows(rule.lhs, start, end))
continue;
if (rule.lhs.equals(Rule.rootCat) && !canBeRoot(start, end))
continue;
String rhsCat = rule.rhs.get(0);
List<Derivation> derivations = chart[start][end].get(rhsCat);
if (Parser.opts.verbose >= 5)
LogInfo.logs("applyCatUnaryRules %s %s %s %s", start, end, rule, chart[start][end]);
if (derivations == null)
continue;
// Prune before applying rules to eliminate cruft!
pruneCell(cellsPruned, rhsCat, start, end, derivations);
for (Derivation deriv : derivations) {
applyRule(start, end, rule, Collections.singletonList(deriv));
}
}
}
// Strategy: walk along the input on span (start:end) and traverse the trie
// to get the list of the rules that could apply by matching the RHS.
// start:end: span we're dealing with.
// i: current token position
// node: contains a link to the RHS that could apply.
// children: the derivations that't we're building up.
// numNew: Keep track of number of new derivations created
private void applyNonCatUnaryRules(int start, int end, int i, Trie node, ArrayList<Derivation> children,
IntRef numNew) {
if (node == null)
return;
if (!coarseAllows(node, start, end))
return;
if (Parser.opts.verbose >= 5) {
LogInfo.logs("applyNonCatUnaryRules(start=%d, end=%d, i=%d, children=[%s], %s rules)", start, end, i,
Joiner.on(", ").join(children), node.rules.size());
}
// Base case: our fencepost has walked to the end of the span, so
// apply the rule on all the children gathered during the walk.
if (i == end) {
Iterator<Rule> ruleIterator = node.rules.iterator();
while (ruleIterator.hasNext()) {
Rule rule = ruleIterator.next();
if (coarseAllows(rule.lhs, start, end)) {
numNew.value += applyRule(start, end, rule, children);
}
}
return;
}
// Advance terminal token
applyNonCatUnaryRules(start, end, i + 1, node.next(ex.token(i)), children, numNew);
// Advance non-terminal category
for (int j = i + 1; j <= end; j++) {
for (Map.Entry<String, List<Derivation>> entry : chart[i][j].entrySet()) {
Trie nextNode = node.next(entry.getKey());
for (Derivation arg : entry.getValue()) {
children.add(arg);
applyNonCatUnaryRules(start, end, j, nextNode, children, numNew);
children.remove(children.size() - 1);
if (mode != Mode.full)
break; // Only need one hypothesis
if (numNew.value >= InteractiveBeamParser.opts.maxNewTreesPerSpan)
return;
}
}
}
}
/* For each span, apply applicable floating rules */
protected void buildFloating(int start, int end) {
for (Rule rule : parser.grammar.getRules()) {
if (!rule.isFloating() || !coarseAllows(rule.lhs, start, end))
continue;
if (rule.rhs.size() == 1) {
/* Apply cat unary rules simply */
String rhsCat = rule.rhs.get(0);
List<Derivation> derivs = chart[start][end].get(rhsCat);
if (derivs == null)
continue;
for (Derivation deriv : derivs)
applyRule(start, end, rule, Collections.singletonList(deriv));
} else {
/* Apply non-cat unary rules by traversing through the subspans */
int derivsCreated = 0;
for (int i = start + 1; i < end; i++) {
derivsCreated += applyFloatingRule(rule, start, end, chart[start][i], chart[i][end]);
derivsCreated += applyFloatingRule(rule, start, end, chart[i][end], chart[start][i]);
}
/* If no derivs created, propagate up */
if (derivsCreated == 0) {
copyDerivs(chart[start][end - 1], chart[start][end]);
if (start != numTokens - 1)
copyDerivs(chart[start + 1][end], chart[start][end]);
}
}
}
// test prune
Set<String> cellsPruned = new HashSet<>();
for (Map.Entry<String, List<Derivation>> entry : chart[start][end].entrySet())
pruneCell(cellsPruned, entry.getKey(), start, end, entry.getValue());
}
protected int applyFloatingRule(Rule rule, int start, int end, Map<String, List<Derivation>> first,
Map<String, List<Derivation>> second) {
List<Derivation> derivs1 = first.get(rule.rhs.get(0));
List<Derivation> derivs2 = second.get(rule.rhs.get(1));
if (derivs1 == null || derivs2 == null)
return 0;
int derivsCreated = 0;
for (Derivation deriv1 : derivs1) {
for (Derivation deriv2 : derivs2) {
List<Derivation> children = new ArrayList<Derivation>();
children.add(deriv1);
children.add(deriv2);
derivsCreated += applyRule(start, end, rule, children);
}
}
return derivsCreated;
}
protected void copyDerivs(Map<String, List<Derivation>> source, Map<String, List<Derivation>> dest) {
if (source == null || dest == null)
return;
for (String cat : source.keySet()) {
List<Derivation> derivations = dest.get(cat);
if (derivations == null)
dest.put(cat, derivations = new ArrayList<>());
/* add only if the formula not already present to ensure no duplicates */
Set<Formula> formulas = new HashSet<Formula>();
for (Derivation deriv : derivations)
formulas.add(deriv.formula);
for (Derivation deriv : source.get(cat)) {
if (!formulas.contains(deriv.formula)) {
derivations.add(deriv);
formulas.add(deriv.formula);
}
}
}
}
protected void addDerivs(List<Derivation> source, List<Derivation> dest) {
if (dest == null || source == null)
return;
dest.addAll(source);
}
// -- Coarse state pruning --
// Remove any (cat, start, end) which isn't reachable from the
// (Rule.rootCat, 0, numTokens)
public void keepTopDownReachable() {
if (numTokens == 0)
return;
Set<String> reachable = new HashSet<>();
collectReachable(reachable, Rule.rootCat, 0, numTokens);
// Remove all derivations associated with (cat, start, end) that aren't
// reachable.
for (int start = 0; start < numTokens; start++) {
for (int end = start + 1; end <= numTokens; end++) {
List<String> toRemoveCats = new LinkedList<>();
for (String cat : chart[start][end].keySet()) {
String key = catStartEndKey(cat, start, end);
if (!reachable.contains(key)) {
toRemoveCats.add(cat);
}
}
Collections.sort(toRemoveCats);
for (String cat : toRemoveCats) {
if (parser.verbose(4)) {
LogInfo.logs("Pruning chart %s(%s,%s)", cat, start, end);
}
chart[start][end].remove(cat);
}
}
}
}
private void collectReachable(Set<String> reachable, String cat, int start, int end) {
String key = catStartEndKey(cat, start, end);
if (reachable.contains(key))
return;
if (!chart[start][end].containsKey(cat)) {
// This should only happen for the root when there are no parses.
return;
}
reachable.add(key);
for (Derivation deriv : chart[start][end].get(cat)) {
for (Derivation subderiv : deriv.children) {
collectReachable(reachable, subderiv.cat, subderiv.start, subderiv.end);
}
}
}
private String catStartEndKey(String cat, int start, int end) {
return cat + ":" + start + ":" + end;
}
// For pruning with the coarse state
protected boolean coarseAllows(Trie node, int start, int end) {
if (coarseState == null)
return true;
return SetUtils.intersects(node.cats, coarseState.chart[start][end].keySet());
}
protected boolean coarseAllows(String cat, int start, int end) {
if (coarseState == null)
return true;
return coarseState.chart[start][end].containsKey(cat);
}
}