diff --git a/core/src/main/java/es/urjc/etsii/grafo/events/types/SolutionGeneratedEvent.java b/core/src/main/java/es/urjc/etsii/grafo/events/types/SolutionGeneratedEvent.java index 5046bf705..26c988da5 100644 --- a/core/src/main/java/es/urjc/etsii/grafo/events/types/SolutionGeneratedEvent.java +++ b/core/src/main/java/es/urjc/etsii/grafo/events/types/SolutionGeneratedEvent.java @@ -34,20 +34,22 @@ public class SolutionGeneratedEvent, I extends Instance> private final long timeToBest; private final Algorithm algorithm; private final SoftReference solution; + private final Map solutionProperties; /** * Create a new SolutionGeneratedEvent * - * @param iteration solution iteration - * @param solution generated solution - * @param experimentName experiment name - * @param algorithm algorithm that generated this solution - * @param executionTime time used to generate this solution - * @param timeToBest time needed ot reach the best solution. timeToBest = totalTime - timeSinceLastModification - * @param metrics both framework calculated and user defined metrics - * @param timeStatsEvents + * @param iteration solution iteration + * @param solution generated solution + * @param experimentName experiment name + * @param algorithm algorithm that generated this solution + * @param executionTime time used to generate this solution + * @param timeToBest time needed to reach the best solution. timeToBest = totalTime - timeSinceLastModification + * @param solutionProperties custom properties computed for the solution + * @param metrics both framework calculated and user defined metrics + * @param timeStatsEvents time statistics events */ - public SolutionGeneratedEvent(boolean success, String iteration, String instancePath, S solution, String experimentName, Algorithm algorithm, long executionTime, long timeToBest, MetricsStorage metrics, List timeStatsEvents) { + public SolutionGeneratedEvent(boolean success, String iteration, String instancePath, S solution, String experimentName, Algorithm algorithm, long executionTime, long timeToBest, Map solutionProperties, MetricsStorage metrics, List timeStatsEvents) { super(); this.success = success; this.iteration = iteration; @@ -57,6 +59,7 @@ public SolutionGeneratedEvent(boolean success, String iteration, String instance this.algorithm = algorithm; this.executionTime = executionTime; this.timeToBest = timeToBest; + this.solutionProperties = solutionProperties != null ? solutionProperties : Map.of(); this.algorithmName = algorithm.getName(); this.metrics = metrics; this.timeStatsEvents = timeStatsEvents; @@ -166,4 +169,12 @@ public MetricsStorage getMetrics() { public boolean isSuccess() { return success; } + + /** + * Get custom properties computed for this solution + * @return Map where key is property name, value is the computed property value + */ + public Map getSolutionProperties() { + return solutionProperties; + } } diff --git a/core/src/main/java/es/urjc/etsii/grafo/executors/Executor.java b/core/src/main/java/es/urjc/etsii/grafo/executors/Executor.java index 0725a7546..bf92c7cad 100644 --- a/core/src/main/java/es/urjc/etsii/grafo/executors/Executor.java +++ b/core/src/main/java/es/urjc/etsii/grafo/executors/Executor.java @@ -180,7 +180,7 @@ protected void processWorkUnitResult(WorkUnitResult r, ProgressBar pb) { io.exportSolution(r, SolutionExportFrequency.ALL); } - var solutionGenerated = new SolutionGeneratedEvent<>(r.success(), r.iteration(), r.instancePath(), r.solution(), r.experimentName(), r.algorithm(), r.executionTime(), r.timeToTarget(), r.metrics(), r.timeData()); + var solutionGenerated = new SolutionGeneratedEvent<>(r.success(), r.iteration(), r.instancePath(), r.solution(), r.experimentName(), r.algorithm(), r.executionTime(), r.timeToTarget(), r.solutionProperties(), r.metrics(), r.timeData()); EventPublisher.getInstance().publishEvent(solutionGenerated); if (log.isDebugEnabled()) { log.debug(String.format("\t%s.\tT(s): %.3f \tTTB(s): %.3f \t%s", r.iteration(), nanosToSecs(r.executionTime()), nanosToSecs(r.timeToTarget()), r.solution())); diff --git a/core/src/main/java/es/urjc/etsii/grafo/io/serializers/excel/ExcelSerializer.java b/core/src/main/java/es/urjc/etsii/grafo/io/serializers/excel/ExcelSerializer.java index da301c58c..1d0c2bf6a 100644 --- a/core/src/main/java/es/urjc/etsii/grafo/io/serializers/excel/ExcelSerializer.java +++ b/core/src/main/java/es/urjc/etsii/grafo/io/serializers/excel/ExcelSerializer.java @@ -125,9 +125,16 @@ public void _serializeResults(String experimentName, List customPropertyKeysSet = new LinkedHashSet<>(); + for (var result : results) { + customPropertyKeysSet.addAll(result.getSolutionProperties().keySet()); + } + String[] customProperties = customPropertyKeysSet.toArray(new String[0]); + + var areaString = String.format("%s1:%s%s", convertNumToColString(0), convertNumToColString(getCommonHeaders().length + customProperties.length - 1), 1_000_000); var area = new AreaReference(areaString, SpreadsheetVersion.EXCEL2007); - headRawSheet(rawSheet); + headRawSheet(rawSheet, customProperties); fillPivotSheet(pivotSheet, area, rawSheet); // Check and fill instance sheet if appropiate @@ -390,7 +397,13 @@ protected enum CType { public void fillRawSheet(SXSSFSheet rawSheet, boolean maximizing, List> results, List referenceResultProviders) { // Best values per instance Map bestValuesPerInstance = bestResultPerInstance(Context.getMainObjective(), results, referenceResultProviders, maximizing); - String[] customProperties = new String[0]; // TODO review and adapt impl + + // Extract unique custom property keys from all results + Set customPropertyKeysSet = new LinkedHashSet<>(); + for (var result : results) { + customPropertyKeysSet.addAll(result.getSolutionProperties().keySet()); + } + String[] customProperties = customPropertyKeysSet.toArray(new String[0]); // Create headers String[] commonHeaders = getCommonHeaders(); @@ -429,6 +442,13 @@ public void fillRawSheet(SXSSFSheet rawSheet, boolean maximizing, List rowIterator = instanceSheet.iterator(); -// -// Assertions.assertTrue(rowIterator.hasNext(), "Missing headers in raw result sheet"); -// var header = rowIterator.next(); -// Assertions.assertEquals(11, header.getLastCellNum(), "Missing headers in instance sheet"); -// // Get the index of "prop1" -// var prop1Index = -1; -// for (int i = 0; i < header.getLastCellNum(); i++) { -// if (header.getCell(i).getStringCellValue().equals("prop1")) { -// prop1Index = i; -// break; -// } -// } -// Assertions.assertTrue(prop1Index >= 0, "Missing prop1 header in raw result sheet"); -// // Get the index of "prop2" -// var prop2Index = -1; -// for (int i = 0; i < header.getLastCellNum(); i++) { -// if (header.getCell(i).getStringCellValue().equals("prop2")) { -// prop2Index = i; -// break; -// } -// } -// Assertions.assertTrue(prop2Index >= 0, "Missing prop2 header in raw result sheet"); -// Assertions.assertTrue(rowIterator.hasNext(), "Missing values in raw result sheet"); -// var firstData = rowIterator.next(); -// Assertions.assertEquals(4, firstData.getCell(prop1Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); -// Assertions.assertEquals(1, firstData.getCell(prop2Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); -// var secondData = rowIterator.next(); -// Assertions.assertEquals(6, secondData.getCell(prop1Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); -// Assertions.assertEquals(2, secondData.getCell(prop2Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); -// } -// } + @Test + void writeExcelWithCustomProperties(@TempDir Path temp) throws IOException { + var instance = new TestInstance("writeCustomPropsTest"); + var excel = initExcel(Optional.empty(), temp, referencesGenerator(10.10, 10.10), TestHelperFactory.simpleInstanceManager(instance)); + var excelPath = temp.resolve("customProperties.xlsx"); + + // Create events with custom properties + var data = Arrays.asList( + TestHelperFactory.solutionGenerated("writeCustomPropsTest", "TestExp", "Alg1", 1, 100.0, 1000, 500, Map.of("prop1", 4, "prop2", 1)), + TestHelperFactory.solutionGenerated("writeCustomPropsTest", "TestExp", "Alg1", 2, 95.0, 1100, 600, Map.of("prop1", 6, "prop2", 2)) + ); + + excel.serializeResults("TestExperiment", data, excelPath); + File excelFile = excelPath.toFile(); + + try (var wb = new XSSFWorkbook(new FileInputStream(excelFile))) { + XSSFSheet rawSheet = wb.getSheet(ExcelSerializer.RAW_SHEET); + Iterator rowIterator = rawSheet.iterator(); + + Assertions.assertTrue(rowIterator.hasNext(), "Missing headers in raw result sheet"); + var header = rowIterator.next(); + Assertions.assertEquals(11, header.getLastCellNum(), "Should have 9 common headers + 2 custom property headers"); + + // Find the index of "prop1" + var prop1Index = -1; + for (int i = 0; i < header.getLastCellNum(); i++) { + if (header.getCell(i) != null && "prop1".equals(header.getCell(i).getStringCellValue())) { + prop1Index = i; + break; + } + } + Assertions.assertTrue(prop1Index >= 0, "Missing prop1 header in raw result sheet"); + + // Find the index of "prop2" + var prop2Index = -1; + for (int i = 0; i < header.getLastCellNum(); i++) { + if (header.getCell(i) != null && "prop2".equals(header.getCell(i).getStringCellValue())) { + prop2Index = i; + break; + } + } + Assertions.assertTrue(prop2Index >= 0, "Missing prop2 header in raw result sheet"); + + Assertions.assertTrue(rowIterator.hasNext(), "Missing values in raw result sheet"); + var firstData = rowIterator.next(); + Assertions.assertEquals(4.0, firstData.getCell(prop1Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); + Assertions.assertEquals(1.0, firstData.getCell(prop2Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); + + var secondData = rowIterator.next(); + Assertions.assertEquals(6.0, secondData.getCell(prop1Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); + Assertions.assertEquals(2.0, secondData.getCell(prop2Index).getNumericCellValue(), "Data of solution custom properties is not correct in raw result sheet"); + } + } } diff --git a/core/src/test/java/es/urjc/etsii/grafo/testutil/TestHelperFactory.java b/core/src/test/java/es/urjc/etsii/grafo/testutil/TestHelperFactory.java index 7a058d1eb..a40051613 100644 --- a/core/src/test/java/es/urjc/etsii/grafo/testutil/TestHelperFactory.java +++ b/core/src/test/java/es/urjc/etsii/grafo/testutil/TestHelperFactory.java @@ -17,7 +17,13 @@ public class TestHelperFactory { public static SolutionGeneratedEvent solutionGenerated(String instanceName, String expName, String algName, int iter, double score, long time, long ttb){ var solution = new TestSolution(new TestInstance(instanceName), score); var algorithm = new TestAlgorithm(algName); - return new SolutionGeneratedEvent<>(true, String.valueOf(iter), instanceName, solution, expName, algorithm, time, ttb, new MetricsStorage(), new ArrayList<>()); + return new SolutionGeneratedEvent<>(true, String.valueOf(iter), instanceName, solution, expName, algorithm, time, ttb, Map.of(), new MetricsStorage(), new ArrayList<>()); + } + + public static SolutionGeneratedEvent solutionGenerated(String instanceName, String expName, String algName, int iter, double score, long time, long ttb, Map solutionProperties){ + var solution = new TestSolution(new TestInstance(instanceName), score); + var algorithm = new TestAlgorithm(algName); + return new SolutionGeneratedEvent<>(true, String.valueOf(iter), instanceName, solution, expName, algorithm, time, ttb, solutionProperties, new MetricsStorage(), new ArrayList<>()); } // public static SolutionGeneratedEvent solutionGenerated(String instanceName, String expName, String algName, int iter, double score, long time, long ttb, Map> properties){