Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions algorithms-modules/algorithms-optimization/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target/
.settings/
.classpath
.project
58 changes: 58 additions & 0 deletions algorithms-modules/algorithms-optimization/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>algorithms-optimization</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>algorithms-optimization</name>

<parent>
<groupId>com.baeldung</groupId>
<artifactId>algorithms-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>${commons-math3.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ojalgo</groupId>
<artifactId>ojalgo</artifactId>
<version>${ojalgo.version}</version>
</dependency>
</dependencies>

<properties>
<commons-math3.version>3.6.1</commons-math3.version>
<ojalgo.version>56.2.0</ojalgo.version>
</properties>

</project>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.baeldung.algorithms.optimization.lp;

public interface AssignmentSolver {

AssignmentSolution solve(double[][] cost);

}
Original file line number Diff line number Diff line change
@@ -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<LinearConstraint> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -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<Arguments> 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}
}
)
);
}
}
1 change: 1 addition & 0 deletions algorithms-modules/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<module>algorithms-miscellaneous-9</module>
<module>algorithms-miscellaneous-10</module>
<module>algorithms-numeric</module>
<module>algorithms-optimization</module>
<module>algorithms-searching</module>
<module>algorithms-sorting</module>
<module>algorithms-sorting-2</module>
Expand Down