Exemple #1
0
        /// <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));
        }