Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution Quality Evaluator #106

Merged
merged 22 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/Bound.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.kit.provideq.toolbox;

/**
* Represents a bound estimation for the solution of a problem.
*
* @param bound the bound value
* @param boundType the type of the bound
* @param executionTime the time it took to estimate the bound
*/
public record Bound(String bound, BoundType boundType, long executionTime) {
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
}
15 changes: 15 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/BoundType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package edu.kit.provideq.toolbox;

/**
* Represents the bound type of a bound.
*/
public enum BoundType {
/**
* An upper bound.
*/
UPPER,
/**
* A lower bound.
*/
LOWER
}
126 changes: 126 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package edu.kit.provideq.toolbox.api;

import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

import edu.kit.provideq.toolbox.Bound;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemManager;
import edu.kit.provideq.toolbox.meta.ProblemManagerProvider;
import edu.kit.provideq.toolbox.meta.ProblemType;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import java.util.UUID;
import org.springdoc.core.fn.builders.operation.Builder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

/**
* This router handles requests regarding {@link Problem} instance solution bound estimations.
* The /bound endpoint is only available for problem types that have an estimator.
*/
@Configuration
@EnableWebFlux
public class EstimationRouter {
public static final String PROBLEM_ID_PARAM_NAME = "problemId";
private ProblemManagerProvider managerProvider;

@Bean
RouterFunction<ServerResponse> getEstimationRoutes() {
return managerProvider.getProblemManagers().stream()
.filter(manager -> manager.getType().getEstimator() != null)
.map(this::defineGetRoute)
.reduce(RouterFunction::and)
.orElse(null);
}

private RouterFunction<ServerResponse> defineGetRoute(ProblemManager<?, ?> manager) {
return route().GET(
getEstimationRouteForProblemType(manager.getType()),
accept(APPLICATION_JSON),
req -> handleGet(manager, req),
ops -> handleGetDocumentation(manager, ops)
).build();
}

private <InputT, ResultT> Mono<ServerResponse> handleGet(
ProblemManager<InputT, ResultT> manager,
ServerRequest req
) {
var problemId = req.pathVariable(PROBLEM_ID_PARAM_NAME);
var problem = findProblemOrThrow(manager, problemId);

Mono<Bound> bound;
try {
bound = problem.estimateBound();
} catch (IllegalStateException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}

return ok().body(bound, new ParameterizedTypeReference<>() {
});
}

private void handleGetDocumentation(
ProblemManager<?, ?> manager,
Builder ops
) {
ProblemType<?, ?> problemType = manager.getType();
ops
.operationId(getEstimationRouteForProblemType(problemType))
.tag(problemType.getId())
.description("Estimates the solution bound for the problem with the given ID.")
.parameter(parameterBuilder().in(ParameterIn.PATH).name(PROBLEM_ID_PARAM_NAME))
.response(responseBuilder()
.responseCode(String.valueOf(HttpStatus.OK.value()))
.content(getOkResponseContent())
);
}

private static org.springdoc.core.fn.builders.content.Builder getOkResponseContent() {
return contentBuilder()
.mediaType(APPLICATION_JSON_VALUE)
.schema(schemaBuilder().implementation(Bound.class));
}

private <InputT, ResultT> Problem<InputT, ResultT> findProblemOrThrow(
ProblemManager<InputT, ResultT> manager,
String id
) {
UUID uuid;
try {
uuid = UUID.fromString(id);
} catch (IllegalArgumentException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid problem ID");
}

return manager.findInstanceById(uuid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"Could not find a problem for this type with this problem ID!"));
}

private String getEstimationRouteForProblemType(ProblemType<?, ?> type) {
return "/problems/%s/{%s}/bound".formatted(type.getId(), PROBLEM_ID_PARAM_NAME);
}

@Autowired
void setManagerProvider(ProblemManagerProvider managerProvider) {
this.managerProvider = managerProvider;
}

}
8 changes: 8 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/api/ProblemDto.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.kit.provideq.toolbox.api;

