mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
uv-resolver: fix conflict marker simplification bug
The particular example I honed in on here was the `e3nn -> sympy 1.13.1` and `e3nn -> sympy 1.13.3` dependency edges. In particular, while the former correctly has a conflict marker, the latter's conflict marker was getting simplified to `true`. This makes the edges trivially overlapping, and results in both of them getting installed simultaneously. (A similar problem happens for the `e3nn -> torch` dependency edges.) Why does this happen? Well, conflict marker simplification works by detecting which extras are known to be enabled (and disabled) for each node in the graph. This ends up being expressed as a set of sets, where each inner set contains items corresponding to "extras is included" or "extra is excluded." The logic then is if _all_ of these sets are satisfied by the conflict marker on the dependency edge, then this conflict marker can be simplified by assuming all of the inclusions/exclusions to be true. In this particular case, we run into an issue where the set of assumptions discovered for `e3nn` is: {test[sevennet]}, {}, {~test[m3gnet], ~test[alignn], test[all]} And the corresponding conflict marker for `e3nn -> sympy 1.13.1` is: extra == 'extra-4-test-all' or extra == 'extra-4-test-chgnet' or (extra != 'extra-4-test-alignn' and extra != 'extra-4-test-m3gnet') And the conflict marker for `e3nn -> sympy 1.13.3` is: extra == 'extra-4-test-alignn' or extra == 'extra-4-test-m3gnet' Evaluating each of the sets above for `sympy 1.13.1`'s conflict marker results in them all being true. Simplifying in turn results in the marker being true. For `sympy 1.13.3`, not all of the sets are satisfied, so this marker is not simplified. I think the fundamental problem here is that our inferences aren't quite rich enough to make these logical leaps. In particular, the conflict marker for `e3nn -> sympy 1.13.3` is not satisfied by _any_ of our sets. One might therefore conclude that this dependency edge is impossible. But! The `test[sevennet]` set doesn't actually rule out `test[m3gnet]` from being included, for example, because there is no conflict. So it is actually possible for this marker to evaluate to true. And I think this reveals the problem: for the `e3nn -> sympy 1.13.1` conflict marker, the inferences don't capture the fact that `test[sevennet]` _might_ have `test[m3gnet]` enabled, and that would in turn result in the conflict marker evaluating to `false`. This directly implies that our simplification here is inappropriate. It would be nice to revisit how we build our inferences here so that they are richer and enable us to make correct logical leaps. For now, we fix this particular bug with a bit of a cop-out: we skip conflict marker simplification when there are ambiguous dependency edges. Fixes #11479
This commit is contained in:
parent
aaf3429e3f
commit
91593d42d9
3 changed files with 27 additions and 6 deletions
|
@ -225,7 +225,21 @@ pub(crate) fn simplify_conflict_markers(
|
|||
}
|
||||
|
||||
for edge_index in (0..graph.edge_count()).map(EdgeIndex::new) {
|
||||
let (from_index, _) = graph.edge_endpoints(edge_index).unwrap();
|
||||
let (from_index, to_index) = graph.edge_endpoints(edge_index).unwrap();
|
||||
// If there are ambiguous edges (i.e., two or more edges
|
||||
// with the same package name), then we specifically skip
|
||||
// conflict marker simplification. It seems that in some
|
||||
// cases, the logic encoded in `inferences` isn't quite enough
|
||||
// to perfectly disambiguate between them. It's plausible we
|
||||
// could do better here, but it requires smarter simplification
|
||||
// logic. ---AG
|
||||
let ambiguous_edges = graph
|
||||
.edges_directed(from_index, Direction::Outgoing)
|
||||
.filter(|edge| graph[to_index].package_name() == graph[edge.target()].package_name())
|
||||
.count();
|
||||
if ambiguous_edges > 1 {
|
||||
continue;
|
||||
}
|
||||
let Some(inference_sets) = inferences.get(&from_index) else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
@ -93,6 +93,13 @@ impl ResolutionGraphNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn package_name(&self) -> Option<&PackageName> {
|
||||
match *self {
|
||||
ResolutionGraphNode::Root => None,
|
||||
ResolutionGraphNode::Dist(ref dist) => Some(&dist.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ResolutionGraphNode {
|
||||
|
|
|
@ -10732,10 +10732,10 @@ fn duplicate_torch_and_sympy_because_of_wrong_inferences() -> Result<()> {
|
|||
dependencies = [
|
||||
{ name = "opt-einsum-fx" },
|
||||
{ name = "scipy" },
|
||||
{ name = "sympy", version = "1.13.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "sympy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-all' or extra == 'extra-4-test-chgnet' or (extra != 'extra-4-test-alignn' and extra != 'extra-4-test-m3gnet')" },
|
||||
{ name = "sympy", version = "1.13.3", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-alignn' or extra == 'extra-4-test-m3gnet'" },
|
||||
{ name = "torch", version = "2.2.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-alignn' or extra == 'extra-4-test-m3gnet'" },
|
||||
{ name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-all' or extra == 'extra-4-test-chgnet' or (extra != 'extra-4-test-alignn' and extra != 'extra-4-test-m3gnet')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/98/8e7102dea93106603383fda23bf96649c397a37b910e7c76086e584cd92d/e3nn-0.4.4.tar.gz", hash = "sha256:51c91a84c1fb72e7e3600000958fa8caad48f8270937090fb8d0f8bfffbb3525", size = 361661 }
|
||||
wheels = [
|
||||
|
@ -11095,7 +11095,7 @@ fn duplicate_torch_and_sympy_because_of_wrong_inferences() -> Result<()> {
|
|||
version = "2.5.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "fsspec", extra = ["http"] },
|
||||
{ name = "fsspec", extra = ["http"], marker = "(extra == 'extra-4-test-alignn' and extra == 'extra-4-test-all') or (extra == 'extra-4-test-alignn' and extra == 'extra-4-test-chgnet') or (extra != 'extra-4-test-alignn' and extra == 'extra-4-test-m3gnet') or (extra != 'extra-4-test-all' and extra != 'extra-4-test-chgnet' and extra == 'extra-4-test-m3gnet')" },
|
||||
{ name = "lightning-utilities" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pytorch-lightning" },
|
||||
|
@ -12062,7 +12062,7 @@ fn duplicate_torch_and_sympy_because_of_wrong_inferences() -> Result<()> {
|
|||
{ name = "opt-einsum" },
|
||||
{ name = "packaging" },
|
||||
{ name = "torch", version = "2.2.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-alignn' or extra == 'extra-4-test-m3gnet'" },
|
||||
{ name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-4-test-all' or extra == 'extra-4-test-chgnet' or (extra != 'extra-4-test-alignn' and extra != 'extra-4-test-m3gnet')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/de/856dab99be0360c7275fee075eb0450a2ec82a54c4c33689606f62e9615b/opt_einsum_fx-0.1.4.tar.gz", hash = "sha256:7eeb7f91ecb70be65e6179c106ea7f64fc1db6319e3d1289a4518b384f81e74f", size = 12969 }
|
||||
wheels = [
|
||||
|
@ -12545,7 +12545,7 @@ fn duplicate_torch_and_sympy_because_of_wrong_inferences() -> Result<()> {
|
|||
version = "2.5.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "fsspec", extra = ["http"] },
|
||||
{ name = "fsspec", extra = ["http"], marker = "(extra == 'extra-4-test-alignn' and extra == 'extra-4-test-all') or (extra == 'extra-4-test-alignn' and extra == 'extra-4-test-chgnet') or (extra != 'extra-4-test-alignn' and extra == 'extra-4-test-m3gnet') or (extra != 'extra-4-test-all' and extra != 'extra-4-test-chgnet' and extra == 'extra-4-test-m3gnet')" },
|
||||
{ name = "lightning-utilities" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pyyaml" },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue