diff --git a/build.gradle b/build.gradle index c10da735e..a71cf7bc4 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ dependencies { implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.1' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.6' implementation group: 'org.reflections', name: 'reflections', version: '0.9.11' + implementation group: 'org.apache.poi', name: 'poi', version: '4.1.1' + implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '4.1.1' testImplementation group: 'junit', name: 'junit', version: '4.12' } @@ -85,16 +87,16 @@ task bundledJar(type: Jar) { } archiveFileName = "${project.name}-bundled.jar" - + /* - * In order to make sure wo don't overwrite NOTICE and LICENSE files coming from dependency + * In order to make sure we don't overwrite NOTICE and LICENSE files coming from dependency * jars with each other, number them while copying */ int i = 1 - rename { name -> name.equals("NOTICE.txt") ? "NOTICE." + (i++) + ".txt" : null } + rename { name -> (name.equals("NOTICE.txt") || name.equals("NOTICE")) ? "NOTICE." + (i++) + ".txt" : null } int j = 1 - rename { name -> name.equals("LICENSE.txt") ? "LICENSE." + (j++) + ".txt" : null } + rename { name -> (name.equals("LICENSE.txt") || name.equals("LICENSE")) ? "LICENSE." + (j++) + ".txt" : null } with jar } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriter.java b/src/main/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriter.java new file mode 100644 index 000000000..1995bcda9 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriter.java @@ -0,0 +1,60 @@ +package at.ac.tuwien.kr.alpha; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.function.BiConsumer; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import at.ac.tuwien.kr.alpha.api.mapper.AnswerSetToObjectMapper; +import at.ac.tuwien.kr.alpha.api.mapper.impl.AnswerSetToWorkbookMapper; +import at.ac.tuwien.kr.alpha.common.AnswerSet; + +public class AnswerSetToXlsxWriter implements BiConsumer { + + private String targetBasePath; + private AnswerSetToObjectMapper answerSetMapper; + + public AnswerSetToXlsxWriter(String targetBasePath) { + this.targetBasePath = targetBasePath; + this.answerSetMapper = new AnswerSetToWorkbookMapper(); + } + + @Override + public void accept(Integer num, AnswerSet as) { + try { + Path outputPath = Paths.get(this.targetBasePath + "." + num + ".xlsx"); + OutputStream os = Files.newOutputStream(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + Workbook wb = this.answerSetMapper.mapFromAnswerSet(as); + wb.write(os); + wb.close(); + os.close(); + System.out.println("Answer set written to file " + outputPath.toString()); + } catch (IOException ex) { + System.err.println("Failed writing answer set as xlsx file! (" + ex.getMessage() + ")"); + } + } + + public static void writeUnsatInfo(Path path) throws IOException { + Workbook workbook = new XSSFWorkbook(); + // first, create a worksheet for 0-arity predicates + Sheet sheet = workbook.createSheet("Unsatisfiable"); + Row row = sheet.createRow(0); + Cell cell = row.createCell(0); + cell.setCellValue("Input is unsatisfiable - No answer sets!"); + sheet.autoSizeColumn(0); + OutputStream os = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + workbook.write(os); + workbook.close(); + os.close(); + } + +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/Main.java b/src/main/java/at/ac/tuwien/kr/alpha/Main.java index 3585792c6..aa31aeb99 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/Main.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/Main.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2016-2019, the Alpha Team. + * Copyright (c) 2016-2020, the Alpha Team. * All rights reserved. * * Additional changes made by Siemens. @@ -29,7 +29,9 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -38,6 +40,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import at.ac.tuwien.kr.alpha.api.Alpha; import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.Program; import at.ac.tuwien.kr.alpha.config.AlphaConfig; @@ -98,9 +101,29 @@ private static void computeAndConsumeAnswerSets(Alpha alpha, InputConfig inputCf if (!alpha.getConfig().isQuiet()) { AtomicInteger counter = new AtomicInteger(0); - stream.forEach(as -> System.out.println("Answer set " + counter.incrementAndGet() + ":" + System.lineSeparator() + as.toString())); + final BiConsumer answerSetHandler; + BiConsumer stdoutPrinter = (n, as) -> { + System.out.println("Answer set " + Integer.toString(n) + ":" + System.lineSeparator() + as.toString()); + }; + if (inputCfg.isWriteAnswerSetsAsXlsx()) { + BiConsumer xlsxWriter = new AnswerSetToXlsxWriter(inputCfg.getAnswerSetFileOutputPath()); + answerSetHandler = stdoutPrinter.andThen(xlsxWriter); + } else { + answerSetHandler = stdoutPrinter; + } + stream.forEach(as -> { + int cnt = counter.incrementAndGet(); + answerSetHandler.accept(cnt, as); + }); if (counter.get() == 0) { System.out.println("UNSATISFIABLE"); + if (inputCfg.isWriteAnswerSetsAsXlsx()) { + try { + AnswerSetToXlsxWriter.writeUnsatInfo(Paths.get(inputCfg.getAnswerSetFileOutputPath() + ".UNSAT.xlsx")); + } catch (IOException ex) { + System.err.println("Failed writing unsat file!"); + } + } } else { System.out.println("SATISFIABLE"); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/Alpha.java b/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java similarity index 98% rename from src/main/java/at/ac/tuwien/kr/alpha/Alpha.java rename to src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java index 5fdae794d..c03fa3691 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/Alpha.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/api/Alpha.java @@ -25,8 +25,9 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package at.ac.tuwien.kr.alpha; +package at.ac.tuwien.kr.alpha.api; +import at.ac.tuwien.kr.alpha.Util; import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AtomStore; import at.ac.tuwien.kr.alpha.common.AtomStoreImpl; diff --git a/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/AnswerSetToObjectMapper.java b/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/AnswerSetToObjectMapper.java new file mode 100644 index 000000000..8f745e556 --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/AnswerSetToObjectMapper.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020, the Alpha Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1) Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2) Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package at.ac.tuwien.kr.alpha.api.mapper; + +import at.ac.tuwien.kr.alpha.common.AnswerSet; + +/** + * Copyright (c) 2020, the Alpha Team. + * + * Interface definition for an adapter that maps from an {@link AnswerSet} to an instance of the implementation's generic type T. + * + * @param the type to which to map answer sets + */ +public interface AnswerSetToObjectMapper { + + T mapFromAnswerSet(AnswerSet answerSet); + +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapper.java b/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapper.java new file mode 100644 index 000000000..f044e19bd --- /dev/null +++ b/src/main/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapper.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2020, the Alpha Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1) Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2) Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package at.ac.tuwien.kr.alpha.api.mapper.impl; + +import java.util.List; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.ac.tuwien.kr.alpha.api.mapper.AnswerSetToObjectMapper; +import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.common.Predicate; +import at.ac.tuwien.kr.alpha.common.atoms.Atom; +import at.ac.tuwien.kr.alpha.common.terms.Term; + +/** + * Implementation of {@link AnswerSetToObjectMapper} that generates an office open xml workbook ("excel file") from a given answer set. + * + * Copyright (c) 2020, the Alpha Team. + */ +public class AnswerSetToWorkbookMapper implements AnswerSetToObjectMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(AnswerSetToWorkbookMapper.class); + + /** + * Creates an xlsx workbook containing all the atoms from the given {@link AnswerSet} with one sheet per predicate. All predicates with arity 0 are listed + * in a special sheet called "flags". Caution, potential resource leak: note that the returned workbook needs to be closed by the caller once it has been + * processed (written to file etc). + */ + @Override + public Workbook mapFromAnswerSet(AnswerSet answerSet) { + LOGGER.debug("Start mapping answer set to workbook"); + Workbook workbook = new XSSFWorkbook(); + // create cell style for header cells + CellStyle headerStyle = this.createHeaderStyle(workbook); + + // first, create a worksheet for 0-arity predicates + Sheet flags = this.createSheetWithHeader(workbook, headerStyle, "Flags", "Flags"); + Sheet currentPredicateSheet; + String[] headerContent; + for (Predicate pred : answerSet.getPredicates()) { + if (pred.getArity() == 0) { + this.writeAtomToSheet(flags, answerSet.getPredicateInstances(pred).first()); + } else { + headerContent = new String[pred.getArity()]; + for (int i = 0; i < headerContent.length; i++) { + headerContent[i] = "Attribute " + Integer.toString(i + 1); + } + currentPredicateSheet = this.createSheetWithHeader(workbook, headerStyle, pred.getName() + "_" + pred.getArity(), headerContent); + for (Atom atom : answerSet.getPredicateInstances(pred)) { + this.writeAtomToSheet(currentPredicateSheet, atom); + } + } + } + return workbook; + } + + private void writeAtomToSheet(Sheet sheet, Atom atom) { + int rownum = -1; + if (sheet.getLastRowNum() == 0 && sheet.getRow(0) == null) { + // sheet is empty, start at row zero + rownum = 0; + } else { + rownum = sheet.getLastRowNum() + 1; + } + Row atomRow = sheet.createRow(rownum); + List terms = atom.getTerms(); + Cell currCell; + if (terms.isEmpty()) { + // 0-arity atom + currCell = atomRow.createCell(0); + currCell.setCellValue(atom.getPredicate().getName()); + sheet.autoSizeColumn(0); + } else { + for (int i = 0; i < terms.size(); i++) { + currCell = atomRow.createCell(i); + currCell.setCellValue(terms.get(i).toString()); + sheet.autoSizeColumn(i); + } + } + } + + private CellStyle createHeaderStyle(Workbook workbook) { + CellStyle headerStyle = workbook.createCellStyle(); + Font headerFont = workbook.createFont(); + headerFont.setFontHeightInPoints((short) 11); + headerFont.setBold(true); // (short) 0x74c4f2 + headerStyle.setFont(headerFont); + headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + return headerStyle; + } + + private Sheet createSheetWithHeader(Workbook wb, CellStyle headerStyle, String sheetName, String... headerContent) { + Sheet retVal = wb.createSheet(sheetName); + Row headerRow = retVal.createRow(0); + Cell cell; + for (int i = 0; i < headerContent.length; i++) { + cell = headerRow.createCell(i); + cell.setCellStyle(headerStyle); + cell.setCellValue(headerContent[i]); + } + return retVal; + } + +} diff --git a/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java b/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java index 0f6b3d873..35981a5a5 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/config/CommandLineParser.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2016-2019, the Alpha Team. + * Copyright (c) 2016-2020, the Alpha Team. * All rights reserved. * * Additional changes made by Siemens. @@ -60,6 +60,7 @@ public class CommandLineParser { * to the handler. */ // "special", i.e. non-configuration options + //@formatter:off private static final Option OPT_HELP = Option.builder("h").longOpt("help").hasArg(false).desc("shows this help").build(); // input-specific options @@ -73,6 +74,8 @@ public class CommandLineParser { .desc("provide the asp program as a string").build(); private static final Option OPT_LITERATE = Option.builder("l").longOpt("literate") .desc("enable literate programming mode (default: " + InputConfig.DEFAULT_LITERATE + ")").build(); + private static final Option OPT_WRITE_XSLX = Option.builder("wx").longOpt("write-xlsx").hasArg(true).argName("path").type(String.class) + .desc("Write answer sets to excel files, i.e. xlsx workbooks (one workbook per answer set)").build(); // general system-wide config private static final Option OPT_GROUNDER = Option.builder("g").longOpt("grounder").hasArg(true).argName("grounder") @@ -109,8 +112,8 @@ public class CommandLineParser { .desc("use counting grid normalization instead of sorting circuit for #count (default: " + SystemConfig.DEFAULT_USE_NORMALIZATION_GRID + ")") .build(); private static final Option OPT_NO_NOGOOD_DELETION = Option.builder("dnd").longOpt("disableNoGoodDeletion") - .desc("disable the deletion of (learned, little active) nogoods (default: " - + SystemConfig.DEFAULT_DISABLE_NOGOOD_DELETION + ")") + .desc("disable the deletion of (learned, little active) nogoods (default: " + + SystemConfig.DEFAULT_DISABLE_NOGOOD_DELETION + ")") .build(); private static final Option OPT_GROUNDER_TOLERANCE_CONSTRAINTS = Option.builder("gtc").longOpt("grounderToleranceConstraints") .desc("grounder tolerance for constraints (default: " + SystemConfig.DEFAULT_GROUNDER_TOLERANCE_CONSTRAINTS + ")") @@ -121,8 +124,10 @@ public class CommandLineParser { .hasArg().argName("tolerance") .build(); private static final Option OPT_GROUNDER_ACCUMULATOR_ENABLED = Option.builder("acc").longOpt("enableAccumulator") - .desc("activates the accumulator grounding strategy by disabling removal of instances from grounder memory in certain cases (default: " + SystemConfig.DEFAULT_GROUNDER_ACCUMULATOR_ENABLED + ")") + .desc("activates the accumulator grounding strategy by disabling removal of instances from grounder memory in certain cases (default: " + + SystemConfig.DEFAULT_GROUNDER_ACCUMULATOR_ENABLED + ")") .build(); + //@formatter:on private static final Options CLI_OPTS = new Options(); @@ -137,6 +142,7 @@ public class CommandLineParser { CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_LITERATE); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_INPUT); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_ASPSTRING); + CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_WRITE_XSLX); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_GROUNDER); CommandLineParser.CLI_OPTS.addOption(CommandLineParser.OPT_SOLVER); @@ -208,6 +214,7 @@ public CommandLineParser(String cmdLineSyntax, Consumer abortAction) { this.inputOptionHandlers.put(CommandLineParser.OPT_FILTER.getOpt(), this::handleFilters); this.inputOptionHandlers.put(CommandLineParser.OPT_ASPSTRING.getOpt(), this::handleAspString); this.inputOptionHandlers.put(CommandLineParser.OPT_LITERATE.getOpt(), this::handleLiterate); + this.inputOptionHandlers.put(CommandLineParser.OPT_WRITE_XSLX.getOpt(), this::handleWriteXlsx); } public AlphaConfig parseCommandLine(String[] args) throws ParseException { @@ -335,16 +342,18 @@ private void handleBranchingHeuristic(Option opt, SystemConfig cfg) throws Parse try { cfg.setBranchingHeuristicName(branchingHeuristicName); } catch (IllegalArgumentException e) { - throw new ParseException("Unknown branching heuristic: " + branchingHeuristicName + ". Please try one of the following: " + Heuristic.listAllowedValues()); + throw new ParseException( + "Unknown branching heuristic: " + branchingHeuristicName + ". Please try one of the following: " + Heuristic.listAllowedValues()); } } - + private void handleMomsStrategy(Option opt, SystemConfig cfg) throws ParseException { String momsStrategyName = opt.getValue(SystemConfig.DEFAULT_MOMS_STRATEGY.name()); try { cfg.setMomsStrategyName(momsStrategyName); } catch (IllegalArgumentException e) { - throw new ParseException("Unknown mom's strategy: " + momsStrategyName + ". Please try one of the following: " + BinaryNoGoodPropagationEstimation.Strategy.listAllowedValues()); + throw new ParseException("Unknown mom's strategy: " + momsStrategyName + ". Please try one of the following: " + + BinaryNoGoodPropagationEstimation.Strategy.listAllowedValues()); } } @@ -365,6 +374,12 @@ private void handleLiterate(Option opt, InputConfig cfg) { cfg.setLiterate(true); } + private void handleWriteXlsx(Option opt, InputConfig cfg) { + cfg.setWriteAnswerSetsAsXlsx(true); + String outputPath = opt.getValue(InputConfig.DEFAULT_OUTFILE_PATH); + cfg.setAnswerSetFileOutputPath(outputPath); + } + private void handleStats(Option opt, SystemConfig cfg) { cfg.setPrintStats(true); } diff --git a/src/main/java/at/ac/tuwien/kr/alpha/config/InputConfig.java b/src/main/java/at/ac/tuwien/kr/alpha/config/InputConfig.java index d7401fb78..8772b2316 100644 --- a/src/main/java/at/ac/tuwien/kr/alpha/config/InputConfig.java +++ b/src/main/java/at/ac/tuwien/kr/alpha/config/InputConfig.java @@ -15,6 +15,8 @@ public class InputConfig { public static final java.util.function.Predicate DEFAULT_FILTER = p -> true; public static final boolean DEFAULT_LITERATE = false; public static final int DEFAULT_NUM_ANSWER_SETS = 0; + public static final boolean DEFAULT_WRITE_XLSX = false; + public static final String DEFAULT_OUTFILE_PATH = "alphaAnswerSet"; // current directory, files named "alphaAnswerSet.{num}.{ext}" private List aspStrings = new ArrayList<>(); private List files = new ArrayList<>(); @@ -22,6 +24,8 @@ public class InputConfig { private int numAnswerSets = InputConfig.DEFAULT_NUM_ANSWER_SETS; private Set desiredPredicates = new HashSet<>(); private Map predicateMethods = new HashMap<>(); + private boolean writeAnswerSetsAsXlsx = InputConfig.DEFAULT_WRITE_XLSX; + private String answerSetFileOutputPath; public static InputConfig forString(String str) { InputConfig retVal = new InputConfig(); @@ -85,4 +89,20 @@ public void setDesiredPredicates(Set desiredPredicates) { this.desiredPredicates = desiredPredicates; } + public boolean isWriteAnswerSetsAsXlsx() { + return this.writeAnswerSetsAsXlsx; + } + + public void setWriteAnswerSetsAsXlsx(boolean writeAnswerSetsAsXslx) { + this.writeAnswerSetsAsXlsx = writeAnswerSetsAsXslx; + } + + public String getAnswerSetFileOutputPath() { + return this.answerSetFileOutputPath; + } + + public void setAnswerSetFileOutputPath(String answerSetFileOutputPath) { + this.answerSetFileOutputPath = answerSetFileOutputPath; + } + } diff --git a/src/test/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriterTest.java b/src/test/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriterTest.java new file mode 100644 index 000000000..a63dbd639 --- /dev/null +++ b/src/test/java/at/ac/tuwien/kr/alpha/AnswerSetToXlsxWriterTest.java @@ -0,0 +1,64 @@ +package at.ac.tuwien.kr.alpha; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.Assert; +import org.junit.Test; + +import at.ac.tuwien.kr.alpha.api.mapper.impl.AnswerSetToWorkbookMapperTest; +import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.common.AnswerSetBuilder; + +public class AnswerSetToXlsxWriterTest { + + @Test + public void writeAnswerSetFilesTest() throws IOException { + AnswerSet as = new AnswerSetBuilder().predicate("bla").instance("blubb", "blubb").instance("foo", "bar").predicate("foo").instance("bar") + .instance("baz").predicate("complex").instance(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)).build(); + Path tmpDir = Files.createTempDirectory("alpha-test-xlsx-output"); + AnswerSetToXlsxWriter writer = new AnswerSetToXlsxWriter(tmpDir.toString() + "/alphaAnswerSet"); + writer.accept(0, as); + File tmpDirFile = tmpDir.toFile(); + File[] generatedFiles = tmpDirFile.listFiles(); + Assert.assertEquals(generatedFiles.length, 1); + File answerSetFile = generatedFiles[0]; + Assert.assertEquals("alphaAnswerSet.0.xlsx", answerSetFile.getName()); + Workbook wb = WorkbookFactory.create(answerSetFile); + AnswerSetToWorkbookMapperTest.assertWorkbookMatchesAnswerSet(wb, as); + wb.close(); + // clean up + answerSetFile.delete(); + tmpDirFile.delete(); + } + + @Test + public void writeUnsatTest() throws IOException { + Path tmpDir = Files.createTempDirectory("alpha-test-xlsx-unsat"); + AnswerSetToXlsxWriter.writeUnsatInfo(Paths.get(tmpDir.toString() + "/alphaAnswerSet.UNSAT.xlsx")); + File tmpDirFile = tmpDir.toFile(); + File[] generatedFiles = tmpDirFile.listFiles(); + Assert.assertEquals(generatedFiles.length, 1); + File unsatFile = generatedFiles[0]; + Assert.assertEquals("alphaAnswerSet.UNSAT.xlsx", unsatFile.getName()); + Workbook wb = WorkbookFactory.create(unsatFile); + Sheet unsatSheet = wb.getSheet("Unsatisfiable"); + Assert.assertNotNull(unsatSheet); + Cell cell = unsatSheet.getRow(0).getCell(0); + Assert.assertNotNull(cell); + String cellValue = cell.getStringCellValue(); + Assert.assertEquals("Input is unsatisfiable - No answer sets!", cellValue); + wb.close(); + // clean up + unsatFile.delete(); + tmpDirFile.delete(); + } + +} diff --git a/src/test/java/at/ac/tuwien/kr/alpha/AlphaTest.java b/src/test/java/at/ac/tuwien/kr/alpha/api/AlphaTest.java similarity index 99% rename from src/test/java/at/ac/tuwien/kr/alpha/AlphaTest.java rename to src/test/java/at/ac/tuwien/kr/alpha/api/AlphaTest.java index cb6568514..28a349edc 100644 --- a/src/test/java/at/ac/tuwien/kr/alpha/AlphaTest.java +++ b/src/test/java/at/ac/tuwien/kr/alpha/api/AlphaTest.java @@ -25,7 +25,7 @@ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package at.ac.tuwien.kr.alpha; +package at.ac.tuwien.kr.alpha.api; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -48,6 +48,7 @@ import org.junit.Ignore; import org.junit.Test; +import at.ac.tuwien.kr.alpha.AnswerSetsParser; import at.ac.tuwien.kr.alpha.common.AnswerSet; import at.ac.tuwien.kr.alpha.common.AnswerSetBuilder; import at.ac.tuwien.kr.alpha.common.DisjunctiveHead; diff --git a/src/test/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapperTest.java b/src/test/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapperTest.java new file mode 100644 index 000000000..cd4a4765e --- /dev/null +++ b/src/test/java/at/ac/tuwien/kr/alpha/api/mapper/impl/AnswerSetToWorkbookMapperTest.java @@ -0,0 +1,98 @@ +package at.ac.tuwien.kr.alpha.api.mapper.impl; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.Assert; +import org.junit.Test; + +import at.ac.tuwien.kr.alpha.api.Alpha; +import at.ac.tuwien.kr.alpha.common.AnswerSet; +import at.ac.tuwien.kr.alpha.common.AnswerSetBuilder; +import at.ac.tuwien.kr.alpha.common.Predicate; +import at.ac.tuwien.kr.alpha.common.atoms.Atom; +import at.ac.tuwien.kr.alpha.common.terms.Term; + +public class AnswerSetToWorkbookMapperTest { + + private AnswerSetToWorkbookMapper mapper = new AnswerSetToWorkbookMapper(); + + @Test + public void smokeTest() throws IOException { + AnswerSet as = new AnswerSetBuilder().predicate("bla").instance("blubb", "blubb").instance("foo", "bar").predicate("foo").instance("bar") + .instance("baz").predicate("complex").instance(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)).build(); + Workbook wb = this.mapper.mapFromAnswerSet(as); + Assert.assertNotNull(wb.getSheet("Flags")); + Assert.assertNotNull(wb.getSheet("bla_2")); + Assert.assertNotNull(wb.getSheet("foo_1")); + Assert.assertNotNull(wb.getSheet("complex_3")); + wb.close(); + } + + @Test + public void solveAndWriteWorkbookTest() { + //@formatter:off + String progstr = "aFlag. oneMoreFlag. yetAnotherFlag. createPs. maxP(5). r(s(1, 2, 3), 4). r(bla, blubb). r(foo, bar(baaz))." + + "p(0) :- createPs. " + + "p(N) :- p(I), N = I + 1, N <= MX, maxP(MX)." + + "q(A, B) :- p(A), p(B)."; + //@formatter:on + Alpha alpha = new Alpha(); + List answerSets = alpha.solve(alpha.readProgramString(progstr, null)).collect(Collectors.toList()); + Assert.assertEquals(1, answerSets.size()); + AnswerSet as = answerSets.get(0); + Workbook answerSetWorkbook = this.mapper.mapFromAnswerSet(as); + AnswerSetToWorkbookMapperTest.assertWorkbookMatchesAnswerSet(answerSetWorkbook, as); + } + + public static void assertWorkbookMatchesAnswerSet(Workbook wb, AnswerSet as) { + for (Predicate pred : as.getPredicates()) { + if (pred.getArity() == 0) { + boolean flagFound = false; + Sheet flagsSheet = wb.getSheet("Flags"); + Assert.assertNotNull(flagsSheet); + for (Row row : flagsSheet) { + if (row.getCell(0).getStringCellValue().equals(pred.getName())) { + flagFound = true; + break; + } + } + Assert.assertTrue("0-arity predicate " + pred.getName() + " not found in workbook!", flagFound); + } else { + Sheet predicateSheet = wb.getSheet(pred.getName() + "_" + pred.getArity()); + for (Atom atom : as.getPredicateInstances(pred)) { + boolean atomFound = false; + Assert.assertNotNull(predicateSheet); + for (Row row : predicateSheet) { + if (AnswerSetToWorkbookMapperTest.rowMatchesAtom(row, atom)) { + atomFound = true; + break; + } + } + Assert.assertTrue("Atom " + atom.toString() + " not found in workbook!", atomFound); + } + } + } + } + + private static boolean rowMatchesAtom(Row row, Atom atom) { + List terms = atom.getTerms(); + Cell cell; + for (int i = 0; i < terms.size(); i++) { + cell = row.getCell(i); + if (cell == null) { + return false; + } + if (!(cell.getStringCellValue().equals(terms.get(i).toString()))) { + return false; + } + } + return true; + } + +}