package edu.stanford.nlp.sempre.freebase;
import com.google.common.base.Joiner;
import edu.stanford.nlp.stats.ClassicCounter;
import edu.stanford.nlp.stats.Counter;
import edu.stanford.nlp.sempre.*;
import fig.basic.LispTree;
import fig.basic.LogInfo;
import fig.basic.MapUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
/**
* Class for keeping info and manipulating FB formulas. For example, given a
* Freebase formula computes the reverse of the formula (flipping arguments) and
* the equivalent formula (using reverse property from Freebase) Reversing works
* now only for chains
*
* @author jonathanberant
*/
public final class FbFormulasInfo {
// Everyone should use the singleton.
private static FbFormulasInfo fbFormulaInfo;
public static FbFormulasInfo getSingleton() {
if (fbFormulaInfo == null) {
fbFormulaInfo = new FbFormulasInfo();
}
return fbFormulaInfo;
}
private FreebaseInfo freebaseInfo = null;
public Map<Formula, BinaryFormulaInfo> binaryFormulaInfoMap = new HashMap<>();
public Map<Formula, UnaryFormulaInfo> unaryFormulaInfoMap = new HashMap<>();
private Map<String, Set<BinaryFormulaInfo>> typeToNumericalPredicates = new HashMap<>();
private Map<String, List<Formula>> atomicExtype2ToBinaryMap = new HashMap<>(); // contains map to all atomic properties
private Map<String, List<Formula>> extype2ToNonCvtBinaryMap = new HashMap<>(); // contains map to all binaries for which extype 1 is not a CVT
private Map<Formula, Set<BinaryFormulaInfo>> cvtExpansionsMap = new HashMap<>();
private Map<String, Set<Formula>> cvtTypeToBinaries = new HashMap<>();
private Comparator<Formula> formulaComparator;
private FbFormulasInfo() {
try {
freebaseInfo = FreebaseInfo.getSingleton();
loadFormulaInfo();
computeNumericalPredicatesMap();
} catch (IOException | NumberFormatException e) {
throw new RuntimeException(e);
}
}
/**
* Map that given a type provides all Freebase predicates that have that type
* as expected type 2 and a number for expected type 1
*/
private void computeNumericalPredicatesMap() {
for (BinaryFormulaInfo info : binaryFormulaInfoMap.values()) {
if (info.expectedType1.equals("fb:type.int") || info.expectedType1.equals("fb:type.float")) {
MapUtils.addToSet(typeToNumericalPredicates, info.expectedType2, info);
}
}
}
public Set<BinaryFormulaInfo> getNumericalPredicates(String expectedType) {
return MapUtils.get(typeToNumericalPredicates, expectedType, new HashSet<BinaryFormulaInfo>());
}
private void computeReverseFormulaInfo() {
List<BinaryFormulaInfo> entriesToAdd = new LinkedList<>();
for (Formula formula : binaryFormulaInfoMap.keySet()) {
BinaryFormulaInfo info = binaryFormulaInfoMap.get(formula);
Formula reverseFormula = Formulas.reverseFormula(formula);
if (!binaryFormulaInfoMap.containsKey(reverseFormula)) {
entriesToAdd.add(
new BinaryFormulaInfo(
reverseFormula,
info.expectedType2, info.expectedType1, info.unitId, info.unitDesc, info.descriptions, info.popularity));
}
}
LogInfo.log("Adding reverse formulas: " + entriesToAdd.size());
for (BinaryFormulaInfo e : entriesToAdd) {
binaryFormulaInfoMap.put(e.formula, e);
}
}
public BinaryFormulaInfo getBinaryInfo(Formula formula) {
return binaryFormulaInfoMap.get(formula);
}
public UnaryFormulaInfo getUnaryInfo(Formula formula) {
return unaryFormulaInfoMap.get(formula);
}
private void loadFormulaInfo() throws IOException {
LogInfo.begin_track("Loading formula info...");
LogInfo.log("Adding schema properties");
binaryFormulaInfoMap = freebaseInfo.createBinaryFormulaInfoMap();
unaryFormulaInfoMap = freebaseInfo.createUnaryFormulaInfoMap();
LogInfo.log("Current number of binary formulas: " + binaryFormulaInfoMap.size());
LogInfo.log("Current number of unary formulas: " + unaryFormulaInfoMap.size());
LogInfo.log("Compuing reverse for schema formulas");
computeReverseFormulaInfo();
LogInfo.log("Current number of binary formulas: " + binaryFormulaInfoMap.size());
for (BinaryFormulaInfo info : binaryFormulaInfoMap.values()) {
MapUtils.addToList(atomicExtype2ToBinaryMap, info.expectedType2, info.formula);
if (!isCvt(info.expectedType1)) {
addMappingFromType2ToFormula(info.expectedType2, info.formula);
}
}
LogInfo.log("Generate formulas through CVTs");
generateCvtFormulas(); // generate formulas for CVTs
LogInfo.log("Current number of binary formulas: " + binaryFormulaInfoMap.size());
// we first sort by popularity
Comparator<Formula> comparator = getPopularityComparator();
sortType2ToBinaryMaps(comparator);
LogInfo.end_track();
}
public void sortType2ToBinaryMaps(Comparator<Formula> comparator) {
this.formulaComparator = comparator;
for (List<Formula> binaries: atomicExtype2ToBinaryMap.values())
Collections.sort(binaries, comparator);
for (List<Formula> binaries: extype2ToNonCvtBinaryMap.values())
Collections.sort(binaries, comparator);
}
public int compare(Formula f1, Formula f2) {
return formulaComparator.compare(f1, f2);
}
/**
* Adding mapping from type 2 to formula - makes sure to insert just one of the 2 equivalent formulas if they exist
*/
private void addMappingFromType2ToFormula(String type2, Formula formula) {
MapUtils.addToList(extype2ToNonCvtBinaryMap, type2, formula);
}
private void generateCvtFormulas() throws FileNotFoundException {
List<BinaryFormulaInfo> toAdd = new ArrayList<>();
for (BinaryFormulaInfo innerInfo : binaryFormulaInfoMap.values()) {
if (isCvt(innerInfo.expectedType1)) { // if expected type 1 is a CVT
MapUtils.addToSet(cvtTypeToBinaries, innerInfo.expectedType1, innerInfo.formula);
List<Formula> outers = atomicExtype2ToBinaryMap.get(innerInfo.expectedType1); // find those whose expected type 2 is that CVT
for (Formula outer : outers) {
BinaryFormulaInfo outerInfo = binaryFormulaInfoMap.get(outer);
if (!isLegalCvt(innerInfo.formula, outer))
continue;
// build new formula
LambdaFormula cvtFormula = new LambdaFormula("x", new JoinFormula(outer, new JoinFormula(innerInfo.formula, new VariableFormula("x"))));
BinaryFormulaInfo newFormulaInfo = binaryFormulaInfoMap.get(cvtFormula);
if (newFormulaInfo == null) {
String exType1 = outerInfo.expectedType1;
if (exType1 == null)
throw new RuntimeException("Missing expected type 1 for formula: " + outer);
List<String> newDescriptions = new LinkedList<>();
newDescriptions.add(outerInfo.descriptions.get(0));
newDescriptions.add(innerInfo.descriptions.get(0));
newFormulaInfo = new BinaryFormulaInfo(cvtFormula, exType1, innerInfo.expectedType2, newDescriptions, Math.min(outerInfo.popularity, innerInfo.popularity));
toAdd.add(newFormulaInfo);
}
MapUtils.addToSet(cvtExpansionsMap, innerInfo.formula, newFormulaInfo);
MapUtils.addToSet(cvtExpansionsMap, outerInfo.formula, newFormulaInfo);
}
}
}
for (BinaryFormulaInfo info : toAdd) {
addMappingFromType2ToFormula(info.expectedType2, info.formula);
binaryFormulaInfoMap.put(info.formula, info);
}
}
private boolean isLegalCvt(Formula inner, Formula outer) {
if (FreebaseInfo.isReverseProperty(inner.toString()) && !FreebaseInfo.isReverseProperty(outer.toString()))
return false;
if (!FreebaseInfo.isReverseProperty(inner.toString()) && FreebaseInfo.isReverseProperty(outer.toString()))
return false;
return true;
}
/** supports chains only */
public boolean hasOpposite(String formula) { return hasOpposite(LispTree.proto.parseFromString(formula)); }
public boolean hasOpposite(Formula formula) { return hasOpposite(formula.toLispTree()); }
private boolean hasOpposite(LispTree tree) {
if (tree.isLeaf()) {
String fbProperty = FreebaseInfo.isReverseProperty(tree.value) ? tree.value.substring(1) : tree.value;
return freebaseInfo.propertyHasOpposite(fbProperty);
} else {
// Un-reverse everything.
String binary1 = tree.child(2).child(0).value;
binary1 = FreebaseInfo.isReverseProperty(binary1) ? binary1.substring(1) : binary1;
String binary2 = tree.child(2).child(1).child(0).value;
binary2 = FreebaseInfo.isReverseProperty(binary2) ? binary2.substring(1) : binary2;
return freebaseInfo.propertyHasOpposite(binary1) && freebaseInfo.propertyHasOpposite(binary2);
}
}
/** supports chains only */
public boolean isReversed(Formula formula) {
LispTree tree = formula.toLispTree();
if (tree.isLeaf())
return FreebaseInfo.isReverseProperty(tree.value);
else
return FreebaseInfo.isReverseProperty(tree.child(2).child(0).value);
}
/** assumes we checked there is an opposite formula */
public Formula equivalentFormula(String formula) {
LispTree tree = LispTree.proto.parseFromString(formula);
return equivalentFormula(tree);
}
public Formula equivalentFormula(Formula formula) {
LispTree tree = formula.toLispTree();
return equivalentFormula(tree);
}
// two formulas can be equivalent because there are two names for every edge using the reverse label
//fb:people.person.place_of_birth --> !fb:location.location.people_born_here
//!fb:people.person.place_of_birth --> fb:location.location.people_born_here
public Formula equivalentFormula(LispTree tree) {
if (tree.isLeaf()) {
boolean rev = FreebaseInfo.isReverseProperty(tree.value);
String fbProperty = rev ? tree.value.substring(1) : tree.value;
String oppositeProperty = freebaseInfo.getOppositeFbProperty(fbProperty);
return rev ? Formulas.newNameFormula(oppositeProperty) : Formulas.newNameFormula("!" + oppositeProperty);
} else {
String binary1 = tree.child(2).child(0).value;
binary1 = FreebaseInfo.isReverseProperty(binary1) ? binary1.substring(1) : binary1;
String binary2 = tree.child(2).child(1).child(0).value;
binary2 = FreebaseInfo.isReverseProperty(binary2) ? binary2.substring(1) : binary2;
String oppositeBinary1 = freebaseInfo.getOppositeFbProperty(binary1);
String oppositeBinary2 = freebaseInfo.getOppositeFbProperty(binary2);
boolean rev = FreebaseInfo.isReverseProperty(tree.child(2).child(0).value);
return buildLambdaFormula(oppositeBinary1, oppositeBinary2, !rev);
}
}
//input: |binary1|=fb:people.place_lived.location,
// |binary2|=fb:people.person.places_lived, |reverse|=true
//output: (lambda x (!fb:people.place_lived.location (!fb:people.person.places_lived (var x))))
public static Formula buildLambdaFormula(String binary1, String binary2, boolean reverse) {
Formula binary1Formula = reverse ? Formulas.newNameFormula("!" + binary1) : Formulas.newNameFormula(binary1);
Formula binary2Formula = reverse ? Formulas.newNameFormula("!" + binary2) : Formulas.newNameFormula(binary2);
Formula join1 = new JoinFormula(binary2Formula, new VariableFormula("x"));
Formula join2 = new JoinFormula(binary1Formula, join1);
return new LambdaFormula("x", join2);
}
//for binary formulas that are paths in the graph, if formula1 is a path s-->t
//then formula2 is the opposite if it is the path t-->s
// fb:people.person.place_of_birth is the opposite of !fb:people.person.place_of_birth
// fb:people.person.place_of_birth is the opposite of fb:
private boolean isOpposite(Formula formula1, Formula formula2) {
if (isReversed(formula1) && !isReversed(formula2)) {
String formula1Desc = formula1.toString().substring(1);
return formula1Desc.equals(formula2.toString());
}
if (isReversed(formula2) && !isReversed(formula1)) {
String formula2Desc = formula2.toString().substring(1);
return formula2Desc.equals(formula1.toString());
}
if (hasOpposite(formula1)) {
Formula equivalentFormula = equivalentFormula(formula1);
if (isReversed(equivalentFormula)) {
String equivalentFormaulDesc = equivalentFormula.toString().substring(1);
return equivalentFormaulDesc.equals(formula2.toString());
} else {
String formula2Desc = formula2.toString().substring(1);
return formula2Desc.equals(equivalentFormula.toString());
}
}
return false;
}
public List<Formula> getBinariesForType2(String type) {
return MapUtils.get(extype2ToNonCvtBinaryMap, type, new ArrayList<Formula>());
}
public List<Formula> getAtomicBinariesForType2(String type) {
return MapUtils.get(atomicExtype2ToBinaryMap, type, new ArrayList<Formula>());
}
public boolean isCvtFormula(BinaryFormulaInfo info) {
return isCvt(info.expectedType1) || isCvt(info.expectedType2);
}
public Set<BinaryFormulaInfo> getCvtExpansions(BinaryFormulaInfo info) {
return MapUtils.getSet(cvtExpansionsMap, info.formula);
}
public Set<Formula> expandCvts(String cvt) {
return MapUtils.getSet(cvtTypeToBinaries, cvt);
}
//For a binary lambda formula that goes through CVTs, find all binaries that can
//be injected to this lambda binary formula.
//example:
//input: (lambda (fb:people.person.places_lived (fb:people.place_lived.location (var x))))
//output: fb:people.place_lived/start_date, fb:people.place_lived.end_date
public List<Formula> getInjectableBinaries(Formula formula) {
List<Formula> res = new ArrayList<>();
if (!(formula instanceof LambdaFormula)) return res;
LambdaFormula lambdaFormula = (LambdaFormula) formula;
Formula first = ((JoinFormula) lambdaFormula.body).relation;
Formula second = ((JoinFormula) ((JoinFormula) lambdaFormula.body).child).relation;
Set<Formula> binaryFormulas = expandCvts(getBinaryInfo(first).expectedType2);
for (Formula binaryFormula : binaryFormulas) {
if (!second.equals(binaryFormula) && !isOpposite(first, binaryFormula)) {
res.add(binaryFormula);
}
}
return res;
}
public boolean isCvt(String type) {
return freebaseInfo.isCvt(type);
}
public Comparator<Formula> getPopularityComparator() {
Counter<Formula> counter = new ClassicCounter<>();
for (Formula binaryFormula : binaryFormulaInfoMap.keySet())
counter.incrementCount(binaryFormula, binaryFormulaInfoMap.get(binaryFormula).popularity);
return new FormulaByCounterComparator(counter);
}
public class FormulaByCounterComparator implements Comparator<Formula> {
private Counter<Formula> fCounter;
public FormulaByCounterComparator(Counter<Formula> fCounter) {
this.fCounter = fCounter;
}
public int compare(Formula f1, Formula f2) {
double count1 = fCounter.getCount(f1);
double count2 = fCounter.getCount(f2);
if (count1 > count2) return -1;
if (count1 < count2) return +1;
double pop1 = binaryFormulaInfoMap.get(f1).popularity;
double pop2 = binaryFormulaInfoMap.get(f2).popularity;
if (pop1 > pop2) return -1;
if (pop1 < pop2) return +1;
return 0;
}
public double getCount(Formula f) { return fCounter.getCount(f); }
}
public class FormulaByFeaturesComparator implements Comparator<Formula> {
private Params params;
public FormulaByFeaturesComparator(Params params) {
this.params = params;
}
public int compare(Formula f1, Formula f2) {
FeatureVector features1 = BridgeFn.getBinaryBridgeFeatures(fbFormulaInfo.getBinaryInfo(f1));
FeatureVector features2 = BridgeFn.getBinaryBridgeFeatures(fbFormulaInfo.getBinaryInfo(f2));
double score1 = features1.dotProduct(params);
double score2 = features2.dotProduct(params);
if (score1 > score2) return -1;
if (score1 < score2) return +1;
double pop1 = binaryFormulaInfoMap.get(f1).popularity;
double pop2 = binaryFormulaInfoMap.get(f2).popularity;
if (pop1 > pop2) return -1;
if (pop1 < pop2) return +1;
return 0;
}
}
//Information from freebase about binary formulas
public static class BinaryFormulaInfo {
public Formula formula; //fb:people.person.place_of_birth
public String expectedType1; //fb:people.person
public String expectedType2; //fb:location.location
public String unitId = ""; //fb:en.meter
public String unitDesc = ""; //Meter
public List<String> descriptions = new LinkedList<>(); // "place of birth"
public double popularity; //Number of instances of binary in KB: 16184.0
public BinaryFormulaInfo(Formula formula, String exType1, String exType2, List<String> descs, double popularity) {
this.formula = formula;
this.expectedType1 = exType1;
this.expectedType2 = exType2;
this.descriptions = descs;
this.popularity = popularity;
this.unitId = "";
this.unitDesc = "";
}
public BinaryFormulaInfo(Formula formula, String exType1, String exType2, String unitId, String unitDesc, List<String> descs, double popularity) {
this.formula = formula;
this.expectedType1 = exType1;
this.expectedType2 = exType2;
this.descriptions = descs;
this.popularity = popularity;
this.unitId = "";
this.unitDesc = "";
}
public String toString() {
return formula.toString() + "\t" + popularity + "\t" + expectedType1 + "\t" + expectedType2 + "\t" + unitId + "\t"
+ unitDesc + "\t" + Joiner.on("###").join(descriptions);
}
public String toReverseString() {
return Formulas.reverseFormula(formula).toString() + "\t" + popularity + "\t" + expectedType2 + "\t" + expectedType1 + "\t" + unitId + "\t"
+ unitDesc + "\t" + Joiner.on("###").join(descriptions);
}
public static List<String> tokenizeFbDescription(String fbDesc) {
List<String> res = new ArrayList<>();
String[] tokens = fbDesc.split("\\s+");
for (String token : tokens) {
token = token.replace("(", "");
token = token.replace(")", "");
token = token.replace("\"", "");
res.add(token);
}
return res;
}
public boolean isComplete() {
if (formula == null || expectedType1 == null || expectedType2 == null ||
expectedType1.equals("") || expectedType2.equals("") || descriptions == null ||
descriptions.size() == 0 ||
popularity == 0.0)
return false;
return true;
}
public SemType getSemType() {
return SemType.newFuncSemType(expectedType2, expectedType1);
}
public String extractDomain(Formula binary) {
LispTree tree = binary.toLispTree();
String property = tree.isLeaf() ? tree.value : tree.child(2).child(0).value;
if (property.startsWith("!"))
property = property.substring(1);
return property.substring(0, property.indexOf('.'));
}
}
public static class UnaryFormulaInfo {
public Formula formula;
public double popularity;
public List<String> descriptions;
public Set<String> types;
public UnaryFormulaInfo(Formula formula, double popularity,
List<String> descriptions, Set<String> types) {
this.formula = formula;
this.popularity = popularity;
this.descriptions = descriptions;
this.types = types;
}
public boolean isComplete() {
if (formula == null || descriptions == null || descriptions.size() == 0 ||
popularity == 0.0)
return false;
return true;
}
public String toString() {
return formula + "\t" + popularity + "\t" + Joiner.on("###").join(descriptions);
}
public String getRepresentativeDescrption() {
if (descriptions.get(0).contains("/") && descriptions.size() > 1)
return descriptions.get(1);
return descriptions.get(0);
}
}
}