Skip to content

Commit 219515a

Browse files
committed
Simplify updating priorities
1 parent 83c0b98 commit 219515a

File tree

2 files changed

+70
-52
lines changed

2 files changed

+70
-52
lines changed

src/internal/partial_solution.rs

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::internal::{
1717
use crate::{DependencyProvider, Package, Term, VersionSet};
1818

1919
type FnvIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
20+
type FnvIndexSet<T> = indexmap::IndexSet<T, BuildHasherDefault<FxHasher>>;
2021

2122
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
2223
pub(crate) struct DecisionLevel(pub(crate) u32);
@@ -63,8 +64,6 @@ pub struct PartialSolution<DP: DependencyProvider> {
6364
/// range.
6465
#[allow(clippy::type_complexity)]
6566
package_assignments: FnvIndexMap<Id<DP::P>, PackageAssignments<DP::P, DP::VS, DP::M>>,
66-
/// Index into `package_assignments` to decide which packages need to be re-prioritized.
67-
prioritize_decision_level: usize,
6867
/// The undecided packages order by their `Priority`.
6968
///
7069
/// The max heap allows quickly `pop`ing the highest priority package.
@@ -74,6 +73,9 @@ pub struct PartialSolution<DP: DependencyProvider> {
7473
#[allow(clippy::type_complexity)]
7574
prioritized_potential_packages:
7675
PriorityQueue<Id<DP::P>, (DP::Priority, Reverse<u32>), BuildHasherDefault<FxHasher>>,
76+
/// Contains all packages that **have** had there assignments changed since
77+
/// the last time `prioritize` has been called on them.
78+
unprioritized_potential_packages: FnvIndexSet<Id<DP::P>>,
7779
/// Whether we have never backtracked, to enable fast path optimizations.
7880
has_ever_backtracked: bool,
7981
}
@@ -181,7 +183,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
181183
current_decision_level: DecisionLevel(0),
182184
package_assignments: FnvIndexMap::default(),
183185
prioritized_potential_packages: PriorityQueue::default(),
184-
prioritize_decision_level: 0,
186+
unprioritized_potential_packages: FnvIndexSet::default(),
185187
has_ever_backtracked: false,
186188
}
187189
}
@@ -234,10 +236,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
234236
}
235237
},
236238
}
237-
assert_eq!(
238-
self.prioritize_decision_level,
239-
self.package_assignments.len()
240-
);
241239
}
242240
let new_idx = self.current_decision_level.0 as usize;
243241
self.current_decision_level = self.current_decision_level.increment();
@@ -288,10 +286,8 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
288286
accumulated_intersection: store[cause].get(package).unwrap().negate(),
289287
};
290288
self.next_global_index += 1;
291-
let pa_last_index = self.package_assignments.len().saturating_sub(1);
292289
match self.package_assignments.entry(package) {
293290
Entry::Occupied(mut occupied) => {
294-
let idx = occupied.index();
295291
let pa = occupied.get_mut();
296292
pa.highest_decision_level = self.current_decision_level;
297293
match &mut pa.assignments_intersection {
@@ -303,10 +299,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
303299
*t = t.intersection(&dated_derivation.accumulated_intersection);
304300
dated_derivation.accumulated_intersection = t.clone();
305301
if t.is_positive() {
306-
// we can use `swap_indices` to make `prioritize_decision_level` only go down by 1
307-
// but the copying is slower then the larger search
308-
self.prioritize_decision_level =
309-
std::cmp::min(self.prioritize_decision_level, idx);
302+
self.unprioritized_potential_packages.insert(package);
310303
}
311304
}
312305
}
@@ -315,8 +308,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
315308
Entry::Vacant(v) => {
316309
let term = dated_derivation.accumulated_intersection.clone();
317310
if term.is_positive() {
318-
self.prioritize_decision_level =
319-
std::cmp::min(self.prioritize_decision_level, pa_last_index);
311+
self.unprioritized_potential_packages.insert(package);
320312
}
321313
v.insert(PackageAssignments {
322314
smallest_decision_level: self.current_decision_level,
@@ -330,50 +322,62 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
330322

331323
#[cold]
332324
pub fn prioritized_packages(&self) -> impl Iterator<Item = (Id<DP::P>, &DP::VS)> {
333-
let check_all = self.prioritize_decision_level
334-
== self.current_decision_level.0.saturating_sub(1) as usize;
335325
let current_decision_level = self.current_decision_level;
336326
self.package_assignments
337-
.get_range(self.prioritize_decision_level..)
327+
.get_range(current_decision_level.0 as usize..)
338328
.unwrap()
339329
.iter()
340-
.filter(move |(_, pa)| {
341-
// We only actually need to update the package if its Been changed
342-
// since the last time we called prioritize.
343-
// Which means it's highest decision level is the current decision level,
344-
// or if we backtracked in the mean time.
345-
check_all || pa.highest_decision_level == current_decision_level
330+
.filter_map(|(&p, pa)| {
331+
Some((p, pa.assignments_intersection.potential_package_filter()?))
346332
})
347-
.filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p))
348333
}
349334

350335
#[cold]
351336
pub fn pick_highest_priority_pkg(
352337
&mut self,
353338
mut prioritizer: impl FnMut(Id<DP::P>, &DP::VS) -> DP::Priority,
354339
) -> Option<Id<DP::P>> {
355-
let check_all = self.prioritize_decision_level
356-
== self.current_decision_level.0.saturating_sub(1) as usize;
357-
let current_decision_level = self.current_decision_level;
358340
let prioritized_potential_packages = &mut self.prioritized_potential_packages;
359-
self.package_assignments
360-
.get_range(self.prioritize_decision_level..)
361-
.unwrap()
362-
.iter()
363-
.filter(|(_, pa)| {
364-
// We only actually need to update the package if it has been changed
365-
// since the last time we called prioritize.
366-
// Which means it's highest decision level is the current decision level,
367-
// or if we backtracked in the meantime.
368-
check_all || pa.highest_decision_level == current_decision_level
341+
while let Some(p) = self.unprioritized_potential_packages.pop() {
342+
let Some(pa) = self.package_assignments.get(&p) else {
343+
continue;
344+
};
345+
let Some(r) = pa.assignments_intersection.potential_package_filter() else {
346+
continue;
347+
};
348+
let priority = prioritizer(p, r);
349+
prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32)));
350+
}
351+
while let Some(p) = self.prioritized_potential_packages.pop().map(|(p, _)| p) {
352+
if self
353+
.package_assignments
354+
.get(&p)
355+
.and_then(|pa| pa.assignments_intersection.potential_package_filter())
356+
.is_some()
357+
{
358+
return Some(p);
359+
}
360+
}
361+
None
362+
}
363+
364+
/// Manually update a package priority that changed independent of the range.
365+
pub fn update_priority(&mut self, package: Id<DP::P>, priority: DP::Priority) {
366+
if self
367+
.package_assignments
368+
.get(&package)
369+
.and_then(|assignment| {
370+
assignment
371+
.assignments_intersection
372+
.potential_package_filter()
369373
})
370-
.filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p))
371-
.for_each(|(p, r)| {
372-
let priority = prioritizer(p, r);
373-
prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32)));
374-
});
375-
self.prioritize_decision_level = self.package_assignments.len();
376-
prioritized_potential_packages.pop().map(|(p, _)| p)
374+
.is_none()
375+
{
376+
// Only prioritize packages up for decision
377+
return;
378+
}
379+
self.prioritized_potential_packages
380+
.push(package, (priority, Reverse(package.into_raw() as u32)));
377381
}
378382

