package edu.stanford.nlp.sempre.freebase; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import edu.stanford.nlp.sempre.freebase.FbFormulasInfo.BinaryFormulaInfo; import edu.stanford.nlp.sempre.freebase.FbFormulasInfo.UnaryFormulaInfo; import edu.stanford.nlp.sempre.*; import fig.basic.*; import java.io.BufferedReader; import java.io.IOException; import java.util.*; /** * Class for keeping info from Freebase schema * @author jonathanberant */ public final class FreebaseInfo { private static FreebaseInfo singleton; public static FreebaseInfo getSingleton() { if (singleton == null) singleton = new FreebaseInfo(); return singleton; } public static class Options { @Option(gloss = "ttl file with schema information") public String schemaPath = "lib/fb_data/93.exec/schema2.ttl"; } public static Options opts = new Options(); // any // - number (boolean, int, float, date) // - text // - entity (people, loc, org, ...) // - cvt // Concrete primitive types public static final String BOOLEAN = CanonicalNames.BOOLEAN; public static final String INT = CanonicalNames.INT; public static final String FLOAT = CanonicalNames.FLOAT; public static final String DATE = CanonicalNames.DATE; public static final String TEXT = CanonicalNames.TEXT; public static final String NUMBER = CanonicalNames.NUMBER; public static final String ENTITY = CanonicalNames.ENTITY; public static final String ANY = CanonicalNames.ANY; // Common entity types public static final String PERSON = "fb:people.person"; // Non-standard abstract types public static final String CVT = "fb:type.cvt"; // Common relations public static final String TYPE = CanonicalNames.TYPE; public static final String NAME = CanonicalNames.NAME; public static final String PROF = "fb:people.person.profession"; public static final String ALIAS = "fb:common.topic.alias"; // mapping from master property to its opposite (e.g., fb:people.person.place_of_birth => fb:location.location.people_born_here) private BiMap<String, String> masterToOppositeMap = HashBiMap.create(); private Set<String> cvts = new HashSet<>(); private Map<String, String> type1Map = new HashMap<>(); // property => type of arg1 private Map<String, String> type2Map = new HashMap<>(); // property => type of arg2 private Map<String, String> unit2Map = new HashMap<>(); // property => unit of arg2 (if exists) private Map<String, List<String>> bDescriptionsMap = new HashMap<>(); // property => descriptions private Map<String, Integer> bPopularityMap = new HashMap<>(); // property => popularity // unary maps private Map<String, Integer> professionPopularityMap = new HashMap<>(); // property => popularity private Map<String, Integer> typePopularityMap = new HashMap<>(); // property => popularity private Map<String, List<String>> professionDescriptionsMap = new HashMap<>(); // property => descriptions private Map<String, List<String>> typeDescriptionsMap = new HashMap<>(); // property => descriptions private Map<String, String> nameMap = new HashMap<String, String>(); // id => name of id public String getArg1Type(String property) { return type1Map.get(property); } public String getArg2Type(String property) { return type2Map.get(property); } private FreebaseInfo() { try { readSchema(); } catch (NumberFormatException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } /** * Go over schema twice - once to populate all fields except descriptions, the second time we populate descriptions after we now what * are the properties we are interested in * @throws NumberFormatException * @throws IOException */ public void readSchema() throws IOException { LogInfo.begin_track("Loading Freebase schema: %s", opts.schemaPath); BufferedReader in = IOUtils.openInHard(opts.schemaPath); // Include mediator types SemTypeHierarchy.singleton.addSupertype(CVT, CVT); SemTypeHierarchy.singleton.addSupertype(CVT, ANY); String line; while ((line = in.readLine()) != null) { String[] tokens = edu.stanford.nlp.sempre.freebase.Utils.parseTriple(line); if (tokens == null) continue; String arg1 = tokens[0]; String property = tokens[1]; String arg2 = tokens[2]; if (property.equals("fb:type.property.reverse_property")) { // reverse_property => opposite_property // Duplicates logically really shouldn't happen but the Freebase RDF // reverse properties are not 1:1. We should monitor this and make // sure we don't lose any alignments. if (masterToOppositeMap.containsKey(arg1)) { // LogInfo.errors("arg1 exists multiple times: %s", line); continue; } if (masterToOppositeMap.inverse().containsKey(arg2)) { // LogInfo.errors("arg2 exists multiple times: %s", line); continue; } masterToOppositeMap.put(arg1, arg2); } else if (property.equals("fb:freebase.type_hints.included_types")) { // included_types => supertypes SemTypeHierarchy.singleton.addSupertype(arg1, arg2); SemTypeHierarchy.singleton.addEntitySupertypes(arg1); SemTypeHierarchy.singleton.addEntitySupertypes(arg2); } else if (property.equals("fb:freebase.type_hints.mediator")) { // mediator => cvt if (arg2.equals("\"true\"^^xsd:boolean")) cvts.add(arg1); else if (arg2.equals("\"false\"^^xsd:boolean")) cvts.remove(arg1); else throw new RuntimeException("Invalid xsd:boolean: " + arg2); } else if (property.equals("fb:type.property.schema")) { // schema => type1 if (type1Map.containsKey(arg1)) LogInfo.errors("%s already has type1 %s, assigning %s", arg1, type1Map.get(arg1), arg2); type1Map.put(arg1, arg2); } else if (property.equals("fb:type.property.expected_type")) { // expected_type => type2 if (type2Map.containsKey(arg1)) LogInfo.errors("%s already has type2 %s, assigning %s", arg1, type2Map.get(arg1), arg2); type2Map.put(arg1, arg2); } else if (property.equals("fb:type.property.unit")) { unit2Map.put(arg1, arg2); } else if (property.equals("fb:user.custom.type.property.num_instances")) { bPopularityMap.put(arg1, edu.stanford.nlp.sempre.freebase.Utils.parseInt(arg2)); } else if (property.equals("fb:user.custom.people.person.profession.num_instances")) { professionPopularityMap.put(arg1, edu.stanford.nlp.sempre.freebase.Utils.parseInt(arg2)); } else if (property.equals("fb:user.custom.type.object.type.num_instances")) { typePopularityMap.put(arg1, edu.stanford.nlp.sempre.freebase.Utils.parseInt(arg2)); } } in.close(); // Second iteration - populate descriptions assumes all properties have the fb:type.property.num_instances field in = IOUtils.openInHard(opts.schemaPath); while ((line = in.readLine()) != null) { String[] tokens = edu.stanford.nlp.sempre.freebase.Utils.parseTriple(line); if (tokens == null) continue; String arg1 = tokens[0]; String property = tokens[1]; String arg2 = tokens[2]; if (property.equals(NAME) || property.equals(ALIAS)) { if (bPopularityMap.containsKey(arg1)) { MapUtils.addToList(bDescriptionsMap, arg1, edu.stanford.nlp.sempre.freebase.Utils.parseStr(arg2).toLowerCase()); } else if (professionPopularityMap.containsKey(arg1)) { MapUtils.addToList(professionDescriptionsMap, arg1, edu.stanford.nlp.sempre.freebase.Utils.parseStr(arg2).toLowerCase()); } else if (typePopularityMap.containsKey(arg1)) { MapUtils.addToList(typeDescriptionsMap, arg1, edu.stanford.nlp.sempre.freebase.Utils.parseStr(arg2).toLowerCase()); } } if (property.equals(NAME)) nameMap.put(arg1, edu.stanford.nlp.sempre.freebase.Utils.parseStr(arg2)); } LogInfo.logs("%d CVTs, (%d,%d) property types, %d property units", cvts.size(), type1Map.size(), type2Map.size(), unit2Map.size()); LogInfo.end_track(); } public Map<Formula, BinaryFormulaInfo> createBinaryFormulaInfoMap() { Map<Formula, FbFormulasInfo.BinaryFormulaInfo> res = new HashMap<>(); for (String property : bPopularityMap.keySet()) { Formula f = Formulas.fromLispTree(LispTree.proto.parseFromString(property)); BinaryFormulaInfo info = new BinaryFormulaInfo(f, type1Map.get(property), type2Map.get(property), unit2Map.get(property), "", bDescriptionsMap.get(property), bPopularityMap.get(property)); if (!info.isComplete()) { continue; } res.put(f, info); } return res; } public Map<Formula, UnaryFormulaInfo> createUnaryFormulaInfoMap() { Map<Formula, FbFormulasInfo.UnaryFormulaInfo> res = new HashMap<Formula, FbFormulasInfo.UnaryFormulaInfo>(); // professions for (String profession : professionPopularityMap.keySet()) { Formula f = new JoinFormula(PROF, new ValueFormula<Value>(new NameValue(profession))); UnaryFormulaInfo info = new UnaryFormulaInfo(f, professionPopularityMap.get(profession), MapUtils.get(professionDescriptionsMap, profession, new LinkedList<String>()), Collections.singleton(PERSON)); if (!info.isComplete()) { continue; } res.put(f, info); } // types for (String type : typePopularityMap.keySet()) { Formula f = new JoinFormula(TYPE, new ValueFormula<Value>(new NameValue(type))); UnaryFormulaInfo info = new UnaryFormulaInfo(f, typePopularityMap.get(type), MapUtils.get(typeDescriptionsMap, type, new LinkedList<String>()), Collections.singleton(type)); if (!info.isComplete()) { continue; } res.put(f, info); } return res; } // fb:people.person.place_of_birth => true public boolean propertyHasOpposite(String property) { return masterToOppositeMap.containsKey(property) || masterToOppositeMap.inverse().containsKey(property); } // fb:people.person.place_of_birth => fb:location.location.people_born_here public String getOppositeFbProperty(String property) { if (masterToOppositeMap.containsKey(property)) return masterToOppositeMap.get(property); if (masterToOppositeMap.inverse().containsKey(property)) return masterToOppositeMap.inverse().get(property); throw new RuntimeException("Property does not have an opposite: " + property); } public String getUnit1(String property) { return typeToUnit(type1Map.get(property), property); } public String getUnit2(String property) { return typeToUnit(type2Map.get(property), property); } // Get the measurement unit associated with this type. // If something is not a number, then return something crude (e.g. fb:type.cvt). // Return null if we don't know anything. public String typeToUnit(String type, String property) { if (type == null) { // LogInfo.errors("No type information for property: %s", property); return null; } if (type.equals(INT) || type.equals(FLOAT)) { String unit = unit2Map.get(property); if (unit == null) { // LogInfo.errors("No unit information for property: %s", property); return NumberValue.unitless; } return unit; } if (type.equals(BOOLEAN) || type.equals(TEXT) || type.equals(DATE)) // Use the type as the unit return type; if (isCvt(type)) return CVT; // CVT return ENTITY; // Entity } public boolean isCvt(String type) { return cvts.contains(type); } public String getPropertyName(String property) { List<String> names = bDescriptionsMap.get(property); if (names == null) return null; return names.get(0); } public String getName(String id) { return nameMap.get(id); } public static boolean isReverseProperty(String property) { return CanonicalNames.isReverseProperty(property); } public static String reverseProperty(String property) { return CanonicalNames.reverseProperty(property); } // fb:en.barack_obama => http://rdf.freebase.com/ns/en/barack_obama public static final String freebaseNamespace = "http://rdf.freebase.com/ns/"; public static String id2uri(String id) { assert id.startsWith("fb:") : id; return freebaseNamespace + id.substring(3).replaceAll("\\.", "/"); } public static String uri2id(String uri) { if (!uri.startsWith(freebaseNamespace)) { LogInfo.logs("Warning: invalid Freebase uri: %s", uri); // Don't do any conversion; this is not necessarily the best thing to do. return uri; } return "fb:" + uri.substring(freebaseNamespace.length()).replaceAll("/", "."); } }