concretizer: handle variant defaults with optimization
- Instead of using default logic, handle variant defaults by minimizing the number of non-default variants in the solution. - This actually seems to be pretty fast, and it fixes the long-standing issue that writing this: spack install hdf5 ^mpich will fail if you don't specify hdf5+mpi. With optimization and allowing enums to be enumerated, the solver seems to be able to quickly discover that +mpi is the only way hdf5 can depend on mpich, and it forces the switch to be thrown.
This commit is contained in:
parent
1cab1b1994
commit
501cb371c9
@ -275,6 +275,19 @@ def pkg_rules(self, pkg):
|
|||||||
self.rule(
|
self.rule(
|
||||||
fn.variant_default_value(pkg.name, name, val),
|
fn.variant_default_value(pkg.name, name, val),
|
||||||
fn.node(pkg.name))
|
fn.node(pkg.name))
|
||||||
|
|
||||||
|
values = variant.values
|
||||||
|
if values is None:
|
||||||
|
values = []
|
||||||
|
elif isinstance(values, spack.variant.DisjointSetsOfValues):
|
||||||
|
union = set()
|
||||||
|
for s in values.sets:
|
||||||
|
union.update(s)
|
||||||
|
values = union
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
self.fact(fn.variant_possible_value(pkg.name, name, value))
|
||||||
|
|
||||||
self.out.write('\n')
|
self.out.write('\n')
|
||||||
|
|
||||||
# default compilers for this package
|
# default compilers for this package
|
||||||
@ -430,6 +443,7 @@ def generate_asp_program(self, specs):
|
|||||||
|
|
||||||
self.h1('Spec Constraints')
|
self.h1('Spec Constraints')
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
|
self.fact(fn.root(spec.name))
|
||||||
for dep in spec.traverse():
|
for dep in spec.traverse():
|
||||||
self.h2('Spec: %s' % str(dep))
|
self.h2('Spec: %s' % str(dep))
|
||||||
self.fact(fn.node(dep.name))
|
self.fact(fn.node(dep.name))
|
||||||
@ -498,6 +512,11 @@ def parse(self, stream, result):
|
|||||||
result.satisfiable = True
|
result.satisfiable = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
match = re.match(r'OPTIMUM FOUND', line)
|
||||||
|
if match:
|
||||||
|
result.satisfiable = True
|
||||||
|
continue
|
||||||
|
|
||||||
match = re.match(r'UNSATISFIABLE', line)
|
match = re.match(r'UNSATISFIABLE', line)
|
||||||
if match:
|
if match:
|
||||||
result.satisfiable = False
|
result.satisfiable = False
|
||||||
@ -517,17 +536,22 @@ def parse(self, stream, result):
|
|||||||
functions = []
|
functions = []
|
||||||
for m in re.finditer(r'(\w+)\(([^)]*)\)', answer):
|
for m in re.finditer(r'(\w+)\(([^)]*)\)', answer):
|
||||||
name, arg_string = m.groups()
|
name, arg_string = m.groups()
|
||||||
args = re.findall(r'"([^"]*)"', arg_string)
|
args = re.split(r'\s*,\s*', arg_string)
|
||||||
|
args = [s.strip('"') if s.startswith('"') else int(s)
|
||||||
|
for s in args]
|
||||||
functions.append((name, args))
|
functions.append((name, args))
|
||||||
|
|
||||||
# Functions don't seem to be in particular order in output.
|
# Functions don't seem to be in particular order in output.
|
||||||
# Sort them here so that nodes are created before directives
|
# Sort them here so that nodes are first, and so created
|
||||||
# that need them, (depends_on(), etc.)
|
# before directives that need them (depends_on(), etc.)
|
||||||
functions.sort(key=lambda f: f[0] != "node")
|
functions.sort(key=lambda f: f[0] != "node")
|
||||||
|
|
||||||
self._specs = {}
|
self._specs = {}
|
||||||
for name, args in functions:
|
for name, args in functions:
|
||||||
action = getattr(self, name)
|
action = getattr(self, name, None)
|
||||||
|
if not action:
|
||||||
|
print("%s(%s)" % (name, ", ".join(str(a) for a in args)))
|
||||||
|
continue
|
||||||
assert action and callable(action)
|
assert action and callable(action)
|
||||||
action(*args)
|
action(*args)
|
||||||
|
|
||||||
@ -570,6 +594,7 @@ def highlight(string):
|
|||||||
string = re.sub(r'\bUNSATISFIABLE', "@R{UNSATISFIABLE}", string)
|
string = re.sub(r'\bUNSATISFIABLE', "@R{UNSATISFIABLE}", string)
|
||||||
string = re.sub(r'\bINCONSISTENT', "@R{INCONSISTENT}", string)
|
string = re.sub(r'\bINCONSISTENT', "@R{INCONSISTENT}", string)
|
||||||
string = re.sub(r'\bSATISFIABLE', "@G{SATISFIABLE}", string)
|
string = re.sub(r'\bSATISFIABLE', "@G{SATISFIABLE}", string)
|
||||||
|
string = re.sub(r'\bOPTIMUM FOUND', "@G{OPTIMUM FOUND}", string)
|
||||||
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
@ -614,7 +639,7 @@ def colorize(string):
|
|||||||
fail_on_error=False)
|
fail_on_error=False)
|
||||||
|
|
||||||
warnings.seek(0)
|
warnings.seek(0)
|
||||||
result.warnings = warnings.read()
|
result.warnings = warnings.read().decode("utf-8")
|
||||||
|
|
||||||
# dump any warnings generated by the solver
|
# dump any warnings generated by the solver
|
||||||
if 'warnings' in dump:
|
if 'warnings' in dump:
|
||||||
|
@ -24,6 +24,11 @@ depends_on(P, D) :- declared_dependency(P, D), not virtual(D), node(P).
|
|||||||
1 { depends_on(P, Q) : provides_virtual(Q, V) } 1
|
1 { depends_on(P, Q) : provides_virtual(Q, V) } 1
|
||||||
:- declared_dependency(P, V), virtual(V), node(P).
|
:- declared_dependency(P, V), virtual(V), node(P).
|
||||||
|
|
||||||
|
needed(D) :- depends_on(_, D), node(D).
|
||||||
|
needed(P) :- root(P).
|
||||||
|
:- node(P), not needed(P).
|
||||||
|
#defined root/1.
|
||||||
|
|
||||||
% real dependencies imply new nodes.
|
% real dependencies imply new nodes.
|
||||||
node(D) :- node(P), depends_on(P, D).
|
node(D) :- node(P), depends_on(P, D).
|
||||||
|
|
||||||
@ -36,9 +41,8 @@ node(D) :- node(P), depends_on(P, D).
|
|||||||
%-----------------------------------------------------------------------------
|
%-----------------------------------------------------------------------------
|
||||||
% Variant semantics
|
% Variant semantics
|
||||||
%-----------------------------------------------------------------------------
|
%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
% one variant value for single-valued variants.
|
% one variant value for single-valued variants.
|
||||||
1 { variant_value(P, V, X) : variant_value(P, V, X) } 1
|
1 { variant_value(P, V, X) : variant_possible_value(P, V, X) } 1
|
||||||
:- node(P), variant(P, V), variant_single_value(P, V).
|
:- node(P), variant(P, V), variant_single_value(P, V).
|
||||||
|
|
||||||
% if a variant is set to anything, it is considered 'set'.
|
% if a variant is set to anything, it is considered 'set'.
|
||||||
@ -47,8 +51,19 @@ variant_set(P, V) :- variant_set(P, V, _).
|
|||||||
% variant_set is an explicitly set variant value. If it's not 'set',
|
% variant_set is an explicitly set variant value. If it's not 'set',
|
||||||
% we revert to the default value. If it is set, we force the set value
|
% we revert to the default value. If it is set, we force the set value
|
||||||
variant_value(P, V, X) :- node(P), variant(P, V), variant_set(P, V, X).
|
variant_value(P, V, X) :- node(P), variant(P, V), variant_set(P, V, X).
|
||||||
variant_value(P, V, X) :- node(P), variant(P, V), not variant_set(P, V),
|
|
||||||
variant_default_value(P, V, X).
|
% prefer default values.
|
||||||
|
variant_not_default(P, V, X, 1)
|
||||||
|
:- variant_value(P, V, X),
|
||||||
|
not variant_default_value(P, V, X),
|
||||||
|
node(P).
|
||||||
|
|
||||||
|
variant_not_default(P, V, X, 0)
|
||||||
|
:- variant_value(P, V, X),
|
||||||
|
variant_default_value(P, V, X),
|
||||||
|
node(P).
|
||||||
|
|
||||||
|
#minimize { N@1,P,V,X : variant_not_default(P, V, X, N) }.
|
||||||
|
|
||||||
% suppress wranings about this atom being unset. It's only set if some
|
% suppress wranings about this atom being unset. It's only set if some
|
||||||
% spec or some package sets it, and without this, clingo will give
|
% spec or some package sets it, and without this, clingo will give
|
||||||
|
Loading…
Reference in New Issue
Block a user