379383
/// If a partial solution has, for every positive derivation,
@@ -413,12 +417,20 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
413417
/// Backtrack the partial solution to a given decision level.
414418
pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) {
415419
self.current_decision_level = decision_level;
416-
self.package_assignments.retain(|_, pa| {
420+
self.package_assignments.retain(|p, pa| {
417421
if pa.smallest_decision_level > decision_level {
418422
// Remove all entries that have a smallest decision level higher than the backtrack target.
419423
false
420424
} else if pa.highest_decision_level <= decision_level {
421425
// Do not change entries older than the backtrack decision level target.
426+
if pa
427+
.assignments_intersection
428+
.potential_package_filter()
429+
.is_some()
430+
&& self.prioritized_potential_packages.get(p).is_none()
431+
{
432+
self.unprioritized_potential_packages.insert(*p);
433+
}
422434
true
423435
} else {
424436
// smallest_decision_level <= decision_level < highest_decision_level
@@ -443,12 +455,15 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
443455
// Reset the assignments intersection.
444456
pa.assignments_intersection =
445457
AssignmentsIntersection::Derivations(last.accumulated_intersection.clone());
458+
459+
self.prioritized_potential_packages.remove(p);
460+
if pa.assignments_intersection.term().is_positive() {
461+
self.unprioritized_potential_packages.insert(*p);
462+
}
446463
true
447464
}
448465
});
449-
// Throw away all stored priority levels, And mark that they all need to be recomputed.
450-
self.prioritized_potential_packages.clear();
451-
self.prioritize_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
466+
// Throw away all stored priority levels and mark them for recomputing
452467
self.has_ever_backtracked = true;
453468
}
454469

@@ -714,12 +729,12 @@ impl<VS: VersionSet> AssignmentsIntersection<VS> {
714729
/// selected version (no "decision")
715730
/// and if it contains at least one positive derivation term
716731
/// in the partial solution.
717-
fn potential_package_filter<P: Package>(&self, package: Id<P>) -> Option<(Id<P>, &VS)> {
732+
fn potential_package_filter(&self) -> Option<&VS> {
718733
match self {
719734
Self::Decision { .. } => None,
720735
Self::Derivations(term_intersection) => {
721736
if term_intersection.is_positive() {
722-
Some((package, term_intersection.unwrap_positive()))
737+
Some(term_intersection.unwrap_positive())
723738
} else {
724739
None
725740
}

src/solver.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ pub fn resolve<DP: DependencyProvider>(
169169
.partial_solution
170170
.term_intersection_for_package(next)
171171
.ok_or_else(|| {
172-
PubGrubError::Failure("a package was chosen but we don't have a term.".into())
172+
PubGrubError::Failure(format!(
173+
"package {} was chosen but we don't have a term.",
174+
next.into_raw()
175+
))
173176
})?;
174177
let decision = dependency_provider
175178
.choose_version(

0 commit comments

Comments
 (0)