Solver.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  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. use Composer\IO\IOInterface;
  13. use Composer\Package\PackageInterface;
  14. use Composer\Repository\RepositoryInterface;
  15. use Composer\Repository\PlatformRepository;
  16. use Composer\Repository\RepositorySet;
  17. /**
  18. * @author Nils Adermann <naderman@naderman.de>
  19. */
  20. class Solver
  21. {
  22. const BRANCH_LITERALS = 0;
  23. const BRANCH_LEVEL = 1;
  24. /** @var PolicyInterface */
  25. protected $policy;
  26. /** @var Pool */
  27. protected $pool = null;
  28. /** @var RepositoryInterface */
  29. protected $installed;
  30. /** @var RuleSet */
  31. protected $rules;
  32. /** @var RuleSetGenerator */
  33. protected $ruleSetGenerator;
  34. /** @var array */
  35. protected $jobs;
  36. /** @var int[] */
  37. protected $updateMap = array();
  38. /** @var RuleWatchGraph */
  39. protected $watchGraph;
  40. /** @var Decisions */
  41. protected $decisions;
  42. /** @var PackageInterface[] */
  43. protected $installedMap;
  44. /** @var int */
  45. protected $propagateIndex;
  46. /** @var array[] */
  47. protected $branches = array();
  48. /** @var Problem[] */
  49. protected $problems = array();
  50. /** @var array */
  51. protected $learnedPool = array();
  52. /** @var array */
  53. protected $learnedWhy = array();
  54. /** @var bool */
  55. public $testFlagLearnedPositiveLiteral = false;
  56. /** @var IOInterface */
  57. protected $io;
  58. /**
  59. * @param PolicyInterface $policy
  60. * @param Pool $pool
  61. * @param RepositoryInterface $installed
  62. * @param IOInterface $io
  63. */
  64. public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
  65. {
  66. $this->io = $io;
  67. $this->policy = $policy;
  68. $this->pool = $pool;
  69. $this->installed = $installed;
  70. }
  71. /**
  72. * @return int
  73. */
  74. public function getRuleSetSize()
  75. {
  76. return count($this->rules);
  77. }
  78. public function getPool()
  79. {
  80. return $this->pool;
  81. }
  82. // aka solver_makeruledecisions
  83. private function makeAssertionRuleDecisions()
  84. {
  85. $decisionStart = count($this->decisions) - 1;
  86. $rulesCount = count($this->rules);
  87. for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
  88. $rule = $this->rules->ruleById[$ruleIndex];
  89. if (!$rule->isAssertion() || $rule->isDisabled()) {
  90. continue;
  91. }
  92. $literals = $rule->getLiterals();
  93. $literal = $literals[0];
  94. if (!$this->decisions->decided($literal)) {
  95. $this->decisions->decide($literal, 1, $rule);
  96. continue;
  97. }
  98. if ($this->decisions->satisfy($literal)) {
  99. continue;
  100. }
  101. // found a conflict
  102. if (RuleSet::TYPE_LEARNED === $rule->getType()) {
  103. $rule->disable();
  104. continue;
  105. }
  106. $conflict = $this->decisions->decisionRule($literal);
  107. if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
  108. $problem = new Problem($this->pool);
  109. $problem->addRule($rule);
  110. $problem->addRule($conflict);
  111. $this->disableProblem($rule);
  112. $this->problems[] = $problem;
  113. continue;
  114. }
  115. // conflict with another job
  116. $problem = new Problem($this->pool);
  117. $problem->addRule($rule);
  118. $problem->addRule($conflict);
  119. // push all of our rules (can only be job rules)
  120. // asserting this literal on the problem stack
  121. foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
  122. if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
  123. continue;
  124. }
  125. $assertRuleLiterals = $assertRule->getLiterals();
  126. $assertRuleLiteral = $assertRuleLiterals[0];
  127. if (abs($literal) !== abs($assertRuleLiteral)) {
  128. continue;
  129. }
  130. $problem->addRule($assertRule);
  131. $this->disableProblem($assertRule);
  132. }
  133. $this->problems[] = $problem;
  134. $this->decisions->resetToOffset($decisionStart);
  135. $ruleIndex = -1;
  136. }
  137. }
  138. protected function setupInstalledMap()
  139. {
  140. $this->installedMap = array();
  141. foreach ($this->installed->getPackages() as $package) {
  142. $this->installedMap[$package->id] = $package;
  143. }
  144. }
  145. /**
  146. * @param bool $ignorePlatformReqs
  147. */
  148. protected function checkForRootRequireProblems($ignorePlatformReqs)
  149. {
  150. foreach ($this->jobs as $job) {
  151. switch ($job['cmd']) {
  152. case 'update':
  153. $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
  154. foreach ($packages as $package) {
  155. if (isset($this->installedMap[$package->id])) {
  156. $this->updateMap[$package->id] = true;
  157. }
  158. }
  159. break;
  160. case 'update-all':
  161. foreach ($this->installedMap as $package) {
  162. $this->updateMap[$package->id] = true;
  163. }
  164. break;
  165. case 'install':
  166. if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
  167. break;
  168. }
  169. if (!$this->pool->whatProvides($job['packageName'], $job['constraint'])) {
  170. $problem = new Problem($this->pool);
  171. $problem->addRule(new GenericRule(array(), null, null, $job));
  172. $this->problems[] = $problem;
  173. }
  174. break;
  175. }
  176. }
  177. }
  178. /**
  179. * @param Request $request
  180. * @param bool $ignorePlatformReqs
  181. * @return array
  182. */
  183. public function solve(Request $request, $ignorePlatformReqs = false)
  184. {
  185. $this->jobs = $request->getJobs();
  186. $this->setupInstalledMap();
  187. $this->io->writeError('Generating rules', true, IOInterface::DEBUG);
  188. $this->ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
  189. $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
  190. $this->checkForRootRequireProblems($ignorePlatformReqs);
  191. $this->decisions = new Decisions($this->pool);
  192. $this->watchGraph = new RuleWatchGraph;
  193. foreach ($this->rules as $rule) {
  194. $this->watchGraph->insert(new RuleWatchNode($rule));
  195. }
  196. /* make decisions based on job/update assertions */
  197. $this->makeAssertionRuleDecisions();
  198. $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
  199. $before = microtime(true);
  200. $this->runSat(true);
  201. $this->io->writeError('', true, IOInterface::DEBUG);
  202. $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);
  203. // decide to remove everything that's installed and undecided
  204. foreach ($this->installedMap as $packageId => $void) {
  205. if ($this->decisions->undecided($packageId)) {
  206. $this->decisions->decide(-$packageId, 1, null);
  207. }
  208. }
  209. if ($this->problems) {
  210. throw new SolverProblemsException($this->problems, $this->installedMap, $this->learnedPool);
  211. }
  212. $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);
  213. return $transaction->getOperations();
  214. }
  215. /**
  216. * Makes a decision and propagates it to all rules.
  217. *
  218. * Evaluates each term affected by the decision (linked through watches)
  219. * If we find unit rules we make new decisions based on them
  220. *
  221. * @param int $level
  222. * @return Rule|null A rule on conflict, otherwise null.
  223. */
  224. protected function propagate($level)
  225. {
  226. while ($this->decisions->validOffset($this->propagateIndex)) {
  227. $decision = $this->decisions->atOffset($this->propagateIndex);
  228. $conflict = $this->watchGraph->propagateLiteral(
  229. $decision[Decisions::DECISION_LITERAL],
  230. $level,
  231. $this->decisions
  232. );
  233. $this->propagateIndex++;
  234. if ($conflict) {
  235. return $conflict;
  236. }
  237. }
  238. return null;
  239. }
  240. /**
  241. * Reverts a decision at the given level.
  242. *
  243. * @param int $level
  244. */
  245. private function revert($level)
  246. {
  247. while (!$this->decisions->isEmpty()) {
  248. $literal = $this->decisions->lastLiteral();
  249. if ($this->decisions->undecided($literal)) {
  250. break;
  251. }
  252. $decisionLevel = $this->decisions->decisionLevel($literal);
  253. if ($decisionLevel <= $level) {
  254. break;
  255. }
  256. $this->decisions->revertLast();
  257. $this->propagateIndex = count($this->decisions);
  258. }
  259. while (!empty($this->branches) && $this->branches[count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) {
  260. array_pop($this->branches);
  261. }
  262. }
  263. /**
  264. * setpropagatelearn
  265. *
  266. * add free decision (a positive literal) to decision queue
  267. * increase level and propagate decision
  268. * return if no conflict.
  269. *
  270. * in conflict case, analyze conflict rule, add resulting
  271. * rule to learnt rule set, make decision from learnt
  272. * rule (always unit) and re-propagate.
  273. *
  274. * returns the new solver level or 0 if unsolvable
  275. *
  276. * @param int $level
  277. * @param string|int $literal
  278. * @param bool $disableRules
  279. * @param Rule $rule
  280. * @return int
  281. */
  282. private function setPropagateLearn($level, $literal, $disableRules, Rule $rule)
  283. {
  284. $level++;
  285. $this->decisions->decide($literal, $level, $rule);
  286. while (true) {
  287. $rule = $this->propagate($level);
  288. if (!$rule) {
  289. break;
  290. }
  291. if ($level == 1) {
  292. return $this->analyzeUnsolvable($rule, $disableRules);
  293. }
  294. // conflict
  295. list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);
  296. if ($newLevel <= 0 || $newLevel >= $level) {
  297. throw new SolverBugException(
  298. "Trying to revert to invalid level ".(int) $newLevel." from level ".(int) $level."."
  299. );
  300. } elseif (!$newRule) {
  301. throw new SolverBugException(
  302. "No rule was learned from analyzing $rule at level $level."
  303. );
  304. }
  305. $level = $newLevel;
  306. $this->revert($level);
  307. $this->rules->add($newRule, RuleSet::TYPE_LEARNED);
  308. $this->learnedWhy[spl_object_hash($newRule)] = $why;
  309. $ruleNode = new RuleWatchNode($newRule);
  310. $ruleNode->watch2OnHighest($this->decisions);
  311. $this->watchGraph->insert($ruleNode);
  312. $this->decisions->decide($learnLiteral, $level, $newRule);
  313. }
  314. return $level;
  315. }
  316. /**
  317. * @param int $level
  318. * @param array $decisionQueue
  319. * @param bool $disableRules
  320. * @param Rule $rule
  321. * @return int
  322. */
  323. private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
  324. {
  325. // choose best package to install from decisionQueue
  326. $literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $decisionQueue, $rule->getRequiredPackage());
  327. $selectedLiteral = array_shift($literals);
  328. // if there are multiple candidates, then branch
  329. if (count($literals)) {
  330. $this->branches[] = array($literals, $level);
  331. }
  332. return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
  333. }
  334. /**
  335. * @param int $level
  336. * @param Rule $rule
  337. * @return array
  338. */
  339. protected function analyze($level, Rule $rule)
  340. {
  341. $analyzedRule = $rule;
  342. $ruleLevel = 1;
  343. $num = 0;
  344. $l1num = 0;
  345. $seen = array();
  346. $learnedLiterals = array(null);
  347. $decisionId = count($this->decisions);
  348. $this->learnedPool[] = array();
  349. while (true) {
  350. $this->learnedPool[count($this->learnedPool) - 1][] = $rule;
  351. foreach ($rule->getLiterals() as $literal) {
  352. // skip the one true literal
  353. if ($this->decisions->satisfy($literal)) {
  354. continue;
  355. }
  356. if (isset($seen[abs($literal)])) {
  357. continue;
  358. }
  359. $seen[abs($literal)] = true;
  360. $l = $this->decisions->decisionLevel($literal);
  361. if (1 === $l) {
  362. $l1num++;
  363. } elseif ($level === $l) {
  364. $num++;
  365. } else {
  366. // not level1 or conflict level, add to new rule
  367. $learnedLiterals[] = $literal;
  368. if ($l > $ruleLevel) {
  369. $ruleLevel = $l;
  370. }
  371. }
  372. }
  373. $l1retry = true;
  374. while ($l1retry) {
  375. $l1retry = false;
  376. if (!$num && !--$l1num) {
  377. // all level 1 literals done
  378. break 2;
  379. }
  380. while (true) {
  381. if ($decisionId <= 0) {
  382. throw new SolverBugException(
  383. "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule."
  384. );
  385. }
  386. $decisionId--;
  387. $decision = $this->decisions->atOffset($decisionId);
  388. $literal = $decision[Decisions::DECISION_LITERAL];
  389. if (isset($seen[abs($literal)])) {
  390. break;
  391. }
  392. }
  393. unset($seen[abs($literal)]);
  394. if ($num && 0 === --$num) {
  395. if ($literal < 0) {
  396. $this->testFlagLearnedPositiveLiteral = true;
  397. }
  398. $learnedLiterals[0] = -$literal;
  399. if (!$l1num) {
  400. break 2;
  401. }
  402. foreach ($learnedLiterals as $i => $learnedLiteral) {
  403. if ($i !== 0) {
  404. unset($seen[abs($learnedLiteral)]);
  405. }
  406. }
  407. // only level 1 marks left
  408. $l1num++;
  409. $l1retry = true;
  410. }
  411. }
  412. $decision = $this->decisions->atOffset($decisionId);
  413. $rule = $decision[Decisions::DECISION_REASON];
  414. }
  415. $why = count($this->learnedPool) - 1;
  416. if (!$learnedLiterals[0]) {
  417. throw new SolverBugException(
  418. "Did not find a learnable literal in analyzed rule $analyzedRule."
  419. );
  420. }
  421. $newRule = new GenericRule($learnedLiterals, Rule::RULE_LEARNED, $why);
  422. return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
  423. }
  424. /**
  425. * @param Problem $problem
  426. * @param Rule $conflictRule
  427. */
  428. private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule)
  429. {
  430. if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
  431. $why = spl_object_hash($conflictRule);
  432. $learnedWhy = $this->learnedWhy[$why];
  433. $problemRules = $this->learnedPool[$learnedWhy];
  434. foreach ($problemRules as $problemRule) {
  435. $this->analyzeUnsolvableRule($problem, $problemRule);
  436. }
  437. return;
  438. }
  439. if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
  440. // package rules cannot be part of a problem
  441. return;
  442. }
  443. $problem->nextSection();
  444. $problem->addRule($conflictRule);
  445. }
  446. /**
  447. * @param Rule $conflictRule
  448. * @param bool $disableRules
  449. * @return int
  450. */
  451. private function analyzeUnsolvable(Rule $conflictRule, $disableRules)
  452. {
  453. $problem = new Problem($this->pool);
  454. $problem->addRule($conflictRule);
  455. $this->analyzeUnsolvableRule($problem, $conflictRule);
  456. $this->problems[] = $problem;
  457. $seen = array();
  458. $literals = $conflictRule->getLiterals();
  459. foreach ($literals as $literal) {
  460. // skip the one true literal
  461. if ($this->decisions->satisfy($literal)) {
  462. continue;
  463. }
  464. $seen[abs($literal)] = true;
  465. }
  466. foreach ($this->decisions as $decision) {
  467. $literal = $decision[Decisions::DECISION_LITERAL];
  468. // skip literals that are not in this rule
  469. if (!isset($seen[abs($literal)])) {
  470. continue;
  471. }
  472. $why = $decision[Decisions::DECISION_REASON];
  473. $problem->addRule($why);
  474. $this->analyzeUnsolvableRule($problem, $why);
  475. $literals = $why->getLiterals();
  476. foreach ($literals as $literal) {
  477. // skip the one true literal
  478. if ($this->decisions->satisfy($literal)) {
  479. continue;
  480. }
  481. $seen[abs($literal)] = true;
  482. }
  483. }
  484. if ($disableRules) {
  485. foreach ($this->problems[count($this->problems) - 1] as $reason) {
  486. $this->disableProblem($reason['rule']);
  487. }
  488. $this->resetSolver();
  489. return 1;
  490. }
  491. return 0;
  492. }
  493. /**
  494. * @param Rule $why
  495. */
  496. private function disableProblem(Rule $why)
  497. {
  498. $job = $why->getJob();
  499. if (!$job) {
  500. $why->disable();
  501. return;
  502. }
  503. // disable all rules of this job
  504. foreach ($this->rules as $rule) {
  505. /** @var Rule $rule */
  506. if ($job === $rule->getJob()) {
  507. $rule->disable();
  508. }
  509. }
  510. }
  511. private function resetSolver()
  512. {
  513. $this->decisions->reset();
  514. $this->propagateIndex = 0;
  515. $this->branches = array();
  516. $this->enableDisableLearnedRules();
  517. $this->makeAssertionRuleDecisions();
  518. }
  519. /**
  520. * enable/disable learnt rules
  521. *
  522. * we have enabled or disabled some of our rules. We now re-enable all
  523. * of our learnt rules except the ones that were learnt from rules that
  524. * are now disabled.
  525. */
  526. private function enableDisableLearnedRules()
  527. {
  528. foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
  529. $why = $this->learnedWhy[spl_object_hash($rule)];
  530. $problemRules = $this->learnedPool[$why];
  531. $foundDisabled = false;
  532. foreach ($problemRules as $problemRule) {
  533. if ($problemRule->isDisabled()) {
  534. $foundDisabled = true;
  535. break;
  536. }
  537. }
  538. if ($foundDisabled && $rule->isEnabled()) {
  539. $rule->disable();
  540. } elseif (!$foundDisabled && $rule->isDisabled()) {
  541. $rule->enable();
  542. }
  543. }
  544. }
  545. /**
  546. * @param bool $disableRules
  547. */
  548. private function runSat($disableRules = true)
  549. {
  550. $this->propagateIndex = 0;
  551. /*
  552. * here's the main loop:
  553. * 1) propagate new decisions (only needed once)
  554. * 2) fulfill jobs
  555. * 3) fulfill all unresolved rules
  556. * 4) minimalize solution if we had choices
  557. * if we encounter a problem, we rewind to a safe level and restart
  558. * with step 1
  559. */
  560. $decisionQueue = array();
  561. $decisionSupplementQueue = array();
  562. /**
  563. * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
  564. */
  565. $disableRules = false;
  566. $level = 1;
  567. $systemLevel = $level + 1;
  568. $installedPos = 0;
  569. while (true) {
  570. if (1 === $level) {
  571. $conflictRule = $this->propagate($level);
  572. if (null !== $conflictRule) {
  573. if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
  574. continue;
  575. }
  576. return;
  577. }
  578. }
  579. // handle job rules
  580. if ($level < $systemLevel) {
  581. $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
  582. foreach ($iterator as $rule) {
  583. if ($rule->isEnabled()) {
  584. $decisionQueue = array();
  585. $noneSatisfied = true;
  586. foreach ($rule->getLiterals() as $literal) {
  587. if ($this->decisions->satisfy($literal)) {
  588. $noneSatisfied = false;
  589. break;
  590. }
  591. if ($literal > 0 && $this->decisions->undecided($literal)) {
  592. $decisionQueue[] = $literal;
  593. }
  594. }
  595. if ($noneSatisfied && count($decisionQueue)) {
  596. // prune all update packages until installed version
  597. // except for requested updates
  598. if (count($this->installed) != count($this->updateMap)) {
  599. $prunedQueue = array();
  600. foreach ($decisionQueue as $literal) {
  601. if (isset($this->installedMap[abs($literal)])) {
  602. $prunedQueue[] = $literal;
  603. if (isset($this->updateMap[abs($literal)])) {
  604. $prunedQueue = $decisionQueue;
  605. break;
  606. }
  607. }
  608. }
  609. $decisionQueue = $prunedQueue;
  610. }
  611. }
  612. if ($noneSatisfied && count($decisionQueue)) {
  613. $oLevel = $level;
  614. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  615. if (0 === $level) {
  616. return;
  617. }
  618. if ($level <= $oLevel) {
  619. break;
  620. }
  621. }
  622. }
  623. }
  624. $systemLevel = $level + 1;
  625. // jobs left
  626. $iterator->next();
  627. if ($iterator->valid()) {
  628. continue;
  629. }
  630. }
  631. if ($level < $systemLevel) {
  632. $systemLevel = $level;
  633. }
  634. $rulesCount = count($this->rules);
  635. $pass = 1;
  636. $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
  637. for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
  638. if ($i == $rulesCount) {
  639. if (1 === $pass) {
  640. $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
  641. } else {
  642. $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
  643. }
  644. $i = 0;
  645. $pass++;
  646. }
  647. $rule = $this->rules->ruleById[$i];
  648. $literals = $rule->getLiterals();
  649. if ($rule->isDisabled()) {
  650. continue;
  651. }
  652. $decisionQueue = array();
  653. // make sure that
  654. // * all negative literals are installed
  655. // * no positive literal is installed
  656. // i.e. the rule is not fulfilled and we
  657. // just need to decide on the positive literals
  658. //
  659. foreach ($literals as $literal) {
  660. if ($literal <= 0) {
  661. if (!$this->decisions->decidedInstall($literal)) {
  662. continue 2; // next rule
  663. }
  664. } else {
  665. if ($this->decisions->decidedInstall($literal)) {
  666. continue 2; // next rule
  667. }
  668. if ($this->decisions->undecided($literal)) {
  669. $decisionQueue[] = $literal;
  670. }
  671. }
  672. }
  673. // need to have at least 2 item to pick from
  674. if (count($decisionQueue) < 2) {
  675. continue;
  676. }
  677. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  678. if (0 === $level) {
  679. return;
  680. }
  681. // something changed, so look at all rules again
  682. $rulesCount = count($this->rules);
  683. $n = -1;
  684. }
  685. if ($level < $systemLevel) {
  686. continue;
  687. }
  688. // minimization step
  689. if (count($this->branches)) {
  690. $lastLiteral = null;
  691. $lastLevel = null;
  692. $lastBranchIndex = 0;
  693. $lastBranchOffset = 0;
  694. for ($i = count($this->branches) - 1; $i >= 0; $i--) {
  695. list($literals, $l) = $this->branches[$i];
  696. foreach ($literals as $offset => $literal) {
  697. if ($literal && $literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) {
  698. $lastLiteral = $literal;
  699. $lastBranchIndex = $i;
  700. $lastBranchOffset = $offset;
  701. $lastLevel = $l;
  702. }
  703. }
  704. }
  705. if ($lastLiteral) {
  706. unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]);
  707. $level = $lastLevel;
  708. $this->revert($level);
  709. $why = $this->decisions->lastReason();
  710. $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
  711. if ($level == 0) {
  712. return;
  713. }
  714. continue;
  715. }
  716. }
  717. break;
  718. }
  719. }
  720. }