blob: 5f77fd0095a3997eecfe6163941589e843bfbf40 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wayang.profiler.log;
import org.apache.wayang.core.optimizer.cardinality.CardinalityEstimate;
import org.apache.wayang.core.optimizer.costs.EstimationContext;
import org.apache.wayang.core.optimizer.costs.LoadEstimate;
import org.apache.wayang.core.optimizer.costs.LoadEstimator;
import org.apache.wayang.core.optimizer.costs.LoadProfileEstimator;
import org.apache.wayang.core.optimizer.costs.LoadProfileEstimators;
import org.apache.wayang.core.util.mathex.Context;
import org.apache.wayang.core.util.mathex.DefaultContext;
import org.apache.wayang.core.util.mathex.Expression;
import org.apache.wayang.core.util.mathex.ExpressionBuilder;
import org.apache.wayang.core.util.mathex.exceptions.EvaluationException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
/**
* Adjustable {@link LoadProfileEstimator} implementation.
*/
public class DynamicLoadEstimator extends LoadEstimator {
/**
* Instance that always estimates a load of {@code 0}.
*/
public static DynamicLoadEstimator zeroLoad = new DynamicLoadEstimator(
(individual, inputCardinalities, outputCardinalities) -> 0d,
individual -> "0",
Collections.emptySet()
);
/**
* Function to estimate the load for given {@link Individual}.
*/
private final SinglePointEstimator singlePointEstimator;
/**
* {@link Variable}s used in the {@link #singlePointEstimator}.
*/
private final Collection<Variable> employedVariables;
/**
* Maps an {@link Individual} to a MathEx specifation {@link String}.
*/
private final Function<Individual, String> specificationBuilder;
@FunctionalInterface
public interface SinglePointEstimator {
double estimate(DynamicEstimationContext context, long[] inputCardinalities, long[] outputCardinalities);
}
/**
* Parses a mathematical expression and provides it as a {@link LoadEstimator.SinglePointEstimationFunction}.
*
* @param templateKey of the {@code expression}
* @param resource that is being estimated
* @param expression a mathematical expression
* @param optimizationSpace in which new {@link Variable}s should be created
* @return the {@link LoadEstimator.SinglePointEstimationFunction}
*/
public static DynamicLoadEstimator createFor(String templateKey,
String resource,
String expression,
OptimizationSpace optimizationSpace) {
// Replace the question marks with variable names.
Collection<Variable> variables = new LinkedList<>();
StringBuilder sb = new StringBuilder(2 * expression.length());
for (int i = 0; i < expression.length(); i++) {
final char c = expression.charAt(i);
if (c == '?') {
String variableId = templateKey + "." + resource + "[" + variables.size() + "]";
final Variable variable = optimizationSpace.getOrCreateVariable(variableId);
variables.add(variable);
sb.append("_var").append(variable.getIndex());
} else {
sb.append(c);
}
}
final Expression expr = ExpressionBuilder.parse(sb.toString()).specify(LoadProfileEstimators.baseContext);
SinglePointEstimator singlePointEstimator =
(individual, inCards, outCards) -> {
Context mathContext = createMathContext(individual, inCards, outCards);
return Math.round(expr.evaluate(mathContext));
};
final Function<Individual, String> specificationBuilder = individual -> {
DefaultContext context = new DefaultContext();
for (Variable variable : variables) {
final double value = variable.getValue(individual);
final String name = "_var" + variable.getIndex();
context.setVariable(name, value);
}
return expr.specify(context).toString();
};
return new DynamicLoadEstimator(singlePointEstimator, specificationBuilder, variables);
}
/**
* Create a mathematical {@link Context} from the parameters.
*
* @param context provides miscellaneous variables and the {@link Individual}
* @param inputCardinalities provides input {@link CardinalityEstimate}s (`in***`)
* @param outputCardinalities provides output {@link CardinalityEstimate}s (`out***`)
* @return the {@link Context}
*/
private static Context createMathContext(final DynamicEstimationContext context,
final long[] inputCardinalities,
final long[] outputCardinalities) {
return new Context() {
@Override
public double getVariable(String variableName) throws EvaluationException {
// Serve "in999" and "out999" variables directly from the cardinality arrays.
if (variableName.startsWith("in") && variableName.length() > 2) {
int accu = 0;
int i;
for (i = 2; i < variableName.length(); i++) {
char c = variableName.charAt(i);
if (!Character.isDigit(c)) break;
accu = 10 * accu + (c - '0');
}
if (i == variableName.length()) return inputCardinalities[accu];
} else if (variableName.startsWith("out") && variableName.length() > 3) {
int accu = 0;
int i;
for (i = 3; i < variableName.length(); i++) {
char c = variableName.charAt(i);
if (!Character.isDigit(c)) break;
accu = 10 * accu + (c - '0');
}
if (i == variableName.length()) return outputCardinalities[accu];
} else if (variableName.startsWith("_var") && variableName.length() > 4) {
int accu = 0;
int i;
for (i = 4; i < variableName.length(); i++) {
char c = variableName.charAt(i);
if (!Character.isDigit(c)) break;
accu = 10 * accu + (c - '0');
}
if (i == variableName.length()) return context.getIndividual().getGenome()[accu];
}
// Otherwise, ask the context for the property.
return context.getDoubleProperty(variableName, Double.NaN);
}
@Override
public ToDoubleFunction<double[]> getFunction(String functionName) throws EvaluationException {
throw new EvaluationException("This context does not provide any functions.");
}
};
}
/**
* Creates a new instance.
*
* @param singlePointEstimator the {@link SinglePointEstimator} to use
* @param specificationBuilder creates a MathEx specification for the new instance
* with the parameters from an {@link Individual}
* @param employedVariables the {@link Variable}s appearing in the {@code singlePointEstimator}
*/
public DynamicLoadEstimator(SinglePointEstimator singlePointEstimator,
Function<Individual, String> specificationBuilder,
Variable... employedVariables) {
this(singlePointEstimator, specificationBuilder, Arrays.asList(employedVariables));
}
/**
* Creates a new instance.
*
* @param singlePointEstimator the {@link SinglePointEstimator} to use
* @param employedVariables the {@link Variable}s appearing in the {@code singlePointEstimator}
*/
public DynamicLoadEstimator(SinglePointEstimator singlePointEstimator,
Function<Individual, String> specificationBuilder,
Collection<Variable> employedVariables) {
super(CardinalityEstimate.EMPTY_ESTIMATE);
this.singlePointEstimator = singlePointEstimator;
this.specificationBuilder = specificationBuilder;
this.employedVariables = employedVariables;
}
@Override
public LoadEstimate calculate(EstimationContext context) {
if (!(context instanceof DynamicEstimationContext)) {
throw new IllegalArgumentException("Invalid estimation context.");
}
final DynamicEstimationContext dynamicContext = (DynamicEstimationContext) context;
final CardinalityEstimate[] inputEstimates = context.getInputCardinalities();
final CardinalityEstimate[] outputEstimates = context.getOutputCardinalities();
long[] inputCardinalities = new long[inputEstimates.length];
long[] outputCardinalities = new long[outputEstimates.length];
for (int i = 0; i < inputEstimates.length; i++) {
inputCardinalities[i] = this.replaceNullCardinality(inputEstimates[i]).getLowerEstimate();
}
for (int i = 0; i < outputEstimates.length; i++) {
outputCardinalities[i] = this.replaceNullCardinality(outputEstimates[i]).getLowerEstimate();
}
double lowerEstimate = this.singlePointEstimator.estimate(
dynamicContext, inputCardinalities, outputCardinalities
);
for (int i = 0; i < inputEstimates.length; i++) {
inputCardinalities[i] = this.replaceNullCardinality(inputEstimates[i]).getUpperEstimate();
}
for (int i = 0; i < outputEstimates.length; i++) {
outputCardinalities[i] = this.replaceNullCardinality(outputEstimates[i]).getUpperEstimate();
}
double upperEstimate = this.singlePointEstimator.estimate(
dynamicContext, inputCardinalities, outputCardinalities
);
return new LoadEstimate(
Math.round(lowerEstimate),
Math.round(upperEstimate),
this.calculateJointProbability(inputEstimates, outputEstimates)
);
}
/**
* Creates a MathEx expression reflecting this instance under the configuration specified by an {@link Individual}.
*
* @param individual specifies values of the employed {@link Variable}s
* @return the MathEx expression
*/
public String toMathEx(Individual individual) {
return this.specificationBuilder.apply(individual);
}
/**
* Get the {@link Variable}s used in this instance.
*
* @return the {@link Variable}s
*/
public Collection<Variable> getEmployedVariables() {
return this.employedVariables;
}
}