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 Comparator #127

Merged
merged 23 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f3e186c
added bound comparison
zaibod Nov 25, 2024
b2ab207
improved api response
zaibod Nov 25, 2024
1f290cc
chore: split overly long line
zaibod Nov 25, 2024
b717ed9
refactor: added alternative problemtype constructor
zaibod Nov 25, 2024
09882c8
added bound comparison
zaibod Nov 25, 2024
a6b0d3c
improved api response
zaibod Nov 25, 2024
0639a78
chore: split overly long line
zaibod Nov 25, 2024
bba23ee
refactor: added alternative problemtype constructor
zaibod Nov 25, 2024
3cd0c77
Merge branch 'feature/solution-quality-comparator' of https://github.…
zaibod Nov 25, 2024
71bb262
added bound comparison
zaibod Nov 25, 2024
54aaccf
improved api response
zaibod Nov 25, 2024
ecabb65
chore: split overly long line
zaibod Nov 25, 2024
a695a16
refactor: added alternative problemtype constructor
zaibod Nov 25, 2024
0cedb74
added bound comparison
zaibod Nov 25, 2024
2dcee16
improved api response
zaibod Nov 25, 2024
e10527d
chore: split overly long line
zaibod Nov 25, 2024
284e397
refactor: added alternative problemtype constructor
zaibod Nov 25, 2024
d30d4ad
refactor: changed bound/solution to float
zaibod Nov 25, 2024
af73aad
Merge branch 'feature/solution-quality-comparator' of https://github.…
zaibod Nov 25, 2024
b80bca9
Revert "Merge branch 'feature/solution-quality-comparator' of https:/…
zaibod Nov 25, 2024
d7531f6
Merge branch 'develop' into feature/solution-quality-comparator
zaibod Nov 25, 2024
871dfe9
Merge branch 'develop' into feature/solution-quality-comparator
zaibod Jan 16, 2025
1696436
Merge branch 'develop' into feature/solution-quality-comparator
zaibod Feb 3, 2025
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
16 changes: 14 additions & 2 deletions src/main/java/edu/kit/provideq/toolbox/BoundType.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@ public enum BoundType {
/**
* An upper bound.
*/
UPPER,
UPPER {
@Override
public float compare(float bound, float actual) {
return 1 - (bound / actual);
}
},
/**
* A lower bound.
*/
LOWER
LOWER {
@Override
public float compare(float bound, float actual) {
return actual / bound - 1;
}
};

public abstract float compare(float bound, float actual);
}
11 changes: 11 additions & 0 deletions src/main/java/edu/kit/provideq/toolbox/api/ComparisonDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.kit.provideq.toolbox.api;

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

public record ComparisonDto(float comparison, BoundWithInfo bound, Solution<?> solution) {
@Override public String toString() {
return "Comparison{comparison=%f, bound=%s, solution=%s}"
.formatted(comparison, bound, solution);
}
}
105 changes: 101 additions & 4 deletions src/main/java/edu/kit/provideq/toolbox/api/EstimationRouter.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

import com.google.common.collect.Streams;
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.NoSuchElementException;
import java.util.UUID;
import java.util.regex.Pattern;
import org.springdoc.core.fn.builders.operation.Builder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
Expand All @@ -42,10 +44,15 @@ public class EstimationRouter {

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

Expand All @@ -61,6 +68,18 @@ private RouterFunction<ServerResponse> defineGetRoute(ProblemManager<?, ?> manag
).build();
}

/**
* Compare Operation: GET /problems/TYPE/{problemId}/bound/compare.
*/
private RouterFunction<ServerResponse> defineCompareRoute(ProblemManager<?, ?> manager) {
return route().GET(
getCompareRouteForProblemType(manager.getType()),
accept(APPLICATION_JSON),
req -> handleCompare(manager, req),
ops -> handleCompareDocumentation(manager, ops)
).build();
}

private <InputT, ResultT> Mono<ServerResponse> handleGet(
ProblemManager<InputT, ResultT> manager,
ServerRequest req
Expand All @@ -80,6 +99,41 @@ private <InputT, ResultT> Mono<ServerResponse> handleGet(
});
}

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

if (problem.getSolution() == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Problem not solved yet!");
}

if (problem.getBound().isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bound not estimated yet!");
}

float bound = problem.getBound().get().bound().value();
var pattern = Pattern.compile(manager.getType().getSolutionPattern());
var solutionMatcher = pattern.matcher(problem.getSolution().getSolutionData().toString());
float solutionValue;
if (solutionMatcher.find()) {
solutionValue = Float.parseFloat(solutionMatcher.group(1));
} else {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not parse solution value!");
}

float comparison = problem.getBound().get().bound().boundType().compare(bound, solutionValue);
ComparisonDto comparisonDto = new ComparisonDto(
comparison,
problem.getBound().get(),
problem.getSolution()
);
return ok().body(Mono.just(comparisonDto), new ParameterizedTypeReference<>() {
});
}

