package edu.stanford.nlp.sempre;
import com.google.common.base.Joiner;
import fig.basic.LogInfo;
import fig.basic.MapUtils;
import fig.basic.Pair;
import fig.basic.StopWatch;
import java.lang.reflect.Array;
import java.util.*;
/**
* Parser that only has information on what categories can parse what spans
* Does not hold backpointers for getting full parse, only reachability information
* Important: assumes that the grammar is binary
* Independent from the Parser code and therefore there is duplicate code (traverse(), keepTopDownReachable())
* @author jonathanberant
*/
public class CoarseParser {
public final Grammar grammar;
private Map<Pair<String, String>, Set<String>> rhsToLhsMap;
ArrayList<Rule> catUnaryRules; // Unary rules with category on RHS
Map<String, List<Rule>> terminalsToRulesList = new HashMap<>();
public CoarseParser(Grammar grammar) {
this.grammar = grammar;
catUnaryRules = new ArrayList<>();
rhsToLhsMap = new HashMap<>();
Map<String, List<Rule>> graph = new HashMap<>(); // Node from LHS to list of rules
for (Rule rule : grammar.rules) {
if (rule.rhs.size() > 2)
throw new RuntimeException("We assume that the grammar is binarized, rule: " + rule);
if (rule.isCatUnary())
MapUtils.addToList(graph, rule.lhs, rule);
else if (rule.rhs.size() == 2) { // binary grammar
MapUtils.addToSet(rhsToLhsMap, Pair.newPair(rule.rhs.get(0), rule.rhs.get(1)), rule.lhs);
} else {
assert rule.isRhsTerminals();
MapUtils.addToList(terminalsToRulesList, Joiner.on(' ').join(rule.rhs), rule);
}
}
// Topologically sort catUnaryRules so that B->C occurs before A->B
Map<String, Boolean> done = new HashMap<>();
for (String node : graph.keySet())
traverse(catUnaryRules, node, graph, done);
LogInfo.logs("Coarse parser: %d catUnaryRules (sorted), %d nonCatUnaryRules", catUnaryRules.size(), grammar.rules.size() - catUnaryRules.size());
}
/** Helper function for transitive closure of unary rules. */
private void traverse(List<Rule> catUnaryRules,
String node,
Map<String, List<Rule>> graph,
Map<String, Boolean> done) {
Boolean d = done.get(node);
if (Boolean.TRUE.equals(d)) return;
if (Boolean.FALSE.equals(d))
throw new RuntimeException("Found cycle of unaries involving " + node);
done.put(node, false);
for (Rule rule : MapUtils.getList(graph, node)) {
traverse(catUnaryRules, rule.rhs.get(0), graph, done);
catUnaryRules.add(rule);
}
done.put(node, true);
}
public CoarseParserState getCoarsePrunedChart(Example ex) {
CoarseParserState res = new CoarseParserState(ex, this);
res.infer();
return res;
}
class CoarseParserState {
private Map<String, List<CategorySpan>>[][] chart;
public final Example example;
public final CoarseParser parser;
private int numTokens;
private long time;
private String[][] phrases;
@SuppressWarnings({ "unchecked" })
public CoarseParserState(Example example, CoarseParser parser) {
this.example = example;
this.parser = parser;
numTokens = example.numTokens();
// Initialize the chart.
this.chart = (HashMap<String, List<CategorySpan>>[][])
Array.newInstance(
HashMap.class,
numTokens, numTokens + 1);
phrases = new String[numTokens][numTokens + 1];
for (int start = 0; start < numTokens; start++) {
StringBuilder sb = new StringBuilder();
for (int end = start + 1; end <= numTokens; end++) {
if (end - start > 1)
sb.append(' ');
sb.append(example.languageInfo.tokens.get(end - 1));
phrases[start][end] = sb.toString();
chart[start][end] = new HashMap<>();
}
}
}
public long getCoarseParseTime() { return time; }
public void infer() {
StopWatch watch = new StopWatch();
watch.start();
// parse with rules with tokens or RHS
parseTokensAndPhrases();
// complete bottom up parsing
for (int len = 1; len <= numTokens; len++)
for (int i = 0; i + len <= numTokens; i++)
build(i, i + len);
// prune away things that are not reachable from the top
keepTopDownReachable();
watch.stop();
time = watch.getCurrTimeLong();
}
public boolean coarseAllows(String cat, int start, int end) {
return chart[start][end].containsKey(cat);
}
private void build(int start, int end) {
handleBinaryRules(start, end);
handleUnaryRules(start, end);
}
private void parseTokensAndPhrases() {
for (int i = 0; i < numTokens; ++i) {
addToChart(Rule.tokenCat, i, i + 1);
addToChart(Rule.lemmaTokenCat, i, i + 1);
}
for (int i = 0; i < numTokens; i++) {
for (int j = i + 1; j <= numTokens; j++) {
addToChart(Rule.phraseCat, i, j);
addToChart(Rule.lemmaPhraseCat, i, j);
}
}
}
private void addToChart(String cat, int start, int end) {
if (Parser.opts.verbose >= 5)
LogInfo.logs("Adding to chart %s(%s,%s)", cat, start, end);
MapUtils.putIfAbsent(chart[start][end], cat, new ArrayList<CategorySpan>());
}
private void addToChart(String parentCat, String childCat, int start, int end) {
if (Parser.opts.verbose >= 5)
LogInfo.logs("Adding to chart %s(%s,%s)-->%s(%s,%s)", parentCat, start, end, childCat, start, end);
MapUtils.addToList(chart[start][end], parentCat, new CategorySpan(childCat, start, end)); }
private void addToChart(String parentCat, String leftCat, String rightCat, int start, int i, int end) {
if (Parser.opts.verbose >= 5)
LogInfo.logs("Adding to chart %s(%s,%s)-->%s(%s,%s) %s(%s,%s)", parentCat, start, end, leftCat, start, i, rightCat, i, end);
MapUtils.addToList(chart[start][end], parentCat, new CategorySpan(leftCat, start, i));
MapUtils.addToList(chart[start][end], parentCat, new CategorySpan(rightCat, i, end));
}
private void handleBinaryRules(int start, int end) {
for (int i = start + 1; i < end; ++i) {
List<String> left = new ArrayList<>(chart[start][i].keySet());
List<String> right = new ArrayList<>(chart[i][end].keySet());
if (i - start == 1) left.add(phrases[start][i]); // handle single terminal
if (end - i == 1) right.add(phrases[i][end]); // handle single terminal
for (String l : left) {
for (String r : right) {
Set<String> parentCats = rhsToLhsMap.get(Pair.newPair(l, r));
if (parentCats != null) {
for (String parentCat : parentCats) {
addToChart(parentCat, l, r, start, i, end);
}
}
}
}
}
}
private void handleUnaryRules(int start, int end) {
// terminals on RHS
for (Rule rule : MapUtils.get(terminalsToRulesList, phrases[start][end], Collections.<Rule>emptyList())) {
addToChart(rule.lhs, start, end);
}
// catUnaryRules
for (Rule rule : parser.catUnaryRules) {
String rhsCat = rule.rhs.get(0);
if (chart[start][end].containsKey(rhsCat)) {
addToChart(rule.lhs, rhsCat, start, end);
}
}
}
public void keepTopDownReachable() {
if (numTokens == 0) return;
Set<CategorySpan> reachable = new HashSet<CategorySpan>();
collectReachable(reachable, new CategorySpan(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<String>();
for (String cat : chart[start][end].keySet()) {
if (!reachable.contains(new CategorySpan(cat, start, end))) {
toRemoveCats.add(cat);
}
}
Collections.sort(toRemoveCats);
for (String cat : toRemoveCats) {
if (Parser.opts.verbose >= 5)
LogInfo.logs("Pruning chart %s(%s,%s)", cat, start, end);
chart[start][end].remove(cat);
}
}
}
}
private void collectReachable(Set<CategorySpan> reachable, CategorySpan catSpan) {
if (reachable.contains(catSpan))
return;
if (!chart[catSpan.start][catSpan.end].containsKey(catSpan.cat)) {
// This should only happen for the root when there are no parses.
return;
}
reachable.add(catSpan);
for (CategorySpan childCatSpan : chart[catSpan.start][catSpan.end].get(catSpan.cat)) {
collectReachable(reachable, childCatSpan);
}
}
}
class CategorySpan {
public final String cat;
public final int start;
public final int end;
public CategorySpan(String cat, int start, int end) {
this.cat = cat;
this.start = start;
this.end = end;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cat == null) ? 0 : cat.hashCode());
result = prime * result + end;
result = prime * result + start;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CategorySpan other = (CategorySpan) obj;
if (cat == null) {
if (other.cat != null)
return false;
} else if (!cat.equals(other.cat))
return false;
if (end != other.end)
return false;
if (start != other.start)
return false;
return true;
}
}
}