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 14 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
10 changes: 10 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,10 @@
package edu.kit.provideq.toolbox;

/**
* Represents a bound value with its type.
*
* @param value the estimated value
* @param boundType the type of the bound
*/
public record Bound(String value, BoundType boundType) {
}
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 bounds.
*/
public enum BoundType {
/**
* An upper bound.
*/
UPPER,
/**
* A lower bound.
*/
LOWER
}
10 changes: 10 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/BoundWithInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package edu.kit.provideq.toolbox;

/**
* Represents a bound estimation for the solution of a problem.
*
* @param bound the value
* @param executionTime the time it took to estimate the value
*/
public record BoundWithInfo(Bound bound, long executionTime) {
}
13 changes: 13 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/api/BoundDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package edu.kit.provideq.toolbox.api;

import edu.kit.provideq.toolbox.BoundType;
import edu.kit.provideq.toolbox.BoundWithInfo;

public record BoundDto(String bound, BoundType boundType, long executionTime) {
public BoundDto(BoundWithInfo boundWithInfo) {
this(
boundWithInfo.bound().value(),
boundWithInfo.bound().boundType(),
boundWithInfo.executionTime());
}
}
130 changes: 130 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,130 @@
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.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.Objects;
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().isPresent())
.map(this::defineGetRoute)
.reduce(RouterFunction::and)
.orElse(null);
}

/**
* Estimate Operation: GET /problems/TYPE/{problemId}/bound.
*/
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<BoundDto> bound;
try {
problem.estimateBound();
bound = Mono.just(new BoundDto(problem.getBound().orElseThrow()));
} catch (IllegalStateException | NoSuchElementException 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(BoundDto.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;
}

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

import edu.kit.provideq.toolbox.BoundWithInfo;
import edu.kit.provideq.toolbox.Solution;
import edu.kit.provideq.toolbox.meta.Problem;
import edu.kit.provideq.toolbox.meta.ProblemSolver;
import edu.kit.provideq.toolbox.meta.ProblemState;
import edu.kit.provideq.toolbox.meta.setting.SolverSetting;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* Data transfer object for {@link Problem problems}, used in REST API request bodies and responses.
Expand All @@ -16,6 +18,7 @@ public class ProblemDto<InputT, ResultT> {
private String typeId;
private InputT input;
private Solution<ResultT> solution;
private Optional<BoundWithInfo> bound;
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
private ProblemState state;
private String solverId;
private List<SolverSetting> solverSettings;
Expand All @@ -38,6 +41,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 +71,10 @@ public Solution<ResultT> getSolution() {
return solution;
}

public Optional<BoundWithInfo> getBound() {
return bound;
}

public ProblemState getState() {
return state;
}
Expand Down Expand Up @@ -100,6 +108,7 @@ public String toString() {
+ ", solverId=" + solverId
+ ", input=" + input
+ ", solution=" + solution
+ ", value=" + bound
+ ", solverSettings=" + solverSettings
+ ", subProblems=" + subProblems
+ '}';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class DemonstratorConfiguration {
public static final ProblemType<String, String> DEMONSTRATOR = new ProblemType<>(
"demonstrator",
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 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
29 changes: 29 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.BoundWithInfo;
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 BoundWithInfo bound;
private ProblemState state;
private ProblemSolver<InputT, ResultT> solver;
private List<SolverSetting> solverSettings;
Expand Down Expand Up @@ -82,6 +84,29 @@ public Mono<Solution<ResultT>> solve() {
});
}

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

var optionalEstimator = this.type.getEstimator();
if (optionalEstimator.isEmpty()) {
throw new IllegalStateException("Cannot estimate value without an estimator!");
}
var estimator = optionalEstimator.get();

long start = System.currentTimeMillis();

var bound = estimator.apply(this.input);
Elscrux marked this conversation as resolved.
Show resolved Hide resolved
long finish = System.currentTimeMillis();
var executionTime = finish - start;

var boundWithExecutionTime = new BoundWithInfo(bound, executionTime);

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

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

public Optional<BoundWithInfo> getBound() {
return Optional.ofNullable(bound);
}
}
23 changes: 20 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,37 @@
package edu.kit.provideq.toolbox.meta;

import edu.kit.provideq.toolbox.Bound;
import java.util.Optional;
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 +55,20 @@ public Class<ResultT> getResultClass() {
return resultClass;
}

/**
* Returns the bound estimator for this problem type.
*/
public Optional<Function<InputT, Bound>> getEstimator() {
return Optional.ofNullable(estimator);
}

@Override
public String toString() {
return "ProblemType{"
+ "id='%s'".formatted(id)
+ ", inputClass=%s".formatted(inputClass)
+ ", resultClass=%s".formatted(resultClass)
+ ", estimator?=%s".formatted(estimator != null)
+ '}';
}
}
Loading
Loading