Solver.php 26 KB

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