diff --git a/python/knapsack/knapsack.py b/python/knapsack/knapsack.py new file mode 100644 index 00000000..e1863aa8 --- /dev/null +++ b/python/knapsack/knapsack.py @@ -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") diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 00000000..3a04554f --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,3 @@ +# This file describes the python package requirements for the knapsack problem solver + +knapsack-pip diff --git a/scripts/install-solver-dependencies.sh b/scripts/install-solver-dependencies.sh index a578b513..1be60548 100755 --- a/scripts/install-solver-dependencies.sh +++ b/scripts/install-solver-dependencies.sh @@ -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" \ No newline at end of file diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java new file mode 100644 index 00000000..e6f7547a --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/KnapsackConfiguration.java @@ -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 KNAPSACK = new ProblemType<>( + "knapsack", + String.class, + String.class + ); + + @Bean + ProblemManager getKnapsackManager( + PythonKnapsackSolver pythonKnapsackSolver, + ResourceProvider resourceProvider + ) { + return new ProblemManager<>( + KNAPSACK, + Set.of(pythonKnapsackSolver), + loadExampleProblems(resourceProvider) + ); + } + + private Set> 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); + } + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/KnapsackSolver.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/KnapsackSolver.java new file mode 100644 index 00000000..1e00ecf2 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/KnapsackSolver.java @@ -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 { + @Override + public ProblemType getProblemType() { + return KnapsackConfiguration.KNAPSACK; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/PythonKnapsackSolver.java b/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/PythonKnapsackSolver.java new file mode 100644 index 00000000..face8d1d --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/knapsack/solvers/PythonKnapsackSolver.java @@ -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> solve( + String input, + SubRoutineResolver subRoutineResolver + ) { + var solution = new Solution(); + + var processResult = context + .getBean( + PythonProcessRunner.class, + knapsackPath, + "knapsack.py") + .addProblemFilePathToProcessCommand() + .addSolutionFilePathToProcessCommand() + .run(getProblemType(), solution.getId(), input); + + return Mono.just(processResult.applyTo(solution)); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 08786e1b..d1c6ccd2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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=/ diff --git a/src/main/resources/edu/kit/provideq/toolbox/knapsack/10-items.txt b/src/main/resources/edu/kit/provideq/toolbox/knapsack/10-items.txt new file mode 100644 index 00000000..08260224 --- /dev/null +++ b/src/main/resources/edu/kit/provideq/toolbox/knapsack/10-items.txt @@ -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 \ No newline at end of file diff --git a/src/test/java/edu/kit/provideq/toolbox/api/KnapsackSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/KnapsackSolversTest.java new file mode 100644 index 00000000..b30b9b6c --- /dev/null +++ b/src/test/java/edu/kit/provideq/toolbox/api/KnapsackSolversTest.java @@ -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 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 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()); + } +} \ No newline at end of file