<p> // (c) 2008 David Roberts
// TODO:
// - support for multiple rules matching goal
// - support for relation as a variable
// - raise error if goal is matched to neither freebase data nor a rule
// - make a better clone() function
// - support for hierarchical query
var log = "";
var goals = [];
var graph = {};
var root_node;
var topic_name = {};
var relation_name = {};
var relation_property = {};
function is_variable(topic) {
var prefix = "/base/inference/variables/";
if(topic.id.substring(0, prefix.length) == prefix)
return true;
else
return false;
}
function combine_objects(list) {
if(list.length == 0) {
return [];
} else if(list.length == 1) {
return list[0];
} else {
var append_sets = list.pop();
var base_sets = combine_objects(list);
if(append_sets.length == 0) {
return base_sets;
} else if(base_sets.length == 0) {
return append_sets;
} else {
var new_list = [];
for(i in base_sets)
for(j in append_sets)
new_list.push(acre.freebase.extend_query(clone(base_sets[i]), append_sets[j]));
return new_list;
}
}
}
function combine_arrays(list) {
if(list.length == 0) {
return [];
} else if(list.length == 1) {
return list[0];
} else {
var append_sets = list.pop();
var base_sets = combine_arrays(list);
if(append_sets.length == 0) {
return base_sets;
} else if(base_sets.length == 0) {
return append_sets;
} else {
var new_list = [];
for(i in base_sets)
for(j in append_sets)
new_list.push(base_sets[i].concat(append_sets[j]));
return new_list;
}
}
}
function graph_push(subject, property, object) {
if(is_variable(subject))
subject = subject.id + subject.scope;
else
subject = subject.id;
if(is_variable(object))
object = object.id + object.scope;
else
object = object.id;
if(typeof graph[subject] != "object")
graph[subject] = {};
if(typeof graph[object] != "object")
graph[object] = {};
var link_id = Math.floor(Math.random()*2147483648);
graph[subject][object] = "link" + link_id + ":" + property;
graph[object][subject] = "!" + "link" + link_id + ":" + property;
log += "PUSHED ONTO GRAPH: " + subject + ", " + property + ", " + object + "\n";
}
function set_query(query) {
for(var i in query) {
var goal = {};
/*goal.r_subject = acre.freebase.MqlRead({
"id" : query[i][0],
"type" : []
}).result;*/
goal.r_subject = {"id" : query[i][0]};
var relation = acre.freebase.MqlRead({
"name" : query[i][1],
"id" : null,
"type" : "/base/inference/relation",
"equivalent_property" : null
}).result;
goal.r_object = {"id" : query[i][2]};
/*goal.r_object = acre.freebase.MqlRead({
"id" : query[i][2],
"type" : []
}).result;*/
relation_name[relation.id] = relation.name;
relation_property[relation.id] = relation.equivalent_property;
goal.relation = {"id" : relation.id}; // TODO
if(is_variable(goal.r_subject))
goal.r_subject.scope = "";
if(is_variable(goal.r_object))
goal.r_object.scope = "";
goals.push(goal);
}
//goals = [goals]; // TODO
log += "INITIAL GOALS: " + JSON.stringify(goals, null, 2) + "\n";
}
function expand_node(node) {
var subject = node[0];
var relation = node[1];
var object = node[2];
var property = relation_property[relation];
if(property != null)
return [subject, property, object];
// TODO move away from goal data structure to id triplets
var goal = {"r_subject" : {"id":subject}, "relation" : {"id":relation}, "r_object" : {"id":object}};
var rule = goal_to_rule(goal); // TODO support multiple rules
// TODO if(rule == null)
var new_goals = rule_replace_vars(clone(rule), goal); // TODO handle variable scopes
// TODO support ORing child relations
var branches = [[]];
for(var i in new_goals) {
var new_goal = new_goals[i];
var subnode = [new_goal.r_subject, new_goal.relation, new_goal.r_object];
branches[0].push(expand_node(subnode));
}
return branches;
}
function run() {
//return; // TODO
while(goals.length > 0) {
//log += "CURRENT GOALS: " + JSON.stringify(goals, null, 2);
var new_goals = [];
for(var i in goals) {
var goal = goals[i];
log += "CONSIDERING GOAL: " + goal.r_subject.id + " " + relation_name[goal.relation.id] + " " + goal.r_object.id + "\n";
if(goal_match_fb(goal)) continue;
var rule = goal_to_rule(goal);
if(rule === null) return [];
new_goals = new_goals.concat(rule_replace_vars(clone(rule), goal));
}
goals = new_goals;
}
var mql_query = inference.node_to_query(root_node);
var mql_result = acre.freebase.MqlRead(mql_query).result;
var result = inference.extract_vars(mql_result);
//log += "MQL QUERY: " + JSON.stringify(mql_query, null, 2) + "\n";
return result;
}
function get_rule(subject, relation, object) {
var q = acre.require("goal_to_rule").query;
q.then_clauses = {
"r_subject" : {"id" : subject},
"relation" : {"id" : relation},
"r_object" : {"id" : object},
};
if(subject == null) q.then_clauses.r_subject.type = "/base/inference/variable";
if(object == null) q.then_clauses.r_object.type = "/base/inference/variable";
var rule = acre.freebase.MqlRead(q).result;
return rule;
}
function goal_to_rule(goal) {
var rule = null;
if(!is_variable(goal.r_subject) && !is_variable(goal.r_object))
rule = get_rule(goal.r_subject.id, goal.relation.id, goal.r_object.id);
if(rule == null && !is_variable(goal.r_object))
rule = get_rule(null, goal.relation.id, goal.r_object.id);
if(rule == null && !is_variable(goal.r_subject))
rule = get_rule(goal.r_subject.id, goal.relation.id, null);
if(rule == null)
rule = get_rule(null, goal.relation.id, null);
if(rule == null) {
log += "UNABLE TO MATCH GOAL TO ANY RULE\n";
return null;
}
log += "GOAL MATCHED TO RULE: " + rule.name + "\n";
for(var i in rule.if_clauses) {
var relation = rule.if_clauses[i].relation;
relation_name[relation.id] = relation.name;
relation_property[relation.id] = relation.equivalent_property;
}
return rule;
}
/*function goal_to_rule(goal) {
var rule = null;
if(!is_variable(goal.r_subject) && !is_variable(goal.r_object)) {
var q = acre.require("goal_to_rule").query;
q.then_clauses = {
"r_subject" : {"id" : goal.r_subject.id},
"relation" : {"id" : goal.relation.id},
"r_object" : {"id" : goal.r_object.id},
};
rule = acre.freebase.MqlRead(q).result;
}
if(rule == null && !is_variable(goal.r_object)) {
var q = acre.require("goal_to_rule").query;
q.then_clauses = {
"r_subject" : {"constrain:type" : "/base/inference/variable", "id": null},
"relation" : {"id" : goal.relation.id},
"r_object" : {"id" : goal.r_object.id},
};
rule = acre.freebase.MqlRead(q).result;
}
if(rule == null && !is_variable(goal.r_subject)) {
var q = acre.require("goal_to_rule").query;
q.then_clauses = {
"r_subject" : {"id" : goal.r_subject.id},
"relation" : {"id" : goal.relation.id},
"r_object" : {"constrain:type" : "/base/inference/variable", "id": null},
};
rule = acre.freebase.MqlRead(q).result;
}
if(rule == null) {
var q = acre.require("goal_to_rule").query;
q.then_clauses = {
"r_subject" : {"constrain:type" : "/base/inference/variable", "id": null},
"relation" : {"id" : goal.relation.id},
"r_object" : {"constrain:type" : "/base/inference/variable", "id": null},
};
rule = acre.freebase.MqlRead(q).result;
}
if(rule == null) {
log += "UNABLE TO MATCH GOAL TO ANY RULE\n";
return null;
}
log += "GOAL MATCHED TO RULE: " + rule.name + "\n";
for(var i in rule.if_clauses) {
var relation = rule.if_clauses[i].relation;
relation_name[relation.id] = relation.name;
relation_property[relation.id] = relation.equivalent_property;
}
return rule;
}*/
function goal_match_fb(goal) {
// find solution to goal from freebase data
var property = relation_property[goal.relation.id];
if(property == null)
return false;
log += "GOAL MATCHED TO PROPERTY: " + property + "\n";
//log += " SUBJECT HAS TYPES: " + JSON.stringify(goal.r_subject.type) + "\n";
//log += " OBJECT HAS TYPES: " + JSON.stringify(goal.r_object.type) + "\n";
graph_push(goal.r_subject, property, goal.r_object);
return true;
}
function rule_replace_vars(rule, goal) {
// map topics in goal to variables in rule
var variable_replacements = {};
if(is_variable(rule.then_clauses.r_subject)) {
log += "MARKING " + rule.then_clauses.r_subject.id + " FOR REPLACEMENT WITH " + goal.r_subject.id + "\n";
variable_replacements[rule.then_clauses.r_subject.id] = goal.r_subject;
}
if(is_variable(rule.then_clauses.r_object)) {
log += "MARKING " + rule.then_clauses.r_object.id + " FOR REPLACEMENT WITH " + goal.r_object.id + "\n";
variable_replacements[rule.then_clauses.r_object.id] = goal.r_object;
}
var scope_id = ":" + Math.floor(Math.random()*2147483648);
// replace variables in rule.if_clauses with topics in goal
for(var i in rule.if_clauses) {
new_topic = variable_replacements[rule.if_clauses[i].r_subject.id];
if(typeof new_topic == "object") {
log += "REPLACING SUBJECT " + rule.if_clauses[i].r_subject.id + " WITH " + new_topic.id + "\n";
rule.if_clauses[i].r_subject = new_topic;
} else {
rule.if_clauses[i].r_subject.scope = scope_id;
}
new_topic = variable_replacements[rule.if_clauses[i].r_object.id];
if(typeof new_topic == "object") {
log += "REPLACING OBJECT " + rule.if_clauses[i].r_object.id + " WITH " + new_topic.id + "\n";
rule.if_clauses[i].r_object = new_topic;
} else {
rule.if_clauses[i].r_object.scope = scope_id;
}
}
return rule.if_clauses;
}
function clone(x) {
return JSON.parse(JSON.stringify(x));
}
function is_empty(x) {
for(var i in x) return false;
return true;
}
function extract_vars(obj) {
var combine_list = [];
for(var key in obj) {
if(key.indexOf(":/base/inference/variable/symbol") != -1) { // is a global variable tag
var symbol = key.substring(0, key.indexOf(':'));
var self_set = {};
self_set[symbol] = obj.id;
combine_list.push([self_set]);
topic_name[obj.id] = obj.name;
} else if(obj instanceof Array) {
combine_list = combine_list.concat(extract_vars(obj[key]));
} else if(typeof obj == "object") {
combine_list.push(extract_vars(obj[key]));
}
}
if(obj instanceof Array)
return combine_list;
else
return combine_objects(combine_list);
}
function explode_andor_tree(list, or_level) {
var combine_list = [];
if(typeof list == "string") // TODO is a fact
return [[list]];
for(var i in list) {
if(or_level)
combine_list = combine_list.concat(explode_andor_tree(list[i], !or_level));
else
combine_list.push(explode_andor_tree(list[i], !or_level));
}
if(or_level)
return combine_list;
else
return combine_arrays(combine_list);
}
function id_to_symbol(id) {
var prefix = "/base/inference/variables/";
if(id.substring(0, prefix.length) == prefix)
return id.substring(prefix.length, id.length);
else
return null;
}
function node_to_query(id, parent) {
symbol = id_to_symbol(id);
var q = {
"id" : (symbol == null ? id : null),
"name" : null
};
if(symbol != null && symbol.indexOf(":") == -1) // is a global variable
q[symbol + ":/base/inference/variable/symbol"] = null;
for(object in inference.graph[id]) {
if(object == parent)
continue;
q[inference.graph[id][object]] = node_to_query(object, id);
}
return [q];
}
</p>