|
@@ -13,6 +13,13 @@
|
|
namespace Composer\DependencyResolver;
|
|
namespace Composer\DependencyResolver;
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * The RuleWatchGraph efficiently propagates decisions to other rules
|
|
|
|
+ *
|
|
|
|
+ * All rules generated for solving a SAT problem should be inserted into the
|
|
|
|
+ * graph. When a decision on a literal is made, the graph can be used to
|
|
|
|
+ * propagate the decision to all other rules involving the rule, leading to
|
|
|
|
+ * other trivial decisions resulting from unit clauses.
|
|
|
|
+ *
|
|
* @author Nils Adermann <naderman@naderman.de>
|
|
* @author Nils Adermann <naderman@naderman.de>
|
|
*/
|
|
*/
|
|
class RuleWatchGraph
|
|
class RuleWatchGraph
|
|
@@ -20,8 +27,16 @@ class RuleWatchGraph
|
|
protected $watchChains = array();
|
|
protected $watchChains = array();
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Alters watch chains for a rule.
|
|
|
|
|
|
+ * Inserts a rule node into the appropriate chains within the graph
|
|
|
|
+ *
|
|
|
|
+ * The node is prepended to the watch chains for each of the two literals it
|
|
|
|
+ * watches.
|
|
|
|
+ *
|
|
|
|
+ * Assertions are skipped because they only depend on a single package and
|
|
|
|
+ * have no alternative literal that could be true, so there is no need to
|
|
|
|
+ * watch chnages in any literals.
|
|
*
|
|
*
|
|
|
|
+ * @param RuleWatchNode $node The rule node to be inserted into the graph
|
|
*/
|
|
*/
|
|
public function insert(RuleWatchNode $node)
|
|
public function insert(RuleWatchNode $node)
|
|
{
|
|
{
|
|
@@ -38,13 +53,41 @@ class RuleWatchGraph
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public function contains($literal)
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Propagates a decision on a literal to all rules watching the literal
|
|
|
|
+ *
|
|
|
|
+ * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
|
|
|
|
+ * (-A|+B|+C) now need to satisfy at least one of the other literals, so
|
|
|
|
+ * that the rule as a whole becomes true, since with +A applied the rule
|
|
|
|
+ * is now (false|+B|+C) so essentialy (+B|+C).
|
|
|
|
+ *
|
|
|
|
+ * This means that all rules watching the literal -A need to be updated to
|
|
|
|
+ * watch 2 other literals which can still be satisfied instead. So literals
|
|
|
|
+ * that conflict with previously made decisions are not an option.
|
|
|
|
+ *
|
|
|
|
+ * Alternatively it can occur that a unit clause results: e.g. if in the
|
|
|
|
+ * above example the rule was (-A|+B), then A turning true means that
|
|
|
|
+ * B must now be decided true as well.
|
|
|
|
+ *
|
|
|
|
+ * @param int $decidedLiteral The literal which was decided (A in our example)
|
|
|
|
+ * @param int $level The level at which the decision took place and at which
|
|
|
|
+ * all resulting decisions should be made.
|
|
|
|
+ * @param Callable $decisionsSatisfyCallback A callback which checks if a
|
|
|
|
+ * literal has already been positively decided and the rule is thus
|
|
|
|
+ * already true and can be skipped.
|
|
|
|
+ * @param Callable $conflictCallback A callback which checks if a literal
|
|
|
|
+ * would conflict with previously made decisions on the same package
|
|
|
|
+ * @param Callable $decideCallback A callback which is responsible for
|
|
|
|
+ * registering decided literals resulting from unit clauses
|
|
|
|
+ * @return Rule|null If a conflict is found the conflicting rule is returned
|
|
|
|
+ */
|
|
|
|
+ public function propagateLiteral($decidedLiteral, $level, $decisionsSatisfyCallback, $conflictCallback, $decideCallback)
|
|
{
|
|
{
|
|
- return isset($this->watchChains[$literal]);
|
|
|
|
- }
|
|
|
|
|
|
+ // we invert the decided literal here, example:
|
|
|
|
+ // A was decided => (-A|B) now requires B to be true, so we look for
|
|
|
|
+ // rules which are fulfilled by -A, rather than A.
|
|
|
|
+ $literal = -$decidedLiteral;
|
|
|
|
|
|
- public function propagateLiteral($literal, $level, $skipCallback, $conflictCallback, $decideCallback)
|
|
|
|
- {
|
|
|
|
if (!isset($this->watchChains[$literal])) {
|
|
if (!isset($this->watchChains[$literal])) {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
@@ -56,7 +99,7 @@ class RuleWatchGraph
|
|
$node = $chain->current();
|
|
$node = $chain->current();
|
|
$otherWatch = $node->getOtherWatch($literal);
|
|
$otherWatch = $node->getOtherWatch($literal);
|
|
|
|
|
|
- if (!$node->getRule()->isDisabled() && !call_user_func($skipCallback, $otherWatch)) {
|
|
|
|
|
|
+ if (!$node->getRule()->isDisabled() && !call_user_func($decisionsSatisfyCallback, $otherWatch)) {
|
|
$ruleLiterals = $node->getRule()->getLiterals();
|
|
$ruleLiterals = $node->getRule()->getLiterals();
|
|
|
|
|
|
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $conflictCallback) {
|
|
$alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $conflictCallback) {
|
|
@@ -84,6 +127,15 @@ class RuleWatchGraph
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Moves a rule node from one watch chain to another
|
|
|
|
+ *
|
|
|
|
+ * The rule node's watched literals are updated accordingly.
|
|
|
|
+ *
|
|
|
|
+ * @param $fromLiteral A literal the node used to watch
|
|
|
|
+ * @param $toLiteral A literal the node should watch now
|
|
|
|
+ * @param $node The rule node to be moved
|
|
|
|
+ */
|
|
protected function moveWatch($fromLiteral, $toLiteral, $node)
|
|
protected function moveWatch($fromLiteral, $toLiteral, $node)
|
|
{
|
|
{
|
|
if (!isset($this->watchChains[$toLiteral])) {
|
|
if (!isset($this->watchChains[$toLiteral])) {
|