diff --git a/qiskit/qubo/qubo_qiskit.py b/qiskit/qubo/qubo_qiskit.py new file mode 100644 index 00000000..44291144 --- /dev/null +++ b/qiskit/qubo/qubo_qiskit.py @@ -0,0 +1,42 @@ +import copy +import sys + +from qiskit.algorithms.minimum_eigensolvers import QAOA +from qiskit.algorithms.optimizers import COBYLA +from qiskit.primitives import Sampler +from qiskit_optimization import QuadraticProgram +from qiskit_optimization.algorithms import MinimumEigenOptimizer +from qiskit_optimization.problems import VarType + +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] + +qp = QuadraticProgram() +qp.read_from_lp_file(input_path) + + +def relax_problem(problem): + """Change all variables to continuous.""" + relaxed_problem = copy.deepcopy(problem) + for variable in relaxed_problem.variables: + variable.vartype = VarType.CONTINUOUS + + return relaxed_problem + +qubo = qp +# qubo = relax_problem(QuadraticProgramToQubo().convert(qp)) +# print(qp.prettyprint()) + +qaoa_mes = QAOA(Sampler(), optimizer=COBYLA(), initial_point=[0.0, 1.0]) + +qaoa = MinimumEigenOptimizer(qaoa_mes) + +qaoa_result = qaoa.solve(qubo) +print(qaoa_result.prettyprint()) + +f = open(output_path, 'w') +f.write(qaoa_result.prettyprint()) +f.close() diff --git a/qiskit/requirements.txt b/qiskit/requirements.txt index b0bb8f99..f04ef842 100644 --- a/qiskit/requirements.txt +++ b/qiskit/requirements.txt @@ -5,5 +5,5 @@ numpy pygmlparser qiskit == 0.44.* -qiskit_optimization +qiskit_optimization[cplex] qiskit-aer diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 49d081b4..142a9875 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -4,6 +4,7 @@ import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; +import edu.kit.provideq.toolbox.qubo.SolveQuboRequest; import edu.kit.provideq.toolbox.sat.SolveSatRequest; /** @@ -38,7 +39,14 @@ public enum ProblemType { * @see * "Explaining Anomalies in Feature Models", Kowal et al., 2026 */ - FEATURE_MODEL_ANOMALY_VOID("feature-model-anomaly-void", SolveFeatureModelRequest.class); + FEATURE_MODEL_ANOMALY_VOID("feature-model-anomaly-void", SolveFeatureModelRequest.class), + /** + * QUBO (Quadratic Unconstrained Binary Optimization) + * A combinatorial optimization problem. + * For a given quadratic term with binary decision variables, + * find the minimal variable assignment of the term. + */ + QUBO("qubo", SolveQuboRequest.class); private final String id; private final Class> requestType; diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/QuboMetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboMetaSolver.java new file mode 100644 index 00000000..7458e573 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/QuboMetaSolver.java @@ -0,0 +1,44 @@ +package edu.kit.provideq.toolbox.qubo; + +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import edu.kit.provideq.toolbox.qubo.solvers.QiskitQuboSolver; +import edu.kit.provideq.toolbox.qubo.solvers.QuboSolver; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Simple {@link MetaSolver} for MaxCut problems. + */ +@Component +public class QuboMetaSolver extends MetaSolver { + + @Autowired + public QuboMetaSolver(QiskitQuboSolver qiskitQuboSolver) { + super(ProblemType.QUBO, qiskitQuboSolver); + } + + @Override + public QuboSolver findSolver( + Problem problem, + List metaSolverSettings) { + return (new ArrayList<>(this.solvers)).get((new Random()).nextInt(this.solvers.size())); + } + + @Override + public List getExampleProblems() { + return List.of(""" + Maximize + 3x + y + Subject To + Binary + x y + End + """); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/SolveQuboRequest.java b/src/main/java/edu/kit/provideq/toolbox/qubo/SolveQuboRequest.java new file mode 100644 index 00000000..47381ab8 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/SolveQuboRequest.java @@ -0,0 +1,11 @@ +package edu.kit.provideq.toolbox.qubo; + +import edu.kit.provideq.toolbox.SolveRequest; + +/** + * POST Requests to /solve/qubo should have a response body of this form. + * The needed formula the qubo formula to solve in the + * LP format. + */ +public class SolveQuboRequest extends SolveRequest { +} diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QiskitQuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QiskitQuboSolver.java new file mode 100644 index 00000000..371370cf --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QiskitQuboSolver.java @@ -0,0 +1,60 @@ +package edu.kit.provideq.toolbox.qubo.solvers; + +import edu.kit.provideq.toolbox.PythonProcessRunner; +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SubRoutinePool; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class QiskitQuboSolver extends QuboSolver { + private final String quboPath; + private final ApplicationContext context; + + @Autowired + public QiskitQuboSolver( + @Value("${qiskit.directory.qubo}") String quboPath, + ApplicationContext context) { + this.quboPath = quboPath; + this.context = context; + } + + @Override + public String getName() { + return "Qiskit Qubo"; + } + + @Override + public boolean canSolve(Problem problem) { + return problem.type() == ProblemType.QUBO; + } + + @Override + public void solve(Problem problem, Solution solution, + SubRoutinePool subRoutinePool) { + // Run Qiskit solver via console + var processResult = context + .getBean( + PythonProcessRunner.class, + quboPath, + "qubo_qiskit.py") + .addProblemFilePathToProcessCommand() + .addSolutionFilePathToProcessCommand() + .problemFileName("problem.lp") + .run(problem.type(), solution.getId(), problem.problemData()); + + // Return if process failed + if (!processResult.success()) { + solution.setDebugData(processResult.output()); + solution.abort(); + return; + } + + solution.setSolutionData(processResult.output()); + solution.complete(); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuboSolver.java b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuboSolver.java new file mode 100644 index 00000000..3224ecd8 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/qubo/solvers/QuboSolver.java @@ -0,0 +1,6 @@ +package edu.kit.provideq.toolbox.qubo.solvers; + +import edu.kit.provideq.toolbox.meta.ProblemSolver; + +public abstract class QuboSolver implements ProblemSolver { +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a179b8b9..3d1d213e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,6 +6,7 @@ gams.directory.sat=${gams.directory}/sat qiskit.directory=qiskit qiskit.directory.max-cut=${qiskit.directory}/max-cut +qiskit.directory.qubo=${qiskit.directory}/qubo cirq.directory=cirq cirq.directory.max-cut=${cirq.directory}/max-cut