From ace4586bf80d8d4dacfbd6ee74d92fb8e6ed5335 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 25 Oct 2021 01:21:19 -0700 Subject: concretizer: rework spack solve output to handle reuse better --- lib/spack/spack/cmd/solve.py | 18 ++++-- lib/spack/spack/solver/asp.py | 77 ++++++++++++++++++++--- lib/spack/spack/solver/concretize.lp | 117 ++++++++++++++++------------------- 3 files changed, 137 insertions(+), 75 deletions(-) diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py index 123c68862d..897eebfa4b 100644 --- a/lib/spack/spack/cmd/solve.py +++ b/lib/spack/spack/cmd/solve.py @@ -122,13 +122,21 @@ def solve(parser, args): tty.msg("Best of %d considered solutions." % result.nmodels) tty.msg("Optimization Criteria:") - maxlen = max(len(s) for s in result.criteria) + maxlen = max(len(s[2]) for s in result.criteria) color.cprint( - "@*{ Priority Criterion %sValue}" % ((maxlen - 10) * " ") + "@*{ Priority Criterion %sInstalled ToBuild}" % ((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)) + + fmt = " @K{%%-8d} %%-%ds%%9s %%7s" % maxlen + for i, (idx, build_idx, name) in enumerate(result.criteria, 1): + color.cprint( + fmt % ( + i, + name, + "-" if build_idx is None else opt[idx], + opt[idx] if build_idx is None else opt[build_idx], + ) + ) print() for spec in result.specs: diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 5fed553080..69f25b39b9 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -2,7 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -from __future__ import print_function +from __future__ import division, print_function import collections import copy @@ -92,6 +92,67 @@ DeclaredVersion = collections.namedtuple( 'DeclaredVersion', ['version', 'idx', 'origin'] ) +# Below numbers are used to map names of criteria to the order +# they appear in the solution. See concretize.lp + +#: Priority offset for "build" criteria (regular criterio shifted to +#: higher priority for specs we have to build) +build_priority_offset = 200 + +#: Priority offset of "fixed" criteria (those w/o build criteria) +fixed_priority_offset = 100 + + +def build_criteria_names(costs, tuples): + """Construct an ordered mapping from criteria names to indices in the cost list.""" + # pull optimization criteria names out of the solution + priorities_names = [] + + num_fixed = 0 + for pred, args in tuples: + if pred != "opt_criterion": + continue + + priority, name = args[:2] + priority = int(priority) + + # add the priority of this opt criterion and its name + priorities_names.append((priority, name)) + + # if the priority is less than fixed_priority_offset, then it + # has an associated build priority -- the same criterion but for + # nodes that we have to build. + if priority < fixed_priority_offset: + build_priority = priority + build_priority_offset + priorities_names.append((build_priority, name)) + else: + num_fixed += 1 + + # sort the criteria by priority + priorities_names = sorted(priorities_names, reverse=True) + + assert len(priorities_names) == len(costs), "Wrong number of optimization criteria!" + + # split list into three parts: build criteria, fixed criteria, non-build criteria + num_criteria = len(priorities_names) + num_build = (num_criteria - num_fixed) // 2 + + build = priorities_names[:num_build] + fixed = priorities_names[num_build:num_build + num_fixed] + installed = priorities_names[num_build + num_fixed:] + + # mapping from priority to index in cost list + indices = dict((p, i) for i, (p, n) in enumerate(priorities_names)) + + # make a list that has each name with its build and non-build priority + criteria = [ + (p - fixed_priority_offset + num_build, None, name) for p, name in fixed + ] + for (i, name), (b, _) in zip(installed, build): + criteria.append((indices[i], indices[b], name)) + + return criteria + def issequence(obj): if isinstance(obj, string_types): @@ -531,13 +592,7 @@ class PyclingoDriver(object): # 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)] + result.criteria = build_criteria_names(min_cost, tuples) # record the number of models the solver considered result.nmodels = len(models) @@ -1635,6 +1690,9 @@ class SpackSolverSetup(object): class SpecBuilder(object): """Class with actions to rebuild a spec from ASP results.""" + #: Attributes that don't need actions + ignored_attributes = ["opt_criterion"] + def __init__(self, specs): self._result = None self._command_line_specs = specs @@ -1797,6 +1855,9 @@ class SpecBuilder(object): self._specs = {} for name, args in function_tuples: + if name in SpecBuilder.ignored_attributes: + continue + action = getattr(self, name, None) # print out unknown actions so we can display them for debugging diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index cab54b9bd0..430f1c81c8 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -793,7 +793,12 @@ build(Package) :- not hash(Package, _), node(Package). % defaults and preferences. This is implemented by bumping the priority of optimization % criteria for built specs -- so that they take precedence over the otherwise % topmost-priority criterion to reuse what is installed. -build_priority(Package, 100) :- build(Package), node(Package). +% +% The priority ranges are: +% 200+ Shifted priorities for build nodes; correspond to priorities 0 - 99. +% 100 - 199 Unshifted priorities. Currently only includes minimizing #builds. +% 0 - 99 Priorities for non-built nodes. +build_priority(Package, 200) :- build(Package), node(Package). build_priority(Package, 0) :- not build(Package), node(Package). #defined installed_hash/2. @@ -807,18 +812,17 @@ build_priority(Package, 0) :- not build(Package), node(Package). % is displayed (clingo doesn't display sums over empty sets by default) % Try hard to reuse installed packages (i.e., minimize the number built) -opt_criterion(17, "number of packages to build (vs. reuse)"). -#minimize { 0@17: #true }. -#minimize { 1@17,Package : build(Package), optimize_for_reuse() }. +opt_criterion(100, "number of packages to build (vs. reuse)"). +#minimize { 0@100: #true }. +#minimize { 1@100,Package : build(Package), optimize_for_reuse() }. #defined optimize_for_reuse/0. % Minimize the number of deprecated versions being used -opt_criterion(116, "(build) deprecated versions used"). -opt_criterion(16, "deprecated versions used"). -#minimize{ 0@116: #true }. -#minimize{ 0@16: #true }. +opt_criterion(14, "deprecated versions used"). +#minimize{ 0@214: #true }. +#minimize{ 0@14: #true }. #minimize{ - 1@16+Priority,Package + 1@14+Priority,Package : deprecated(Package, _), build_priority(Package, Priority) }. @@ -827,33 +831,30 @@ opt_criterion(16, "deprecated versions used"). % 1. Version weight % 2. Number of variants with a non default value, if not set % for the root(Package) -opt_criterion(115, "(build) version weight"). -opt_criterion(15, "version weight"). -#minimize{ 0@115: #true }. -#minimize{ 0@15: #true }. +opt_criterion(13, "version weight"). +#minimize{ 0@213: #true }. +#minimize{ 0@13: #true }. #minimize { - Weight@15+Priority + Weight@13+Priority : root(Package),version_weight(Package, Weight), build_priority(Package, Priority) }. -opt_criterion(114, "(build) number of non-default variants (roots)"). -opt_criterion(14, "number of non-default variants (roots)"). -#minimize{ 0@114: #true }. -#minimize{ 0@14: #true }. +opt_criterion(12, "number of non-default variants (roots)"). +#minimize{ 0@212: #true }. +#minimize{ 0@12: #true }. #minimize { - Weight@14+Priority,Package,Variant,Value + Weight@12+Priority,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), root(Package), build_priority(Package, Priority) }. -opt_criterion(112, "(build) preferred providers for roots"). -opt_criterion(12, "preferred providers for roots"). -#minimize{ 0@112 : #true }. -#minimize{ 0@12: #true }. +opt_criterion(11, "preferred providers for roots"). +#minimize{ 0@211 : #true }. +#minimize{ 0@11: #true }. #minimize{ - Weight@12+Priority,Provider,Virtual + Weight@11+Priority,Provider,Virtual : provider_weight(Provider, Virtual, Weight), root(Provider), build_priority(Provider, Priority) @@ -862,12 +863,11 @@ opt_criterion(12, "preferred providers for roots"). % 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(111, "(build) number of values in multi-valued variants (root)"). -opt_criterion(11, "number of values in multi-valued variants (root)"). -#minimize{ 0@111 : #true }. -#minimize{ 0@11 : #true }. +opt_criterion(10, "number of values in multi-valued variants (root)"). +#minimize{ 0@210 : #true }. +#minimize{ 0@10 : #true }. #maximize { - 1@11+Priority,Package,Variant,Value + 1@10+Priority,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), not variant_single_value(Package, Variant), root(Package), @@ -875,11 +875,11 @@ opt_criterion(11, "number of values in multi-valued variants (root)"). }. % Try to use default variants or variants that have been set -opt_criterion(110, "(build) number of non-default variants (non-roots)"). -opt_criterion(10, "number of non-default variants (non-roots)"). -#minimize{ 0@10: #true }. +opt_criterion(9, "number of non-default variants (non-roots)"). +#minimize{ 0@209: #true }. +#minimize{ 0@9: #true }. #minimize { - Weight@10+Priority,Package,Variant,Value + Weight@9+Priority,Package,Variant,Value : variant_not_default(Package, Variant, Value, Weight), not root(Package), build_priority(Package, Priority) @@ -887,62 +887,57 @@ opt_criterion(10, "number of non-default variants (non-roots)"). % Minimize the weights of the providers, i.e. use as much as % possible the most preferred providers -opt_criterion(109, "(build) preferred providers (non-roots)"). -opt_criterion(9, "preferred providers (non-roots)"). -#minimize{ 0@109: #true }. -#minimize{ 0@9: #true }. +opt_criterion(8, "preferred providers (non-roots)"). +#minimize{ 0@208: #true }. +#minimize{ 0@8: #true }. #minimize{ - Weight@9+Priority,Provider,Virtual + Weight@8+Priority,Provider,Virtual : provider_weight(Provider, Virtual, Weight), not root(Provider), build_priority(Provider, Priority) }. % Try to minimize the number of compiler mismatches in the DAG. -opt_criterion(108, "(build) compiler mismatches"). -opt_criterion(8, "compiler mismatches"). -#minimize{ 0@108: #true }. -#minimize{ 0@8: #true }. +opt_criterion(7, "compiler mismatches"). +#minimize{ 0@207: #true }. +#minimize{ 0@7: #true }. #minimize{ - 1@8+Priority,Package,Dependency + 1@7+Priority,Package,Dependency : compiler_mismatch(Package, Dependency), build_priority(Package, Priority) }. % Try to minimize the number of compiler mismatches in the DAG. -opt_criterion(107, "(build) OS mismatches"). -opt_criterion(7, "OS mismatches"). -#minimize{ 0@107: #true }. -#minimize{ 0@7: #true }. +opt_criterion(6, "OS mismatches"). +#minimize{ 0@206: #true }. +#minimize{ 0@6: #true }. #minimize{ - 1@7+Priority,Package,Dependency + 1@6+Priority,Package,Dependency : node_os_mismatch(Package, Dependency), build_priority(Package, Priority) }. -opt_criterion(106, "(build) non-preferred OSes"). -#minimize{ 0@106: #true }. -#minimize{ 0@6: #true }. +opt_criterion(5, "non-preferred OS's"). +#minimize{ 0@205: #true }. +#minimize{ 0@5: #true }. #minimize{ - Weight@6+Priority,Package + Weight@5+Priority,Package : node_os_weight(Package, Weight), build_priority(Package, Priority) }. % Choose more recent versions for nodes -opt_criterion(105, "(build) version badness"). opt_criterion(5, "version badness"). -#minimize{ 0@105: #true }. -#minimize{ 0@5: #true }. +#minimize{ 0@204: #true }. +#minimize{ 0@4: #true }. #minimize{ - Weight@5+Priority,Package + Weight@4+Priority,Package : version_weight(Package, Weight), build_priority(Package, Priority) }. % Try to use preferred compilers -opt_criterion(103, "(build) non-preferred compilers"). opt_criterion(3, "non-preferred compilers"). -#minimize{ 0@103: #true }. +#minimize{ 0@203: #true }. #minimize{ 0@3: #true }. #minimize{ Weight@3+Priority,Package @@ -952,9 +947,8 @@ opt_criterion(3, "non-preferred compilers"). % Minimize the number of mismatches for targets in the DAG, try % to select the preferred target. -opt_criterion(102, "(build) target mismatches"). opt_criterion(2, "target mismatches"). -#minimize{ 0@102: #true }. +#minimize{ 0@202: #true }. #minimize{ 0@2: #true }. #minimize{ 1@2+Priority,Package,Dependency @@ -962,9 +956,8 @@ opt_criterion(2, "target mismatches"). build_priority(Package, Priority) }. -opt_criterion(101, "(build) non-preferred targets"). opt_criterion(1, "non-preferred targets"). -#minimize{ 0@101: #true }. +#minimize{ 0@201: #true }. #minimize{ 0@1: #true }. #minimize{ Weight@1+Priority,Package -- cgit v1.2.3-70-g09d2