package edu.stanford.nlp.sempre.tables.alter;
import java.util.*;
import edu.stanford.nlp.sempre.*;
import fig.basic.*;
/**
* Choose a subset based on diversity score (entropy).
*/
public class EntropySubsetChooser implements SubsetChooser {
public static class Options {
@Option(gloss = "check correctness")
public boolean entropyCheckCorrectness = false;
}
public static Options opts = new Options();
private final int numAlteredTables, numRetainedTables;
private final boolean alsoTrySmallerSubsets;
public EntropySubsetChooser(int numAlteredTables, int numRetainedTables, boolean alsoTrySmallerSubsets) {
this.numAlteredTables = numAlteredTables;
this.numRetainedTables = numRetainedTables;
this.alsoTrySmallerSubsets = alsoTrySmallerSubsets;
}
@Override
public Subset chooseSubset(String id, DenotationData denotationData) {
return chooseSubset(id, denotationData, Collections.emptyList());
}
@Override
public Subset chooseSubset(String id, DenotationData denotationData, Collection<Integer> forbiddenTables) {
if (this.numRetainedTables == 0) return null;
List<Integer> bestGraphIndices = null;
double bestScore = 0;
int n = denotationData.getRepresentativeIndices().size();
EquivClassComputer computer1 = opts.entropyCheckCorrectness ? null : new EquivClassComputerNaive(denotationData);
EquivClassComputer computer2 = opts.entropyCheckCorrectness ? null : new EquivClassComputerGroup(denotationData);
EquivClassComputer computer3 = new EquivClassComputerFast(denotationData);
Iterator<List<Integer>> itr;
if (alsoTrySmallerSubsets)
itr = new Subset.SubsetSizeAtMostKIterator(numAlteredTables, numRetainedTables);
else
itr = new Subset.SubsetSizeKIterator(numAlteredTables, numRetainedTables);
while (itr.hasNext()) {
List<Integer> graphIndices = itr.next();
if (!Subset.areDisjoint(graphIndices, forbiddenTables)) continue;
// If N is the number of representatives,
// H(C) = sum_i c_i/N log(N/c_i) = log(N) - (1/N) sum_i c_i log(c_i)
// normalized = 1 - [sum_i c_i log(c_i)] / [N log(N)]
Collection<Integer> groupSizes = computer3.getGroupSizes(graphIndices);
double accum = 0;
for (int c : groupSizes)
accum += c * Math.log(c);
double entropy = 1 - accum / (n * Math.log(n));
// Update
if (entropy > bestScore) {
bestGraphIndices = graphIndices;
bestScore = entropy;
if (BatchTableAlterer.opts.verbose >= 2) {
LogInfo.logs("entropy = %8.3f from tables %s", bestScore, bestGraphIndices);
}
}
// Check
if (opts.entropyCheckCorrectness) {
List<Integer> naive = new ArrayList<>(computer1.getGroupSizes(graphIndices));
Collections.sort(naive);
List<Integer> group = new ArrayList<>(computer2.getGroupSizes(graphIndices));
Collections.sort(group);
List<Integer> fast = new ArrayList<>(computer3.getGroupSizes(graphIndices));
Collections.sort(fast);
if (!naive.equals(group)) {
LogInfo.logs("Incorrect (group) %s", graphIndices);
LogInfo.logs("%s", naive);
LogInfo.logs("%s", group);
throw new RuntimeException();
}
if (!naive.equals(fast)) {
LogInfo.logs("Incorrect (fast) %s", graphIndices);
LogInfo.logs("%s", naive);
LogInfo.logs("%s", fast);
throw new RuntimeException();
}
}
}
if (bestGraphIndices != null) {
bestGraphIndices.add(0, 0);
return new Subset(id, bestGraphIndices, bestScore);
} else {
return new Subset(id, numRetainedTables, 0.0);
}
}
/**
* Given a subset s = {s1,...,sl} of graph indices {1,...,k}:
* - Group i's by the values of (denotations[i][s1], ..., denotations[i][sl])
* - Return the list of group sizes
*
* The computation is done in amortized n * (k choose l)
* where n is the number of possible i's (number of representative formulas).
*/
public interface EquivClassComputer {
public Collection<Integer> getGroupSizes(List<Integer> graphIndices);
}
public static class EquivClassComputerNaive implements EquivClassComputer {
private final DenotationData denotationData;
public EquivClassComputerNaive(DenotationData denotationData) {
this.denotationData = denotationData;
}
public Collection<Integer> getGroupSizes(List<Integer> graphIndices) {
Map<List<Value>, Integer> counts = new HashMap<>();
for (int i : denotationData.getRepresentativeIndices()) {
List<Value> denotationsForDeriv = new ArrayList<>();
for (int j : graphIndices)
denotationsForDeriv.add(denotationData.getDenotation(i, j));
MapUtils.incr(counts, denotationsForDeriv);
}
return counts.values();
}
}
public static class EquivClassComputerGroup implements EquivClassComputer {
private final int n, k;
private final int[][] uniqueIds;
private List<Integer> previousGraphIndices = new ArrayList<>();
private final List<List<List<Integer>>> groupStack = new ArrayList<>();
private final List<List<Integer>> initialGroup = new ArrayList<>();
public EquivClassComputerGroup(DenotationData denotationData) {
n = denotationData.getRepresentativeIndices().size();
k = denotationData.numAlteredTables;
uniqueIds = new int[n][k + 1];
for (int j = 1; j <= k; j++) {
Map<Value, Integer> uniqueDenotationsForTable = new HashMap<>();
for (int i = 0; i < n; i++) {
int derivIndex = denotationData.getRepresentativeIndices().get(i);
Value denotation = denotationData.getDenotation(derivIndex, j);
Integer uniqueId = uniqueDenotationsForTable.get(denotation);
if (uniqueId == null) {
uniqueDenotationsForTable.put(denotation, uniqueIds[i][j] = uniqueDenotationsForTable.size());
} else {
uniqueIds[i][j] = uniqueId;
}
}
}
List<Integer> group = new ArrayList<>();
for (int i = 0; i < n; i++) group.add(i);
initialGroup.add(group);
}
public Collection<Integer> getGroupSizes(List<Integer> graphIndices) {
// Reduce to common prefix
int sizeAgreed = 0;
while (sizeAgreed < previousGraphIndices.size() && sizeAgreed < graphIndices.size()
&& previousGraphIndices.get(sizeAgreed) == graphIndices.get(sizeAgreed))
sizeAgreed++;
while (groupStack.size() > sizeAgreed)
groupStack.remove(groupStack.size() - 1);
previousGraphIndices = new ArrayList<Integer>(graphIndices);
// Group the rest
for (int j = sizeAgreed; j < graphIndices.size(); j++) {
int sj = graphIndices.get(j);
List<List<Integer>> previousGroups = groupStack.isEmpty() ? initialGroup : groupStack.get(j - 1);
List<List<Integer>> groups = new ArrayList<>();
for (List<Integer> group : previousGroups) {
if (group.size() == 1) {
groups.add(group);
continue;
}
Map<Integer, List<Integer>> idToGroups = new HashMap<>();
for (int index : group)
MapUtils.addToList(idToGroups, uniqueIds[index][sj], index);
groups.addAll(idToGroups.values());
}
groupStack.add(groups);
}
List<Integer> groupSizes = new ArrayList<>();
for (List<Integer> group : groupStack.get(groupStack.size() - 1))
groupSizes.add(group.size());
return groupSizes;
}
}
public static class EquivClassComputerFast implements EquivClassComputer {
private final int n;
private final int[][] uniqueIds;
private List<Integer> previousGraphIndices = new ArrayList<>();
private final List<Integer> groups = new ArrayList<>();
private final List<int[]> breakpointStack = new ArrayList<>();
private final int[] initialBreakpoint;
public EquivClassComputerFast(DenotationData denotationData) {
n = denotationData.getRepresentativeIndices().size();
uniqueIds = denotationData.toArray(denotationData.getRepresentativeIndices());
initialBreakpoint = new int[] {0, n};
for (int i = 0; i < n; i++) groups.add(i);
}
public Collection<Integer> getGroupSizes(List<Integer> graphIndices) {
// Reduce to common prefix
int sizeAgreed = 0;
while (sizeAgreed < previousGraphIndices.size() && sizeAgreed < graphIndices.size()
&& previousGraphIndices.get(sizeAgreed) == graphIndices.get(sizeAgreed))
sizeAgreed++;
while (breakpointStack.size() > sizeAgreed)
breakpointStack.remove(breakpointStack.size() - 1);
previousGraphIndices = new ArrayList<Integer>(graphIndices);
// Group the rest
for (int j = sizeAgreed; j < graphIndices.size(); j++) {
int sj = graphIndices.get(j), top = 0;
int[] previousBreakpoints = breakpointStack.isEmpty() ? initialBreakpoint : breakpointStack.get(j - 1);
int[] newBreakpoints = new int[n + 1];
newBreakpoints[top++] = 0;
for (int u = 0; previousBreakpoints[u] < n; u++) {
int s = previousBreakpoints[u], t = previousBreakpoints[u + 1];
if (t > s + 1) {
Collections.sort(groups.subList(s, t), (x, y) -> uniqueIds[x][sj] - uniqueIds[y][sj]);
for (int i = s + 1; i < t; i++) {
if (uniqueIds[groups.get(i - 1)][sj] != uniqueIds[groups.get(i)][sj])
newBreakpoints[top++] = i;
}
}
newBreakpoints[top++] = t;
}
breakpointStack.add(newBreakpoints);
}
List<Integer> groupSizes = new ArrayList<>();
int[] breakpoints = breakpointStack.get(breakpointStack.size() - 1);
for (int u = 0; breakpoints[u] < n; u++)
groupSizes.add(breakpoints[u + 1] - breakpoints[u]);
return groupSizes;
}
}
}