/**
*
*/
package xhail.core.entities;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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.InputStates;
import xhail.core.parser.Parser;
import xhail.core.parser.Splitter;
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.Placemarker;
import xhail.core.terms.Scheme;
/**
* @author stefano
*
*/
public class Problem implements Solvable {
public static class Builder implements Buildable<Problem> {
private Set<String> background = new LinkedHashSet<>();
private Config config;
private Set<Display> displays = new LinkedHashSet<>();
private Set<String> domains = new LinkedHashSet<>();
private Set<Example> examples = new LinkedHashSet<>();
private Map<String, Set<Integer>> lookup = new HashMap<>();
private Set<ModeB> modeBs = new LinkedHashSet<>();
private Set<ModeH> modeHs = new LinkedHashSet<>();
public Builder(Config config) {
if (null == config)
throw new IllegalArgumentException("Illegal 'config' argument in Problem.Builder(Config): " + config);
this.config = config;
}
public Builder addBackground(String statement) {
if (null != statement) {
statement = statement.trim();
if (statement.startsWith("#compute"))
Logger.warning(config.isMute(), "'#compute' statements are not supported and will be ignored");
else if (statement.startsWith("#hide"))
Logger.warning(config.isMute(), "'#hide' statements are not supported and will be ignored");
else if (statement.startsWith("#show"))
Logger.warning(config.isMute(), "'#show' statements are not supported and will be ignored");
else if (statement.startsWith("#display") && statement.endsWith("."))
addDisplay(Parser.parseDisplay(statement.substring("#display".length(), statement.length() - 1).trim()));
else if (statement.startsWith("#example") && statement.endsWith("."))
addExample(Parser.parseExample(statement.substring("#example".length(), statement.length() - 1).trim()));
else if (statement.startsWith("#modeb") && statement.endsWith("."))
addMode(Parser.parseModeB(statement.substring("#modeb".length(), statement.length() - 1).trim()));
else if (statement.startsWith("#modeh") && statement.endsWith("."))
addMode(Parser.parseModeH(statement.substring("#modeh".length(), statement.length() - 1).trim()));
else if (statement.startsWith("#domain"))
domains.add(statement);
else
background.add(statement);
}
return this;
}
public Builder addDisplay(Display display) {
if (null == display)
throw new IllegalArgumentException("Illegal 'display' argument in Problem.Builder.addDisplay(Display): " + display);
this.displays.add(display);
return this;
}
public Builder addExample(Example example) {
if (null != example)
examples.add(example);
return this;
}
public Builder addMode(ModeB mode) {
if (null != mode)
modeBs.add(mode);
return this;
}
public Builder addMode(ModeH mode) {
if (null != mode)
modeHs.add(mode);
return this;
}
@Override
public Problem build() {
lookup.clear();
for (Display display : displays) {
String key = display.getIdentifier();
Set<Integer> arities = lookup.get(key);
if (null == arities) {
arities = new HashSet<>();
lookup.put(key, arities);
}
arities.add(display.getArity());
}
return new Problem(this);
}
public Builder clear() {
this.background.clear();
this.displays.clear();
this.examples.clear();
this.lookup.clear();
this.modeBs.clear();
this.modeHs.clear();
return this;
}
public Builder clearDisplay() {
this.displays.clear();
this.lookup.clear();
return this;
}
public Builder clearExamples() {
this.examples.clear();
return this;
}
public Builder clearModeBs() {
this.modeBs.clear();
return this;
}
public Builder clearModeHs() {
this.modeHs.clear();
return this;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Builder other = (Builder) obj;
if (background == null) {
if (other.background != null)
return false;
} else if (!background.equals(other.background))
return false;
if (config == null) {
if (other.config != null)
return false;
} else if (!config.equals(other.config))
return false;
if (displays == null) {
if (other.displays != null)
return false;
} else if (!displays.equals(other.displays))
return false;
if (domains == null) {
if (other.domains != null)
return false;
} else if (!domains.equals(other.domains))
return false;
if (examples == null) {
if (other.examples != null)
return false;
} else if (!examples.equals(other.examples))
return false;
if (lookup == null) {
if (other.lookup != null)
return false;
} else if (!lookup.equals(other.lookup))
return false;
if (modeBs == null) {
if (other.modeBs != null)
return false;
} else if (!modeBs.equals(other.modeBs))
return false;
if (modeHs == null) {
if (other.modeHs != null)
return false;
} else if (!modeHs.equals(other.modeHs))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((background == null) ? 0 : background.hashCode());
result = prime * result + ((config == null) ? 0 : config.hashCode());
result = prime * result + ((displays == null) ? 0 : displays.hashCode());
result = prime * result + ((domains == null) ? 0 : domains.hashCode());
result = prime * result + ((examples == null) ? 0 : examples.hashCode());
result = prime * result + ((lookup == null) ? 0 : lookup.hashCode());
result = prime * result + ((modeBs == null) ? 0 : modeBs.hashCode());
result = prime * result + ((modeHs == null) ? 0 : modeHs.hashCode());
return result;
}
public Builder parse(InputStream stream) {
if (null == stream)
throw new IllegalArgumentException("Illegal 'stream' argument in Problem.Builder.parse(InputStream): " + stream);
for (String statement : new Splitter(InputStates.INITIAL).parse(stream))
addBackground(statement);
return this;
}
public Builder parse(Path path) {
if (null == path)
throw new IllegalArgumentException("Illegal 'path' argument in Problem.Builder.parse(Path): " + path);
try {
parse(new FileInputStream(path.toFile()));
} catch (FileNotFoundException e) {
Logger.error("cannot find file '" + path.getFileName().toString() + "'");
}
return this;
}
public Builder removeBackground(String statement) {
if (null != statement) {
statement = statement.trim();
if (statement.startsWith("#compute")) {
} else if (statement.startsWith("#hide")) {
} else if (statement.startsWith("#show")) {
} else if (statement.startsWith("#display") && statement.endsWith(".")) {
removeDisplay(Parser.parseDisplay(statement.substring("#display".length(), statement.length() - 1).trim()));
} else if (statement.startsWith("#example") && statement.endsWith(".")) {
removeExample(Parser.parseExample(statement.substring("#example".length(), statement.length() - 1).trim()));
} else if (statement.startsWith("#modeb") && statement.endsWith(".")) {
removeMode(Parser.parseModeB(statement.substring("#modeb".length(), statement.length() - 1).trim()));
} else if (statement.startsWith("#modeh") && statement.endsWith(".")) {
removeMode(Parser.parseModeH(statement.substring("#modeh".length(), statement.length() - 1).trim()));
} else if (statement.startsWith("#domain")) {
domains.remove(statement);
} else
background.remove(statement);
}
return this;
}
public Builder removeDisplay(Display display) {
if (null == display)
throw new IllegalArgumentException("Illegal 'display' argument in Problem.Builder.removeDisplay(Display): " + display);
this.displays.remove(display);
return this;
}
public Builder removeExample(Example example) {
if (null != example)
examples.remove(example);
return this;
}
public Builder removeMode(ModeB mode) {
if (null != mode)
modeBs.remove(mode);
return this;
}
public Builder removeMode(ModeH mode) {
if (null != mode)
modeHs.remove(mode);
return this;
}
}
private final String[] background;
private final Config config;
private final Display[] displays;
private final String[] domains;
private final Example[] examples;
private final Map<String, Set<Integer>> lookup;
private final ModeB[] modeBs;
private final ModeH[] modeHs;
private Set<String> refinements = new HashSet<>();
private Problem(Builder builder) {
if (null == builder)
throw new IllegalArgumentException("Illegal 'builder' argument in Problem(Problem.Builder): " + builder);
this.background = builder.background.toArray(new String[builder.background.size()]);
this.config = builder.config;
this.displays = builder.displays.toArray(new Display[builder.displays.size()]);
this.domains = builder.domains.toArray(new String[builder.domains.size()]);
this.examples = builder.examples.toArray(new Example[builder.examples.size()]);
this.lookup = builder.lookup;
this.modeBs = builder.modeBs.toArray(new ModeB[builder.modeBs.size()]);
this.modeHs = builder.modeHs.toArray(new ModeH[builder.modeHs.size()]);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Problem other = (Problem) obj;
if (!Arrays.equals(background, other.background))
return false;
if (config == null) {
if (other.config != null)
return false;
} else if (!config.equals(other.config))
return false;
if (!Arrays.equals(displays, other.displays))
return false;
if (!Arrays.equals(domains, other.domains))
return false;
if (!Arrays.equals(examples, other.examples))
return false;
if (lookup == null) {
if (other.lookup != null)
return false;
} else if (!lookup.equals(other.lookup))
return false;
if (!Arrays.equals(modeBs, other.modeBs))
return false;
if (!Arrays.equals(modeHs, other.modeHs))
return false;
if (refinements == null) {
if (other.refinements != null)
return false;
} else if (!refinements.equals(other.refinements))
return false;
return true;
}
public final String[] getBackground() {
return background;
}
public final Config getConfig() {
return config;
}
public final Display[] getDisplays() {
return displays;
}
public final String[] getDomains() {
return domains;
}
public final Example[] getExamples() {
return examples;
}
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 : displays)
result.add(String.format("#show %s/%d.", display.getIdentifier(), display.getArity()));
for (Example example : examples)
result.add(String.format("#show %s/%d.", example.getAtom().getIdentifier(), example.getAtom().getArity()));
for (ModeH mode : modeHs) {
Scheme scheme = mode.getScheme();
result.add(String.format("#show %s/%d.", scheme.getIdentifier(), scheme.getArity()));
result.add(String.format("#show abduced_%s/%d.", scheme.getIdentifier(), scheme.getArity()));
for (Placemarker placemarker : scheme.getPlacemarkers())
result.add(String.format("#show %s/1.", placemarker.getIdentifier()));
}
for (ModeB mode : modeBs) {
Scheme scheme = mode.getScheme();
result.add(String.format("#show %s/%d.", scheme.getIdentifier(), scheme.getArity()));
for (Placemarker placemarker : scheme.getPlacemarkers())
result.add(String.format("#show %s/1.", placemarker.getIdentifier()));
}
return result;
}
public final ModeB[] getModeBs() {
return modeBs;
}
public final ModeH[] getModeHs() {
return modeHs;
}
public final Collection<String> getRefinements() {
return refinements;
}
public final boolean hasBackground() {
return background.length > 0;
}
public final boolean hasDisplays() {
return displays.length > 0;
}
public final boolean hasDomains() {
return domains.length > 0;
}
public final boolean hasExamples() {
return examples.length > 0;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(background);
result = prime * result + ((config == null) ? 0 : config.hashCode());
result = prime * result + Arrays.hashCode(displays);
result = prime * result + Arrays.hashCode(domains);
result = prime * result + Arrays.hashCode(examples);
result = prime * result + ((lookup == null) ? 0 : lookup.hashCode());
result = prime * result + Arrays.hashCode(modeBs);
result = prime * result + Arrays.hashCode(modeHs);
result = prime * result + ((refinements == null) ? 0 : refinements.hashCode());
return result;
}
public final boolean hasModes() {
return modeBs.length > 0 || modeHs.length > 0;
}
public final boolean lookup(Atom atom) {
if (null == atom)
throw new IllegalArgumentException("Illegal 'atom' argument in Problem.lookup(Atom): " + atom);
String key = atom.getIdentifier();
if (!this.lookup.containsKey(key))
return false;
return this.lookup.get(key).contains(atom.getArity());
}
@Override
public boolean save(int iter, OutputStream stream) {
return Utils.save(this, iter, stream);
}
private volatile int count = 0;
public final int count() {
return count;
}
public final Answers solve() {
Answers.Builder builder = new Answers.Builder(config);
if (background.length > 0 || examples.length > 0 || modeHs.length > 0 || modeBs.length > 0) {
int iter = 0;
Set<Collection<Clause>> generalisations = new HashSet<>();
while (!builder.isMeaningful() && iter <= config.getIterations()) {
if (config.isDebug())
Utils.saveTemp(this, iter, Paths.get(String.format("%s_abd%d.lp", config.getName(), iter)));
int iit = 0;
Values values = new Values();
Dialler dialler = new Dialler.Builder(config, this).build();
Map.Entry<Values, Collection<Collection<String>>> entry = Answers.timeAbduction(iter, dialler);
for (Collection<String> output : entry.getValue()) {
if (builder.size() > 0 && config.isTerminate())
break;
Grounding grounding = Answers.timeDeduction(this, output);
if (config.isDebug()) {
Logger.message(String.format("*** Info (%s): found Delta: %s", Logger.SIGNATURE, StringUtils.join(grounding.getDelta(), " ")));
Logger.message(String.format("*** Info (%s): found Kernel: %s", Logger.SIGNATURE, StringUtils.join(grounding.getKernel(), " ")));
Logger.message(String.format("*** Info (%s): found Generalisation: %s", Logger.SIGNATURE, StringUtils.join(grounding.getGeneralisation(), " ")));
if (grounding.needsInduction())
Utils.saveTemp(grounding, iter, Paths.get(String.format("%s_abd%d_ind%d.lp", config.getName(), iter, iit++)));
}
Set<Clause> generalisation = new HashSet<Clause>();
Collections.addAll(generalisation, grounding.getGeneralisation());
if (!generalisations.contains(generalisation)) {
values = grounding.solve(values, builder);
// always add refinements, hopefully it won't be used!
refinements.add(grounding.asBadSolution());
generalisations.add(generalisation);
}
count = builder.size();
}
iter += 1;
}
if (builder.size() > 0 && config.isTerminate())
System.out.println(String.format("*** Info (%s): search for hypotheses terminated after the first match", Logger.SIGNATURE));
if (!builder.isMeaningful())
System.out.println(String.format("*** Info (%s): no meaningful answers, try more iterations (--iter,-i <num>)", Logger.SIGNATURE));
}
return builder.build();
}
@Override
public String toString() {
return "Problem [\n background=" + Arrays.toString(background) + ",\n config=" + config + ",\n displays=" + Arrays.toString(displays)
+ ",\n domains=" + Arrays.toString(domains) + ",\n examples=" + Arrays.toString(examples) + ",\n modeBs=" + Arrays.toString(modeBs)
+ ",\n modeHs=" + Arrays.toString(modeHs) + "\n]";
}
}