diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelReader.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelReader.java index b988a57..1c13b6b 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelReader.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelReader.java @@ -8,6 +8,11 @@ import java.util.Iterator; import java.util.List; +/** + * ExcelReader class that reads the input from an Excel file. + * It expects two sheets: one for items ('items') and one for + * knapsacks ('knapsacks'). Each sheet should have a header row. + */ public class ExcelReader { public Input readExcelFile(String filePath) throws IOException { diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelWriter.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelWriter.java index 8767601..76e7450 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelWriter.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/ExcelWriter.java @@ -6,8 +6,19 @@ import java.io.IOException; import java.util.List; +/** + * ExcelWriter class to write the solution to an Excel file. + * It creates two sheets: one for assignments and one for unassigned items. + */ public class ExcelWriter { + /** + * Writes the solution to an Excel file. + * + * @param solution The solution containing assignments and unassigned items. + * @param filePath The path where the Excel file will be saved. + * @throws IOException If an I/O error occurs while writing the file. + */ public void writeSolutionToExcel(Solution solution, String filePath) throws IOException { Workbook workbook = new XSSFWorkbook(); @@ -56,7 +67,10 @@ private void createUnassignedSheet(Sheet sheet, List unassigned) { } } -// Assuming these classes are defined as follows: +/** + * Represents a solution containing assignments of items to knapsacks + * and a list of unassigned items. + */ class Solution { private List assignments; private List unassigned; @@ -75,6 +89,9 @@ public List getUnassigned() { } } +/** + * Represents an assignment of an item to a knapsack. + */ class Assignment { private String itemId; private String knapsackId; diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java index 883091a..9add2e5 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Main.java @@ -11,6 +11,11 @@ public final class Main { + /** + * Main entry point for the application. + * + * @param args Command line arguments. + */ public static void main(String[] args) { try { // Parse arguments. @@ -43,7 +48,7 @@ public static void main(String[] args) { // Setup Gurobi environment and model. GRBEnv env = new GRBEnv(true); - env.set("OutputFlag", "0"); // Disable output if needed + // env.set("OutputFlag", "0"); // Disable output if needed env.start(); GRBModel model = new GRBModel(env); @@ -53,9 +58,10 @@ public static void main(String[] args) { // Create assignment variable for each item in each knapsack. // Variables are binary, indicating whether an item is assigned to a knapsack. List variables = new ArrayList<>(); - List inputItems = input.getItems(); - for (Knapsack knapsack : input.getKnapsacks()) { - for (Item item : inputItems) { + List items = input.getItems(); + List knapsacks = input.getKnapsacks(); + for (Knapsack knapsack : knapsacks) { + for (Item item : items) { variables.add(model.addVar(0.0, 1.0, 0.0, GRB.BINARY, knapsack.getId() + "_" + item.getId())); } } @@ -64,29 +70,29 @@ public static void main(String[] args) { model.update(); // Create capacity constraint. - for (int i = 0; i < input.getKnapsacks().size(); ++i) { - Knapsack knapsack = input.getKnapsacks().get(i); + for (int i = 0; i < knapsacks.size(); ++i) { + Knapsack knapsack = knapsacks.get(i); GRBLinExpr knapsackExpr = new GRBLinExpr(); - for (int j = 0; j < inputItems.size(); ++j) { - knapsackExpr.addTerm(inputItems.get(j).getWeight(), variables.get(i * inputItems.size() + j)); + for (int j = 0; j < items.size(); ++j) { + knapsackExpr.addTerm(items.get(j).getWeight(), variables.get(i * items.size() + j)); } model.addConstr(knapsackExpr, GRB.LESS_EQUAL, knapsack.getCapacity(), "capacity_" + knapsack.getId()); } // Ensure that each item can only be assigned once. - for (int j = 0; j < inputItems.size(); ++j) { + for (int j = 0; j < items.size(); ++j) { GRBLinExpr itemExpr = new GRBLinExpr(); - for (int i = 0; i < input.getKnapsacks().size(); ++i) { - itemExpr.addTerm(1.0, variables.get(i * inputItems.size() + j)); + for (int i = 0; i < knapsacks.size(); ++i) { + itemExpr.addTerm(1.0, variables.get(i * items.size() + j)); } - model.addConstr(itemExpr, GRB.LESS_EQUAL, 1.0, "item_assignment_" + inputItems.get(j).getId()); + model.addConstr(itemExpr, GRB.LESS_EQUAL, 1.0, "item_assignment_" + items.get(j).getId()); } // Create the objective function. GRBLinExpr objectiveExpr = new GRBLinExpr(); - for (int i = 0; i < input.getKnapsacks().size(); ++i) { - for (int j = 0; j < inputItems.size(); ++j) { - objectiveExpr.addTerm(inputItems.get(j).getValue(), variables.get(i * inputItems.size() + j)); + for (int i = 0; i < knapsacks.size(); ++i) { + for (int j = 0; j < items.size(); ++j) { + objectiveExpr.addTerm(items.get(j).getValue(), variables.get(i * items.size() + j)); } } model.setObjective(objectiveExpr, GRB.MAXIMIZE); @@ -98,13 +104,13 @@ public static void main(String[] args) { List assignments = new ArrayList<>(); Set unassignedItems = new HashSet<>(); for (int i = 0; i < variables.size(); ++i) { - int knapsackIndex = i / inputItems.size(); - int itemIndex = i % inputItems.size(); + int knapsackIndex = i / items.size(); + int itemIndex = i % items.size(); if (variables.get(i).get(GRB.DoubleAttr.X) > 0.5) { assignments - .add(new Assignment(inputItems.get(itemIndex).getId(), input.getKnapsacks().get(knapsackIndex).getId())); + .add(new Assignment(items.get(itemIndex).getId(), knapsacks.get(knapsackIndex).getId())); } else { - unassignedItems.add(inputItems.get(itemIndex).getId()); + unassignedItems.add(items.get(itemIndex).getId()); } } Solution solution = new Solution(assignments, new ArrayList<>(unassignedItems)); @@ -125,7 +131,11 @@ public static void main(String[] args) { "Gurobi", convertStatus(model.get(GRB.IntAttr.Status)), model.get(GRB.IntAttr.NumVars), - model.get(GRB.IntAttr.NumConstrs)); + model.get(GRB.IntAttr.NumConstrs), + items.size(), + knapsacks.size(), + assignments.size(), + unassignedItems.size()); // Write output. Output.write(output, options.getOutputPath()); @@ -140,6 +150,11 @@ public static void main(String[] args) { } } + /** + * Prepares the output directory by creating necessary subdirectories. + * + * @param outputPath The path to the output directory. + */ private static void prepareOutputDirectory(String outputPath) { // Prepare output directory if it does not exist. Path solutionsPath = Paths.get(outputPath, "solutions"); @@ -164,6 +179,12 @@ private static void prepareOutputDirectory(String outputPath) { } } + /** + * Converts Gurobi status codes to human-readable strings. + * + * @param status The Gurobi status code. + * @return A string representation of the status. + */ public static String convertStatus(int status) { switch (status) { case GRB.Status.LOADED: diff --git a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java index eaa3f68..57da944 100644 --- a/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java +++ b/java-gurobi-multiknapsack-multiexcel/src/main/java/com/nextmv/example/Output.java @@ -1,35 +1,59 @@ package com.nextmv.example; -import java.util.List; -import java.util.ArrayList; import java.nio.file.Files; import java.nio.file.Paths; import com.google.gson.Gson; +/** + * Output class that wraps the result of the optimization run. + * Since this app outputs non-JSON data, the output only contains + * the statistics of the run. The solution is written to separate + * files in the output directory. + */ public class Output { + /** + * StatisticsRun contains more generic metrics about the run. + */ private final class StatisticsRun { private double duration; } + /** + * StatisticsResult contains metrics about the optimization result. + */ private final class StatisticsResult { private double value; private StatisticsResultCustom custom; } + /** + * StatisticsResultCustom contains custom metrics about the optimization + * result. + */ private final class StatisticsResultCustom { private String provider; private String status; private int variables; private int constraints; + private int items; + private int knapsacks; + private int assigned; + private int unassigned; } + /** + * Statistics is the root object for the metrics/statistics. + */ private final class Statistics { private String schema = "v1"; private StatisticsRun run; private StatisticsResult result; } + /** + * The statistics object that contains all the metrics. + */ private final Statistics statistics; public Output( @@ -38,7 +62,11 @@ public Output( String provider, String status, int variables, - int constraints) { + int constraints, + int items, + int knapsacks, + int assigned, + int unassigned) { this.statistics = new Statistics(); this.statistics.run = new StatisticsRun(); this.statistics.run.duration = duration; @@ -49,6 +77,10 @@ public Output( this.statistics.result.custom.status = status; this.statistics.result.custom.constraints = constraints; this.statistics.result.custom.variables = variables; + this.statistics.result.custom.items = items; + this.statistics.result.custom.knapsacks = knapsacks; + this.statistics.result.custom.assigned = assigned; + this.statistics.result.custom.unassigned = unassigned; } public static void write(Output output, String outputPath) {