Skip to content

Commit

Permalink
Merge pull request #77 from ProvideQ/feat/knapsack-problem-mask
Browse files Browse the repository at this point in the history
Added Knapsack problem mask and solver
  • Loading branch information
koalamitice authored Aug 12, 2024
2 parents ba18fd2 + 44cc988 commit 794636c
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 0 deletions.
45 changes: 45 additions & 0 deletions python/knapsack/knapsack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Knapsack solver using the knapsack-pip library

import sys
from knapsack01 import HSKnapsack

if len(sys.argv) != 3:
raise TypeError('This script expects exactly 2 arguments. Input file (argument 1) and output file (argument 2).')

input_path = sys.argv[1]
output_path = sys.argv[2]

with open(input_path, 'r') as input_file:
lines = input_file.readlines()

# first line gives number of items
number_items : int = int(lines[0])

profits: list[int] = []
weights: list[int] = []

# read items into profits, weights lists
for i in range(number_items):
item = lines[i + 1].split(' ')
profits.append(int(item[1]))
weights.append(int(item[2]))

# last line contains maximum capacity of the knapsack
capacity: int = int(lines[-1])

# let the library solve the problem ^^
knapsack = HSKnapsack(capacity, profits, weights)
max_profit, max_solution = knapsack.maximize()

# max_solution only contains indicator whether item is present, collect items for solutions in a list
items_in_solution: list[list[int]] = []
for i in range(number_items):
if max_solution[i] == 1:
items_in_solution.append([profits[i], weights[i]])

# returning the solution as a single line with the maximum profit
# followed by the items present represented by profit/weight
with open(output_path, 'w') as output_file:
output_file.write(str(max_profit) + '\n')
for item in items_in_solution:
output_file.write(f"{item[0]} {item[1]}\n")
3 changes: 3 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This file describes the python package requirements for the knapsack problem solver

knapsack-pip
1 change: 1 addition & 0 deletions scripts/install-solver-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ source /opt/conda/bin/activate gams
pip install -r "$REPO_DIR/gams/requirements.txt"
pip install -r "$REPO_DIR/qiskit/requirements.txt"
pip install -r "$REPO_DIR/cirq/requirements.txt"
pip install -r "$REPO_DIR/python/requirements.txt"
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package edu.kit.provideq.toolbox.knapsack;

import edu.kit.provideq.toolbox.ResourceProvider;
import edu.kit.provideq.toolbox.knapsack.solvers.PythonKnapsackSolver;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemManager;
import edu.kit.provideq.toolbox.meta.ProblemType;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Definition and registration of the Knapsack problem.
*/
@Configuration
public class KnapsackConfiguration {
/**
* An optimization problem:
* For given items each with a weight and value, determine which items are part of a collection
* where the total weight is less than or equal to a given limit
* and the sum of values is as large as possible.
*/
public static final ProblemType<String, String> KNAPSACK = new ProblemType<>(
"knapsack",
String.class,
String.class
);

@Bean
ProblemManager<String, String> getKnapsackManager(
PythonKnapsackSolver pythonKnapsackSolver,
ResourceProvider resourceProvider
) {
return new ProblemManager<>(
KNAPSACK,
Set.of(pythonKnapsackSolver),
loadExampleProblems(resourceProvider)
);
}

private Set<Problem<String, String>> loadExampleProblems(
ResourceProvider resourceProvider
) {
try {
var problemInputStream = Objects.requireNonNull(
getClass().getResourceAsStream("10-items.txt"),
"10-items example for Knapsack is unavailable!"
);
var problem = new Problem<>(KNAPSACK);
problem.setInput(resourceProvider.readStream(problemInputStream));
return Set.of(problem);
} catch (IOException e) {
throw new RuntimeException("Could not load example problems", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package edu.kit.provideq.toolbox.knapsack.solvers;

import edu.kit.provideq.toolbox.knapsack.KnapsackConfiguration;
import edu.kit.provideq.toolbox.meta.ProblemSolver;
import edu.kit.provideq.toolbox.meta.ProblemType;

/**
* A solver for Knapsack problems.
*/
public abstract class KnapsackSolver implements ProblemSolver<String, String> {
@Override
public ProblemType<String, String> getProblemType() {
return KnapsackConfiguration.KNAPSACK;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package edu.kit.provideq.toolbox.knapsack.solvers;

import edu.kit.provideq.toolbox.PythonProcessRunner;
import edu.kit.provideq.toolbox.Solution;
import edu.kit.provideq.toolbox.meta.SubRoutineResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class PythonKnapsackSolver extends KnapsackSolver {
private final String knapsackPath;
private final ApplicationContext context;

@Autowired
public PythonKnapsackSolver(
@Value("${python.directory}/knapsack") String knapsackPath,
ApplicationContext context) {
this.knapsackPath = knapsackPath;
this.context = context;
}

@Override
public String getName() {
return "Python Knapsack";
}

@Override
public Mono<Solution<String>> solve(
String input,
SubRoutineResolver subRoutineResolver
) {
var solution = new Solution<String>();

var processResult = context
.getBean(
PythonProcessRunner.class,
knapsackPath,
"knapsack.py")
.addProblemFilePathToProcessCommand()
.addSolutionFilePathToProcessCommand()
.run(getProblemType(), solution.getId(), input);

return Mono.just(processResult.applyTo(solution));
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ qiskit.directory.qubo=${qiskit.directory}/qubo
cirq.directory=cirq
cirq.directory.max-cut=${cirq.directory}/max-cut

python.directory=python
python.directory.knapsack=${python.directory}/knapsack

examples.directory=examples
springdoc.swagger-ui.path=/
12 changes: 12 additions & 0 deletions src/main/resources/edu/kit/provideq/toolbox/knapsack/10-items.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
10
1 505 23
2 352 26
3 458 20
4 220 18
5 354 32
6 414 27
7 498 29
8 545 26
9 473 30
10 543 27
99
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package edu.kit.provideq.toolbox.api;

import static edu.kit.provideq.toolbox.knapsack.KnapsackConfiguration.KNAPSACK;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import edu.kit.provideq.toolbox.SolutionStatus;
import edu.kit.provideq.toolbox.meta.ProblemManagerProvider;
import edu.kit.provideq.toolbox.meta.ProblemSolver;
import edu.kit.provideq.toolbox.meta.ProblemState;
import java.time.Duration;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@SpringBootTest
@AutoConfigureMockMvc
public class KnapsackSolversTest {
@Autowired
private WebTestClient client;

@Autowired
private ProblemManagerProvider problemManagerProvider;

@BeforeEach
void beforeEach() {
this.client = this.client.mutate()
.responseTimeout(Duration.ofSeconds(20))
.build();
}

@SuppressWarnings("OptionalGetWithoutIsPresent")
Stream<Arguments> provideArguments() {
var problemManager = problemManagerProvider.findProblemManagerForType(KNAPSACK).get();

return ApiTestHelper.getAllArgumentCombinations(problemManager)
.map(list -> Arguments.of(list.get(0), list.get(1)));
}

@ParameterizedTest
@MethodSource("provideArguments")
void testKnapsackSolver(ProblemSolver<String, String> solver, String input) {
var problem = ApiTestHelper.createProblem(client, solver, input, KNAPSACK);
assertEquals(ProblemState.SOLVED, problem.getState());
assertNotNull(problem.getSolution());
assertEquals(SolutionStatus.SOLVED, problem.getSolution().getStatus());
}
}

0 comments on commit 794636c

Please sign in to comment.