/// <summary> /// Generates a solver system (in z3: context) based on a variability model. The solver system can be used to check for satisfiability of configurations as well as optimization. /// Additionally to <see cref="Z3Solver.GetInitializedBooleanSolverSystem(out List{BoolExpr}, out Dictionary{BinaryOption, BoolExpr}, out Dictionary{BoolExpr, BinaryOption}, VariabilityModel, bool, int)"/>, this method supports numeric features. /// Note that we do not support Henard's randomized approach here, because it is defined only on boolean constraints. /// </summary> /// <param name="variables">Empty input, outputs a list of CSP terms that correspond to the configuration options of the variability model</param> /// <param name="optionToTerm">A map to get for a given configuration option the corresponding CSP term of the constraint system</param> /// <param name="termToOption">A map that gives for a given CSP term the corresponding configuration option of the variability model</param> /// <param name="vm">The variability model for which we generate a constraint system</param> /// <param name="randomSeed">The z3 random seed</param> /// <returns>The generated constraint system consisting of logical terms representing configuration options as well as their constraints.</returns> internal static Tuple <Context, BoolExpr> GetInitializedSolverSystem(out List <Expr> variables, out Dictionary <ConfigurationOption, Expr> optionToTerm, out Dictionary <Expr, ConfigurationOption> termToOption, VariabilityModel vm, int randomSeed = 0) { // Create a context and turn on model generation Context context = new Context(new Dictionary <string, string>() { { "model", "true" } }); // Assign the out-parameters variables = new List <Expr>(); optionToTerm = new Dictionary <ConfigurationOption, Expr>(); termToOption = new Dictionary <Expr, ConfigurationOption>(); // Create the binary configuration options foreach (BinaryOption binOpt in vm.BinaryOptions) { BoolExpr booleanVariable = GenerateBooleanVariable(context, binOpt.Name); variables.Add(booleanVariable); optionToTerm.Add(binOpt, booleanVariable); termToOption.Add(booleanVariable, binOpt); } // Create the numeric configuration options foreach (NumericOption numOpt in vm.NumericOptions) { Expr numericVariable = GenerateDoubleVariable(context, numOpt.Name); variables.Add(numericVariable); optionToTerm.Add(numOpt, numericVariable); termToOption.Add(numericVariable, numOpt); } // Initialize variables for constraint parsing List <List <ConfigurationOption> > alreadyHandledAlternativeOptions = new List <List <ConfigurationOption> >(); List <BoolExpr> andGroup = new List <BoolExpr>(); // Parse the constraints of the binary (boolean) configuration options foreach (BinaryOption current in vm.BinaryOptions) { BoolExpr expr = (BoolExpr)optionToTerm[current]; if (current.Parent == null || current.Parent == vm.Root) { if (current.Optional == false && current.Excluded_Options.Count == 0) { andGroup.Add(expr); } } if (current.Parent != null && current.Parent != vm.Root) { BoolExpr parent = (BoolExpr)optionToTerm[(BinaryOption)current.Parent]; andGroup.Add(context.MkImplies(expr, parent)); if (current.Optional == false && current.Excluded_Options.Count == 0) { andGroup.Add(context.MkImplies(parent, expr)); } } // Alternative or other exclusion constraints if (current.Excluded_Options.Count > 0) { List <ConfigurationOption> alternativeOptions = current.collectAlternativeOptions(); if (alternativeOptions.Count > 0) { // Check whether we handled this group of alternatives already foreach (List <ConfigurationOption> alternativeGroup in alreadyHandledAlternativeOptions) { foreach (ConfigurationOption alternative in alternativeGroup) { if (current == alternative) { goto handledAlternative; } } } // It is not allowed that an alternative group has no parent element BoolExpr parent = null; if (current.Parent == null) { parent = context.MkTrue(); } else { parent = (BoolExpr)optionToTerm[(BinaryOption)current.Parent]; } BoolExpr[] terms = new BoolExpr[alternativeOptions.Count + 1]; terms[0] = expr; int i = 1; foreach (BinaryOption altEle in alternativeOptions) { BoolExpr temp = (BoolExpr)optionToTerm[altEle]; terms[i] = temp; i++; } BoolExpr[] exactlyOneOfN = new BoolExpr[] { context.MkAtMost(terms, 1), context.MkOr(terms) }; andGroup.Add(context.MkImplies(parent, context.MkAnd(exactlyOneOfN))); alreadyHandledAlternativeOptions.Add(alternativeOptions); // Go-To label handledAlternative : { } } // Excluded option(s) as cross-tree constraint(s) List <List <ConfigurationOption> > nonAlternative = current.getNonAlternativeExlcudedOptions(); if (nonAlternative.Count > 0) { foreach (var excludedOption in nonAlternative) { BoolExpr[] orTerm = new BoolExpr[excludedOption.Count]; int i = 0; foreach (var opt in excludedOption) { BoolExpr target = (BoolExpr)optionToTerm[(BinaryOption)opt]; orTerm[i] = target; i++; } andGroup.Add(context.MkImplies(expr, context.MkNot(context.MkOr(orTerm)))); } } } // Handle implies if (current.Implied_Options.Count > 0) { foreach (List <ConfigurationOption> impliedOr in current.Implied_Options) { BoolExpr[] orTerms = new BoolExpr[impliedOr.Count]; // Possible error: if a binary option implies a numeric option for (int i = 0; i < impliedOr.Count; i++) { orTerms[i] = (BoolExpr)optionToTerm[(BinaryOption)impliedOr.ElementAt(i)]; } andGroup.Add(context.MkImplies((BoolExpr)optionToTerm[current], context.MkOr(orTerms))); } } } // Parse the constraints (ranges, step) of the numeric features foreach (NumericOption numOpt in vm.NumericOptions) { Expr numExpression = optionToTerm[numOpt]; List <double> allValues = numOpt.getAllValues(); List <BoolExpr> valueExpressions = new List <BoolExpr>(); foreach (double value in allValues) { FPNum fpNum = context.MkFPNumeral(value, context.MkFPSortDouble()); if (!numericLookUpTable.ContainsKey(fpNum.ToString())) { numericLookUpTable.Add(fpNum.ToString(), value); } valueExpressions.Add(context.MkEq(numExpression, fpNum)); } andGroup.Add(context.MkOr(valueExpressions.ToArray())); } // Parse the boolean cross-tree constraints foreach (string constraint in vm.BinaryConstraints) { bool and = false; string[] terms; if (constraint.Contains("&")) { and = true; terms = constraint.Split('&'); } else { terms = constraint.Split('|'); } BoolExpr[] smtTerms = new BoolExpr[terms.Count()]; int i = 0; foreach (string t in terms) { string optName = t.Trim(); if (optName.StartsWith("-") || optName.StartsWith("!")) { optName = optName.Substring(1); BinaryOption binOpt = vm.getBinaryOption(optName); BoolExpr boolVar = (BoolExpr)optionToTerm[binOpt]; boolVar = context.MkNot(boolVar); smtTerms[i] = boolVar; } else { BinaryOption binOpt = vm.getBinaryOption(optName); BoolExpr boolVar = (BoolExpr)optionToTerm[binOpt]; smtTerms[i] = boolVar; } i++; } if (and) { andGroup.Add(context.MkAnd(smtTerms)); } else { andGroup.Add(context.MkOr(smtTerms)); } } // Parse the non-boolean constraints Dictionary <BinaryOption, Expr> optionMapping = new Dictionary <BinaryOption, Expr>(); if (vm.NonBooleanConstraints.Count > 0) { foreach (NonBooleanConstraint nonBooleanConstraint in vm.NonBooleanConstraints) { andGroup.Add(ProcessMixedConstraint(nonBooleanConstraint, optionMapping, context, optionToTerm)); } } // Parse the mixed constraints // Note that this step is currently omitted due to critical performance issues. // Therefore, we check whether the mixed constraints are satisfied after finding the configuration. //if (vm.MixedConstraints.Count > 0) //{ //foreach (MixedConstraint constr in vm.MixedConstraints) //{ // andGroup.Add(ProcessMixedConstraint(constr, optionMapping, context, optionToTerm)); //} //} // Return the initialized system BoolExpr generalConstraint = context.MkAnd(andGroup.ToArray()); return(new Tuple <Context, BoolExpr>(context, generalConstraint)); }
/// <summary> /// Parses a z3 solution into a configuration. /// This method also supports numeric variables. /// </summary> /// <param name="variables">List of all variables in the z3 context.</param> /// <param name="model">Solution of the context.</param> /// <param name="termToOption">Map from variables to binary options.</param> /// <param name="optionsToConsider">The options that are considered for the solution.</param> /// <returns>Configuration parsed from the solution.</returns> public static Tuple <List <BinaryOption>, Dictionary <NumericOption, double> > RetrieveConfiguration( List <Expr> variables, Model model, Dictionary <Expr, ConfigurationOption> termToOption, List <ConfigurationOption> optionsToConsider = null) { List <BinaryOption> binOpts = new List <BinaryOption>(); Dictionary <NumericOption, double> config = new Dictionary <NumericOption, double>(); foreach (Expr variable in variables) { if (optionsToConsider != null && !optionsToConsider.Contains(termToOption[variable])) { continue; } Expr allocation = model.Eval(variable, completion: true); if (allocation.GetType() == typeof(BoolExpr)) { BoolExpr boolExpr = (BoolExpr)allocation; if (boolExpr.IsTrue) { binOpts.Add((BinaryOption)termToOption[variable]); } } else { // In this case, we have a numeric variable FPNum fpNum = (FPNum)allocation; config.Add((NumericOption)termToOption[variable], Z3Solver.lookUpNumericValue(fpNum.ToString())); } } return(new Tuple <List <BinaryOption>, Dictionary <NumericOption, double> >(binOpts, config)); }