diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py index 64219b300f..e81161de90 100644 --- a/lib/spack/spack/cmd/solve.py +++ b/lib/spack/spack/cmd/solve.py @@ -10,6 +10,7 @@ import sys import llnl.util.tty as tty +import llnl.util.tty.color as color import spack import spack.cmd @@ -23,15 +24,20 @@ level = 'long' #: output options -show_options = ('asp', 'output', 'solutions') +show_options = ('asp', 'opt', 'output', 'solutions') def setup_parser(subparser): # Solver arguments subparser.add_argument( - '--show', action='store', default=('solutions'), - help="outputs: a list with any of: " - "%s (default), all" % ', '.join(show_options)) + '--show', action='store', default='opt,solutions', + help="select outputs: comma-separated list of: \n" + " asp asp program text\n" + " opt optimization criteria for best model\n" + " output raw clingo output\n" + " solutions models found by asp program\n" + " all all of the above" + ) subparser.add_argument( '--models', action='store', type=int, default=0, help="number of solutions to search (default 0 for all)") @@ -41,10 +47,10 @@ def setup_parser(subparser): subparser, ['long', 'very_long', 'install_status']) subparser.add_argument( '-y', '--yaml', action='store_const', dest='format', default=None, - const='yaml', help='print concrete spec as YAML') + const='yaml', help='print concrete spec as yaml') subparser.add_argument( '-j', '--json', action='store_const', dest='format', default=None, - const='json', help='print concrete spec as YAML') + const='json', help='print concrete spec as json') subparser.add_argument( '-c', '--cover', action='store', default='nodes', choices=['nodes', 'edges', 'paths'], @@ -113,9 +119,18 @@ def solve(parser, args): best = min(result.answers) opt, _, answer = best - if not args.format: - tty.msg("Best of %d answers." % result.nmodels) - tty.msg("Optimization: %s" % opt) + if ("opt" in dump) and (not args.format): + tty.msg("Best of %d considered solutions." % result.nmodels) + tty.msg("Optimization Criteria:") + + maxlen = max(len(s) for s in result.criteria) + color.cprint( + "@*{ Priority Criterion %sValue}" % ((maxlen - 10) * " ") + ) + for i, (name, val) in enumerate(zip(result.criteria, opt)): + fmt = " @K{%%-8d} %%-%ds%%5d" % maxlen + color.cprint(fmt % (i + 1, name, val)) + print() # iterate over roots from command line for input_spec in specs: diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 179dff2052..712a21b05b 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -207,6 +207,9 @@ def __init__(self, asp=None): self.answers = [] self.cores = [] + # names of optimization criteria + self.criteria = [] + def print_cores(self): for core in self.cores: tty.msg( @@ -354,6 +357,7 @@ def stringify(x): return x.string or str(x) if result.satisfiable: + # build spec from the best model builder = SpecBuilder(specs) min_cost, best_model = min(models) tuples = [ @@ -361,8 +365,20 @@ def stringify(x): for sym in best_model ] answers = builder.build_specs(tuples) + + # add best spec to the results result.answers.append((list(min_cost), 0, answers)) + # pull optimization criteria names out of the solution + criteria = [ + (int(args[0]), args[1]) for name, args in tuples + if name == "opt_criterion" + ] + result.criteria = [t[1] for t in sorted(criteria, reverse=True)] + + # record the number of models the solver considered + result.nmodels = len(models) + elif cores: symbols = dict( (a.literal, a.symbol) diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index cbde0076fb..898927a8a8 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -705,19 +705,31 @@ no_flags(Package, FlagType) %----------------------------------------------------------------------------- % How to optimize the spec (high to low priority) %----------------------------------------------------------------------------- +% Each criterion below has: +% 1. an opt_criterion(ID, Name) fact that describes the criterion, and +% 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion +% is displayed (clingo doesn't display sums over empty sets by default) % The highest priority is to minimize the: % 1. Version weight % 2. Number of variants with a non default value, if not set % for the root(Package) -#minimize { Weight@15 : root(Package),version_weight(Package, Weight)}. +opt_criterion(15, "version weight"). +#minimize{ 0@15 : #true }. +#minimize { Weight@15 : root(Package),version_weight(Package, Weight) }. + +opt_criterion(14, "number of non-default variants (roots)"). +#minimize{ 0@14 : #true }. #minimize { Weight@14,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), root(Package) }. + % If the value is a multivalued variant there could be multiple % values set as default. Since a default value has a weight of 0 we % need to maximize their number below to ensure they're all set +opt_criterion(13, "multi-valued variants + preferred providers for roots"). +#minimize{ 0@13 : #true }. #maximize { 1@13,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), @@ -730,19 +742,27 @@ no_flags(Package, FlagType) }. % Try to use default variants or variants that have been set +opt_criterion(11, "number of non-default variants (non-roots)"). +#minimize{ 0@11 : #true }. #minimize { Weight@11,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), not root(Package) }. + % Minimize the weights of the providers, i.e. use as much as % possible the most preferred providers +opt_criterion(9, "number of non-default providers (non-roots)"). +#minimize{ 0@9 : #true }. #minimize{ Weight@9,Provider : provider_weight(Provider, Weight), not root(Provider) }. + % If the value is a multivalued variant there could be multiple % values set as default. Since a default value has a weight of 0 we % need to maximize their number below to ensure they're all set +opt_criterion(8, "count of non-root multi-valued variants"). +#minimize{ 0@8 : #true }. #maximize { 1@8,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), @@ -754,18 +774,29 @@ no_flags(Package, FlagType) % while minimizing the number of nodes. This is done because % a maximization on the number of matches for compilers is highly % correlated to a preference to have as many nodes as possible +opt_criterion(7, "compiler matches + number of nodes"). +#minimize{ 0@7 : #true }. #minimize{ 1@7,Package : node(Package) }. #maximize{ Weight@7,Package : compiler_version_match(Package, Weight) }. % Choose more recent versions for nodes +opt_criterion(6, "version badness"). +#minimize{ 0@6 : #true }. #minimize{ Weight@6,Package : version_weight(Package, Weight) }. % Try to use preferred compilers +opt_criterion(5, "non-preferred compilers"). +#minimize{ 0@5 : #true }. #minimize{ Weight@5,Package : compiler_weight(Package, Weight) }. % Maximize the number of matches for targets in the DAG, try % to select the preferred target. +opt_criterion(4, "target matches"). +#minimize{ 0@4 : #true }. #maximize{ Weight@4,Package : node_target_match(Package, Weight) }. + +opt_criterion(3, "non-preferred targets"). +#minimize{ 0@3 : #true }. #minimize{ Weight@3,Package : node_target_weight(Package, Weight) }. diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp index f67c4b6c3c..c1231ca62a 100644 --- a/lib/spack/spack/solver/display.lp +++ b/lib/spack/spack/solver/display.lp @@ -8,6 +8,9 @@ % % This section determines what parts of the model are printed at the end %============================================================================== + +% Spec-related functions. +% Used to build the result of the solve. #show node/1. #show depends_on/3. #show version/2. @@ -21,12 +24,7 @@ #show node_flag_compiler_default/1. #show node_flag_source/2. #show no_flags/2. - -#show variant_not_default/4. -#show provider_weight/2. -#show version_weight/2. -#show compiler_version_match/2. -#show compiler_weight/2. -#show node_target_match/2. -#show node_target_weight/2. #show external_spec_selected/2. + +% names of optimization criteria +#show opt_criterion/2.