RuleWatchGraph.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\DependencyResolver;
  12. /**
  13. * The RuleWatchGraph efficiently propagates decisions to other rules
  14. *
  15. * All rules generated for solving a SAT problem should be inserted into the
  16. * graph. When a decision on a literal is made, the graph can be used to
  17. * propagate the decision to all other rules involving the literal, leading to
  18. * other trivial decisions resulting from unit clauses.
  19. *
  20. * @author Nils Adermann <naderman@naderman.de>
  21. */
  22. class RuleWatchGraph
  23. {
  24. protected $watchChains = array();
  25. /**
  26. * Inserts a rule node into the appropriate chains within the graph
  27. *
  28. * The node is prepended to the watch chains for each of the two literals it
  29. * watches.
  30. *
  31. * Assertions are skipped because they only depend on a single package and
  32. * have no alternative literal that could be true, so there is no need to
  33. * watch changes in any literals.
  34. *
  35. * @param RuleWatchNode $node The rule node to be inserted into the graph
  36. */
  37. public function insert(RuleWatchNode $node)
  38. {
  39. if ($node->getRule()->isAssertion()) {
  40. return;
  41. }
  42. foreach (array($node->watch1, $node->watch2) as $literal) {
  43. if (!isset($this->watchChains[$literal])) {
  44. $this->watchChains[$literal] = new RuleWatchChain;
  45. }
  46. $this->watchChains[$literal]->unshift($node);
  47. }
  48. }
  49. /**
  50. * Propagates a decision on a literal to all rules watching the literal
  51. *
  52. * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
  53. * (-A|+B|+C) now need to satisfy at least one of the other literals, so
  54. * that the rule as a whole becomes true, since with +A applied the rule
  55. * is now (false|+B|+C) so essentially (+B|+C).
  56. *
  57. * This means that all rules watching the literal -A need to be updated to
  58. * watch 2 other literals which can still be satisfied instead. So literals
  59. * that conflict with previously made decisions are not an option.
  60. *
  61. * Alternatively it can occur that a unit clause results: e.g. if in the
  62. * above example the rule was (-A|+B), then A turning true means that
  63. * B must now be decided true as well.
  64. *
  65. * @param int $decidedLiteral The literal which was decided (A in our example)
  66. * @param int $level The level at which the decision took place and at which
  67. * all resulting decisions should be made.
  68. * @param Decisions $decisions Used to check previous decisions and to
  69. * register decisions resulting from propagation
  70. * @return Rule|null If a conflict is found the conflicting rule is returned
  71. */
  72. public function propagateLiteral($decidedLiteral, $level, $decisions)
  73. {
  74. // we invert the decided literal here, example:
  75. // A was decided => (-A|B) now requires B to be true, so we look for
  76. // rules which are fulfilled by -A, rather than A.
  77. $literal = -$decidedLiteral;
  78. if (!isset($this->watchChains[$literal])) {
  79. return null;
  80. }
  81. $chain = $this->watchChains[$literal];
  82. $chain->rewind();
  83. while ($chain->valid()) {
  84. $node = $chain->current();
  85. $otherWatch = $node->getOtherWatch($literal);
  86. if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
  87. $ruleLiterals = $node->getRule()->getLiterals();
  88. $alternativeLiterals = array_filter($ruleLiterals, function ($ruleLiteral) use ($literal, $otherWatch, $decisions) {
  89. return $literal !== $ruleLiteral &&
  90. $otherWatch !== $ruleLiteral &&
  91. !$decisions->conflict($ruleLiteral);
  92. });
  93. if ($alternativeLiterals) {
  94. reset($alternativeLiterals);
  95. $this->moveWatch($literal, current($alternativeLiterals), $node);
  96. continue;
  97. }
  98. if ($decisions->conflict($otherWatch)) {
  99. return $node->getRule();
  100. }
  101. $decisions->decide($otherWatch, $level, $node->getRule());
  102. }
  103. $chain->next();
  104. }
  105. return null;
  106. }
  107. /**
  108. * Moves a rule node from one watch chain to another
  109. *
  110. * The rule node's watched literals are updated accordingly.
  111. *
  112. * @param $fromLiteral A literal the node used to watch
  113. * @param $toLiteral A literal the node should watch now
  114. * @param $node The rule node to be moved
  115. */
  116. protected function moveWatch($fromLiteral, $toLiteral, $node)
  117. {
  118. if (!isset($this->watchChains[$toLiteral])) {
  119. $this->watchChains[$toLiteral] = new RuleWatchChain;
  120. }
  121. $node->moveWatch($fromLiteral, $toLiteral);
  122. $this->watchChains[$fromLiteral]->remove();
  123. $this->watchChains[$toLiteral]->unshift($node);
  124. }
  125. }