Solver.php 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531
  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\Repository\RepositoryInterface;
  13. use Composer\Package\PackageInterface;
  14. use Composer\DependencyResolver\Operation;
  15. /**
  16. * @author Nils Adermann <naderman@naderman.de>
  17. */
  18. class Solver
  19. {
  20. protected $policy;
  21. protected $pool;
  22. protected $installed;
  23. protected $rules;
  24. protected $updateAll;
  25. protected $ruleToJob = array();
  26. protected $addedMap = array();
  27. protected $updateMap = array();
  28. protected $watches = array();
  29. protected $removeWatches = array();
  30. protected $decisionMap;
  31. protected $installedMap;
  32. protected $installedPackages;
  33. protected $packageToFeatureRule = array();
  34. protected $decisionQueue = array();
  35. protected $decisionQueueWhy = array();
  36. protected $decisionQueueFree = array();
  37. protected $propagateIndex;
  38. protected $branches = array();
  39. protected $problems = array();
  40. protected $learnedPool = array();
  41. protected $recommendsIndex;
  42. public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
  43. {
  44. $this->policy = $policy;
  45. $this->pool = $pool;
  46. $this->installed = $installed;
  47. $this->rules = new RuleSet;
  48. }
  49. /**
  50. * Creates a new rule for the requirements of a package
  51. *
  52. * This rule is of the form (-A|B|C), where B and C are the providers of
  53. * one requirement of the package A.
  54. *
  55. * @param PackageInterface $package The package with a requirement
  56. * @param array $providers The providers of the requirement
  57. * @param int $reason A RULE_* constant describing the
  58. * reason for generating this rule
  59. * @param mixed $reasonData Any data, e.g. the requirement name,
  60. * that goes with the reason
  61. * @return Rule The generated rule or null if tautological
  62. */
  63. protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
  64. {
  65. $literals = array(new Literal($package, false));
  66. foreach ($providers as $provider) {
  67. // self fulfilling rule?
  68. if ($provider === $package) {
  69. return null;
  70. }
  71. $literals[] = new Literal($provider, true);
  72. }
  73. return new Rule($literals, $reason, $reasonData);
  74. }
  75. /**
  76. * Create a new rule for updating a package
  77. *
  78. * If package A1 can be updated to A2 or A3 the rule is (A1|A2|A3).
  79. *
  80. * @param PackageInterface $package The package to be updated
  81. * @param array $updates An array of update candidate packages
  82. * @param int $reason A RULE_* constant describing the
  83. * reason for generating this rule
  84. * @param mixed $reasonData Any data, e.g. the package name, that
  85. * goes with the reason
  86. * @return Rule The generated rule or null if tautology
  87. */
  88. protected function createUpdateRule(PackageInterface $package, array $updates, $reason, $reasonData = null)
  89. {
  90. $literals = array(new Literal($package, true));
  91. foreach ($updates as $update) {
  92. $literals[] = new Literal($update, true);
  93. }
  94. return new Rule($literals, $reason, $reasonData);
  95. }
  96. /**
  97. * Creates a new rule for installing a package
  98. *
  99. * The rule is simply (A) for a package A to be installed.
  100. *
  101. * @param PackageInterface $package The package to be installed
  102. * @param int $reason A RULE_* constant describing the
  103. * reason for generating this rule
  104. * @param mixed $reasonData Any data, e.g. the package name, that
  105. * goes with the reason
  106. * @return Rule The generated rule
  107. */
  108. protected function createInstallRule(PackageInterface $package, $reason, $reasonData = null)
  109. {
  110. return new Rule(new Literal($package, true));
  111. }
  112. /**
  113. * Creates a rule to install at least one of a set of packages
  114. *
  115. * The rule is (A|B|C) with A, B and C different packages. If the given
  116. * set of packages is empty an impossible rule is generated.
  117. *
  118. * @param array $packages The set of packages to choose from
  119. * @param int $reason A RULE_* constant describing the reason for
  120. * generating this rule
  121. * @param mixed $reasonData Any data, e.g. the package name, that goes with
  122. * the reason
  123. * @return Rule The generated rule
  124. */
  125. protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
  126. {
  127. $literals = array();
  128. foreach ($packages as $package) {
  129. $literals[] = new Literal($package, true);
  130. }
  131. return new Rule($literals, $reason, $reasonData);
  132. }
  133. /**
  134. * Creates a rule to remove a package
  135. *
  136. * The rule for a package A is (-A).
  137. *
  138. * @param PackageInterface $package The package to be removed
  139. * @param int $reason A RULE_* constant describing the
  140. * reason for generating this rule
  141. * @param mixed $reasonData Any data, e.g. the package name, that
  142. * goes with the reason
  143. * @return Rule The generated rule
  144. */
  145. protected function createRemoveRule(PackageInterface $package, $reason, $reasonData = null)
  146. {
  147. return new Rule(array(new Literal($package, false)), $reason, $reasonData);
  148. }
  149. /**
  150. * Creates a rule for two conflicting packages
  151. *
  152. * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
  153. * and B the provider.
  154. *
  155. * @param PackageInterface $issuer The package declaring the conflict
  156. * @param Package $provider The package causing the conflict
  157. * @param int $reason A RULE_* constant describing the
  158. * reason for generating this rule
  159. * @param mixed $reasonData Any data, e.g. the package name, that
  160. * goes with the reason
  161. * @return Rule The generated rule
  162. */
  163. protected function createConflictRule(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
  164. {
  165. // ignore self conflict
  166. if ($issuer === $provider) {
  167. return null;
  168. }
  169. return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
  170. }
  171. /**
  172. * Adds a rule unless it duplicates an existing one of any type
  173. *
  174. * To be able to directly pass in the result of one of the rule creation
  175. * methods the rule may also be null to indicate that no rule should be
  176. * added.
  177. *
  178. * @param int $type A TYPE_* constant defining the rule type
  179. * @param Rule $newRule The rule about to be added
  180. */
  181. private function addRule($type, Rule $newRule = null) {
  182. if ($newRule) {
  183. if ($this->rules->containsEqual($newRule)) {
  184. return;
  185. }
  186. $this->rules->add($newRule, $type);
  187. }
  188. }
  189. protected function addRulesForPackage(PackageInterface $package)
  190. {
  191. $workQueue = new \SplQueue;
  192. $workQueue->enqueue($package);
  193. while (!$workQueue->isEmpty()) {
  194. $package = $workQueue->dequeue();
  195. if (isset($this->addedMap[$package->getId()])) {
  196. continue;
  197. }
  198. $this->addedMap[$package->getId()] = true;
  199. if (!$this->policy->installable($this, $this->pool, $this->installedMap, $package)) {
  200. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRemoveRule($package, Rule::RULE_NOT_INSTALLABLE, (string) $package));
  201. continue;
  202. }
  203. foreach ($package->getRequires() as $link) {
  204. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  205. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, (string) $link));
  206. foreach ($possibleRequires as $require) {
  207. $workQueue->enqueue($require);
  208. }
  209. }
  210. foreach ($package->getConflicts() as $link) {
  211. $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  212. foreach ($possibleConflicts as $conflict) {
  213. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, (string) $link));
  214. }
  215. }
  216. // check obsoletes and implicit obsoletes of a package
  217. $isInstalled = (isset($this->installedMap[$package->getId()]));
  218. foreach ($package->getReplaces() as $link) {
  219. $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  220. foreach ($obsoleteProviders as $provider) {
  221. if ($provider === $package) {
  222. continue;
  223. }
  224. $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
  225. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createConflictRule($package, $provider, $reason, (string) $link));
  226. }
  227. }
  228. // check implicit obsoletes
  229. // for installed packages we only need to check installed/installed problems,
  230. // as the others are picked up when looking at the uninstalled package.
  231. if (!$isInstalled) {
  232. $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
  233. foreach ($obsoleteProviders as $provider) {
  234. if ($provider === $package) {
  235. continue;
  236. }
  237. if ($isInstalled && !isset($this->installedMap[$provider->getId()])) {
  238. continue;
  239. }
  240. $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
  241. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, (string) $package));
  242. }
  243. }
  244. }
  245. }
  246. /**
  247. * Adds all rules for all update packages of a given package
  248. *
  249. * @param PackageInterface $package Rules for this package's updates are to
  250. * be added
  251. * @param bool $allowAll Whether downgrades are allowed
  252. */
  253. private function addRulesForUpdatePackages(PackageInterface $package)
  254. {
  255. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
  256. $this->addRulesForPackage($package);
  257. foreach ($updates as $update) {
  258. $this->addRulesForPackage($update);
  259. }
  260. }
  261. /**
  262. * Alters watch chains for a rule.
  263. *
  264. * Next1/2 always points to the next rule that is watching the same package.
  265. * The watches array contains rules to start from for each package
  266. *
  267. */
  268. private function addWatchesToRule(Rule $rule)
  269. {
  270. // skip simple assertions of the form (A) or (-A)
  271. if ($rule->isAssertion()) {
  272. return;
  273. }
  274. if (!isset($this->watches[$rule->watch1])) {
  275. $this->watches[$rule->watch1] = null;
  276. }
  277. $rule->next1 = $this->watches[$rule->watch1];
  278. $this->watches[$rule->watch1] = $rule;
  279. if (!isset($this->watches[$rule->watch2])) {
  280. $this->watches[$rule->watch2] = null;
  281. }
  282. $rule->next2 = $this->watches[$rule->watch2];
  283. $this->watches[$rule->watch2] = $rule;
  284. }
  285. /**
  286. * Put watch2 on rule's literal with highest level
  287. */
  288. private function watch2OnHighest(Rule $rule)
  289. {
  290. $literals = $rule->getLiterals();
  291. // if there are only 2 elements, both are being watched anyway
  292. if ($literals < 3) {
  293. return;
  294. }
  295. $watchLevel = 0;
  296. foreach ($literals as $literal) {
  297. $level = abs($this->decisionMap[$literal->getPackageId()]);
  298. if ($level > $watchLevel) {
  299. $rule->watch2 = $literal->getId();
  300. $watchLevel = $level;
  301. }
  302. }
  303. }
  304. private function findDecisionRule(PackageInterface $package)
  305. {
  306. foreach ($this->decisionQueue as $i => $literal) {
  307. if ($package === $literal->getPackage()) {
  308. return $this->decisionQueueWhy[$i];
  309. }
  310. }
  311. return null;
  312. }
  313. // aka solver_makeruledecisions
  314. private function makeAssertionRuleDecisions()
  315. {
  316. // do we need to decide a SYSTEMSOLVABLE at level 1?
  317. $decisionStart = count($this->decisionQueue);
  318. for ($ruleIndex = 0; $ruleIndex < count($this->rules); $ruleIndex++) {
  319. $rule = $this->rules->ruleById($ruleIndex);
  320. if ($rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
  321. continue;
  322. }
  323. $literals = $rule->getLiterals();
  324. $literal = $literals[0];
  325. if (!$this->decided($literal->getPackage())) {
  326. $this->decisionQueue[] = $literal;
  327. $this->decisionQueueWhy[] = $rule;
  328. $this->addDecision($literal, 1);
  329. continue;
  330. }
  331. if ($this->decisionsSatisfy($literal)) {
  332. continue;
  333. }
  334. // found a conflict
  335. if (RuleSet::TYPE_LEARNED === $rule->getType()) {
  336. $rule->disable();
  337. continue;
  338. }
  339. $conflict = $this->findDecisionRule($literal->getPackage());
  340. /** TODO: handle conflict with systemsolvable? */
  341. if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
  342. $problem = new Problem;
  343. if ($rule->getType() == RuleSet::TYPE_JOB) {
  344. $job = $this->ruleToJob[$rule->getId()];
  345. $problem->addJobRule($job, $rule);
  346. $problem->addRule($conflict);
  347. $this->disableProblem($job);
  348. } else {
  349. $problem->addRule($rule);
  350. $problem->addRule($conflict);
  351. $this->disableProblem($rule);
  352. }
  353. $this->problems[] = $problem;
  354. continue;
  355. }
  356. // conflict with another job or update/feature rule
  357. $problem = new Problem;
  358. $problem->addRule($rule);
  359. $problem->addRule($conflict);
  360. // push all of our rules (can only be feature or job rules)
  361. // asserting this literal on the problem stack
  362. foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
  363. if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
  364. continue;
  365. }
  366. $assertRuleLiterals = $assertRule->getLiterals();
  367. $assertRuleLiteral = $assertRuleLiterals[0];
  368. if ($literal->getPackageId() !== $assertRuleLiteral->getPackageId()) {
  369. continue;
  370. }
  371. if ($assertRule->getType() === RuleSet::TYPE_JOB) {
  372. $job = $this->ruleToJob[$assertRule->getId()];
  373. $problem->addJobRule($job, $assertRule);
  374. $this->disableProblem($job);
  375. } else {
  376. $problem->addRule($assertRule);
  377. $this->disableProblem($assertRule);
  378. }
  379. }
  380. $this->problems[] = $problem;
  381. // start over
  382. while (count($this->decisionQueue) > $decisionStart) {
  383. $decisionLiteral = array_pop($this->decisionQueue);
  384. array_pop($this->decisionQueueWhy);
  385. unset($this->decisionQueueFree[count($this->decisionQueue)]);
  386. $this->decisionMap[$decisionLiteral->getPackageId()] = 0;
  387. }
  388. $ruleIndex = -1;
  389. }
  390. foreach ($this->rules as $rule) {
  391. if (!$rule->isWeak() || !$rule->isAssertion() || $rule->isDisabled()) {
  392. continue;
  393. }
  394. $literals = $rule->getLiterals();
  395. $literal = $literals[0];
  396. if ($this->decisionMap[$literal->getPackageId()] == 0) {
  397. $this->decisionQueue[] = $literal;
  398. $this->decisionQueueWhy[] = $rule;
  399. $this->addDecision($literal, 1);
  400. continue;
  401. }
  402. if ($this->decisionsSatisfy($literals[0])) {
  403. continue;
  404. }
  405. // conflict, but this is a weak rule => disable
  406. if ($rule->getType() == RuleSet::TYPE_JOB) {
  407. $why = $this->ruleToJob[$rule->getId()];
  408. } else {
  409. $why = $rule;
  410. }
  411. $this->disableProblem($why);
  412. }
  413. }
  414. protected function setupInstalledMap()
  415. {
  416. $this->installedPackages = $this->installed->getPackages();
  417. $this->installedMap = array();
  418. foreach ($this->installedPackages as $package) {
  419. $this->installedMap[$package->getId()] = $package;
  420. }
  421. }
  422. public function solve(Request $request)
  423. {
  424. $this->jobs = $request->getJobs();
  425. $this->setupInstalledMap();
  426. if (version_compare(PHP_VERSION, '5.3.4', '>=')) {
  427. $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1);
  428. } else {
  429. $this->decisionMap = array_fill(0, $this->pool->getMaxId() + 1, 0);
  430. }
  431. foreach ($this->jobs as $job) {
  432. foreach ($job['packages'] as $package) {
  433. switch ($job['cmd']) {
  434. case 'update':
  435. if (isset($this->installedMap[$package->getId()])) {
  436. $this->updateMap[$package->getId()] = true;
  437. }
  438. break;
  439. }
  440. }
  441. switch ($job['cmd']) {
  442. case 'update-all':
  443. foreach ($this->installedMap as $package) {
  444. $this->updateMap[$package->getId()] = true;
  445. }
  446. break;
  447. }
  448. }
  449. foreach ($this->installedMap as $package) {
  450. $this->addRulesForPackage($package);
  451. }
  452. foreach ($this->installedMap as $package) {
  453. $this->addRulesForUpdatePackages($package);
  454. }
  455. foreach ($this->jobs as $job) {
  456. foreach ($job['packages'] as $package) {
  457. switch ($job['cmd']) {
  458. case 'install':
  459. $this->installCandidateMap[$package->getId()] = true;
  460. $this->addRulesForPackage($package);
  461. break;
  462. }
  463. }
  464. }
  465. foreach ($this->installedMap as $package) {
  466. $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
  467. $rule = $this->createUpdateRule($package, $updates, Rule::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
  468. $this->packageToFeatureRule[$package->getId()] = $rule;
  469. }
  470. foreach ($this->jobs as $job) {
  471. switch ($job['cmd']) {
  472. case 'install':
  473. if (empty($job['packages'])) {
  474. $problem = new Problem();
  475. $problem->addJobRule($job);
  476. $this->problems[] = $problem;
  477. } else {
  478. $rule = $this->createInstallOneOfRule($job['packages'], Rule::RULE_JOB_INSTALL, $job['packageName']);
  479. $this->addRule(RuleSet::TYPE_JOB, $rule);
  480. $this->ruleToJob[$rule->getId()] = $job;
  481. }
  482. break;
  483. case 'remove':
  484. // remove all packages with this name including uninstalled
  485. // ones to make sure none of them are picked as replacements
  486. // todo: cleandeps
  487. foreach ($job['packages'] as $package) {
  488. $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE);
  489. $this->addRule(RuleSet::TYPE_JOB, $rule);
  490. $this->ruleToJob[$rule->getId()] = $job;
  491. }
  492. break;
  493. case 'lock':
  494. foreach ($job['packages'] as $package) {
  495. if (isset($this->installedMap[$package->getId()])) {
  496. $rule = $this->createInstallRule($package, Rule::RULE_JOB_LOCK);
  497. } else {
  498. $rule = $this->createRemoveRule($package, Rule::RULE_JOB_LOCK);
  499. }
  500. $this->addRule(RuleSet::TYPE_JOB, $rule);
  501. $this->ruleToJob[$rule->getId()] = $job;
  502. }
  503. break;
  504. }
  505. }
  506. foreach ($this->rules as $rule) {
  507. $this->addWatchesToRule($rule);
  508. }
  509. /* make decisions based on job/update assertions */
  510. $this->makeAssertionRuleDecisions();
  511. $installRecommended = 0;
  512. $this->runSat(true, $installRecommended);
  513. if ($this->problems) {
  514. throw new SolverProblemsException($this->problems);
  515. }
  516. return $this->createTransaction();
  517. }
  518. protected function createTransaction()
  519. {
  520. $transaction = array();
  521. $installMeansUpdateMap = array();
  522. foreach ($this->decisionQueue as $i => $literal) {
  523. $package = $literal->getPackage();
  524. // !wanted & installed
  525. if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
  526. $literals = array();
  527. if (isset($this->packageToFeatureRule[$package->getId()])) {
  528. $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
  529. }
  530. foreach ($literals as $updateLiteral) {
  531. if (!$updateLiteral->equals($literal)) {
  532. $installMeansUpdateMap[$updateLiteral->getPackageId()] = $package;
  533. }
  534. }
  535. }
  536. }
  537. foreach ($this->decisionQueue as $i => $literal) {
  538. $package = $literal->getPackage();
  539. // wanted & installed || !wanted & !installed
  540. if ($literal->isWanted() == (isset($this->installedMap[$package->getId()]))) {
  541. continue;
  542. }
  543. if ($literal->isWanted()) {
  544. if (isset($installMeansUpdateMap[$literal->getPackageId()])) {
  545. $source = $installMeansUpdateMap[$literal->getPackageId()];
  546. $transaction[] = new Operation\UpdateOperation(
  547. $source, $package, $this->decisionQueueWhy[$i]
  548. );
  549. // avoid updates to one package from multiple origins
  550. unset($installMeansUpdateMap[$literal->getPackageId()]);
  551. $ignoreRemove[$source->getId()] = true;
  552. } else {
  553. $transaction[] = new Operation\InstallOperation(
  554. $package, $this->decisionQueueWhy[$i]
  555. );
  556. }
  557. } else if (!isset($ignoreRemove[$package->getId()])) {
  558. $transaction[] = new Operation\UninstallOperation(
  559. $package, $this->decisionQueueWhy[$i]
  560. );
  561. }
  562. }
  563. foreach ($this->decisionMap as $packageId => $decision) {
  564. if ($packageId === 0) {
  565. continue;
  566. }
  567. if (0 == $decision && isset($this->installedMap[$packageId])) {
  568. $transaction[] = new Operation\UninstallOperation(
  569. $this->pool->packageById($packageId), null
  570. );
  571. }
  572. }
  573. return array_reverse($transaction);
  574. }
  575. protected function literalFromId($id)
  576. {
  577. $package = $this->pool->packageById(abs($id));
  578. return new Literal($package, $id > 0);
  579. }
  580. protected function addDecision(Literal $l, $level)
  581. {
  582. assert($this->decisionMap[$l->getPackageId()] == 0);
  583. if ($l->isWanted()) {
  584. $this->decisionMap[$l->getPackageId()] = $level;
  585. } else {
  586. $this->decisionMap[$l->getPackageId()] = -$level;
  587. }
  588. }
  589. protected function addDecisionId($literalId, $level)
  590. {
  591. $packageId = abs($literalId);
  592. assert($this->decisionMap[$packageId] == 0);
  593. if ($literalId > 0) {
  594. $this->decisionMap[$packageId] = $level;
  595. } else {
  596. $this->decisionMap[$packageId] = -$level;
  597. }
  598. }
  599. protected function decisionsContain(Literal $l)
  600. {
  601. return (
  602. $this->decisionMap[$l->getPackageId()] > 0 && $l->isWanted() ||
  603. $this->decisionMap[$l->getPackageId()] < 0 && !$l->isWanted()
  604. );
  605. }
  606. protected function decisionsContainId($literalId)
  607. {
  608. $packageId = abs($literalId);
  609. return (
  610. $this->decisionMap[$packageId] > 0 && $literalId > 0 ||
  611. $this->decisionMap[$packageId] < 0 && $literalId < 0
  612. );
  613. }
  614. protected function decisionsSatisfy(Literal $l)
  615. {
  616. return ($l->isWanted() && $this->decisionMap[$l->getPackageId()] > 0) ||
  617. (!$l->isWanted() && $this->decisionMap[$l->getPackageId()] <= 0);
  618. }
  619. protected function decisionsConflict(Literal $l)
  620. {
  621. return (
  622. $this->decisionMap[$l->getPackageId()] > 0 && !$l->isWanted() ||
  623. $this->decisionMap[$l->getPackageId()] < 0 && $l->isWanted()
  624. );
  625. }
  626. protected function decisionsConflictId($literalId)
  627. {
  628. $packageId = abs($literalId);
  629. return (
  630. ($this->decisionMap[$packageId] > 0 && $literalId < 0) ||
  631. ($this->decisionMap[$packageId] < 0 && $literalId > 0)
  632. );
  633. }
  634. protected function decided(PackageInterface $p)
  635. {
  636. return $this->decisionMap[$p->getId()] != 0;
  637. }
  638. protected function undecided(PackageInterface $p)
  639. {
  640. return $this->decisionMap[$p->getId()] == 0;
  641. }
  642. protected function decidedInstall(PackageInterface $p) {
  643. return $this->decisionMap[$p->getId()] > 0;
  644. }
  645. protected function decidedRemove(PackageInterface $p) {
  646. return $this->decisionMap[$p->getId()] < 0;
  647. }
  648. /**
  649. * Makes a decision and propagates it to all rules.
  650. *
  651. * Evaluates each term affected by the decision (linked through watches)
  652. * If we find unit rules we make new decisions based on them
  653. *
  654. * @return Rule|null A rule on conflict, otherwise null.
  655. */
  656. protected function propagate($level)
  657. {
  658. while ($this->propagateIndex < count($this->decisionQueue)) {
  659. // we invert the decided literal here, example:
  660. // A was decided => (-A|B) now requires B to be true, so we look for
  661. // rules which are fulfilled by -A, rather than A.
  662. $literal = $this->decisionQueue[$this->propagateIndex]->inverted();
  663. $this->propagateIndex++;
  664. // /* foreach rule where 'pkg' is now FALSE */
  665. //for (rp = watches + pkg; *rp; rp = next_rp)
  666. if (!isset($this->watches[$literal->getId()])) {
  667. continue;
  668. }
  669. $prevRule = null;
  670. for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
  671. $nextRule = $rule->getNext($literal);
  672. if ($rule->isDisabled()) {
  673. continue;
  674. }
  675. $otherWatch = $rule->getOtherWatch($literal);
  676. if ($this->decisionsContainId($otherWatch)) {
  677. continue;
  678. }
  679. $ruleLiterals = $rule->getLiterals();
  680. if (sizeof($ruleLiterals) > 2) {
  681. foreach ($ruleLiterals as $ruleLiteral) {
  682. if ($otherWatch !== $ruleLiteral->getId() &&
  683. !$this->decisionsConflict($ruleLiteral)) {
  684. if ($literal->getId() === $rule->watch1) {
  685. $rule->watch1 = $ruleLiteral->getId();
  686. $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
  687. } else {
  688. $rule->watch2 = $ruleLiteral->getId();
  689. $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
  690. }
  691. if ($prevRule) {
  692. if ($prevRule->next1 == $rule) {
  693. $prevRule->next1 = $nextRule;
  694. } else {
  695. $prevRule->next2 = $nextRule;
  696. }
  697. } else {
  698. $this->watches[$literal->getId()] = $nextRule;
  699. }
  700. $this->watches[$ruleLiteral->getId()] = $rule;
  701. $rule = $prevRule;
  702. continue 2;
  703. }
  704. }
  705. }
  706. // yay, we found a unit clause! try setting it to true
  707. if ($this->decisionsConflictId($otherWatch)) {
  708. return $rule;
  709. }
  710. $this->addDecisionId($otherWatch, $level);
  711. $this->decisionQueue[] = $this->literalFromId($otherWatch);
  712. $this->decisionQueueWhy[] = $rule;
  713. }
  714. }
  715. return null;
  716. }
  717. /**
  718. * Reverts a decision at the given level.
  719. */
  720. private function revert($level)
  721. {
  722. while (!empty($this->decisionQueue)) {
  723. $literal = $this->decisionQueue[count($this->decisionQueue) - 1];
  724. if (!$this->decisionMap[$literal->getPackageId()]) {
  725. break;
  726. }
  727. $decisionLevel = abs($this->decisionMap[$literal->getPackageId()]);
  728. if ($decisionLevel <= $level) {
  729. break;
  730. }
  731. /** TODO: implement recommendations
  732. *if (v > 0 && solv->recommendations.count && v == solv->recommendations.elements[solv->recommendations.count - 1])
  733. * solv->recommendations.count--;
  734. */
  735. $this->decisionMap[$literal->getPackageId()] = 0;
  736. array_pop($this->decisionQueue);
  737. array_pop($this->decisionQueueWhy);
  738. $this->propagateIndex = count($this->decisionQueue);
  739. }
  740. while (!empty($this->branches)) {
  741. list($literals, $branchLevel) = $this->branches[count($this->branches) - 1];
  742. if ($branchLevel >= $level) {
  743. break;
  744. }
  745. array_pop($this->branches);
  746. }
  747. $this->recommendsIndex = -1;
  748. }
  749. /**-------------------------------------------------------------------
  750. *
  751. * setpropagatelearn
  752. *
  753. * add free decision (solvable to install) to decisionq
  754. * increase level and propagate decision
  755. * return if no conflict.
  756. *
  757. * in conflict case, analyze conflict rule, add resulting
  758. * rule to learnt rule set, make decision from learnt
  759. * rule (always unit) and re-propagate.
  760. *
  761. * returns the new solver level or 0 if unsolvable
  762. *
  763. */
  764. private function setPropagateLearn($level, Literal $literal, $disableRules, Rule $rule)
  765. {
  766. assert($rule != null);
  767. assert($literal != null);
  768. $level++;
  769. $this->addDecision($literal, $level);
  770. $this->decisionQueue[] = $literal;
  771. $this->decisionQueueWhy[] = $rule;
  772. $this->decisionQueueFree[count($this->decisionQueueWhy) - 1] = true;
  773. while (true) {
  774. $rule = $this->propagate($level);
  775. if (!$rule) {
  776. break;
  777. }
  778. if ($level == 1) {
  779. return $this->analyzeUnsolvable($rule, $disableRules);
  780. }
  781. // conflict
  782. list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);
  783. assert($newLevel > 0);
  784. assert($newLevel < $level);
  785. $level = $newLevel;
  786. $this->revert($level);
  787. assert($newRule != null);
  788. $this->addRule(RuleSet::TYPE_LEARNED, $newRule);
  789. $this->learnedWhy[$newRule->getId()] = $why;
  790. $this->watch2OnHighest($newRule);
  791. $this->addWatchesToRule($newRule);
  792. $this->addDecision($learnLiteral, $level);
  793. $this->decisionQueue[] = $learnLiteral;
  794. $this->decisionQueueWhy[] = $newRule;
  795. }
  796. return $level;
  797. }
  798. private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
  799. {
  800. // choose best package to install from decisionQueue
  801. $literals = $this->policy->selectPreferedPackages($this->pool, $this->installedMap, $decisionQueue);
  802. $selectedLiteral = array_shift($literals);
  803. // if there are multiple candidates, then branch
  804. if (count($literals)) {
  805. $this->branches[] = array($literals, $level);
  806. }
  807. return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
  808. }
  809. protected function analyze($level, $rule)
  810. {
  811. $ruleLevel = 1;
  812. $num = 0;
  813. $l1num = 0;
  814. $seen = array();
  815. $learnedLiterals = array(null);
  816. $decisionId = count($this->decisionQueue);
  817. $this->learnedPool[] = array();
  818. while(true) {
  819. $this->learnedPool[count($this->learnedPool) - 1][] = $rule;
  820. foreach ($rule->getLiterals() as $literal) {
  821. // skip the one true literal
  822. if ($this->decisionsSatisfy($literal)) {
  823. continue;
  824. }
  825. if (isset($seen[$literal->getPackageId()])) {
  826. continue;
  827. }
  828. $seen[$literal->getPackageId()] = true;
  829. $l = abs($this->decisionMap[$literal->getPackageId()]);
  830. if (1 === $l) {
  831. $l1num++;
  832. } else if ($level === $l) {
  833. $num++;
  834. } else {
  835. // not level1 or conflict level, add to new rule
  836. $learnedLiterals[] = $literal;
  837. if ($l > $ruleLevel) {
  838. $ruleLevel = $l;
  839. }
  840. }
  841. }
  842. $l1retry = true;
  843. while ($l1retry) {
  844. $l1retry = false;
  845. if (!$num && !--$l1num) {
  846. // all level 1 literals done
  847. break 2;
  848. }
  849. while (true) {
  850. assert($decisionId > 0);
  851. $decisionId--;
  852. $literal = $this->decisionQueue[$decisionId];
  853. if (isset($seen[$literal->getPackageId()])) {
  854. break;
  855. }
  856. }
  857. unset($seen[$literal->getPackageId()]);
  858. if ($num && 0 === --$num) {
  859. $learnedLiterals[0] = $this->literalFromId(-$literal->getPackageId());
  860. if (!$l1num) {
  861. break 2;
  862. }
  863. foreach ($learnedLiterals as $i => $learnedLiteral) {
  864. if ($i !== 0) {
  865. unset($seen[$literal->getPackageId()]);
  866. }
  867. }
  868. // only level 1 marks left
  869. $l1num++;
  870. $l1retry = true;
  871. }
  872. $rule = $this->decisionQueueWhy[$decisionId];
  873. }
  874. }
  875. $why = count($this->learnedPool) - 1;
  876. assert($learnedLiterals[0] !== null);
  877. $newRule = new Rule($learnedLiterals, Rule::RULE_LEARNED, $why);
  878. return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
  879. }
  880. private function analyzeUnsolvableRule($problem, $conflictRule, &$lastWeakWhy)
  881. {
  882. $why = $conflictRule->getId();
  883. if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
  884. $learnedWhy = $this->learnedWhy[$why];
  885. $problemRules = $this->learnedPool[$learnedWhy];
  886. foreach ($problemRules as $problemRule) {
  887. $this->analyzeUnsolvableRule($problem, $problemRule, $lastWeakWhy);
  888. }
  889. return;
  890. }
  891. if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
  892. // package rules cannot be part of a problem
  893. return;
  894. }
  895. if ($conflictRule->isWeak()) {
  896. /** TODO why > or < lastWeakWhy? */
  897. if (!$lastWeakWhy || $why > $lastWeakWhy->getId()) {
  898. $lastWeakWhy = $conflictRule;
  899. }
  900. }
  901. if ($conflictRule->getType() == RuleSet::TYPE_JOB) {
  902. $job = $this->ruleToJob[$conflictRule->getId()];
  903. $problem->addJobRule($job, $conflictRule);
  904. } else {
  905. $problem->addRule($conflictRule);
  906. }
  907. }
  908. private function analyzeUnsolvable($conflictRule, $disableRules)
  909. {
  910. $lastWeakWhy = null;
  911. $problem = new Problem;
  912. $problem->addRule($conflictRule);
  913. $this->analyzeUnsolvableRule($problem, $conflictRule, $lastWeakWhy);
  914. $this->problems[] = $problem;
  915. $seen = array();
  916. $literals = $conflictRule->getLiterals();
  917. /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
  918. if (sizeof($literals) == 2) {
  919. $literals[1] = $this->literalFromId($conflictRule->watch2);
  920. }
  921. */
  922. foreach ($literals as $literal) {
  923. // skip the one true literal
  924. if ($this->decisionsSatisfy($literal)) {
  925. continue;
  926. }
  927. $seen[$literal->getPackageId()] = true;
  928. }
  929. $decisionId = count($this->decisionQueue);
  930. while ($decisionId > 0) {
  931. $decisionId--;
  932. $literal = $this->decisionQueue[$decisionId];
  933. // skip literals that are not in this rule
  934. if (!isset($seen[$literal->getPackageId()])) {
  935. continue;
  936. }
  937. $why = $this->decisionQueueWhy[$decisionId];
  938. $problem->addRule($why);
  939. $this->analyzeUnsolvableRule($problem, $why, $lastWeakWhy);
  940. $literals = $why->getLiterals();
  941. /* unnecessary because unlike rule.d, watch2 == 2nd literal, unless watch2 changed
  942. if (sizeof($literals) == 2) {
  943. $literals[1] = $this->literalFromId($why->watch2);
  944. }
  945. */
  946. foreach ($literals as $literal) {
  947. // skip the one true literal
  948. if ($this->decisionsSatisfy($literal)) {
  949. continue;
  950. }
  951. $seen[$literal->getPackageId()] = true;
  952. }
  953. }
  954. if ($lastWeakWhy) {
  955. array_pop($this->problems);
  956. if ($lastWeakWhy->getType() === RuleSet::TYPE_JOB) {
  957. $why = $this->ruleToJob[$lastWeakWhy];
  958. } else {
  959. $why = $lastWeakWhy;
  960. }
  961. $this->disableProblem($why);
  962. $this->resetSolver();
  963. return true;
  964. }
  965. if ($disableRules) {
  966. foreach ($this->problems[count($this->problems) - 1] as $reason) {
  967. if ($reason['job']) {
  968. $this->disableProblem($reason['job']);
  969. } else {
  970. $this->disableProblem($reason['rule']);
  971. }
  972. }
  973. $this->resetSolver();
  974. return true;
  975. }
  976. return false;
  977. }
  978. private function disableProblem($why)
  979. {
  980. if ($why instanceof Rule) {
  981. $why->disable();
  982. } else if (is_array($why)) {
  983. // disable all rules of this job
  984. foreach ($this->ruleToJob as $ruleId => $job) {
  985. if ($why === $job) {
  986. $this->rules->ruleById($ruleId)->disable();
  987. }
  988. }
  989. }
  990. }
  991. private function resetSolver()
  992. {
  993. while ($literal = array_pop($this->decisionQueue)) {
  994. $this->decisionMap[$literal->getPackageId()] = 0;
  995. }
  996. $this->decisionQueueWhy = array();
  997. $this->decisionQueueFree = array();
  998. $this->recommendsIndex = -1;
  999. $this->propagateIndex = 0;
  1000. $this->recommendations = array();
  1001. $this->branches = array();
  1002. $this->enableDisableLearnedRules();
  1003. $this->makeAssertionRuleDecisions();
  1004. }
  1005. /*-------------------------------------------------------------------
  1006. * enable/disable learnt rules
  1007. *
  1008. * we have enabled or disabled some of our rules. We now reenable all
  1009. * of our learnt rules except the ones that were learnt from rules that
  1010. * are now disabled.
  1011. */
  1012. private function enableDisableLearnedRules()
  1013. {
  1014. foreach ($this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
  1015. $why = $this->learnedWhy[$rule->getId()];
  1016. $problemRules = $this->learnedPool[$why];
  1017. $foundDisabled = false;
  1018. foreach ($problemRules as $problemRule) {
  1019. if ($problemRule->isDisabled()) {
  1020. $foundDisabled = true;
  1021. break;
  1022. }
  1023. }
  1024. if ($foundDisabled && $rule->isEnabled()) {
  1025. $rule->disable();
  1026. } else if (!$foundDisabled && $rule->isDisabled()) {
  1027. $rule->enable();
  1028. }
  1029. }
  1030. }
  1031. private function runSat($disableRules = true, $installRecommended = false)
  1032. {
  1033. $this->propagateIndex = 0;
  1034. // /*
  1035. // * here's the main loop:
  1036. // * 1) propagate new decisions (only needed once)
  1037. // * 2) fulfill jobs
  1038. // * 3) try to keep installed packages
  1039. // * 4) fulfill all unresolved rules
  1040. // * 5) install recommended packages
  1041. // * 6) minimalize solution if we had choices
  1042. // * if we encounter a problem, we rewind to a safe level and restart
  1043. // * with step 1
  1044. // */
  1045. $decisionQueue = array();
  1046. $decisionSupplementQueue = array();
  1047. $disableRules = array();
  1048. $level = 1;
  1049. $systemLevel = $level + 1;
  1050. $minimizationSteps = 0;
  1051. $installedPos = 0;
  1052. while (true) {
  1053. if (1 === $level) {
  1054. $conflictRule = $this->propagate($level);
  1055. if ($conflictRule !== null) {
  1056. if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
  1057. continue;
  1058. } else {
  1059. return;
  1060. }
  1061. }
  1062. }
  1063. // handle job rules
  1064. if ($level < $systemLevel) {
  1065. $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
  1066. foreach ($iterator as $rule) {
  1067. if ($rule->isEnabled()) {
  1068. $decisionQueue = array();
  1069. $noneSatisfied = true;
  1070. foreach ($rule->getLiterals() as $literal) {
  1071. if ($this->decisionsSatisfy($literal)) {
  1072. $noneSatisfied = false;
  1073. break;
  1074. }
  1075. $decisionQueue[] = $literal;
  1076. }
  1077. if ($noneSatisfied && count($decisionQueue)) {
  1078. // prune all update packages until installed version
  1079. // except for requested updates
  1080. if (count($this->installed) != count($this->updateMap)) {
  1081. $prunedQueue = array();
  1082. foreach ($decisionQueue as $literal) {
  1083. if (isset($this->installedMap[$literal->getPackageId()])) {
  1084. $prunedQueue[] = $literal;
  1085. if (isset($this->updateMap[$literal->getPackageId()])) {
  1086. $prunedQueue = $decisionQueue;
  1087. break;
  1088. }
  1089. }
  1090. }
  1091. $decisionQueue = $prunedQueue;
  1092. }
  1093. }
  1094. if ($noneSatisfied && count($decisionQueue)) {
  1095. $oLevel = $level;
  1096. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  1097. if (0 === $level) {
  1098. return;
  1099. }
  1100. if ($level <= $oLevel) {
  1101. break;
  1102. }
  1103. }
  1104. }
  1105. }
  1106. $systemLevel = $level + 1;
  1107. // jobs left
  1108. $iterator->next();
  1109. if ($iterator->valid()) {
  1110. continue;
  1111. }
  1112. }
  1113. if ($level < $systemLevel) {
  1114. $systemLevel = $level;
  1115. }
  1116. for ($i = 0, $n = 0; $n < count($this->rules); $i++, $n++) {
  1117. if ($i == count($this->rules)) {
  1118. $i = 0;
  1119. }
  1120. $rule = $this->rules->ruleById($i);
  1121. $literals = $rule->getLiterals();
  1122. if ($rule->isDisabled()) {
  1123. continue;
  1124. }
  1125. $decisionQueue = array();
  1126. // make sure that
  1127. // * all negative literals are installed
  1128. // * no positive literal is installed
  1129. // i.e. the rule is not fulfilled and we
  1130. // just need to decide on the positive literals
  1131. //
  1132. foreach ($literals as $literal) {
  1133. if (!$literal->isWanted()) {
  1134. if (!$this->decidedInstall($literal->getPackage())) {
  1135. continue 2; // next rule
  1136. }
  1137. } else {
  1138. if ($this->decidedInstall($literal->getPackage())) {
  1139. continue 2; // next rule
  1140. }
  1141. if ($this->undecided($literal->getPackage())) {
  1142. $decisionQueue[] = $literal;
  1143. }
  1144. }
  1145. }
  1146. // need to have at least 2 item to pick from
  1147. if (count($decisionQueue) < 2) {
  1148. continue;
  1149. }
  1150. $oLevel = $level;
  1151. $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);
  1152. if (0 === $level) {
  1153. return;
  1154. }
  1155. // open suse sat-solver uses this, but why is $level == 1 trouble?
  1156. // SYSTEMSOLVABLE related? we don't have that, so should work
  1157. //if ($level < $systemLevel || $level == 1) {
  1158. if ($level < $systemLevel) {
  1159. break; // trouble
  1160. }
  1161. // something changed, so look at all rules again
  1162. $n = -1;
  1163. }
  1164. // minimization step
  1165. if (count($this->branches)) {
  1166. $lastLiteral = null;
  1167. $lastLevel = null;
  1168. $lastBranchIndex = 0;
  1169. $lastBranchOffset = 0;
  1170. for ($i = count($this->branches) - 1; $i >= 0; $i--) {
  1171. list($literals, $level) = $this->branches[$i];
  1172. foreach ($literals as $offset => $literal) {
  1173. if ($literal && $literal->isWanted() && $this->decisionMap[$literal->getPackageId()] > $level + 1) {
  1174. $lastLiteral = $literal;
  1175. $lastBranchIndex = $i;
  1176. $lastBranchOffset = $offset;
  1177. $lastLevel = $level;
  1178. }
  1179. }
  1180. }
  1181. if ($lastLiteral) {
  1182. $this->branches[$lastBranchIndex][$lastBranchOffset] = null;
  1183. $minimizationSteps++;
  1184. $level = $lastLevel;
  1185. $this->revert($level);
  1186. $why = $this->decisionQueueWhy[count($this->decisionQueueWhy) - 1];
  1187. $oLevel = $level;
  1188. $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);
  1189. if ($level == 0) {
  1190. return;
  1191. }
  1192. continue;
  1193. }
  1194. }
  1195. break;
  1196. }
  1197. }
  1198. private function printDecisionMap()
  1199. {
  1200. echo "\nDecisionMap: \n";
  1201. foreach ($this->decisionMap as $packageId => $level) {
  1202. if ($packageId === 0) {
  1203. continue;
  1204. }
  1205. if ($level > 0) {
  1206. echo ' +' . $this->pool->packageById($packageId)."\n";
  1207. } elseif ($level < 0) {
  1208. echo ' -' . $this->pool->packageById($packageId)."\n";
  1209. } else {
  1210. echo ' ?' . $this->pool->packageById($packageId)."\n";
  1211. }
  1212. }
  1213. echo "\n";
  1214. }
  1215. private function printDecisionQueue()
  1216. {
  1217. echo "DecisionQueue: \n";
  1218. foreach ($this->decisionQueue as $i => $literal) {
  1219. echo ' ' . $literal . ' ' . $this->decisionQueueWhy[$i]." level ".$this->decisionMap[$literal->getPackageId()]."\n";
  1220. }
  1221. echo "\n";
  1222. }
  1223. private function printWatches()
  1224. {
  1225. echo "\nWatches:\n";
  1226. foreach ($this->watches as $literalId => $watch) {
  1227. echo ' '.$this->literalFromId($literalId)."\n";
  1228. $queue = array(array(' ', $watch));
  1229. while (!empty($queue)) {
  1230. list($indent, $watch) = array_pop($queue);
  1231. echo $indent.$watch;
  1232. if ($watch) {
  1233. echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]";
  1234. }
  1235. echo "\n";
  1236. if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) {
  1237. if ($watch->next1 == $watch) {
  1238. echo $indent." 1 *RECURSION*";
  1239. }
  1240. if ($watch->next2 == $watch) {
  1241. echo $indent." 2 *RECURSION*";
  1242. }
  1243. } elseif ($watch && ($watch->next1 || $watch->next2)) {
  1244. $indent = str_replace(array('1', '2'), ' ', $indent);
  1245. array_push($queue, array($indent.' 2 ', $watch->next2));
  1246. array_push($queue, array($indent.' 1 ', $watch->next1));
  1247. }
  1248. }
  1249. echo "\n";
  1250. }
  1251. }
  1252. }