New constraint solver works!

It even explains conflicts.  It just doesn't spit out the list of
other constraints on conflicted packages yet (easy to do).

Left to do:
- Remove old solver code
- Call nudge()
- More nuanced cost function
- Clean up solver.js a little
- Proper handling of pre-release versions
- Lots more tests of different scenarios!
This commit is contained in:
David Greenspan
2015-01-30 16:45:28 -08:00
parent 2de487f631
commit ff4398fe1c
7 changed files with 651 additions and 225 deletions

View File

@@ -13,7 +13,7 @@ CS.CatalogCache = function () {
// A map derived from the keys of _dependencies, for ease of iteration.
// "package" -> ["versions", ...]
// Versions in the array are unique but not sorted, unless the `.sorted`
// property is set on the array.
// property is set on the array. The array is never empty.
this._versions = {};
};

View File

@@ -5,22 +5,48 @@ var CS = ConstraintSolver;
// and it holds the data loaded from the Catalog as well. It can be
// serialized to JSON and read back in for testing purposes.
CS.Input = function (dependencies, constraints, catalogCache, options) {
var self = this;
options = options || {};
this.dependencies = dependencies;
this.constraints = constraints;
this.upgrade = options.upgrade || [];
this.anticipatedPrereleases = options.anticipatedPrereleases || {};
this.previousSolution = options.previousSolution || null;
self.dependencies = dependencies;
self.constraints = constraints;
self.upgrade = options.upgrade || [];
self.anticipatedPrereleases = options.anticipatedPrereleases || {};
self.previousSolution = options.previousSolution || null;
check(this.dependencies, [String]);
check(this.constraints, [PackageConstraintType]);
check(this.upgrade, [String]);
check(this.anticipatedPrereleases,
check(self.dependencies, [String]);
check(self.constraints, [PackageConstraintType]);
check(self.upgrade, [String]);
check(self.anticipatedPrereleases,
Match.ObjectWithValues(Match.ObjectWithValues(Boolean)));
check(this.previousSolution, Match.OneOf(Object, null));
check(self.previousSolution, Match.OneOf(Object, null));
this.catalogCache = catalogCache;
self.catalogCache = catalogCache;
self._dependencySet = {}; // package name -> true
_.each(self.dependencies, function (d) {
self._dependencySet[d] = true;
});
self._upgradeSet = {};
_.each(self.upgrade, function (u) {
self._upgradeSet[u] = true;
});
};
CS.Input.prototype.isKnownPackage = function (p) {
return this.catalogCache.hasPackage(p);
};
CS.Input.prototype.isRootDependency = function (p) {
return _.has(this._dependencySet, p);
};
CS.Input.prototype.isUpgrading = function (p) {
return _.has(this._upgradeSet, p);
};
CS.Input.prototype.isInPreviousSolution = function (p) {
return !! (this.previousSolution && _.has(this.previousSolution, p));
};
CS.Input.prototype.loadFromCatalog = function (catalogLoader) {
@@ -34,9 +60,11 @@ CS.Input.prototype.loadFromCatalog = function (catalogLoader) {
_.each(self.constraints, function (constraint) {
packagesToLoad[constraint.name] = true;
});
_.each(self.previousSolution, function (version, package) {
packagesToLoad[package] = true;
});
if (self.previousSolution) {
_.each(self.previousSolution, function (version, package) {
packagesToLoad[package] = true;
});
}
// Load packages into the cache (if they aren't loaded already).
catalogLoader.loadAllVersionsRecursive(_.keys(packagesToLoad));
@@ -114,3 +142,6 @@ var PackageConstraintType = Match.OneOf(
check(c.vConstraint, VersionConstraintType);
return c.constructor !== Object;
}));
CS.Input.VersionConstraintType = VersionConstraintType;
CS.Input.PackageConstraintType = PackageConstraintType;

View File

@@ -38,214 +38,9 @@ CS.PackagesResolver.prototype.resolve = function (dependencies, constraints,
};
var newResolveWithInput = function (input, _nudge) {
if (input.previousSolution || input.upgrade.length) {
// XXX Bail out to the old solver for now.
console.log("Bailing to old solver...");
return CS.PackagesResolver._resolveWithInput(input, _nudge);
}
console.log("Using new solver...");
var solver = new CS.Solver(input);
var cache = input.catalogCache;
// Packages that are mentioned but aren't found in the CatalogCache
var unknownPackages = {}; // package name -> true
var packageVersionsRequiringPackage = {}; // package -> [package-and-version]
var rootDeps = {}; // package name -> true
_.each(input.dependencies, function (p) {
if (! cache.hasPackage(p)) {
unknownPackages[p] = true;
}
rootDeps[p] = true;
});
var solver = new Logic.Solver;
var resolverOptions = {
anticipatedPrereleases: input.anticipatedPrereleases
};
var allConstraints = [];
var addConstraint = function (pv, p2, vConstraint) {
var p2Versions = cache.getPackageVersions(p2);
var okVersions = _.filter(p2Versions, function (v2) {
return CS.isConstraintSatisfied(p2, vConstraint,
v2, resolverOptions);
});
var okPVersions = _.map(okVersions, function (v2) {
return p2 + ' ' + v2;
});
// If we select this version of `p` and we select some version
// of `p2`, we must select an "ok" version.
var constraintName = "constraint#" + allConstraints.length;
allConstraints.push([pv, p2, vConstraint]);
if (pv !== null) {
solver.require(Logic.implies(constraintName,
Logic.or(Logic.not(pv),
Logic.not(p2),
okPVersions)));
} else {
solver.require(Logic.implies(constraintName,
Logic.or(Logic.not(p2),
okPVersions)));
}
};
cache.eachPackage(function (p, versions) {
// ["foo 1.0.0", "foo 1.0.1", ...] for a given "foo"
var packageAndVersions = _.map(versions, function (v) {
return p + ' ' + v;
});
// At most one of ["foo 1.0.0", "foo 1.0.1", ...] is true.
solver.require(Logic.atMostOne(packageAndVersions));
// The variable "foo" is true if and only if at least one of the
// variables ["foo 1.0.0", "foo 1.0.1", ...] is true.
solver.require(Logic.equiv(p, Logic.or(packageAndVersions)));
_.each(versions, function (v) {
var pv = p + ' ' + v;
_.each(cache.getDependencyMap(p, v), function (dep) {
// `dep` is a CS.Dependency
var p2 = dep.pConstraint.name;
if (! cache.hasPackage(p2)) {
unknownPackages[p2] = true;
}
var constr = dep.pConstraint.constraintString;
if (! dep.isWeak) {
packageVersionsRequiringPackage[p2] =
(packageVersionsRequiringPackage[p2] || []);
packageVersionsRequiringPackage[p2].push(pv);
}
if (constr) {
addConstraint(pv, p2, dep.pConstraint.vConstraint);
}
});
});
});
_.each(packageVersionsRequiringPackage, function (pvs, p) {
// pvs are all the package-and-versions that require p.
// We want to select p if-and-only-if we select one of the pvs
// (except for top-level dependencies).
if (! _.has(rootDeps, p)) {
solver.require(Logic.equiv(p, Logic.or(pvs)));
}
});
// For good measure, disallow any packages that were mentioned in
// dependencies or constraints but aren't available in the catalog.
solver.forbid(_.keys(unknownPackages));
solver.require(input.dependencies);
_.each(input.constraints, function (c) {
addConstraint(null, c.name, c.vConstraint);
});
var allConstraintVars = _.map(allConstraints, function (c, i) {
return "constraint#" + i;
});
var allConstraintsOn = Logic.and(allConstraintVars);
var solution = solver.solveAssuming(allConstraintsOn);
if (! solution) {
var errorMessage;
var looseSolution = solver.solve();
if (! looseSolution) {
errorMessage = 'unknown package';
} else {
// try to use as many constraints as possible
looseSolution = solver.maximize(looseSolution, allConstraintVars, 1);
var numConstraintsOn = looseSolution.getWeightedSum(allConstraintVars, 1);
console.log(">>> Needed to remove " + (allConstraints.length -
numConstraintsOn) + " constraints" +
" to get a solution.");
for (var i = 0; i < allConstraints.length; i++) {
if (! looseSolution.evaluate("constraint#" + i)) {
console.log("Skipped: " + JSON.stringify(allConstraints[i]));
}
}
errorMessage = 'conflict';
}
var e = new Error(errorMessage);
e.constraintSolverError = true;
throw e;
}
solver.require(allConstraintsOn);
// optimize
_.each(solution.getTrueVars(), function (x) {
if (x.indexOf(' ') >= 0) {
var pv = CS.PackageAndVersion.fromString(x);
var package = pv.package;
var version = pv.version;
var otherVersions = cache.getPackageVersions(package); // sorted
if (_.has(rootDeps, package)) {
// try to make newer
_.find(otherVersions, function (v) {
var trialPV = package + ' ' + v;
if (PV.lessThan(v, version)) {
solver.forbid(trialPV);
} else {
var newSolution = solver.solveAssuming(Logic.not(trialPV));
if (newSolution) {
solution = newSolution;
solver.forbid(trialPV);
} else {
return true;
}
}
return false;
});
}
}
});
_.each(solution.getTrueVars(), function (x) {
if (x.indexOf(' ') >= 0) {
var pv = CS.PackageAndVersion.fromString(x);
var package = pv.package;
var version = pv.version;
var otherVersions = cache.getPackageVersions(package); // sorted
if (! _.has(rootDeps, package)) {
// try to make older
otherVersions = _.clone(otherVersions);
otherVersions.reverse();
_.find(otherVersions, function (v) {
var trialPV = package + ' ' + v;
if (PV.lessThan(version, v)) {
solver.forbid(trialPV);
} else {
var newSolution = solver.solveAssuming(Logic.not(trialPV));
if (newSolution) {
solution = newSolution;
solver.forbid(trialPV);
} else {
return true;
}
}
return false;
});
}
}
});
// read out solution
var versionMap = {};
_.each(solution.getTrueVars(), function (x) {
if (x.indexOf(' ') >= 0) {
var pv = CS.PackageAndVersion.fromString(x);
versionMap[pv.package] = pv.version;
}
});
return {
neededToUseUnanticipatedPrereleases: false, // XXX
answer: versionMap
};
return solver.getSolution();
};
// Exposed for tests.
@@ -605,3 +400,9 @@ CS.isConstraintSatisfied = function (package, vConstraint, version, options) {
}
});
};
CS.throwConstraintSolverError = function (message) {
var e = new Error(message);
e.constraintSolverError = true;
throw e;
};

