-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use intermediate satisfier causes in priority statistics #291
Open
Eh2406
wants to merge
1
commit into
pubgrub-rs:dev
Choose a base branch
from
Eh2406:conflict_count
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,7 +116,7 @@ impl<DP: DependencyProvider> State<DP> { | |
&mut self, | ||
package: Id<DP::P>, | ||
) -> Result<SmallVec<(Id<DP::P>, IncompDpId<DP>)>, NoSolutionError<DP>> { | ||
let mut root_causes = SmallVec::default(); | ||
let mut satisfier_causes = SmallVec::default(); | ||
self.unit_propagation_buffer.clear(); | ||
self.unit_propagation_buffer.push(package); | ||
while let Some(current_package) = self.unit_propagation_buffer.pop() { | ||
|
@@ -169,12 +169,11 @@ impl<DP: DependencyProvider> State<DP> { | |
} | ||
} | ||
if let Some(incompat_id) = conflict_id { | ||
let (package_almost, root_cause) = | ||
self.conflict_resolution(incompat_id) | ||
.map_err(|terminal_incompat_id| { | ||
self.build_derivation_tree(terminal_incompat_id) | ||
})?; | ||
root_causes.push((package, root_cause)); | ||
let (package_almost, root_cause) = self | ||
.conflict_resolution(incompat_id, &mut satisfier_causes) | ||
.map_err(|terminal_incompat_id| { | ||
self.build_derivation_tree(terminal_incompat_id) | ||
})?; | ||
self.unit_propagation_buffer.clear(); | ||
self.unit_propagation_buffer.push(package_almost); | ||
// Add to the partial solution with incompat as cause. | ||
|
@@ -190,16 +189,46 @@ impl<DP: DependencyProvider> State<DP> { | |
} | ||
} | ||
// If there are no more changed packages, unit propagation is done. | ||
Ok(root_causes) | ||
Ok(satisfier_causes) | ||
} | ||
|
||
/// Return the root cause or the terminal incompatibility. | ||
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation> | ||
/// | ||
/// Usually by the time we have a conflict `unit_propagation` has done a lot of work. | ||
/// So the actual conflict we find is important, but not particularly actionable. | ||
/// It says something like "the dependency on package X and the dependency on package Y are incompatible". | ||
/// To make it actionable we want to track it back to decisions that made the dependency required. | ||
/// "The decision on B is incompatible with the decision on C, | ||
/// because unit propagation from just those decisions will lead to the conflict about X and Y" | ||
/// is much more actionable, backtrack until one of those decisions can be revisited. | ||
/// To make a practical, we really only need one of the terms to be a decision. | ||
/// We may as well leave the other terms general. Something like | ||
/// "the dependency on the package X is incompatible with the decision on C" tends to work out pretty well. | ||
/// Then if A turns out to also have a dependency on X the resulting root cause is still useful. | ||
/// Of course, this is more heuristics than science. If the output is too general, then `unit_propagation` will | ||
/// handle the confusion by calling us again with the next most specific conflict it comes across. | ||
/// If the output is to specific, then the outer `solver` loop will eventually end up calling us again | ||
/// until all possibilities are enumerated. | ||
/// | ||
/// This function combines incompatibilities with things that make the problem inevitable to end up with a | ||
/// more useful incompatibility. For the correctness of the PubGrub algorithm only the final output is required. | ||
/// By banning the final output, unit propagation will prevent the intermediate steps from occurring again, | ||
/// at least prevent the exact same way. However, the statistics collected for `prioritize`may want | ||
/// to analyze those intermediate steps. For example we might start with "there is no version 1 of Z", | ||
/// and `conflict_resolution` may be able to determine that "that was inevitable when we picked version 1 of X" | ||
/// which was inevitable when picked W and ... and version 1 of B, which was depended on by version 1 of A. | ||
/// Therefore the root cause may simplify all the way down to "we cannot pick version 1 of A". | ||
/// This will prevent us going down this path again. However when we start looking at version 2 of A, | ||
/// and discover that it depends on version 2 of B, we will want to prioritize the chain of intermediate steps | ||
/// to confirm if it has a problem with the same shape. | ||
/// The `satisfier_causes` argument keeps track of these intermediate steps so that the caller can use. | ||
#[allow(clippy::type_complexity)] | ||
#[cold] | ||
fn conflict_resolution( | ||
&mut self, | ||
incompatibility: IncompDpId<DP>, | ||
satisfier_causes: &mut SmallVec<(Id<DP::P>, IncompDpId<DP>)>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this turns out to show up in anyone's profile while they don't use the output, we can replace it by a callback, but for now |
||
) -> Result<(Id<DP::P>, IncompDpId<DP>), IncompDpId<DP>> { | ||
let mut current_incompat_id = incompatibility; | ||
let mut current_incompat_changed = false; | ||
|
@@ -223,6 +252,7 @@ impl<DP: DependencyProvider> State<DP> { | |
previous_satisfier_level, | ||
); | ||
log::info!("backtrack to {:?}", previous_satisfier_level); | ||
satisfier_causes.push((package, current_incompat_id)); | ||
return Ok((package, current_incompat_id)); | ||
} | ||
SatisfierSearch::SameDecisionLevels { satisfier_cause } => { | ||
|
@@ -234,6 +264,7 @@ impl<DP: DependencyProvider> State<DP> { | |
); | ||
log::info!("prior cause: {}", prior_cause.display(&self.package_store)); | ||
current_incompat_id = self.incompatibility_store.alloc(prior_cause); | ||
satisfier_causes.push((package, current_incompat_id)); | ||
current_incompat_changed = true; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried some editing:
How will we apply the root cause?
In which direction are "too general" and "too specific" to be understood here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I like the edits. I have a nit about
I'm going to quibble about the term
decision
in this sentence. In my braindecision
implies something that became constrained because of a call tochoose_version
/add_decision
. Because we check for conflicts inadd_version
simple cases end up handled there. We generally do not end up here with a conflict that is already in terms of decisions. Instead We start with something abstract likeX (in some range) depends on Y (in some range)
. It's the job of this function to peel back the layers, figure out why the partial solution has an assignment (which is not necessarily decision) that is disjoint with the mentioned range for Y and why the partial solution has an assignment that is a subset of the mentioned range for X.When we discover that A has a dependency on X
unit_propagation
will rescan the root cause"the dependency on the package X is incompatible with the decision on C"
, and remove that version of C from the available range stored in partial solution.I intended for to specific to mean referring to exact decided versions. "B == 123 is incompatible with C == 456" would be very specific. Extremely actionable,
unit_propagation
needs to do very little work to figure out if it's relevant. But also not likely to be reusable. We are likely to end up withO(# versions of B * # versions of C)
of these incompatibilities laying around. "any version of X is incompatible with any version of Y" would be very general. There will only ever need to be one, and it will be relevant and useful any time X or Y come up again. But, it takes a lot of work to figure out how it relates to decisions or where we need to backtrack to.Obviously the wording of the paragraphs needs work, thank you for helping me with it!