/**
*
*/
package xhail.core.entities;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.StringUtils;
import xhail.core.Buildable;
import xhail.core.Config;
import xhail.core.Dialler;
import xhail.core.Logger;
import xhail.core.Utils;
import xhail.core.parser.Parser;
import xhail.core.statements.Display;
import xhail.core.statements.Example;
import xhail.core.statements.ModeB;
import xhail.core.statements.ModeH;
import xhail.core.terms.Atom;
import xhail.core.terms.Clause;
import xhail.core.terms.Literal;
import xhail.core.terms.Scheme;
import xhail.core.terms.SchemeTerm;
import xhail.core.terms.Term;
import xhail.core.terms.Variable;
/**
* @author stefano
*
*/
public class Grounding implements Solvable {
public static class Builder implements Buildable<Grounding> {
private Set<Literal> covered = new HashSet<>();
private Set<Atom> delta = new HashSet<>();
private Set<Atom> facts = new HashSet<>();
private Set<Atom> model = new HashSet<>();
private Problem problem;
private Set<Literal> uncovered = new HashSet<>();
public Builder(Problem problem) {
if (null == problem)
throw new IllegalArgumentException("Illegal 'problem' argument in Grounding.Builder(Problem): " + problem);
this.problem = problem;
}
public Builder addAtom(Atom atom) {
if (null == atom)
throw new IllegalArgumentException("Illegal 'atom' argument in Grounding.Builder.addAtom(Atom): " + atom);
if (atom.getIdentifier().startsWith("abduced_")) {
delta.add(new Atom.Builder(atom.getIdentifier().substring("abduced_".length())).addTerms(atom.getTerms()).build());
} else {
if (problem.getConfig().isFull() && problem.hasDisplays() && problem.lookup(atom))
model.add(atom);
facts.add(atom);
}
return this;
}
public Builder addAtoms(Collection<Atom> atoms) {
if (null == atoms)
throw new IllegalArgumentException("Illegal 'atoms' argument in Grounding.Builder.addAtoms(Collection<Atom>): " + atoms);
for (Atom atom : atoms)
addAtom(atom);
return this;
}
@Override
public Grounding build() {
covered.clear();
uncovered.clear();
for (Example example : problem.getExamples()) {
Atom atom = example.getAtom();
if (example.isNegated() != facts.contains(atom))
covered.add(new Literal.Builder(atom).setNegated(example.isNegated()).build());
else
uncovered.add(new Literal.Builder(atom).setNegated(example.isNegated()).build());
}
return new Grounding(this);
}
public Builder clear() {
this.covered.clear();
this.delta.clear();
this.facts.clear();
this.model.clear();
this.uncovered.clear();
return this;
}
public Builder parse(Collection<String> answer) {
if (null == answer)
throw new IllegalArgumentException("Illegal 'answer' argument in Grounding.Builder.parse(Collection<String>): " + answer);
for (String atom : answer)
addAtom(Parser.parseToken(atom));
return this;
}
public Builder removeAtom(Atom atom) {
if (null == atom)
throw new IllegalArgumentException("Illegal 'atom' argument in Grounding.Builder.removeAtom(Atom): " + atom);
if (atom.getIdentifier().startsWith("abduced_")) {
delta.remove(new Atom.Builder(atom.getIdentifier().substring("abduced_".length())).addTerms(atom.getTerms()).build());
} else {
if (problem.getConfig().isFull() && problem.hasDisplays() && problem.lookup(atom))
model.remove(atom);
facts.remove(atom);
}
return this;
}
public Builder removeAtoms(Collection<Atom> atoms) {
if (null == atoms)
throw new IllegalArgumentException("Illegal 'atoms' argument in Grounding.Builder.removeAtoms(Collection<Atom>): " + atoms);
for (Atom atom : atoms)
removeAtom(atom);
return this;
}
}
private final Config config;
private final int count;
private final Literal[] covered;
private final Atom[] delta;
private final Set<Atom> facts;
private Clause[] generalisation;
private Clause[] kernel;
private final Atom[] model;
private final Problem problem;
private final Map<SchemeTerm, Set<Atom>> table;
private final Literal[] uncovered;
private Grounding(Builder builder) {
if (null == builder)
throw new IllegalArgumentException("Illegal 'builder' argument in Grounding(Grounding.Builder): " + builder);
this.config = builder.problem.getConfig();
this.count = builder.delta.size();
this.covered = builder.covered.toArray(new Literal[builder.covered.size()]);
this.delta = builder.delta.toArray(new Atom[builder.delta.size()]);
this.facts = builder.facts;
this.model = builder.model.toArray(new Atom[builder.model.size()]);
this.problem = builder.problem;
this.table = SchemeTerm.lookup(builder.problem.getModeHs(), builder.problem.getModeBs(), builder.facts);
this.uncovered = builder.uncovered.toArray(new Literal[builder.uncovered.size()]);
}
public final String asBadSolution() {
return String.format("bad_solution:-%snumber_abduced(%d).", count > 0 ? StringUtils.join(delta, ",") + "," : "", count);
}
public String[] asClauses() {
Set<String> result = new LinkedHashSet<>();
Clause[] clauses = getGeneralisation();
if (clauses.length > 0) {
result.add("{ use_clause_literal(V1,0) }:-clause(V1).");
boolean hasLiterals = false;
for (int clauseId = 0; !hasLiterals && clauseId < clauses.length; clauseId++)
hasLiterals = clauses[clauseId].getBody().length > 0;
if (hasLiterals)
result.add("{ use_clause_literal(V1,V2) }:-clause(V1),literal(V1,V2).");
for (int clauseId = 0; clauseId < clauses.length; clauseId++) {
result.add(String.format("%% %s", clauses[clauseId]));
Literal[] literals = clauses[clauseId].getBody();
result.add(String.format("clause(%d).", clauseId));
for (int literalId = 1; literalId <= literals.length; literalId++)
result.add(String.format("literal(%d,%d).", clauseId, literalId));
for (int level = 0; level < clauses[clauseId].getLevels(); level++)
result.add(String.format(":-not clause_level(%d,%d),clause_level(%d,%d).", clauseId, level, clauseId, 1 + level));
result.add(String.format("clause_level(%d,0):-use_clause_literal(%d,0).", clauseId, clauseId));
for (int literalId = 1; literalId <= literals.length; literalId++)
result.add(String.format("clause_level(%d,%d):-use_clause_literal(%d,%d).", clauseId, literals[literalId - 1].getLevel(), clauseId,
literalId));
Atom head = clauses[clauseId].getHead();
result.add(String.format("#minimize[ use_clause_literal(%d,0) =%d @%d ].", clauseId, head.getWeight(), head.getPriority()));
for (int literalId = 1; literalId <= literals.length; literalId++)
result.add(String.format("#minimize[ use_clause_literal(%d,%d) =%d @%d ].", clauseId, literalId, //
literals[literalId - 1].getWeight(), literals[literalId - 1].getPriority()));
Set<String> set = new LinkedHashSet<>();
for (String type : head.getTypes())
set.add(type);
String[] array = new String[literals.length];
for (int literalId = 1; literalId <= literals.length; literalId++) {
String variables = literals[literalId - 1].hasVariables() ? "," + StringUtils.join(literals[literalId - 1].getVariables(), ",") : "";
array[literalId - 1] = String.format("try_clause_literal(%d,%d%s)", clauseId, literalId, variables);
for (String type : literals[literalId - 1].getTypes())
set.add(type);
}
String typesAll = !set.isEmpty() ? "," + StringUtils.join(set, ",") : "";
String literalsAll = array.length > 0 ? "," + StringUtils.join(array, ",") : "";
result.add(String.format("%s:-use_clause_literal(%d,0)%s%s.", head, clauseId, literalsAll, typesAll));
for (int literalId = 1; literalId <= literals.length; literalId++) {
String variables = literals[literalId - 1].hasVariables() ? "," + StringUtils.join(literals[literalId - 1].getVariables(), ",") : "";
String types = literals[literalId - 1].hasTypes() ? "," + StringUtils.join(literals[literalId - 1].getTypes(), ",") : "";
result.add(String.format("try_clause_literal(%d,%d%s):-use_clause_literal(%d,%d),%s%s.", //
clauseId, literalId, variables, clauseId, literalId, literals[literalId - 1], types));
result.add(String.format("try_clause_literal(%d,%d%s):-not use_clause_literal(%d,%d)%s.", //
clauseId, literalId, variables, clauseId, literalId, types));
}
}
}
return result.toArray(new String[result.size()]);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Grounding other = (Grounding) obj;
if (!Arrays.equals(covered, other.covered))
return false;
if (!Arrays.equals(delta, other.delta))
return false;
if (facts == null) {
if (other.facts != null)
return false;
} else if (!facts.equals(other.facts))
return false;
if (!Arrays.equals(generalisation, other.generalisation))
return false;
if (!Arrays.equals(kernel, other.kernel))
return false;
if (!Arrays.equals(model, other.model))
return false;
if (table == null) {
if (other.table != null)
return false;
} else if (!table.equals(other.table))
return false;
if (problem == null) {
if (other.problem != null)
return false;
} else if (!problem.equals(other.problem))
return false;
if (!Arrays.equals(uncovered, other.uncovered))
return false;
return true;
}
public final String[] getBackground() {
return problem.getBackground();
}
public final Config getConfig() {
return config;
}
public final int getCount() {
return count;
}
public final Literal[] getCovered() {
return covered;
}
public final Atom[] getDelta() {
return delta;
}
public final Display[] getDisplays() {
return problem.getDisplays();
}
public final String[] getDomains() {
return problem.getDomains();
}
public final Example[] getExamples() {
return problem.getExamples();
}
public final Collection<Atom> getFacts() {
return facts;
}
public final Collection<String> getFilters() {
Set<String> result = new TreeSet<>();
result.add("#hide.");
// result.add("#show display_fact/1.");
// result.add("#show covered_example/2.");
// result.add("#show number_abduced/1.");
// result.add("#show uncovered_example/2.");
result.add("#show use_clause_literal/2.");
for (Display display : problem.getDisplays())
result.add(String.format("#show %s/%d.", display.getIdentifier(), display.getArity()));
for (Example example : problem.getExamples())
result.add(String.format("#show %s/%d.", example.getAtom().getIdentifier(), example.getAtom().getArity()));
return result;
}
public final Clause[] getGeneralisation() {
if (null == generalisation) {
Set<Clause> set = new LinkedHashSet<>();
for (Clause clause : getKernel()) {
Map<Term, Variable> map = new HashMap<>();
Clause.Builder builder = new Clause.Builder();
Atom atom = clause.getHead();
for (ModeH mode : problem.getModeHs()) {
Scheme scheme = mode.getScheme();
if (SchemeTerm.subsumes(scheme, atom, facts))
builder.setHead((Atom) scheme.generalises(atom, map));
}
for (Literal literal : clause.getBody()) {
atom = literal.getAtom();
for (ModeB mode : problem.getModeBs()) {
Scheme scheme = mode.getScheme();
if (SchemeTerm.subsumes(scheme, atom, facts))
builder.addLiteral(new Literal.Builder((Atom) scheme.generalises(atom, map)).setNegated(literal.isNegated())
.setLevel(literal.getLevel()).build());
}
}
set.add(builder.build());
}
generalisation = set.toArray(new Clause[set.size()]);
}
return generalisation;
}
public final Clause[] getKernel() {
if (null == kernel) {
Set<Clause> set = new LinkedHashSet<>();
for (Atom alpha : delta)
for (ModeH head : problem.getModeHs()) {
Scheme scheme = head.getScheme();
if (SchemeTerm.subsumes(scheme, alpha, facts)) {
Clause.Builder builder = new Clause.Builder().setHead(//
new Atom.Builder(alpha).setWeight(head.getWeigth()).setPriority(head.getPriority()).build());
Collection<Term> substitutes = SchemeTerm.findSubstitutes(scheme, alpha);
if (null != substitutes) {
int level = 0;
Set<Term> usables = new HashSet<>(substitutes);
Set<Term> used = new HashSet<Term>();
Set<Term> next = new HashSet<Term>();
while (!usables.isEmpty()) {
level += 1;
for (ModeB mode : problem.getModeBs()) {
scheme = mode.getScheme();
if (mode.isNegated()) {
Map<Atom, Collection<Term>> found = SchemeTerm.generateAndOutput(scheme, usables, table, facts);
for (Atom atom : found.keySet()) {
builder.addLiteral(new Literal.Builder( //
new Atom.Builder(atom).setWeight(mode.getWeigth()).setPriority(mode.getPriority()).build() //
).setNegated(mode.isNegated()).setLevel(level).build());
next.addAll(found.get(atom));
}
} else {
Map.Entry<Collection<Atom>, Collection<Term>> found = SchemeTerm.matchAndOutput(scheme, table.get(scheme), usables);
for (Atom atom : found.getKey())
builder.addLiteral(new Literal.Builder( //
new Atom.Builder(atom).setWeight(mode.getWeigth()).setPriority(mode.getPriority()).build() //
).setNegated(mode.isNegated()).setLevel(level).build());
next.addAll(found.getValue());
}
}
used.addAll(usables);
next.removeAll(used);
usables.clear();
usables.addAll(next);
next.clear();
}
}
set.add(builder.build());
}
}
kernel = set.toArray(new Clause[set.size()]);
}
return kernel;
}
public final ModeB[] getModeBs() {
return problem.getModeBs();
}
public final ModeH[] getModeHs() {
return problem.getModeHs();
}
public final Atom[] getModel() {
return model;
}
public final Problem getProblem() {
return problem;
}
public final Map<SchemeTerm, Set<Atom>> getTable() {
return table;
}
public final Literal[] getUncovered() {
return uncovered;
}
public final boolean hasBackground() {
return problem.hasBackground();
}
public final boolean hasCovered() {
return covered.length > 0;
}
public final boolean hasDelta() {
return delta.length > 0;
}
public final boolean hasDisplays() {
return problem.hasDisplays();
}
public final boolean hasDomains() {
return problem.getDomains().length > 0;
}
public final boolean hasExamples() {
return problem.hasExamples();
}
public final boolean hasGeneralisation() {
return getGeneralisation().length > 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(covered);
result = prime * result + Arrays.hashCode(delta);
result = prime * result + ((facts == null) ? 0 : facts.hashCode());
result = prime * result + Arrays.hashCode(generalisation);
result = prime * result + Arrays.hashCode(kernel);
result = prime * result + Arrays.hashCode(model);
result = prime * result + ((table == null) ? 0 : table.hashCode());
result = prime * result + ((problem == null) ? 0 : problem.hashCode());
result = prime * result + Arrays.hashCode(uncovered);
return result;
}
public final boolean hasKernel() {
return getKernel().length > 0;
}
public final boolean hasModel() {
return model.length > 0;
}
public final boolean hasModes() {
return problem.hasModes();
}
public final boolean hasUncovered() {
return uncovered.length > 0;
}
public final boolean lookup(Atom atom) {
if (null == atom)
throw new IllegalArgumentException("Illegal 'atom' argument in Grounding.lookup(Atom): " + atom);
return problem.lookup(atom);
}
public final boolean needsInduction() {
return getGeneralisation().length > 0;
}
@Override
public boolean save(int iter, OutputStream stream) {
return Utils.save(this, iter, stream);
}
public Values solve(Values values, Answers.Builder builder) {
if (null == values)
throw new IllegalArgumentException("Illegal 'values' argument in Grounding.solve(int, Values, Answers.Builder): " + values);
if (null == builder)
throw new IllegalArgumentException("Illegal 'builder' argument in Grounding.solve(int, Values, Answers.Builder): " + builder);
Values result = values;
if (this.needsInduction()) {
Dialler dialler = new Dialler.Builder(config, this, values).build();
Map.Entry<Values, Collection<Collection<String>>> entry = Answers.timeInduction(1, dialler);
result = entry.getKey();
for (Collection<String> output : entry.getValue()) {
if (builder.size() > 0 && config.isTerminate())
break;
Hypothesis hypothesis = Answers.timeDeduction(this, output);
if (config.isDebug())
Logger.message(String.format("*** Info (%s): found Hypothesis: %s", Logger.SIGNATURE, StringUtils.join(hypothesis.getHypotheses(), " ")));
builder.put(entry.getKey(), new Answer.Builder(this).setHypothesis(hypothesis).build());
}
} else
builder.put(new Values(), new Answer.Builder(this).build());
return result;
}
@Override
public String toString() {
return "Grounding [\n covered=" + Arrays.toString(covered) + ",\n delta=" + Arrays.toString(delta) + ",\n facts=" + facts + ",\n generalisation="
+ Arrays.toString(generalisation) + ",\n kernel=" + Arrays.toString(kernel) + ",\n model=" + Arrays.toString(model) + ",\n table=" + table
+ ",\n problem=" + problem + ",\n uncovered=" + Arrays.toString(uncovered) + "\n]";
}
}