View File

@@ -12,7 +12,7 @@ Package.onUse(function (api) {
api.use(['underscore', 'ejson', 'check', 'package-version-parser',
'binary-heap', 'random', 'logic-solver']);
api.addFiles(['datatypes.js', 'catalog-cache.js', 'catalog-loader.js',
'constraint-solver-input.js']);
'constraint-solver-input.js', 'solver.js']);
api.addFiles(['constraint-solver.js', 'resolver.js', 'constraints-list.js',
'resolver-state.js', 'priority-queue.js'], ['server']);
});

View File

@@ -0,0 +1,584 @@
var CS = ConstraintSolver;
var PV = PackageVersion;
CS.Solver = function (input, options) {
var self = this;
check(input, CS.Input);
self.input = input;
self.errors = []; // [String]
self.debugLog = null;
if (options && options.debugLog) {
self.debugLog = [];
}
};
CS.Solver.prototype.getSolution = function () {
var self = this;
self.logic = new Logic.Solver;
self._requireTopLevelDependencies(); // may throw
// "bar" -> ["foo 1.0.0", ...] if "foo 1.0.0" requires "bar"
self._requirers = {};
// package names we come across that aren't in the cache
self._unknownPackages = {}; // package name -> true
// populates _requirers and _unknownPackages:
self._enforceStrongDependencies();
// if this is greater than 0, we will throw an error later
// and say what they are, after we run the constraints
// and the cost function.
self._numUnknownPackagesNeeded = self._minimizeUnknownPackages();
self._constraintSatisfactionOptions = {
anticipatedPrereleases: self.input.anticipatedPrereleases
};
self._allConstraints = []; // added to by self._addConstraint(...)
self._numConflicts = self._enforceConstraints();
self._costFunction = self._generateCostFunction();
self._minimizeCostFunction();
self._solution = self.logic.solve();
if (! self._solution) {
// can't get here; should be in a satisfiable state (or have thrown)
throw new Error("Unexpected unsatisfiability");
}
self._throwUnknownPackages();
self._throwConflicts();
var versionMap = {};
_.each(self._solution.getTrueVars(), function (x) {
if (x.indexOf(' ') >= 0) {
var pv = CS.PackageAndVersion.fromString(x);
versionMap[pv.package] = pv.version;
}
});
return {
neededToUseUnanticipatedPrereleases: false, // XXX
answer: versionMap
};
};
var getVersionInfo = _.memoize(PV.parse);
var pvVar = function (p, v) {
return p + ' ' + v;
};
CS.Solver.prototype._requireTopLevelDependencies = function () {
var self = this;
var input = self.input;
_.each(input.dependencies, function (p) {
if (! input.isKnownPackage(p)) {
// Unknown package at top level
self.errors.push('unknown package: ' + p);
} else {
self.logic.require(p);
if (self.debugLog) {
self.debugLog.push('REQUIRE ' + p);
}
}
});
self.throwAnyErrors();
};
CS.Solver.prototype._enforceStrongDependencies = function () {
var self = this;
var input = self.input;
var cache = input.catalogCache;
var unknownPackages = self._unknownPackages;
var requirers = self._requirers;
cache.eachPackage(function (p, versions) {
// ["foo 1.0.0", "foo 1.0.1", ...] for a given "foo"
var packageAndVersions = _.map(versions, function (v) {
return pvVar(p, v);
});
// At most one of ["foo 1.0.0", "foo 1.0.1", ...] is true.
self.logic.require(Logic.atMostOne(packageAndVersions));
if (self.debugLog) {
self.debugLog.push("AT MOST ONE: " +
(packageAndVersions.join(', ') || '[]'));
}
// The variable "foo" is true if and only if at least one of the
// variables ["foo 1.0.0", "foo 1.0.1", ...] is true.
// Note that this doesn't apply to unknown packages (packages
// that aren't in the cache), which aren't visited here.
// We will forbid them later, and generate a good error message
// if that leads to unsatisfiability.
self.logic.require(Logic.equiv(p, Logic.or(packageAndVersions)));
if (self.debugLog) {
self.debugLog.push(p + ' IFF ONE OF: ' +
(packageAndVersions.join(', ') || '[]'));
}
_.each(versions, function (v) {
var pv = pvVar(p, v);
_.each(cache.getDependencyMap(p, v), function (dep) {
// `dep` is a CS.Dependency
var p2 = dep.pConstraint.name;
if (! input.isKnownPackage(p2)) {
unknownPackages[p2] = true;
}
if (! dep.isWeak) {
requirers[p2] = (requirers[p2] || []);
requirers[p2].push(pv);
}
});
});
});
_.each(requirers, function (pvs, p) {
// pvs are all the package-versions that require p.
// We want to select p if-and-only-if we select one of the pvs
// (except when p is a root dependency, in which case
// we've already required it).
if (! input.isRootDependency(p)) {
self.logic.require(Logic.equiv(p, Logic.or(pvs)));
if (self.debugLog) {
self.debugLog.push(p + ' IFF ONE OF: ' +
(pvs.join(', ') || '[]'));
}
}
});
};
CS.Solver.prototype.throwAnyErrors = function () {
if (this.errors.length) {
CS.throwConstraintSolverError(this.errors.join('\n'));
}
};
CS.Solver.prototype._minimizeUnknownPackages = function () {
var self = this;
var unknownPackages = _.keys(self._unknownPackages);
var useAnyUnknown = Logic.or(unknownPackages);
var sol = self.logic.solve();
if (! sol) {
// so far we have no version constraints, and it's a valid solution
// to just select some version of every package, and also select
// all the non-existent packages that are mentioned, since we haven't
// forbid them yet.
throw new Error("Unexpected unsatisfiability");
}
if (self.logic.solveAssuming(Logic.not(useAnyUnknown))) {
if (self.debugLog) {
self.debugLog.push('FORBID: ' +
(unknownPackages.join(', ') || '[]'));
}
self.logic.forbid(unknownPackages);
return [];
} else {
// apparently we can't ignore some of the unknown packages;
// we have to use at least one. this will become an error,
// but we don't want to throw it yet so that we can run
// the cost function so that we are showing realistic versions
// in the error.
sol = self.logic.minimize(sol, unknownPackages, 1);
var result = sol.getWeightedSum(unknownPackages, 1);
if (self.debugLog) {
self.debugLog.push('AT MOST ' + result + ' OF: ' +
(unknownPackages.join(', ') || '[]'));
}
return result;
}
};
CS.Solver.prototype._generateCostFunction = function () {
var self = this;
// classify packages into categories, which determine what we
// are supposed to be optimizing about the version and with what
// priority.
var costFunc = new CS.Solver.CostFunction();
// 1 if we change the major version of a root dep with previous version
costFunc.addComponent('previous_root_major');
// 1 if we move a root dep backwards in the same major version
costFunc.addComponent('previous_root_incompat');
// 1 if we change a root dep from previous version
costFunc.addComponent('previous_root_change');
// number of versions forward or backward we move a root dep
costFunc.addComponent('previous_root_distance');
costFunc.addComponent('previous_indirect_major');
costFunc.addComponent('previous_indirect_incompat');
costFunc.addComponent('previous_indirect_change');
costFunc.addComponent('previous_indirect_distance');
// XXX probably need some more nuance here.
// In general, we want packages we're upgrading and new root dependencies
// (just added or in case of no previous solution) to be as new as possible,
// so we penalize oldness. New indirect dependencies should be as old as
// possible, so we penalize newness.
costFunc.addComponent('upgrade_oldness');
costFunc.addComponent('new_root_oldness');
costFunc.addComponent('new_indirect_newness');
var input = self.input;
input.catalogCache.eachPackage(function (p, versions) {
if (input.isUpgrading(p)) {
_.each(versions, function (v, i) {
var pv = pvVar(p, v);
costFunc.addToComponent('upgrade_oldness', pv,
versions.length - 1 - i);
});
} else if (input.isInPreviousSolution(p)) {
var previous = input.previousSolution[p];
var previousVInfo = getVersionInfo(previous);
if (input.isRootDependency(p)) {
// previous_root
var firstGteIndex = versions.length;
// previous version should be in versions array, but we don't
// want to assume that
var previousFound = false;
_.each(versions, function (v, i) {
var vInfo = getVersionInfo(v);
var pv = pvVar(p, v);
if (vInfo.major !== previousVInfo.major) {
costFunc.addToComponent('previous_root_major', pv, 1);
} else if (PV.lessThan(vInfo, previousVInfo)) {
costFunc.addToComponent('previous_root_incompat', pv, 1);
}
if (v !== previous) {
costFunc.addToComponent('previous_root_change', pv, 1);
}
if (firstGteIndex === versions.length &&
! PV.lessThan(vInfo, previousVInfo)) {
firstGteIndex = i;
if (v === previous) {
previousFound = true;
}
}
});
_.each(versions, function (v, i) {
var pv = pvVar(p, v);
if (i < firstGteIndex) {
costFunc.addToComponent('previous_root_distance', pv,
firstGteIndex - i);
} else {
costFunc.addToComponent('previous_root_distance', pv,
i - firstGteIndex +
(previousFound ? 0 : 1));
}
});
} else {
// previous_indirect
var firstGteIndex = versions.length;
// previous version should be in versions array, but we don't
// want to assume that
var previousFound = false;
_.each(versions, function (v, i) {
var vInfo = getVersionInfo(v);
var pv = pvVar(p, v);
if (vInfo.major !== previousVInfo.major) {
costFunc.addToComponent('previous_indirect_major', pv, 1);
} else if (PV.lessThan(vInfo, previousVInfo)) {
costFunc.addToComponent('previous_indirect_incompat', pv, 1);
}
if (v !== previous) {
costFunc.addToComponent('previous_indirect_change', pv, 1);
}
if (firstGteIndex === versions.length &&
! PV.lessThan(vInfo, previousVInfo)) {
firstGteIndex = i;
if (v === previous) {
previousFound = true;
}
}
});
_.each(versions, function (v, i) {
var pv = pvVar(p, v);
if (i < firstGteIndex) {
costFunc.addToComponent('previous_indirect_distance', pv,
firstGteIndex - i);
} else {
costFunc.addToComponent('previous_indirect_distance', pv,
i - firstGteIndex +
(previousFound ? 0 : 1));
}
});
}
} else {
if (input.isRootDependency(p)) {
// new_root
_.each(versions, function (v, i) {
var pv = pvVar(p, v);
costFunc.addToComponent('new_root_oldness', pv,
versions.length - 1 - i);
});
} else {
// new_indirect
_.each(versions, function (v, i) {
var pv = pvVar(p, v);
costFunc.addToComponent('new_indirect_newness', pv, i);
});
}
}
});
return costFunc;
};
var getDebugLogForWeightedSum = function (solution, terms, weights) {
if (typeof weights === 'number') {
weights = _.map(terms, function () { return weights; });
}
return 'REQUIRE ' + (_.map(terms, function (t, i) {
return weights[i] + '*(' + t + ')';
}).join(' + ') || '0')+ ' = ' + solution.getWeightedSum(terms, weights);
};
CS.Solver.prototype._minimizeCostFunction = function () {
var self = this;
var sol = self.logic.solve();
if (! sol) {
// we've already been checking as we go along that the
// problem is still solvable
throw new Error("Unexpected unsatisfiability");
}
var costFunc = self._costFunction;
_.each(costFunc.components, function (comp) {
sol = self.logic.minimize(sol, comp.terms, comp.weights);
if (self.debugLog) {
self.debugLog.push(getDebugLogForWeightedSum(
sol, comp.terms, comp.weights));
}
});
};
CS.Solver.prototype._addConstraint = function (fromVar, toPackage, vConstraint) {
// fromVar is a return value of pvVar(p, v), or null for a top-level constraint
check(fromVar, Match.OneOf(String, null));
check(toPackage, String); // package name
check(vConstraint, CS.Input.VersionConstraintType);
var self = this;
var allConstraints = self._allConstraints;
var newConstraint = new CS.Solver.Constraint(
"constraint#" + allConstraints.length, fromVar, toPackage, vConstraint);
allConstraints.push(newConstraint);
var targetVersions = self.input.catalogCache.getPackageVersions(toPackage);
var okVersions = _.compact(_.map(targetVersions, function (v) {
if (newConstraint.isSatisfiedBy(v, self._constraintSatisfactionOptions)) {
return pvVar(toPackage, v);
} else {
return null;
}
}));
// We logically require that IF:
//
// - the constraint var is true, meaning the constraint is active and not
// being skipped for conflict-detection purposes; and
// - fromVar is true, meaning we have selected the package version having
// the constraint, or is non-existent, meaning this is a top-level
// constraint; and
// - toPackage is true, meaning we have selected the package that the
// constraint is about
//
// ... then one of the versions of toPackage that satisfies the constraint
// must be selected.
self.logic.require(
Logic.implies(newConstraint.varName,
Logic.or(fromVar ? Logic.not(fromVar) : [],
Logic.not(toPackage),
okVersions)));
if (self.debugLog) {
var conditions = [newConstraint.varName];
if (fromVar) {
conditions.push('(' + fromVar + ')');
}
conditions.push(toPackage);
self.debugLog.push('IF ' + conditions.join(' AND ') + ' THEN ONE OF: ' +
(okVersions.join(', ') || '[]'));
}
};
// Register the constraints with the logic solver, but don't actually
// enforce them yet (so we can do conflict detection).
CS.Solver.prototype._enforceConstraints = function () {
var self = this;
var cache = self.input.catalogCache;
// top-level constraints
_.each(self.input.constraints, function (c) {
self._addConstraint(null, c.name, c.vConstraint);
});
// constraints specified by package versions
cache.eachPackage(function (p, versions) {
_.each(versions, function (v) {
var pv = pvVar(p, v);
_.each(cache.getDependencyMap(p, v), function (dep) {
// `dep` is a CS.Dependency
var p2 = dep.pConstraint.name;
if (self.input.isKnownPackage(p2)) {
self._addConstraint(pv, p2, dep.pConstraint.vConstraint);
}
});
});
});
// minimize conflicts
var allConstraints = self._allConstraints;
var allConstraintVars = _.pluck(allConstraints, 'varName');
var allConstraintsActive = Logic.and(allConstraintVars);
var sol = self.logic.solveAssuming(allConstraintsActive);
if (sol) {
self.logic.require(allConstraintVars);
if (self.debugLog) {
self.debugLog.push('REQUIRE: ' +
(allConstraintVars.join(', ') || '[]'));
}
return 0; // no conflicts
}
// Couldn't solve with all constraints. Figure out how many constraints
// we need to skip to achieve satisfiability, and later we will report
// them as conflicts to the user.
// First solve with no constraints necessarily active (as a sanity check
// and as a starting point for optimization).
sol = self.logic.solve();
if (! sol) {
// We should either still be satisfiable or have thrown an error.
throw new Error("Unexpected unsatisfiability");
}
sol = self.logic.maximize(sol, allConstraintVars, 1);
if (self.debugLog) {
self.debugLog.push(getDebugLogForWeightedSum(
sol, allConstraintVars, 1));
}
return allConstraintVars.length - sol.getWeightedSum(allConstraintVars, 1);
};
CS.Solver.prototype._throwUnknownPackages = function () {
var self = this;
if (! self._numUnknownPackagesNeeded) {
return;
}
var solution = self._solution;
var unknownPackages = _.keys(self._unknownPackages);
var unknownPackagesNeeded = _.filter(unknownPackages, function (p) {
return solution.evaluate(p);
});
_.each(unknownPackagesNeeded, function (p) {
self.errors.push('unknown package: ' + p);
});
self.throwAnyErrors();
};
CS.Solver.prototype._throwConflicts = function () {
var self = this;
if (! self._numConflicts) {
return;
}
var allConstraints = self._allConstraints;
var solution = self._solution;
_.each(allConstraints, function (c) {
// c is a CS.Solver.Constraint
if (! solution.evaluate(c.varName)) {
// skipped this constraint
var possibleVersions =
self.input.catalogCache.getPackageVersions(c.toPackage);
var chosenVersion = _.find(possibleVersions, function (v) {
return solution.evaluate(pvVar(c.toPackage, v));
});
if (! chosenVersion) {
// this can't happen, because for a constraint to be a problem,
// we must have chosen some version of the package it applies to!
throw new Error("Internal error: Version not found");
}
var error = (
'conflict: constraint ' + c.toPackage + '@' + c.vConstraint.raw +
' is not satisfied by ' + c.toPackage + ' ' + chosenVersion + '.');
self.errors.push(error);
// XXX explain further -- what are the other constraints that
// must have conflicted with this one, and what package versions do
// they come from, and by what path were those package versions
// reached?
}
});
// always throws, never returns
self.throwAnyErrors();
throw new Error("Internal error: conflicts could not be explained");
};
CS.Solver.CostFunction = function () {
this.components = [];
this.componentsByName = {};
};
CS.Solver.CostFunction.prototype.addComponent = function (name, terms, weights) {
check(name, String);
terms = terms || [];
check(terms, [String]);
weights = weights || [];
check(weights, [Logic.WholeNumber]);
var comp = {name: name,
terms: terms,
weights: weights};
this.components.push(comp);
this.componentsByName[name] = comp;
};
CS.Solver.CostFunction.prototype.addToComponent = function (
compName, term, weight) {
check(compName, String);
check(term, String);
check(weight, Logic.WholeNumber);
if (! _.has(this.componentsByName, compName)) {
throw new Error("No such cost function component: " + compName);
}
var comp = this.componentsByName[compName];
comp.terms.push(term);
comp.weights.push(weight);
};
CS.Solver.Constraint = function (varName, fromVar, toPackage, vConstraint) {
this.varName = varName;
this.fromVar = fromVar;
this.toPackage = toPackage;
this.vConstraint = vConstraint;
check(this.varName, String);
// this.fromVar is a return value of pvVar(p, v), or null for a
// top-level constraint
check(this.fromVar, Match.OneOf(String, null));
check(this.toPackage, String); // package name
check(this.vConstraint, CS.Input.VersionConstraintType);
};
CS.Solver.Constraint.prototype.isSatisfiedBy = function (v2, options) {
return CS.isConstraintSatisfied(this.toPackage, this.vConstraint,
v2, options);
};

View File

@@ -1054,9 +1054,15 @@ Logic.weightedSum = function (formulas, weights) {
weights = _.map(formulas, function () { return weights; });
}
_check(weights, [Logic.WholeNumber]);
if (! (formulas.length === weights.length && formulas.length)) {
throw new Error("Formula array and weight array must be same length (> 0)");
if (formulas.length !== weights.length) {
throw new Error("Formula array and weight array must be same length" +
"; they are " + formulas.length + " and " + weights.length);
}
if (formulas.length === 0) {
return new Logic.Bits([]);
}
var binaryWeighted = [];
_.each(formulas, function (f, i) {
var w = weights[i];

View File

@@ -3,6 +3,10 @@ var minMax = function (solver, solution, costTerms, costWeights, optFormula, isM
var curCost = curSolution.getWeightedSum(costTerms, costWeights);
var weightedSum = (optFormula || Logic.weightedSum(costTerms, costWeights));
solver.require((isMin ? Logic.lessThanOrEqual : Logic.greaterThanOrEqual)(
weightedSum, Logic.constantBits(curCost)));
while (isMin ? curCost > 0 : true) {
var improvement = (isMin ? Logic.lessThan : Logic.greaterThan)(
weightedSum, Logic.constantBits(curCost));