Solver.php 26 KB

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