aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-sat-resolver/src/decisions.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
committernsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
commit8cc1ba8a02c0318b65658f1634de378c780392b9 (patch)
treefdd5cb61e488018891a486b25991b87c84220bb8 /crates/mozart-sat-resolver/src/decisions.rs
parent72b2e877c01e67ba7edd37e34ac2eadb7a1c62c4 (diff)
downloadphp-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.gz
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.zst
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.zip
refactor(workspace): consolidate crates into mozart-core
Merged mozart-archiver, mozart-autoload, mozart-registry, mozart-sat-resolver, and mozart-vcs into mozart-core to align the source layout with Composer's structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-sat-resolver/src/decisions.rs')
-rw-r--r--crates/mozart-sat-resolver/src/decisions.rs263
1 files changed, 0 insertions, 263 deletions
diff --git a/crates/mozart-sat-resolver/src/decisions.rs b/crates/mozart-sat-resolver/src/decisions.rs
deleted file mode 100644
index e9cc935..0000000
--- a/crates/mozart-sat-resolver/src/decisions.rs
+++ /dev/null
@@ -1,263 +0,0 @@
-use crate::error::SolverBugError;
-use crate::pool::{Literal, PackageId, literal_to_package_id};
-use crate::rule_set::RuleId;
-use indexmap::IndexMap;
-
-/// A decision entry: which literal was decided and which rule caused it.
-#[derive(Debug, Clone)]
-pub struct Decision {
- pub literal: Literal,
- pub rule_id: RuleId,
-}
-
-/// Tracks all decisions (variable assignments) made during solving.
-///
-/// Port of Composer's Decisions.php.
-pub struct Decisions {
- /// Package ID → signed level. Positive = install, negative = uninstall.
- /// The absolute value is the decision level.
- decision_map: IndexMap<PackageId, i32>,
- /// Queue of decisions in order.
- decision_queue: Vec<Decision>,
-}
-
-impl Decisions {
- pub fn new() -> Self {
- Decisions {
- decision_map: IndexMap::new(),
- decision_queue: Vec::new(),
- }
- }
-
- /// Record a decision.
- pub fn decide(
- &mut self,
- literal: Literal,
- level: i32,
- rule_id: RuleId,
- ) -> Result<(), SolverBugError> {
- let package_id = literal_to_package_id(literal);
- let previous = self.decision_map.get(&package_id).copied().unwrap_or(0);
- if previous != 0 {
- return Err(SolverBugError {
- message: format!(
- "Trying to decide literal {literal} on level {level}, \
- even though package {package_id} was previously decided as {previous}."
- ),
- });
- }
-
- if literal > 0 {
- self.decision_map.insert(package_id, level);
- } else {
- self.decision_map.insert(package_id, -level);
- }
-
- self.decision_queue.push(Decision { literal, rule_id });
- Ok(())
- }
-
- /// Check if literal is satisfied (true in current assignment).
- pub fn satisfy(&self, literal: Literal) -> bool {
- let package_id = literal_to_package_id(literal);
- match self.decision_map.get(&package_id) {
- Some(&val) => (literal > 0 && val > 0) || (literal < 0 && val < 0),
- None => false,
- }
- }
-
- /// Check if literal conflicts with current assignment.
- pub fn conflict(&self, literal: Literal) -> bool {
- let package_id = literal_to_package_id(literal);
- match self.decision_map.get(&package_id) {
- Some(&val) => (val > 0 && literal < 0) || (val < 0 && literal > 0),
- None => false,
- }
- }
-
- /// Check if package has been decided.
- pub fn decided(&self, literal_or_id: i32) -> bool {
- let package_id = literal_or_id.unsigned_abs();
- self.decision_map.get(&package_id).copied().unwrap_or(0) != 0
- }
-
- /// Check if package is undecided.
- pub fn undecided(&self, literal_or_id: i32) -> bool {
- !self.decided(literal_or_id)
- }
-
- /// Check if package is decided for installation.
- pub fn decided_install(&self, literal_or_id: i32) -> bool {
- let package_id = literal_or_id.unsigned_abs();
- self.decision_map.get(&package_id).copied().unwrap_or(0) > 0
- }
-
- /// Get the decision level for a package (0 if undecided).
- pub fn decision_level(&self, literal_or_id: i32) -> i32 {
- let package_id = literal_or_id.unsigned_abs();
- self.decision_map
- .get(&package_id)
- .copied()
- .unwrap_or(0)
- .abs()
- }
-
- /// Get the rule ID that caused a decision for a package.
- pub fn decision_rule(&self, literal_or_id: i32) -> Result<RuleId, SolverBugError> {
- let package_id = literal_or_id.unsigned_abs();
- for decision in &self.decision_queue {
- if literal_to_package_id(decision.literal) == package_id {
- return Ok(decision.rule_id);
- }
- }
- Err(SolverBugError {
- message: format!("Did not find a decision rule for {literal_or_id}"),
- })
- }
-
- /// Get decision at a specific offset in the queue.
- pub fn at_offset(&self, offset: usize) -> &Decision {
- &self.decision_queue[offset]
- }
-
- /// Check if an offset is valid.
- pub fn valid_offset(&self, offset: usize) -> bool {
- offset < self.decision_queue.len()
- }
-
- /// Get the rule ID of the last decision.
- pub fn last_reason(&self) -> RuleId {
- self.decision_queue.last().unwrap().rule_id
- }
-
- /// Get the literal of the last decision.
- pub fn last_literal(&self) -> Literal {
- self.decision_queue.last().unwrap().literal
- }
-
- /// Clear all decisions.
- pub fn reset(&mut self) {
- while let Some(decision) = self.decision_queue.pop() {
- let pkg_id = literal_to_package_id(decision.literal);
- self.decision_map.insert(pkg_id, 0);
- }
- }
-
- /// Remove decisions after the given offset (keep offset+1 items).
- pub fn reset_to_offset(&mut self, offset: usize) {
- while self.decision_queue.len() > offset + 1 {
- let decision = self.decision_queue.pop().unwrap();
- let pkg_id = literal_to_package_id(decision.literal);
- self.decision_map.insert(pkg_id, 0);
- }
- }
-
- /// Remove the last decision.
- pub fn revert_last(&mut self) {
- let decision = self.decision_queue.pop().unwrap();
- let pkg_id = literal_to_package_id(decision.literal);
- self.decision_map.insert(pkg_id, 0);
- }
-
- /// Number of decisions.
- pub fn len(&self) -> usize {
- self.decision_queue.len()
- }
-
- /// Whether there are no decisions.
- pub fn is_empty(&self) -> bool {
- self.decision_queue.is_empty()
- }
-
- /// Iterate decisions in reverse order (newest first).
- /// Used by analyzeUnsolvable in Composer.
- pub fn iter_reverse(&self) -> impl Iterator<Item = &Decision> {
- self.decision_queue.iter().rev()
- }
-}
-
-impl Default for Decisions {
- fn default() -> Self {
- Self::new()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_decide_and_satisfy() {
- let mut d = Decisions::new();
- d.decide(1, 1, 0).unwrap(); // install package 1 at level 1
-
- assert!(d.satisfy(1));
- assert!(!d.satisfy(-1));
- assert!(d.conflict(-1));
- assert!(!d.conflict(1));
- assert!(d.decided(1));
- assert!(d.decided_install(1));
- }
-
- #[test]
- fn test_decide_negative() {
- let mut d = Decisions::new();
- d.decide(-1, 1, 0).unwrap(); // don't install package 1
-
- assert!(d.satisfy(-1));
- assert!(!d.satisfy(1));
- assert!(d.conflict(1));
- assert!(d.decided(1));
- assert!(!d.decided_install(1));
- }
-
- #[test]
- fn test_undecided() {
- let d = Decisions::new();
- assert!(d.undecided(1));
- assert!(!d.decided(1));
- assert!(!d.satisfy(1));
- assert!(!d.conflict(1));
- }
-
- #[test]
- fn test_revert_last() {
- let mut d = Decisions::new();
- d.decide(1, 1, 0).unwrap();
- d.decide(2, 2, 1).unwrap();
-
- assert!(d.decided(2));
- d.revert_last();
- assert!(d.undecided(2));
- assert!(d.decided(1));
- }
-
- #[test]
- fn test_reset_to_offset() {
- let mut d = Decisions::new();
- d.decide(1, 1, 0).unwrap();
- d.decide(2, 2, 1).unwrap();
- d.decide(3, 3, 2).unwrap();
-
- d.reset_to_offset(0); // keep only first decision
- assert_eq!(d.len(), 1);
- assert!(d.decided(1));
- assert!(d.undecided(2));
- assert!(d.undecided(3));
- }
-
- #[test]
- fn test_double_decide_error() {
- let mut d = Decisions::new();
- d.decide(1, 1, 0).unwrap();
- assert!(d.decide(1, 2, 1).is_err());
- }
-
- #[test]
- fn test_decision_level() {
- let mut d = Decisions::new();
- d.decide(1, 3, 0).unwrap();
- assert_eq!(d.decision_level(1), 3);
- assert_eq!(d.decision_level(2), 0); // undecided
- }
-}