1 // SPDX-License-Identifier: MPL-2.0 2 3 //! A Memory acts like a structured partial solution 4 //! where terms are regrouped by package in a [Map](crate::type_aliases::Map). 5 6 use crate::internal::arena::Arena; 7 use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; 8 use crate::internal::small_map::SmallMap; 9 use crate::package::Package; 10 use crate::range::Range; 11 use crate::term::Term; 12 use crate::type_aliases::{Map, SelectedDependencies}; 13 use crate::version::Version; 14 15 use super::small_vec::SmallVec; 16 17 #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 18 pub struct DecisionLevel(pub u32); 19 20 impl DecisionLevel { increment(self) -> Self21 pub fn increment(self) -> Self { 22 Self(self.0 + 1) 23 } 24 } 25 26 /// The partial solution contains all package assignments, 27 /// organized by package and historically ordered. 28 #[derive(Clone, Debug)] 29 pub struct PartialSolution<P: Package, V: Version> { 30 next_global_index: u32, 31 current_decision_level: DecisionLevel, 32 package_assignments: Map<P, PackageAssignments<P, V>>, 33 } 34 35 /// Package assignments contain the potential decision and derivations 36 /// that have already been made for a given package, 37 /// as well as the intersection of terms by all of these. 38 #[derive(Clone, Debug)] 39 struct PackageAssignments<P: Package, V: Version> { 40 smallest_decision_level: DecisionLevel, 41 highest_decision_level: DecisionLevel, 42 dated_derivations: SmallVec<DatedDerivation<P, V>>, 43 assignments_intersection: AssignmentsIntersection<V>, 44 } 45 46 #[derive(Clone, Debug)] 47 pub struct DatedDerivation<P: Package, V: Version> { 48 global_index: u32, 49 decision_level: DecisionLevel, 50 cause: IncompId<P, V>, 51 } 52 53 #[derive(Clone, Debug)] 54 enum AssignmentsIntersection<V: Version> { 55 Decision((u32, V, Term<V>)), 56 Derivations(Term<V>), 57 } 58 59 #[derive(Clone, Debug)] 60 pub enum SatisfierSearch<P: Package, V: Version> { 61 DifferentDecisionLevels { 62 previous_satisfier_level: DecisionLevel, 63 }, 64 SameDecisionLevels { 65 satisfier_cause: IncompId<P, V>, 66 }, 67 } 68 69 impl<P: Package, V: Version> PartialSolution<P, V> { 70 /// Initialize an empty PartialSolution. empty() -> Self71 pub fn empty() -> Self { 72 Self { 73 next_global_index: 0, 74 current_decision_level: DecisionLevel(0), 75 package_assignments: Map::default(), 76 } 77 } 78 79 /// Add a decision. add_decision(&mut self, package: P, version: V)80 pub fn add_decision(&mut self, package: P, version: V) { 81 // Check that add_decision is never used in the wrong context. 82 if cfg!(debug_assertions) { 83 match self.package_assignments.get_mut(&package) { 84 None => panic!("Derivations must already exist"), 85 Some(pa) => match &pa.assignments_intersection { 86 // Cannot be called when a decision has already been taken. 87 AssignmentsIntersection::Decision(_) => panic!("Already existing decision"), 88 // Cannot be called if the versions is not contained in the terms intersection. 89 AssignmentsIntersection::Derivations(term) => { 90 debug_assert!(term.contains(&version)) 91 } 92 }, 93 } 94 } 95 self.current_decision_level = self.current_decision_level.increment(); 96 let mut pa = self 97 .package_assignments 98 .get_mut(&package) 99 .expect("Derivations must already exist"); 100 pa.highest_decision_level = self.current_decision_level; 101 pa.assignments_intersection = AssignmentsIntersection::Decision(( 102 self.next_global_index, 103 version.clone(), 104 Term::exact(version), 105 )); 106 self.next_global_index += 1; 107 } 108 109 /// Add a derivation. add_derivation( &mut self, package: P, cause: IncompId<P, V>, store: &Arena<Incompatibility<P, V>>, )110 pub fn add_derivation( 111 &mut self, 112 package: P, 113 cause: IncompId<P, V>, 114 store: &Arena<Incompatibility<P, V>>, 115 ) { 116 use std::collections::hash_map::Entry; 117 let term = store[cause].get(&package).unwrap().negate(); 118 let dated_derivation = DatedDerivation { 119 global_index: self.next_global_index, 120 decision_level: self.current_decision_level, 121 cause, 122 }; 123 self.next_global_index += 1; 124 match self.package_assignments.entry(package) { 125 Entry::Occupied(mut occupied) => { 126 let mut pa = occupied.get_mut(); 127 pa.highest_decision_level = self.current_decision_level; 128 match &mut pa.assignments_intersection { 129 // Check that add_derivation is never called in the wrong context. 130 AssignmentsIntersection::Decision(_) => { 131 panic!("add_derivation should not be called after a decision") 132 } 133 AssignmentsIntersection::Derivations(t) => { 134 *t = t.intersection(&term); 135 } 136 } 137 pa.dated_derivations.push(dated_derivation); 138 } 139 Entry::Vacant(v) => { 140 v.insert(PackageAssignments { 141 smallest_decision_level: self.current_decision_level, 142 highest_decision_level: self.current_decision_level, 143 dated_derivations: SmallVec::One([dated_derivation]), 144 assignments_intersection: AssignmentsIntersection::Derivations(term), 145 }); 146 } 147 } 148 } 149 150 /// Extract potential packages for the next iteration of unit propagation. 151 /// Return `None` if there is no suitable package anymore, which stops the algorithm. 152 /// A package is a potential pick if there isn't an already 153 /// selected version (no "decision") 154 /// and if it contains at least one positive derivation term 155 /// in the partial solution. potential_packages(&self) -> Option<impl Iterator<Item = (&P, &Range<V>)>>156 pub fn potential_packages(&self) -> Option<impl Iterator<Item = (&P, &Range<V>)>> { 157 let mut iter = self 158 .package_assignments 159 .iter() 160 .filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p)) 161 .peekable(); 162 if iter.peek().is_some() { 163 Some(iter) 164 } else { 165 None 166 } 167 } 168 169 /// If a partial solution has, for every positive derivation, 170 /// a corresponding decision that satisfies that assignment, 171 /// it's a total solution and version solving has succeeded. extract_solution(&self) -> Option<SelectedDependencies<P, V>>172 pub fn extract_solution(&self) -> Option<SelectedDependencies<P, V>> { 173 let mut solution = Map::default(); 174 for (p, pa) in &self.package_assignments { 175 match &pa.assignments_intersection { 176 AssignmentsIntersection::Decision((_, v, _)) => { 177 solution.insert(p.clone(), v.clone()); 178 } 179 AssignmentsIntersection::Derivations(term) => { 180 if term.is_positive() { 181 return None; 182 } 183 } 184 } 185 } 186 Some(solution) 187 } 188 189 /// Backtrack the partial solution to a given decision level. backtrack( &mut self, decision_level: DecisionLevel, store: &Arena<Incompatibility<P, V>>, )190 pub fn backtrack( 191 &mut self, 192 decision_level: DecisionLevel, 193 store: &Arena<Incompatibility<P, V>>, 194 ) { 195 self.current_decision_level = decision_level; 196 self.package_assignments.retain(|p, pa| { 197 if pa.smallest_decision_level > decision_level { 198 // Remove all entries that have a smallest decision level higher than the backtrack target. 199 false 200 } else if pa.highest_decision_level <= decision_level { 201 // Do not change entries older than the backtrack decision level target. 202 true 203 } else { 204 // smallest_decision_level <= decision_level < highest_decision_level 205 // 206 // Since decision_level < highest_decision_level, 207 // We can be certain that there will be no decision in this package assignments 208 // after backtracking, because such decision would have been the last 209 // assignment and it would have the "highest_decision_level". 210 211 // Truncate the history. 212 while pa.dated_derivations.last().map(|dd| dd.decision_level) > Some(decision_level) 213 { 214 pa.dated_derivations.pop(); 215 } 216 debug_assert!(!pa.dated_derivations.is_empty()); 217 218 // Update highest_decision_level. 219 pa.highest_decision_level = pa.dated_derivations.last().unwrap().decision_level; 220 221 // Recompute the assignments intersection. 222 pa.assignments_intersection = AssignmentsIntersection::Derivations( 223 pa.dated_derivations 224 .iter() 225 .fold(Term::any(), |acc, dated_derivation| { 226 let term = store[dated_derivation.cause].get(&p).unwrap().negate(); 227 acc.intersection(&term) 228 }), 229 ); 230 true 231 } 232 }); 233 } 234 235 /// We can add the version to the partial solution as a decision 236 /// if it doesn't produce any conflict with the new incompatibilities. 237 /// In practice I think it can only produce a conflict if one of the dependencies 238 /// (which are used to make the new incompatibilities) 239 /// is already in the partial solution with an incompatible version. add_version( &mut self, package: P, version: V, new_incompatibilities: std::ops::Range<IncompId<P, V>>, store: &Arena<Incompatibility<P, V>>, )240 pub fn add_version( 241 &mut self, 242 package: P, 243 version: V, 244 new_incompatibilities: std::ops::Range<IncompId<P, V>>, 245 store: &Arena<Incompatibility<P, V>>, 246 ) { 247 let exact = Term::exact(version.clone()); 248 let not_satisfied = |incompat: &Incompatibility<P, V>| { 249 incompat.relation(|p| { 250 if p == &package { 251 Some(&exact) 252 } else { 253 self.term_intersection_for_package(p) 254 } 255 }) != Relation::Satisfied 256 }; 257 258 // Check none of the dependencies (new_incompatibilities) 259 // would create a conflict (be satisfied). 260 if store[new_incompatibilities].iter().all(not_satisfied) { 261 self.add_decision(package, version); 262 } 263 } 264 265 /// Check if the terms in the partial solution satisfy the incompatibility. relation(&self, incompat: &Incompatibility<P, V>) -> Relation<P>266 pub fn relation(&self, incompat: &Incompatibility<P, V>) -> Relation<P> { 267 incompat.relation(|package| self.term_intersection_for_package(package)) 268 } 269 270 /// Retrieve intersection of terms related to package. term_intersection_for_package(&self, package: &P) -> Option<&Term<V>>271 pub fn term_intersection_for_package(&self, package: &P) -> Option<&Term<V>> { 272 self.package_assignments 273 .get(package) 274 .map(|pa| pa.assignments_intersection.term()) 275 } 276 277 /// Figure out if the satisfier and previous satisfier are of different decision levels. satisfier_search( &self, incompat: &Incompatibility<P, V>, store: &Arena<Incompatibility<P, V>>, ) -> (P, SatisfierSearch<P, V>)278 pub fn satisfier_search( 279 &self, 280 incompat: &Incompatibility<P, V>, 281 store: &Arena<Incompatibility<P, V>>, 282 ) -> (P, SatisfierSearch<P, V>) { 283 let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store); 284 let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map 285 .iter() 286 .max_by_key(|(_p, (_, global_index, _))| global_index) 287 .unwrap(); 288 let satisfier_package = satisfier_package.clone(); 289 let previous_satisfier_level = Self::find_previous_satisfier( 290 incompat, 291 &satisfier_package, 292 satisfied_map, 293 &self.package_assignments, 294 store, 295 ); 296 if previous_satisfier_level < satisfier_decision_level { 297 let search_result = SatisfierSearch::DifferentDecisionLevels { 298 previous_satisfier_level, 299 }; 300 (satisfier_package, search_result) 301 } else { 302 let satisfier_pa = self.package_assignments.get(&satisfier_package).unwrap(); 303 let dd = &satisfier_pa.dated_derivations[satisfier_index]; 304 let search_result = SatisfierSearch::SameDecisionLevels { 305 satisfier_cause: dd.cause, 306 }; 307 (satisfier_package, search_result) 308 } 309 } 310 311 /// A satisfier is the earliest assignment in partial solution such that the incompatibility 312 /// is satisfied by the partial solution up to and including that assignment. 313 /// 314 /// Returns a map indicating for each package term, when that was first satisfied in history. 315 /// If we effectively found a satisfier, the returned map must be the same size that incompat. 316 /// 317 /// Question: This is possible since we added a "global_index" to every dated_derivation. 318 /// It would be nice if we could get rid of it, but I don't know if then it will be possible 319 /// to return a coherent previous_satisfier_level. find_satisfier( incompat: &Incompatibility<P, V>, package_assignments: &Map<P, PackageAssignments<P, V>>, store: &Arena<Incompatibility<P, V>>, ) -> SmallMap<P, (usize, u32, DecisionLevel)>320 fn find_satisfier( 321 incompat: &Incompatibility<P, V>, 322 package_assignments: &Map<P, PackageAssignments<P, V>>, 323 store: &Arena<Incompatibility<P, V>>, 324 ) -> SmallMap<P, (usize, u32, DecisionLevel)> { 325 let mut satisfied = SmallMap::Empty; 326 for (package, incompat_term) in incompat.iter() { 327 let pa = package_assignments.get(package).expect("Must exist"); 328 satisfied.insert( 329 package.clone(), 330 pa.satisfier(package, incompat_term, Term::any(), store), 331 ); 332 } 333 satisfied 334 } 335 336 /// Earliest assignment in the partial solution before satisfier 337 /// such that incompatibility is satisfied by the partial solution up to 338 /// and including that assignment plus satisfier. find_previous_satisfier( incompat: &Incompatibility<P, V>, satisfier_package: &P, mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>, package_assignments: &Map<P, PackageAssignments<P, V>>, store: &Arena<Incompatibility<P, V>>, ) -> DecisionLevel339 fn find_previous_satisfier( 340 incompat: &Incompatibility<P, V>, 341 satisfier_package: &P, 342 mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>, 343 package_assignments: &Map<P, PackageAssignments<P, V>>, 344 store: &Arena<Incompatibility<P, V>>, 345 ) -> DecisionLevel { 346 // First, let's retrieve the previous derivations and the initial accum_term. 347 let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); 348 let (satisfier_index, _gidx, _dl) = satisfied_map.get_mut(satisfier_package).unwrap(); 349 350 let accum_term = if *satisfier_index == satisfier_pa.dated_derivations.len() { 351 match &satisfier_pa.assignments_intersection { 352 AssignmentsIntersection::Derivations(_) => panic!("must be a decision"), 353 AssignmentsIntersection::Decision((_, _, term)) => term.clone(), 354 } 355 } else { 356 let dd = &satisfier_pa.dated_derivations[*satisfier_index]; 357 store[dd.cause].get(satisfier_package).unwrap().negate() 358 }; 359 360 let incompat_term = incompat 361 .get(satisfier_package) 362 .expect("satisfier package not in incompat"); 363 364 satisfied_map.insert( 365 satisfier_package.clone(), 366 satisfier_pa.satisfier(satisfier_package, incompat_term, accum_term, store), 367 ); 368 369 // Finally, let's identify the decision level of that previous satisfier. 370 let (_, &(_, _, decision_level)) = satisfied_map 371 .iter() 372 .max_by_key(|(_p, (_, global_index, _))| global_index) 373 .unwrap(); 374 decision_level.max(DecisionLevel(1)) 375 } 376 } 377 378 impl<P: Package, V: Version> PackageAssignments<P, V> { satisfier( &self, package: &P, incompat_term: &Term<V>, start_term: Term<V>, store: &Arena<Incompatibility<P, V>>, ) -> (usize, u32, DecisionLevel)379 fn satisfier( 380 &self, 381 package: &P, 382 incompat_term: &Term<V>, 383 start_term: Term<V>, 384 store: &Arena<Incompatibility<P, V>>, 385 ) -> (usize, u32, DecisionLevel) { 386 // Term where we accumulate intersections until incompat_term is satisfied. 387 let mut accum_term = start_term; 388 // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. 389 for (idx, dated_derivation) in self.dated_derivations.iter().enumerate() { 390 let this_term = store[dated_derivation.cause].get(package).unwrap().negate(); 391 accum_term = accum_term.intersection(&this_term); 392 if accum_term.subset_of(incompat_term) { 393 // We found the derivation causing satisfaction. 394 return ( 395 idx, 396 dated_derivation.global_index, 397 dated_derivation.decision_level, 398 ); 399 } 400 } 401 // If it wasn't found in the derivations, 402 // it must be the decision which is last (if called in the right context). 403 match self.assignments_intersection { 404 AssignmentsIntersection::Decision((global_index, _, _)) => ( 405 self.dated_derivations.len(), 406 global_index, 407 self.highest_decision_level, 408 ), 409 AssignmentsIntersection::Derivations(_) => { 410 panic!("This must be a decision") 411 } 412 } 413 } 414 } 415 416 impl<V: Version> AssignmentsIntersection<V> { 417 /// Returns the term intersection of all assignments (decision included). term(&self) -> &Term<V>418 fn term(&self) -> &Term<V> { 419 match self { 420 Self::Decision((_, _, term)) => term, 421 Self::Derivations(term) => term, 422 } 423 } 424 425 /// A package is a potential pick if there isn't an already 426 /// selected version (no "decision") 427 /// and if it contains at least one positive derivation term 428 /// in the partial solution. potential_package_filter<'a, P: Package>( &'a self, package: &'a P, ) -> Option<(&'a P, &'a Range<V>)>429 fn potential_package_filter<'a, P: Package>( 430 &'a self, 431 package: &'a P, 432 ) -> Option<(&'a P, &'a Range<V>)> { 433 match self { 434 Self::Decision(_) => None, 435 Self::Derivations(term_intersection) => { 436 if term_intersection.is_positive() { 437 Some((package, term_intersection.unwrap_positive())) 438 } else { 439 None 440 } 441 } 442 } 443 } 444 } 445