package ru.bmstu.datalog.algo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import ru.bmstu.datalog.data.Argument;
import ru.bmstu.datalog.data.Predicate;
import ru.bmstu.datalog.data.Request;
import ru.bmstu.datalog.data.Rule;
import ru.bmstu.datalog.data.DatalogData;
import ru.bmstu.datalog.storage.DatalogStorage;
/**
* Implementation of an algorithm to compute the expression on Datalog.
* @author art-vybor
*/
public class DatalogAlgo {
private static HashSet<Rule> rules; //Set of rules.
private static HashSet<Request> requests; //Set of request.
private static HashSet<Predicate> requestCash; //Request cache.
private static HashSet<PredicateIdentifier> ruleNames; //A set of rules.
private static DatalogStorage storage; //Storage of facts.
private static DatalogStorage answerSet; //Storage of intermediate answers.
private static int currentVariable; //Variable required for the designation of variables.
/**
* Returns a response.
* Used program for rules and requests, storagePath for storage(it must be existing database).
* @param program
* @param storagePath
* @return
*/
public static HashSet<Predicate> getAnswer(DatalogData program, String storagePath) {
storage = new DatalogStorage(storagePath, false);
return initAndStartAlgo(program);
}
/**
* Returns a response.
* Used data for facts,rules and requests. After, facts are placed in tempStorage.
* @param data
* @return
*/
public static HashSet<Predicate> getAnswer(DatalogData data) {
storage = new DatalogStorage("tempStorage", false);
storage.clear();
storage.insertFacts(data.getFacts());
return initAndStartAlgo(data);
}
/**
* Returns a response.
* Used program for rules and requests, data for facts. After, facts are placed in tempStorage.
* @param program
* @param data
* @return
*/
public static HashSet<Predicate> getAnswer(DatalogData program, DatalogData data) {
storage = new DatalogStorage("tempStorage", false);
storage.clear();
storage.insertFacts(data.getFacts());
return initAndStartAlgo(program);
}
/**
* Initializes the necessary variables, prepare data, and runs the algorithm.
* @param data
* @return
*/
private static HashSet<Predicate> initAndStartAlgo(DatalogData data) {
//TODO пофиксить случай запуска QSQ для предиката от констант
//initialization
rules = data.getRules();
requests = data.getRequests();
currentVariable = 0;
requestCash = new HashSet<Predicate>();
answerSet = new DatalogStorage("answerStorage", true);
//preprocessoring
sortPredicateInRule();
evaluateVariables();
initRuleNames();
//the algorithm
HashSet<Predicate> answer = executeAlgo();
//correct shutdown
storage.close();
answerSet.close();
return answer;
}
//---------------------------------------------------------------------------------------
//--------------Algorithm----------------------------------------------------------------
//---------------------------------------------------------------------------------------
/**
* Returns a response to a pre-set program.
* @return {@link HashSet<Predicate>}
*/
private static HashSet<Predicate> executeAlgo() {
HashSet<Predicate> answer = new HashSet<Predicate>();
for (Request request : requests) {
answerSet.clear();
executeRequest(request.getPredicate());
answer.addAll(answerSet.select(request.getPredicate()));
}
return answer;
}
/**
* Function performs a request to update the answerSet.
* @param request
*/
private static void executeRequest(Predicate request) {
if (requestCash.contains(request)) return; //Do we already made the same request? If yes is interrupted.
requestCash.add(request);
answerSet.insert(storage.select(request)); //Add to answer all the possible answers of the zero level.
int size = -1;
int answerSize = answerSet.size();
while (size != answerSize) {
size = answerSize;
for (Rule rule : getRulesForRequest(request)) { //For all transformed rules with a suitable head.
ArrayList<Unifier> unifierList = new ArrayList<Unifier>(); //Start with a list of substitutions, which consists of a single empty substitution.
unifierList.add(new Unifier());
for (int i = 0; i < rule.getBody().size(); ++i) {
Predicate predicate = rule.getBody().get(i); //Iterate through all the predicates of the body using select Function
ArrayList<Unifier> localUnifierList = new ArrayList<Unifier>();
for (Unifier unifier : unifierList) { //For each permutation of the list.
Predicate localPredicate = unifier.unifyPredicate(predicate); //Apply it to the predicate.
HashSet<Predicate> tempAnswers;
if (ruleNames.contains(new PredicateIdentifier(predicate))) { //If the predicate occurs in the program as the head rules: //TODO вынести проверку на уровень выше
executeRequest(getRequest(localPredicate)); //Recursively run executeRequest of this predicate.
tempAnswers = answerSet.select(localPredicate); //Select an answer from the set answerSet.
} else { //Else
tempAnswers = storage.select(localPredicate); //Select an answer from BD.
}
for (Predicate tempAnswer : tempAnswers) { //For each answer we get a new permutation.
Unifier localUnifier = unifier.getNewUnifier(localPredicate, tempAnswer);
localUnifierList.add(localUnifier);
}
}
unifierList = localUnifierList;
}
for (Unifier unifier : unifierList) { //From the list of substitutions get the right answer.
answerSet.insert(unifier.unifyPredicate(rule.getHead()));
}
}
answerSize = answerSet.size();
}
}
/**
* Provides a new query based on predicate.
* @param predicate
* @return {@link Predicate}
*/
private static Predicate getRequest(Predicate predicate) {
ArrayList<Argument> args = new ArrayList<Argument>();
HashMap<String, Integer> hash = new HashMap<String, Integer>();
for (Argument arg : predicate.getArgs()) {
arg = new Argument(arg);
if (arg.isVariable())
evaluateVariable(hash, arg);
args.add(arg);
}
return new Predicate(predicate.getName(),args);
}
/**
* Return all rules with a suitable head which transformed to the form required for the current request.
* @param request
* @return
*/
private static ArrayList<Rule> getRulesForRequest(Predicate request) {
ArrayList<Rule> answer = new ArrayList<Rule>();
for (Rule rule : rules) {
Unifier unifier = Unifier.produceMostGeneralUnifier(rule.getHead(), request);
if (unifier != null) {
Predicate answerHead = unifier.unifyPredicate(rule.getHead());
ArrayList<Predicate> answerBody = new ArrayList<Predicate>();
for (Predicate predicate : rule.getBody())
answerBody.add(unifier.unifyPredicate(predicate));
answer.add(new Rule(answerHead, answerBody));
}
}
return answer;
}
/**
* Evalute variables for rules and requests.
*/
private static void evaluateVariables() {
for (Rule rule : rules) {
HashMap<String, Integer> hash = new HashMap<String, Integer>();
for (Argument arg : rule.getHead().getArgs())
evaluateVariable(hash, arg);
for (Predicate predicate : rule.getBody())
for (Argument arg : predicate.getArgs())
evaluateVariable(hash, arg);
}
for (Request request : requests) {
HashMap<String, Integer> hash = new HashMap<String, Integer>();
for (Argument arg : request.getPredicate().getArgs())
evaluateVariable(hash, arg);
}
}
//---------------------------------------------------------------------------------------
//--------------Functions for preprocessoring--------------------------------------------
//---------------------------------------------------------------------------------------
/**
* Sorts the predicates in the body of the rule descending number of constants
*/
private static void sortPredicateInRule() {
for (Rule rule: rules) {
Collections.sort(rule.getBody(),new Comparator<Predicate>() {
@Override
public int compare(Predicate p1, Predicate p2) {
int c1 = 0;
int c2 = 0;
for (Argument arg : p1.getArgs()) {
if (arg.isConstant()) c1++;
}
for (Argument arg : p2.getArgs()) {
if (arg.isConstant()) c2++;
}
return c2-c1;
}
});
}
}
/**
* Evalute variable for arg.
* @param hash
* @param arg
*/
private static void evaluateVariable(HashMap<String, Integer> hash, Argument arg) {
if (arg.isVariable()) {
if (arg.getConstant() == "_") {
arg.setVariable(currentVariable++);
} else {
if (hash.containsKey(arg.getConstant()) == false) {
arg.setVariable(currentVariable);
hash.put(arg.getConstant(), currentVariable++);
} else {
arg.setVariable(hash.get(arg.getConstant()));
}
}
}
}
/**
* Add all the rules in the ruleNames.
*/
private static void initRuleNames() {
ruleNames = new HashSet<PredicateIdentifier>();
for (Rule rule : rules) {
ruleNames.add(new PredicateIdentifier(rule.getHead()));
}
}
}