diff --git a/algorithms-modules/algorithms-optimization/.gitignore b/algorithms-modules/algorithms-optimization/.gitignore new file mode 100644 index 000000000000..30b2b7442c55 --- /dev/null +++ b/algorithms-modules/algorithms-optimization/.gitignore @@ -0,0 +1,4 @@ +/target/ +.settings/ +.classpath +.project \ No newline at end of file diff --git a/algorithms-modules/algorithms-optimization/pom.xml b/algorithms-modules/algorithms-optimization/pom.xml new file mode 100644 index 000000000000..7f2f1c5bc44a --- /dev/null +++ b/algorithms-modules/algorithms-optimization/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + algorithms-optimization + 0.0.1-SNAPSHOT + algorithms-optimization + + + com.baeldung + algorithms-modules + 1.0.0-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + + + + org.apache.commons + commons-math3 + ${commons-math3.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.ojalgo + ojalgo + ${ojalgo.version} + + + + + 3.6.1 + 56.2.0 + + + \ No newline at end of file diff --git a/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolution.java b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolution.java new file mode 100644 index 000000000000..b1bd86d07f0f --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolution.java @@ -0,0 +1,47 @@ +package com.baeldung.algorithms.optimization.lp; + +public class AssignmentSolution { + + private double totalCost; + private double[][] assignment; + + public AssignmentSolution(double totalCost, double[][] assignment) { + this.totalCost = totalCost; + this.assignment = assignment; + } + + public double getTotalCost() { + return totalCost; + } + + public void setTotalCost(double totalCost) { + this.totalCost = totalCost; + } + + public double[][] getAssignment() { + return assignment; + } + + public void setAssignment(double[][] assignment) { + this.assignment = assignment; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("AssignmentSolution\n"); + sb.append("Total Cost: ").append(totalCost).append("\n"); + sb.append("Assignment Matrix:\n"); + for (int i = 0; i < assignment.length; i++) { + sb.append("[ "); + for (int j = 0; j < assignment[i].length; j++) { + sb.append(String.format("%.0f", assignment[i][j])); + if (j < assignment[i].length - 1) { + sb.append(", "); + } + } + sb.append(" ]\n"); + } + return sb.toString(); + } +} diff --git a/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolver.java b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolver.java new file mode 100644 index 000000000000..86422b398208 --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/AssignmentSolver.java @@ -0,0 +1,7 @@ +package com.baeldung.algorithms.optimization.lp; + +public interface AssignmentSolver { + + AssignmentSolution solve(double[][] cost); + +} diff --git a/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/CommonsMathAssignmentSolver.java b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/CommonsMathAssignmentSolver.java new file mode 100644 index 000000000000..a7f354a05666 --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/CommonsMathAssignmentSolver.java @@ -0,0 +1,80 @@ +package com.baeldung.algorithms.optimization.lp; + +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.math3.optim.PointValuePair; +import org.apache.commons.math3.optim.linear.LinearConstraint; +import org.apache.commons.math3.optim.linear.LinearConstraintSet; +import org.apache.commons.math3.optim.linear.LinearObjectiveFunction; +import org.apache.commons.math3.optim.linear.NonNegativeConstraint; +import org.apache.commons.math3.optim.linear.Relationship; +import org.apache.commons.math3.optim.linear.SimplexSolver; +import org.apache.commons.math3.optim.nonlinear.scalar.GoalType; + +public class CommonsMathAssignmentSolver implements AssignmentSolver { + + public AssignmentSolution solve(double[][] t) { + + int volunteers = t.length; + int locations = t[0].length; + int vars = volunteers * locations; + + // Objective function coefficients + double[] x = new double[vars]; + for (int i = 0; i < volunteers; i++) { + for (int j = 0; j < locations; j++) { + x[index(i, j, locations)] = t[i][j]; + } + } + + LinearObjectiveFunction objective = new LinearObjectiveFunction(x, 0); + + Collection constraints = new ArrayList<>(); + + // Each volunteer assigned to exactly one location + for (int i = 0; i < volunteers; i++) { + double[] x_i = new double[vars]; + for (int j = 0; j < locations; j++) { + x_i[index(i, j, locations)] = 1.0; + } + constraints.add(new LinearConstraint(x_i, Relationship.EQ, 1.0)); + } + + // Each location gets exactly one volunteer + for (int j = 0; j < locations; j++) { + double[] x_j = new double[vars]; + for (int i = 0; i < volunteers; i++) { + x_j[index(i, j, locations)] = 1.0; + } + constraints.add(new LinearConstraint(x_j, Relationship.EQ, 1.0)); + } + + // Solve LP + SimplexSolver solver = new SimplexSolver(); + PointValuePair solution = solver.optimize( + objective, + new LinearConstraintSet(constraints), + GoalType.MINIMIZE, + new NonNegativeConstraint(true) + ); + + double totalCost = solution.getValue(); + double[] point = solution.getPoint(); + + // Rebuild assignment matrix + double[][] assignment = new double[volunteers][locations]; + for (int i = 0; i < volunteers; i++) { + for (int j = 0; j < locations; j++) { + assignment[i][j] = point[index(i, j, locations)]; + } + } + + return new AssignmentSolution(totalCost, assignment); + } + + private int index(int i, int j, int locations) { + return i * locations + j; + } + +} diff --git a/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/OjAlgoAssignmentSolver.java b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/OjAlgoAssignmentSolver.java new file mode 100644 index 000000000000..1097e9cae786 --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/main/java/com/baeldung/algorithms/optimization/lp/OjAlgoAssignmentSolver.java @@ -0,0 +1,57 @@ +package com.baeldung.algorithms.optimization.lp; + +import org.ojalgo.optimisation.ExpressionsBasedModel; +import org.ojalgo.optimisation.Expression; +import org.ojalgo.optimisation.Variable; + +public class OjAlgoAssignmentSolver implements AssignmentSolver { + + public AssignmentSolution solve(double[][] t) { + + int volunteers = t.length; + int locations = t[0].length; + + ExpressionsBasedModel model = new ExpressionsBasedModel(); + Variable[][] x = new Variable[volunteers][locations]; + + // Create binary decision variables + for (int i = 0; i < volunteers; i++) { + for (int j = 0; j < locations; j++) { + x[i][j] = model + .newVariable("Assignment_" + i + "_" + j) + .binary() + .weight(t[i][j]); + } + } + + // Each volunteer is assigned to exactly one location + for (int i = 0; i < volunteers; i++) { + Expression volunteerConstraint = model.addExpression("Volunteer_" + i).level(1); + for (int j = 0; j < locations; j++) { + volunteerConstraint.set(x[i][j], 1); + } + } + + // Each location gets exactly one volunteer + for (int j = 0; j < locations; j++) { + Expression locationConstraint = model.addExpression("Location_" + j).level(1); + for (int i = 0; i < volunteers; i++) { + locationConstraint.set(x[i][j], 1); + } + } + + // Solve + var result = model.minimise(); + double totalCost = result.getValue(); + + // Extract assignment matrix + double[][] assignment = new double[volunteers][locations]; + for (int i = 0; i < volunteers; i++) { + for (int j = 0; j < locations; j++) { + assignment[i][j] = x[i][j].getValue().doubleValue(); + } + } + + return new AssignmentSolution(totalCost, assignment); + } +} diff --git a/algorithms-modules/algorithms-optimization/src/main/resources/logback.xml b/algorithms-modules/algorithms-optimization/src/main/resources/logback.xml new file mode 100644 index 000000000000..7d900d8ea884 --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/algorithms-modules/algorithms-optimization/src/test/java/com/baeldung/algorithms/optimization/lp/AssignmentSolverTest.java b/algorithms-modules/algorithms-optimization/src/test/java/com/baeldung/algorithms/optimization/lp/AssignmentSolverTest.java new file mode 100644 index 000000000000..df6981b4a82c --- /dev/null +++ b/algorithms-modules/algorithms-optimization/src/test/java/com/baeldung/algorithms/optimization/lp/AssignmentSolverTest.java @@ -0,0 +1,73 @@ +package com.baeldung.algorithms.optimization.lp; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class AssignmentSolverTest { + + @ParameterizedTest + @MethodSource("assignmentMatrices") + void whenSolveAssignmentMatrixByOjAlgo_thenTheTotalCostIsMinimized(double[][] cost, double expectedTotalCost, double[][] expectedAssignment) { + // given + AssignmentSolver solver = new OjAlgoAssignmentSolver(); + + // when + AssignmentSolution solution = solver.solve(cost); + + // then + assertThat(solution.getTotalCost()).isEqualTo(expectedTotalCost); + assertThat(solution.getAssignment()).isEqualTo(expectedAssignment); + } + + @ParameterizedTest + @MethodSource("assignmentMatrices") + void whenSolveAssignmentMatrixByCommonMaths_thenTheTotalCostIsMinimized(double[][] cost, double expectedTotalCost, double[][] expectedAssignment) { + // given + AssignmentSolver solver = new CommonsMathAssignmentSolver(); + + // when + AssignmentSolution solution = solver.solve(cost); + + // then + assertThat(solution.getTotalCost()).isEqualTo(expectedTotalCost); + assertThat(solution.getAssignment()).isEqualTo(expectedAssignment); + } + + static Stream assignmentMatrices() { + return Stream.of( + Arguments.of( + new double[][] { + {27, 6, 21}, + {18, 12, 9}, + {15, 24, 3} + }, + 27.0, + new double[][] { + {0, 1, 0}, + {1, 0, 0}, + {0, 0, 1} + } + ), + Arguments.of( + new double[][] { + {9, 2, 7, 8}, + {6, 4, 3, 7}, + {5, 8, 1, 8}, + {7, 6, 9, 4} + }, + 13.0, + new double[][] { + {0, 1, 0, 0}, + {1, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1} + } + ) + ); + } +} diff --git a/algorithms-modules/pom.xml b/algorithms-modules/pom.xml index be78261bb788..b027784dfc88 100644 --- a/algorithms-modules/pom.xml +++ b/algorithms-modules/pom.xml @@ -26,6 +26,7 @@ algorithms-miscellaneous-9 algorithms-miscellaneous-10 algorithms-numeric + algorithms-optimization algorithms-searching algorithms-sorting algorithms-sorting-2