More AMPL fixes

Change-Id: I824333f8bad128ec936429e06069481db4b335a1
This commit is contained in:
Rudi Schlatte
2024-02-07 17:16:33 +02:00
parent 677656d9a9
commit 47d803529c
2 changed files with 60 additions and 31 deletions

View File

@@ -36,6 +36,7 @@ public class AMPLGenerator {
generateVariablesSection(app, out); generateVariablesSection(app, out);
generateMetricsSection(app, out); generateMetricsSection(app, out);
generateConstants(app, out);
generatePerformanceIndicatorsSection(app, out); generatePerformanceIndicatorsSection(app, out);
generateCostParameterSection(app, out); generateCostParameterSection(app, out);
generateUtilityFunctions(app, out); generateUtilityFunctions(app, out);
@@ -44,15 +45,27 @@ public class AMPLGenerator {
return result.toString(); return result.toString();
} }
// Utility functions that are constant go here
private static void generateConstants(NebulousApp app, PrintWriter out) {
out.println("# Constants");
for (JsonNode f : app.getUtilityFunctions().values()) {
if (!(f.get("type").asText().equals("constant")))
continue;
out.format("param %s;%n", f.get("name").asText());
}
out.println();
}
private static void generateConstraints(NebulousApp app, PrintWriter out) { private static void generateConstraints(NebulousApp app, PrintWriter out) {
// We only care about SLOs defined over performance indicators // We only care about SLOs defined over performance indicators
out.println("# Constraints. For constraints we don't have name from GUI, must be created"); out.println("# Constraints. For constraints we don't have name from GUI, must be created");
ObjectNode slo = app.getOriginalAppMessage().withObject(NebulousApp.constraints_path); int counter = 0;
Set<String> performance_indicators = app.getPerformanceIndicators().keySet(); for (JsonNode slo : app.getEffectiveConstraints()) {
if (!containsPerformanceIndicator(slo, performance_indicators)) return; out.format("subject to constraint_{} : ", counter);
out.print("subject to constraint_0 : "); emitCondition(out, slo);
emitCondition(out, slo); out.println(";");
out.println(";"); counter = counter + 1;
}
} }
private static void emitCondition(PrintWriter out, JsonNode condition){ private static void emitCondition(PrintWriter out, JsonNode condition){
@@ -82,22 +95,11 @@ public class AMPLGenerator {
c.get("value").asText()); c.get("value").asText());
} }
/**
* We only want to emit constraints over at least one performance
* indicator. Those have the key of a performance indicator in a "key"
* field.
*/
private static boolean containsPerformanceIndicator (JsonNode constraint, Set<String> performance_indicators) {
for (String key : constraint.findValuesAsText("metricName")) {
if (performance_indicators.contains(key)) return true;
}
return false;
}
private static void generateUtilityFunctions(NebulousApp app, PrintWriter out) { private static void generateUtilityFunctions(NebulousApp app, PrintWriter out) {
out.println("# Utility functions"); out.println("# Utility functions");
for (JsonNode f : app.getUtilityFunctions().values()) { for (JsonNode f : app.getUtilityFunctions().values()) {
if (f.get("type").asText().equals("constant"))
continue;
String formula = replaceVariables( String formula = replaceVariables(
f.at("/expression/formula").asText(), f.at("/expression/formula").asText(),
f.withArray("/expression/variables")); f.withArray("/expression/variables"));
@@ -119,8 +121,12 @@ public class AMPLGenerator {
out.println("# Performance indicators = composite metrics that have at least one variable in their formula"); out.println("# Performance indicators = composite metrics that have at least one variable in their formula");
for (final JsonNode m : app.getPerformanceIndicators().values()) { for (final JsonNode m : app.getPerformanceIndicators().values()) {
String name = m.get("name").asText(); String name = m.get("name").asText();
String formula = m.get("formula").asText();
out.format("var %s;%n", name); out.format("var %s;%n", name);
}
out.println("# Performance indicator formulas");
for (final JsonNode m : app.getPerformanceIndicators().values()) {
String name = m.get("name").asText();
String formula = m.get("formula").asText();
out.format("subject to define_%s : %s = %s;%n", name, name, formula); out.format("subject to define_%s : %s = %s;%n", name, name, formula);
} }
out.println(); out.println();
@@ -152,33 +158,37 @@ public class AMPLGenerator {
/** /**
* Calculate all metrics that are actually used. * Calculate all metrics that are actually used.
* *
* NOTE: may also contain variable names. This is not a problem as long
* as we use the result of this method to filter out unused metrics, but
* must be fixed if we use this method for other purposes as well.
*
* @param app the NebulousApp. * @param app the NebulousApp.
* @return The set of raw or composite metrics that are used in * @return The set of raw or composite metrics that are used in
* performance indicators, constraints or utility functions. * performance indicators, constraints or utility functions.
*/ */
private static Set<String> usedMetrics(NebulousApp app) { private static Set<String> usedMetrics(NebulousApp app) {
// TODO: should we also add metrics that are used in other metrics
// that are used elsewhere, or does the solver take care of that?
Set<String> result = new HashSet<>(); Set<String> result = new HashSet<>();
Set<String> allMetrics = new HashSet<>(app.getRawMetrics().keySet());
allMetrics.addAll(app.getCompositeMetrics().keySet());
// collect from performance indicators // collect from performance indicators
for (final JsonNode indicator : app.getPerformanceIndicators().values()) { for (final JsonNode indicator : app.getPerformanceIndicators().values()) {
// FIXME: note that here we collect also variables
indicator.withArray("arguments").elements() indicator.withArray("arguments").elements()
.forEachRemaining(node -> result.add(node.asText())); .forEachRemaining(node -> {
if (allMetrics.contains(node.asText()))
result.add(node.asText());
});
} }
// collect from constraints // collect from constraints
ObjectNode slo = app.getOriginalAppMessage().withObject(NebulousApp.constraints_path); for (JsonNode slo : app.getEffectiveConstraints()) {
slo.findValuesAsText("metricName").forEach(result::add); slo.findValuesAsText("metricName").forEach(metricName -> {
if (allMetrics.contains(metricName)) result.add(metricName);
});
}
// collect from utility functions // collect from utility functions
for (JsonNode function : app.getUtilityFunctions().values()) { for (JsonNode function : app.getUtilityFunctions().values()) {
function.withArray("/expression/variables") function.withArray("/expression/variables")
.findValuesAsText("value") .findValuesAsText("value")
.forEach(result::add); .forEach(name -> {
if (allMetrics.contains(name)) result.add(name);
});
} }
return result; return result;
} }

View File

@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -92,6 +93,12 @@ public class NebulousApp {
@Getter private Map<String, JsonNode> performanceIndicators = new HashMap<>(); @Getter private Map<String, JsonNode> performanceIndicators = new HashMap<>();
/** The app's utility functions; the AMPL solver will optimize for one of these. */ /** The app's utility functions; the AMPL solver will optimize for one of these. */
@Getter private Map<String, JsonNode> utilityFunctions = new HashMap<>(); @Getter private Map<String, JsonNode> utilityFunctions = new HashMap<>();
/**
* The constraints that are actually effective: if a constraint does not
* contain a variable, we cannot influence it via the solver
*/
@Getter private Set<JsonNode> effectiveConstraints = new HashSet<>();
/** When an app gets deployed or redeployed, this is where we send the AMPL file */ /** When an app gets deployed or redeployed, this is where we send the AMPL file */
private Publisher ampl_message_channel; private Publisher ampl_message_channel;
/** Have we ever been deployed? I.e., when we rewrite KubeVela, are there /** Have we ever been deployed? I.e., when we rewrite KubeVela, are there
@@ -167,6 +174,18 @@ public class NebulousApp {
// What's left is neither a raw nor composite metric. // What's left is neither a raw nor composite metric.
utilityFunctions.put(f.get("name").asText(), f); utilityFunctions.put(f.get("name").asText(), f);
} }
// In the current app message, constraints is not an array. When this
// changes, wrap this for loop in another loop over the constraints
// (Constraints are called sloViolations in the app message).
for (String key : app_message.withObject(constraints_path).findValuesAsText("metricName")) {
// Constraints that do not use variables, directly or via
// performance indicators, will be ignored.
if (kubevela_variable_paths.keySet().contains(key)
|| performanceIndicators.keySet().contains(key)) {
effectiveConstraints.add(app_message.withObject(constraints_path));
break;
}
}
log.debug("New App instantiated: Name='{}', UUID='{}'", name, UUID); log.debug("New App instantiated: Name='{}', UUID='{}'", name, UUID);
} }