diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 22ec984c501..e566b2fa944 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -13,7 +13,7 @@ import re import types import warnings -from typing import Tuple, Union +from typing import Dict, List, Tuple, Union import archspec.cpu @@ -155,8 +155,14 @@ def getter(node): build_priority_offset = 100_000 -def build_criteria_names(costs, opt_criteria): +def build_criteria_names( + costs: List[int], opt_criteria: List["AspFunction"], max_depth: int +) -> Dict[str, Union[int, List[int]]]: """Construct an ordered mapping from criteria names to costs.""" + print(costs) + print(len(costs)) + print(max_depth) + print(opt_criteria) # ensure names of all criteria are unique names = {criterion.args[0] for criterion in opt_criteria} @@ -171,19 +177,46 @@ def build_criteria_names(costs, opt_criteria): # - N reuse criteria # ... + # split opt criteria into two lists + fixed_criteria = [oc for oc in opt_criteria if oc.args[1] == "fixed"] + leveled_criteria = [oc for oc in opt_criteria if oc.args[1] == "leveled"] + + print("FIXED:", len(fixed_criteria)) + print("LEVELED:", len(leveled_criteria)) + + n_depths = max_depth + 1 # depths start at zero + total_criteria = len(fixed_criteria) + len(leveled_criteria) * 2 * n_depths + print(total_criteria) + # opt_criteria has all the named criteria, which is all but the errors. # So we can figure out how many build criteria there are up front. - n_build_criteria = len(opt_criteria) - 2 + # the -2 here accounts for: + # - number of specs not concretized + # - number of packages to build (vs. reuse) + n_leveled_criteria = len(opt_criteria) - 2 - # number of criteria *not* including errors - n_named_criteria = len(opt_criteria) + n_build_criteria + n_leveled_criteria = n_build_criteria * 2 * n_depths + total_criteria = n_leveled_criteria + 2 - # opt_criteria are in order, highest to lowest, as written in concretize.lp - # put costs in the same order as opt criteria + assert len(costs) == total_criteria + + # For each level, opt_criteria are in order, highest to lowest, as written in + # concretize.lp put costs in the same order as opt criteria start = len(costs) - n_named_criteria + + # build criteria count should be divisible by n_depths or we're doing something wrong. + assert n_build_criteria % n_depths == 0 + + criteria = {} ordered_costs = costs[start:] ordered_costs.insert(1, ordered_costs.pop(n_build_criteria + 1)) + for i, (priority, type, name) in enumerate(c.args for c in opt_criteria): + if type == "fixed": + criteria[name] = ordered_costs[i] + else: + criteria[name] = ordered_costs[i::n_build_criteria] + # list of build cost, reuse cost, and name of each criterion criteria: List[Tuple[int, int, str]] = [] for i, (priority, name) in enumerate(c.args for c in opt_criteria): @@ -694,11 +727,10 @@ def solve(self, setup, specs, reuse=None, output=None, control=None): self.control = control or default_clingo_control() # set up the problem -- this generates facts and rules self.assumptions = [] - timer.start("setup") - with self.control.backend() as backend: - self.backend = backend - setup.setup(self, specs, reuse=reuse) - timer.stop("setup") + with timer.measure("setup"): + with self.control.backend() as backend: + self.backend = backend + setup.setup(self, specs, reuse=reuse) timer.start("load") # read in the main ASP program and display logic -- these are @@ -775,14 +807,18 @@ def on_model(model): # build specs from spec attributes in the model spec_attrs = extract_functions(best_model, "attr") - answers = builder.build_specs(spec_attrs) + with timer.measure("build_specs"): + answers = builder.build_specs(spec_attrs) # add best spec to the results result.answers.append((list(min_cost), 0, answers, spec_attrs)) # get optimization criteria criteria = extract_functions(best_model, "opt_criterion") - result.criteria = build_criteria_names(min_cost, criteria) + depths = extract_functions(best_model, "depth") + print(depths) + max_depth = max(d.args[1] for d in depths) + result.criteria = build_criteria_names(min_cost, criteria, max_depth) # record the number of models the solver considered result.nmodels = len(models) diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp index fc3c72d978a..1c8525cdf0c 100644 --- a/lib/spack/spack/solver/concretize.lp +++ b/lib/spack/spack/solver/concretize.lp @@ -24,11 +24,13 @@ literal_not_solved(ID) :- not literal_solved(ID), literal(ID). 1 { literal_solved(ID) : literal(ID) }. % priority ranges for optimization criteria +% note that clingo's weight_t is int32_t, so the max priority we can use is 2,147,483,647 #const error_prio = 10000000. #const solve_prio = 1000000. -#const build_prio = 100000. +#const build_prio = 100000. % n_nodes x depth_offset x max levels needs to be less than this +#const depth_offset = 100. % depth_offset-1 is the max id for leveled criteria -opt_criterion(solve_prio, "number of input specs not concretized"). +opt_criterion(solve_prio, "fixed", "number of input specs not concretized"). #minimize{ 0@solve_prio: #true }. #minimize{ 1@solve_prio,ID : literal_not_solved(ID) }. @@ -1112,15 +1114,9 @@ build_priority(Package, 0) :- attr("node", Package), not optimize_for_reuse(). %----------------------------------------------------------------------------- #const max_depth = 10. -% roots have depth 0 +% roots have depth 0. depth(Package, 0) :- attr("root", Package). -% possible optimization -% adding this to the minimization below doesn't seem to help. -%possible_dependency(Dependent, Package) :- dependency_condition(_, Dependent, Package). -%possible_dependency(Dependent, Package) :- -% dependency_condition(_, Dependent, Virtual), possible_provider(Package, Virtual). - % other nodes' depth is the minimum depth of any dependent plus one. depth(Package, N+1) :- N = #min{ @@ -1159,190 +1155,146 @@ depth(Package, N+1) :- % 2. a `#minimize{ 0@2 : #true }.` statement that ensures the criterion % is displayed (clingo doesn't display sums over empty sets by default) +% Ensure that values are returned by clingo for every distinct optimization criterion. +% Some criteria are "fixed" and have only one bucket. Others are summed into multiple +% buckets -- per build priority and per depth in the graph. +% If we don't do this, it's very hard to read the sums back. We use `0@...` because +% it doesn't affect the sums -- it just ensure that clingo returns them. + +% "fixed" criteria have one bucket -- their priority. +#minimize{ 0@N: opt_criterion(N, "fixed", _) }. + +% "leveled" criteria sum into a bucket per depth in the graph, per build priority +%#minimize{ +% @(((max_depth - D - 1) * depth_offset) + N + build_prio) +% : opt_criterion(N, "leveled", _), depth(_, D) +%}. +#minimize{ + 0@(((max_depth - D - 1) * depth_offset) + N) + : opt_criterion(N, "leveled", _), depth(_, D) +}. + % Try hard to reuse installed packages (i.e., minimize the number built) -opt_criterion(build_prio, "number of packages to build (vs. reuse)"). -#minimize { 0@build_prio: #true }. -#minimize { 1@build_prio,Package : build(Package), optimize_for_reuse() }. +opt_criterion(build_prio, "fixed", "number of packages to build (vs. reuse)"). #defined optimize_for_reuse/0. + % A condition group specifies one or more specs that must be satisfied. % Specs declared first are preferred, so we assign increasing weights and % minimize the weights. -opt_criterion(75, "requirement weight"). -#minimize{ 0@75+build_prio: #true }. -#minimize{ 0@75: #true }. -#minimize { - Weight@75+Priority - : requirement_weight(Package, Weight), - build_priority(Package, Priority) -}. +opt_criterion(65, "leveled", "requirement weight"). +%#minimize { +% Weight@(65 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : requirement_weight(Package, Weight), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Minimize the number of deprecated versions being used -opt_criterion(73, "deprecated versions used"). -#minimize{ 0@73+build_prio: #true }. -#minimize{ 0@73: #true }. -#minimize{ - 1@73+Priority,Package - : attr("deprecated", Package, _), - build_priority(Package, Priority) -}. +opt_criterion(60, "leveled", "deprecated versions used"). +%#minimize{ +% 1@(60 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : attr("deprecated", Package, _), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Minimize the: % 1. Version weight % 2. Number of variants with a non default value, if not set % for the root package. -opt_criterion(70, "ROOTS: version badness"). -#minimize{ 0@70+build_prio: #true }. -#minimize{ 0@70: #true }. -#minimize { - Weight@70+Priority - : attr("root", Package), - version_weight(Package, Weight), - build_priority(Package, Priority) -}. +opt_criterion(55, "leveled", "version badness"). +%#minimize { +% Weight@(55 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : version_weight(Package, Weight), +% build_priority(Package, Priority), +% depth(Package, D) +%}. -opt_criterion(65, "ROOTS: number of non-default variants"). -#minimize{ 0@65+build_prio: #true }. -#minimize{ 0@65: #true }. -#minimize { - 1@65+Priority,Package,Variant,Value - : variant_not_default(Package, Variant, Value), - attr("root", Package), - build_priority(Package, Priority) -}. +opt_criterion(50, "leveled", "number of non-default variants"). +%#minimize { +% 1@(50 + ((max_depth - D - 1) * depth_offset) + Priority), Package, Variant, Value +% : variant_not_default(Package, Variant, Value), +% build_priority(Package, Priority), +% depth(Package, D) +%}. -opt_criterion(60, "ROOTS: preferred providers"). -#minimize{ 0@60+build_prio: #true }. -#minimize{ 0@60: #true }. -#minimize{ - Weight@60+Priority,Provider,Virtual - : provider_weight(Provider, Virtual, Weight), - attr("root", Provider), - build_priority(Provider, Priority) -}. +opt_criterion(45, "leveled", "preferred providers"). +%#minimize{ +% Weight@(45 + ((max_depth - D - 1) * depth_offset) + Priority), Provider, Virtual +% : provider_weight(Provider, Virtual, Weight), +% build_priority(Provider, Priority), +% depth(Package, D) +%}. -opt_criterion(55, "ROOTS: default values of variants not being used"). -#minimize{ 0@55+build_prio: #true }. -#minimize{ 0@55: #true }. -#minimize{ - 1@55+Priority,Package,Variant,Value - : variant_default_not_used(Package, Variant, Value), - attr("root", Package), - build_priority(Package, Priority) -}. +opt_criterion(40, "leveled", "default values of variants not being used"). +%#minimize{ +% 1@(40 + ((max_depth - D - 1) * depth_offset) + Priority), Package, Variant, Value +% : variant_default_not_used(Package, Variant, Value), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Try to use default variants or variants that have been set -opt_criterion(50, "NON-ROOTS: number of non-default variants"). -#minimize{ 0@50+build_prio: #true }. -#minimize{ 0@50: #true }. -#minimize { - 1@50+Priority,Package,Variant,Value - : variant_not_default(Package, Variant, Value), - not attr("root", Package), - build_priority(Package, Priority) -}. +opt_criterion(35, "leveled", "compiler mismatches (not from CLI)"). +%#minimize{ +% 1@(35 + ((max_depth - D - 1) * depth_offset) + Priority), Dependent, Package +% : compiler_mismatch(Dependent, Package), +% build_priority(Package, Priority), +% depth(Package, D) +%}. -% Minimize the weights of the providers, i.e. use as much as -% possible the most preferred providers -opt_criterion(45, "NON-ROOTS: preferred providers"). -#minimize{ 0@45+build_prio: #true }. -#minimize{ 0@45: #true }. -#minimize{ - Weight@45+Priority,Provider,Virtual - : provider_weight(Provider, Virtual, Weight), - not attr("root", Provider), - build_priority(Provider, Priority) -}. +opt_criterion(30, "leveled", "compiler mismatches (from CLI)"). +%#minimize{ +% 1@(30 + ((max_depth - D - 1) * depth_offset) + Priority), Dependent, Package +% : compiler_mismatch_required(Dependent, Package), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Try to minimize the number of compiler mismatches in the DAG. -opt_criterion(40, "compiler mismatches that are not from CLI"). -#minimize{ 0@40+build_prio: #true }. -#minimize{ 0@40: #true }. -#minimize{ - 1@40+Priority,Package,Dependency - : compiler_mismatch(Package, Dependency), - build_priority(Package, Priority) -}. - -opt_criterion(39, "compiler mismatches from CLI"). -#minimize{ 0@39+build_prio: #true }. -#minimize{ 0@39: #true }. -#minimize{ - 1@39+Priority,Package,Dependency - : compiler_mismatch_required(Package, Dependency), - build_priority(Package, Priority) -}. - -% Try to minimize the number of compiler mismatches in the DAG. -opt_criterion(35, "OS mismatches"). -#minimize{ 0@35+build_prio: #true }. -#minimize{ 0@35: #true }. -#minimize{ - 1@35+Priority,Package,Dependency - : node_os_mismatch(Package, Dependency), - build_priority(Package, Priority) -}. - -opt_criterion(30, "non-preferred OS's"). -#minimize{ 0@30+build_prio: #true }. -#minimize{ 0@30: #true }. -#minimize{ - Weight@30+Priority,Package - : node_os_weight(Package, Weight), - build_priority(Package, Priority) -}. - -% Choose more recent versions for nodes -opt_criterion(25, "NON-ROOTS: version badness"). -#minimize{ 0@25+build_prio: #true }. -#minimize{ 0@25: #true }. -#minimize{ - Weight@25+Priority,Package - : not attr("root", Package), - version_weight(Package, Weight), - build_priority(Package, Priority) -}. - -% Try to use all the default values of variants -opt_criterion(20, "NON-ROOTS: default values of variants not being used"). -#minimize{ 0@20+build_prio: #true }. -#minimize{ 0@20: #true }. -#minimize{ - 1@20+Priority,Package,Variant,Value - : variant_default_not_used(Package, Variant, Value), - not attr("root", Package), - build_priority(Package, Priority) -}. +opt_criterion(25, "leveled", "OS mismatches"). +%#minimize{ +% 1@(25 + ((max_depth - D - 1) * depth_offset) + Priority), Dependent, Package +% : node_os_mismatch(Dependent, Package), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Try to use preferred compilers -opt_criterion(15, "non-preferred compilers"). -#minimize{ 0@15+build_prio: #true }. -#minimize{ 0@15: #true }. -#minimize{ - Weight@15+Priority,Package - : compiler_weight(Package, Weight), - build_priority(Package, Priority) -}. +opt_criterion(20, "leveled", "non-preferred compilers"). +%#minimize{ +% Weight@(20 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : compiler_weight(Package, Weight), +% build_priority(Package, Priority), +% depth(Package, D) +%}. + +opt_criterion(15, "leveled", "non-preferred OS's"). +%#minimize{ +% Weight@(15 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : node_os_weight(Package, Weight), +% build_priority(Package, Priority), +% depth(Package, D) +%}. % Minimize the number of mismatches for targets in the DAG, try % to select the preferred target. -opt_criterion(10, "target mismatches"). -#minimize{ 0@10+build_prio: #true }. -#minimize{ 0@10: #true }. -#minimize{ - 1@10+Priority,Package,Dependency - : node_target_mismatch(Package, Dependency), - build_priority(Package, Priority) -}. +opt_criterion(10, "leveled", "target mismatches"). +%#minimize{ +% 1@(10 + ((max_depth - D - 1) * depth_offset) + Priority), Dependent, Package +% : node_target_mismatch(Dependent, Package), +% build_priority(Package, Priority), +% depth(Package, D) +%}. -opt_criterion(5, "non-preferred targets"). -#minimize{ 0@5+build_prio: #true }. -#minimize{ 0@5: #true }. -#minimize{ - Weight@5+Priority,Package - : node_target_weight(Package, Weight), - build_priority(Package, Priority) -}. +opt_criterion(5, "leveled", "non-preferred targets"). +%#minimize{ +% Weight@(5 + ((max_depth - D - 1) * depth_offset) + Priority), Package +% : node_target_weight(Package, Weight), +% build_priority(Package, Priority), +% depth(Package, D) +%}. %----------------- % Domain heuristic diff --git a/lib/spack/spack/solver/display.lp b/lib/spack/spack/solver/display.lp index 1f308f03648..c09394951ab 100644 --- a/lib/spack/spack/solver/display.lp +++ b/lib/spack/spack/solver/display.lp @@ -15,7 +15,7 @@ #show attr/4. % names of optimization criteria -#show opt_criterion/2. +#show opt_criterion/3. % error types #show error/2. @@ -27,3 +27,12 @@ % debug #show depth/2. +%#show depends_on/2. + +%node(Package) :- attr("node", Package). +%#show node/1. + +%version(Package, Version) :- attr("version", Package, Version). +%#show version/2. + +#show impose/1.