import edu.kit.provideq.toolbox.Bound;
import edu.kit.provideq.toolbox.Solution;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemSolver;
Expand All @@ -16,6 +17,7 @@ public class ProblemDto<InputT, ResultT> {
private String typeId;
private InputT input;
private Solution<ResultT> solution;
private Bound bound;
private ProblemState state;
private String solverId;
private List<SolverSetting> solverSettings;
Expand All @@ -38,6 +40,7 @@ public static <InputT, ResultT> ProblemDto<InputT, ResultT> fromProblem(
dto.typeId = problem.getType().getId();
dto.input = problem.getInput().orElse(null);
dto.solution = problem.getSolution();
dto.bound = problem.getBound();
dto.state = problem.getState();
dto.solverId = problem.getSolver()
.map(ProblemSolver::getId)
Expand Down Expand Up @@ -67,6 +70,10 @@ public Solution<ResultT> getSolution() {
return solution;
}

public Bound getBound() {
return bound;
}

public ProblemState getState() {
return state;
}
Expand Down Expand Up @@ -100,6 +107,7 @@ public String toString() {
+ ", solverId=" + solverId
+ ", input=" + input
+ ", solution=" + solution
+ ", bound=" + bound
+ ", solverSettings=" + solverSettings
+ ", subProblems=" + subProblems
+ '}';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package edu.kit.provideq.toolbox.demonstrators;

import edu.kit.provideq.toolbox.Bound;
import edu.kit.provideq.toolbox.BoundType;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemManager;
import edu.kit.provideq.toolbox.meta.ProblemType;
import java.util.Set;
import java.util.function.Function;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -16,9 +19,14 @@ public class DemonstratorConfiguration {
public static final ProblemType<String, String> DEMONSTRATOR = new ProblemType<>(
"demonstrator",
String.class,
String.class
String.class,
null
);

private Function<String, Bound> demonstratorEstimator() {
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
throw new UnsupportedOperationException("Estimation of this problem type is not supported");
}

@Bean
ProblemManager<String, String> getDemonstratorManager(
CplexMipDemonstrator cplexMipDemonstrator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class DeadFeatureConfiguration {
public static final ProblemType<String, String> FEATURE_MODEL_ANOMALY_DEAD = new ProblemType<>(
"feature-model-anomaly-dead",
String.class,
String.class
String.class,
null
);

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class VoidModelConfiguration {
public static final ProblemType<String, String> FEATURE_MODEL_ANOMALY_VOID = new ProblemType<>(
"feature-model-anomaly-void",
String.class,
String.class
String.class,
null
);

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class KnapsackConfiguration {
public static final ProblemType<String, String> KNAPSACK = new ProblemType<>(
"knapsack",
String.class,
String.class
String.class,
null
);

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public class MaxCutConfiguration {
public static final ProblemType<String, String> MAX_CUT = new ProblemType<>(
"max-cut",
String.class,
String.class
String.class,
null
);

@Bean
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/meta/Problem.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.kit.provideq.toolbox.meta;

import edu.kit.provideq.toolbox.Bound;
import edu.kit.provideq.toolbox.Solution;
import edu.kit.provideq.toolbox.meta.setting.SolverSetting;
import java.util.Collections;
Expand Down Expand Up @@ -29,6 +30,7 @@ public class Problem<InputT, ResultT> {

private InputT input;
private Solution<ResultT> solution;
private Bound bound;
private ProblemState state;
private ProblemSolver<InputT, ResultT> solver;
private List<SolverSetting> solverSettings;
Expand Down Expand Up @@ -82,6 +84,26 @@ public Mono<Solution<ResultT>> solve() {
});
}

public Mono<Bound> estimateBound() {
if (this.input == null) {
throw new IllegalStateException("Cannot estimate bound without input!");
}

if (this.type.getEstimator() == null) {
throw new IllegalStateException("Cannot estimate bound without an estimator!");
}

long start = System.currentTimeMillis();

var bound = this.type.getEstimator().apply(this.input);
long finish = System.currentTimeMillis();
var executionTime = finish - start;
var boundWithExecutionTime = new Bound(bound.bound(), bound.boundType(), executionTime);

return Mono.just(boundWithExecutionTime)
.doOnNext(boundValue -> this.bound = boundValue);
}

public UUID getId() {
return id;
}
Expand Down Expand Up @@ -208,4 +230,8 @@ public String toString() {
+ ", solver=" + solver
+ '}';
}

public Bound getBound() {
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
return bound;
}
}
22 changes: 19 additions & 3 deletions src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
package edu.kit.provideq.toolbox.meta;

import edu.kit.provideq.toolbox.Bound;
import java.util.function.Function;

/**
* The type of problem to solve.
*/
public class ProblemType<InputT, ResultT> {
private final String id;
private final Class<InputT> inputClass;
private final Class<ResultT> resultClass;
private final Function<InputT, Bound> estimator;

/**
* Defines a new problem type.
*
* @param id a unique string identifier for this type of problem.
* @param inputClass the Java class object corresponding to the {@link InputT} type parameter.
* @param id a unique string identifier for this type of problem.
* @param inputClass the Java class object corresponding to the {@link InputT} type parameter.
* @param resultClass the Java class object corresponding to the {@link ResultT} type parameter.
* @param estimator the bound estimator for this problem type.
* null if estimation is not supported.
*/
public ProblemType(
String id,
Class<InputT> inputClass,
Class<ResultT> resultClass
Class<ResultT> resultClass,
Function<InputT, Bound> estimator
) {
this.id = id;
this.inputClass = inputClass;
this.resultClass = resultClass;
this.estimator = estimator;
}

/**
Expand All @@ -46,12 +54,20 @@ public Class<ResultT> getResultClass() {
return resultClass;
}

/**
* Returns the bound estimator for this problem type.
*/
public Function<InputT, Bound> getEstimator() {
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
return estimator;
}

@Override
public String toString() {
return "ProblemType{"
+ "id='%s'".formatted(id)
+ ", inputClass=%s".formatted(inputClass)
+ ", resultClass=%s".formatted(resultClass)
+ ", estimator?=%s".formatted(estimator != null)
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


Elscrux marked this conversation as resolved.
Show resolved Hide resolved
/**
* Definition and registration of the "Quadratic Unconstrained Binary Optimization" problem.
*/
Expand All @@ -28,7 +29,8 @@ public class QuboConfiguration {
public static final ProblemType<String, String> QUBO = new ProblemType<>(
"qubo",
String.class,
String.class
String.class,
null
);

@Bean
Expand Down
Loading
Loading