| /* |
| * 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; |
| } |
| |
| } |