private void handleGetDocumentation(
ProblemManager<?, ?> manager,
Builder ops
Expand All @@ -96,16 +150,59 @@ private void handleGetDocumentation(
);
}

private void handleCompareDocumentation(
ProblemManager<?, ?> manager,
Builder ops
) {
ProblemType<?, ?> problemType = manager.getType();
ops
.operationId(getCompareRouteForProblemType(problemType))
.tag(problemType.getId())
.description("Compares the solution value with the 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(comparisonOkResponseContent())
);
}

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

private static org.springdoc.core.fn.builders.content.Builder comparisonOkResponseContent() {
return contentBuilder()
.mediaType(APPLICATION_JSON_VALUE)
.schema(schemaBuilder().implementation(ComparisonDto.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);
}

private String getCompareRouteForProblemType(ProblemType<?, ?> type) {
return getEstimationRouteForProblemType(type) + "/compare";
}

@Autowired
void setManagerProvider(ProblemManagerProvider managerProvider) {
this.managerProvider = managerProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ public class DemonstratorConfiguration {
public static final ProblemType<String, String> DEMONSTRATOR = new ProblemType<>(
"demonstrator",
String.class,
String.class,
null
String.class
);

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

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

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package edu.kit.provideq.toolbox.knapsack;

import edu.kit.provideq.toolbox.Bound;
import edu.kit.provideq.toolbox.BoundType;
import edu.kit.provideq.toolbox.ResourceProvider;
import edu.kit.provideq.toolbox.exception.MissingExampleException;
import edu.kit.provideq.toolbox.knapsack.solvers.PythonKnapsackSolver;
Expand All @@ -8,8 +10,14 @@
import edu.kit.provideq.toolbox.meta.ProblemManager;
import edu.kit.provideq.toolbox.meta.ProblemType;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -18,6 +26,41 @@
*/
@Configuration
public class KnapsackConfiguration {
/**
* an upper bound estimator by solving the greedy fractional knapsack problem.
*/
private static final Function<String, Bound> estimator = input -> {
var parts = input.split("\n");
var weightLimit = Integer.parseInt(parts[parts.length - 1]);
// map item value to item weight
List<Map.Entry<Integer, Integer>> items = new ArrayList<>();
for (int i = 1; i < parts.length - 1; i++) {
var item = parts[i].split(" ");
items.add(new AbstractMap.SimpleEntry<>(
Integer.parseInt(item[1]),
Integer.parseInt(item[2]))
);
}
items.sort(Comparator.comparingInt(a -> -a.getKey() / a.getValue()));
// keep adding items until the weight limit is reached, then add a fraction of the next item
float bound = 0;
int currentWeight = 0;
while (currentWeight < weightLimit) {
var item = items.remove(0);
var value = item.getKey();
var weight = item.getValue();
if (currentWeight + weight <= weightLimit) {
bound += value;
currentWeight += weight;
} else {
var fraction = (weightLimit - currentWeight) / (float) weight;
bound += (fraction * value);
break;
}
}
return new Bound(bound, BoundType.UPPER);
};

/**
* An optimization problem:
* For given items each with a weight and value, determine which items are part of a collection
Expand All @@ -28,7 +71,8 @@ public class KnapsackConfiguration {
"knapsack",
String.class,
String.class,
null
estimator,
"^(\\d+)"
);

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

@Bean
Expand Down
24 changes: 23 additions & 1 deletion src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ProblemType<InputT, ResultT> {
private final Class<InputT> inputClass;
private final Class<ResultT> resultClass;
private final Function<InputT, Bound> estimator;
private final String solutionPattern;

/**
* Defines a new problem type.
Expand All @@ -26,12 +27,29 @@ public ProblemType(
String id,
Class<InputT> inputClass,
Class<ResultT> resultClass,
Function<InputT, Bound> estimator
Function<InputT, Bound> estimator,
String solutionPattern
) {
this.id = id;
this.inputClass = inputClass;
this.resultClass = resultClass;
this.estimator = estimator;
this.solutionPattern = solutionPattern;
}

/**
* Defines a new problem type without estimation support.
*
* @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.
*/
public ProblemType(
String id,
Class<InputT> inputClass,
Class<ResultT> resultClass
) {
this(id, inputClass, resultClass, null, null);
}

/**
Expand Down Expand Up @@ -62,6 +80,10 @@ public Optional<Function<InputT, Bound>> getEstimator() {
return Optional.ofNullable(estimator);
}

public String getSolutionPattern() {
return solutionPattern;
}

@Override
public String toString() {
return "ProblemType{"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ public class QuboConfiguration {
public static final ProblemType<String, String> QUBO = new ProblemType<>(
"qubo",
String.class,
String.class,
null
String.class
);

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ public class SatConfiguration {
public static final ProblemType<String, DimacsCnfSolution> SAT = new ProblemType<>(
"sat",
String.class,
DimacsCnfSolution.class,
null
DimacsCnfSolution.class
);

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

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

@Bean
Expand Down
Loading