Skip to content

Commit

Permalink
Implement support for the Quantagonia QUBO solver on PlanQK
Browse files Browse the repository at this point in the history
  • Loading branch information
Elscrux committed Dec 20, 2023
1 parent bd41ea8 commit 3b98e9b
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public boolean canSolve(Problem<String> problem) {
@Override
public Mono<Solution<String>> solve(Problem<String> problem,
Solution<String> solution,
SubRoutinePool subRoutinePool) {
SolveOptions solveOptions) {
// Run Qiskit solver via console
var processResult = context
.getBean(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package edu.kit.provideq.toolbox.qubo.solvers;

import com.fasterxml.jackson.annotation.JsonProperty;
import de.asbestian.jplex.input.LpFileReader;
import de.asbestian.jplex.input.Variable;
import edu.kit.provideq.toolbox.Solution;
import edu.kit.provideq.toolbox.authentication.AuthenticationOptions;
import edu.kit.provideq.toolbox.integration.planqk.PlanQkApi;
import edu.kit.provideq.toolbox.integration.planqk.PlanQkRunner;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemType;
import edu.kit.provideq.toolbox.meta.SolveOptions;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.eclipse.collections.api.map.ImmutableMap;
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;

/**
* {@link ProblemType#QUBO} solver using the Quantagonia QUBO solver hosted on the PlanQK platform.
*/
@Component
public class QuantagoniaQuboSolver extends QuboSolver {
private final String quboPath;

private final ApplicationContext context;

@Autowired
public QuantagoniaQuboSolver(
@Value("${qiskit.directory.qubo}") String quboPath,
ApplicationContext context) {
this.quboPath = quboPath;
this.context = context;
}

@Override
public AuthenticationOptions getAuthenticationOptions() {
return new AuthenticationOptions(
"PlanQK",
"PlanQK Docs: https://docs.platform.planqk.de/applications.html",
true);
}

@Override
public String getName() {
return "Quantagonia QUBO via PlanQK";
}

@Override
public boolean canSolve(Problem<String> problem) {
return problem.type() == ProblemType.QUBO;
}

@Override
public Mono<Solution<String>> solve(Problem<String> problem,
Solution<String> solution,
SolveOptions solveOptions) {
// Check if token is provided
if (solveOptions.authentication() == null || solveOptions.authentication().token() == null) {
solution.setDebugData("No token provided");
solution.abort();
return Mono.just(solution);
}

// Convert problem data string to buffered reader
var problemDataReader = new BufferedReader(new StringReader(problem.problemData()));

// Parse lp data
LpFileReader lpReader = new LpFileReader(problemDataReader);

// Convert lp to quantagonia qubo problem
var quboProblem = new QuantagoniaQuboProblem();
switch (lpReader.getObjective(0).sense()) {
case MAX:
quboProblem.sense = "MAXIMIZE";
break;
case MIN:
quboProblem.sense = "MINIMIZE";
break;
case UNDEF:
solution.setDebugData("Objective sense is undefined");
solution.abort();
return Mono.just(solution);
default:
throw new IllegalArgumentException();
}

var variables = new HashSet<String>();
for (Variable variable : lpReader.getContinuousVariables()) {
System.out.println(variable.name());

// This name is either
// - a single variable (x0)
// - a product of variables (x0 * x1)
// - an exponent of a variable (x0 ^ 2)
String name = variable.name();

// Check product
String[] factors = name.split("\\*");
if (factors.length > 1) {
ImmutableMap<String, Double> coefficients = lpReader.getObjective(0).coefficients();
var coefficient = coefficients.get(name);

// Get factors (x0 => 0)
double i = getVariableIndex(factors[0]);
double j = getVariableIndex(factors[1]);

variables.add(factors[0].trim());
variables.add(factors[1].trim());

// Add quadratic term at i j with coefficient / -4
quboProblem.matrix.quadratic.add(List.of(i, j, coefficient / -4));
} else {
// Check exponent
String[] exponent = name.split("\\^");
if (exponent.length > 1) {
ImmutableMap<String, Double> coefficients = lpReader.getObjective(0).coefficients();
var coefficient = coefficients.get(name);

// Get factor (x0 => 0)
double i = Double.parseDouble(exponent[0].trim().replace("x", ""));

variables.add(exponent[0].trim());

// Add quadratic term at i i with coefficient / 2
quboProblem.matrix.linear.add(List.of(i, i, Math.abs(coefficient / 2)));
}

// Ignore single variable
}
}

quboProblem.matrix.numberOfVariables = variables.size();

// Run Quantagonia QUBO solver via PlanQK
return context.getBean(
PlanQkRunner.class,
quboPath)
.problemProperties(new PlanQkRunner.ProblemProperties("/v1/hqp/job"))
.statusProperties(
new PlanQkRunner.StatusProperties<>(
QuantagoniaQuboStatus.class,
"/v1/hqp/job/%s/status",
status -> switch (status) {
case Finished -> PlanQkApi.JobStatus.SUCCEEDED;
case Terminated, Error -> PlanQkApi.JobStatus.FAILED;
case Running, Timeout -> PlanQkApi.JobStatus.RUNNING;
case Created -> PlanQkApi.JobStatus.PENDING;
})
)
.solutionProperties(new PlanQkRunner.SolutionProperties<>(
QuantagoniaQuboSolution.class,
"/v1/hqp/job/%s/results"
))
.addAuthentication(solveOptions.authentication().token())
.run(
"/quantagonia/quantagonia-s-free-qubo-solver/1.0.0",
quboProblem,
QuantagoniaQuboSolution.class)
.flatMap(solutionResult -> {
// Return if process failed
if (!solutionResult.success()) {
solution.setDebugData(solutionResult.output());
solution.abort();
return Mono.just(solution);
}

String solutionString = String.join("\n",
solutionResult.solution().solution.stream().map(Object::toString).toList());

solution.setSolutionData(solutionString);
solution.setDebugData(solutionResult.solution().log);

solution.complete();

return Mono.just(solution);
});
}

/**
* Get the index of a variable from its name.
* Currently only supports variables of the form x0, x1, x2, ...
*
* @param variableName The name of the variable
* @return The index of the variable
*/
double getVariableIndex(String variableName) {
return Double.parseDouble(variableName.trim().replace("x", ""));
}

static class QuantagoniaQuboProblem {
public String sense;

public Matrix matrix = new Matrix();

public static class Matrix {
@JsonProperty("n")
public int numberOfVariables;

public List<List<Double>> linear = new ArrayList<>();

public List<List<Double>> quadratic = new ArrayList<>();
}
}

enum QuantagoniaQuboStatus {
Finished,
Terminated,
Error,
Running,
Created,
Timeout
}

static class QuantagoniaQuboSolution {
public String log;
public String objective;
public List<Integer> solution;
}
}
6 changes: 0 additions & 6 deletions src/main/resources/examples/qubo/linear-problem

This file was deleted.

9 changes: 9 additions & 0 deletions src/main/resources/examples/qubo/qubo1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Maximize
-2.15956824*x0^2 - 0.03075656*x0*x1 - 0.44910424*x0*x2 + 0.27371876*x0*x3
+ 0.04067172*x0*x4 + 0.0335906*x0*x5 - 0.21845774*x1^2 + 0.12173696*x1*x2
+ 0.008018*x1*x3 - 0.02683716*x1*x4 - 0.0591748*x1*x5 - 1.970706*x2^2
- 0.09229252*x2*x3 + 0.2099914*x2*x4 - 0.03616476*x2*x5 - 1.2087634*x3^2
- 0.1496046*x3*x4 + 0.03781288*x3*x5 - 1.59679268*x4^2 - 0.30467804*x4*x5
- 2.16929088*x5^2 + 3.418*x0 + 2.0913*x1 + 6.2415*x2 + 4.4436*x3 + 10.892*x4
+ 3.4051*x5
End

0 comments on commit 3b98e9b

Please sign in to comment.