- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 565
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
Coercion discovery fails to be transitive #15303
Comments
comment:1
The problem is that in the present implementation, both coercions are stored on As far as I understand, the coercion model should behave as a digraph on the parents, where a coercion between parents exists if there is a path from one to the other. In that model, coercion existence should be transitive, so the behaviour described is a bug. One way to work around it is, at least for coercion discovery, to store the coercion always on the codomain. By #14711 this can now happen without the implication that the codomain will keep the domain alive. We would have to store it in a place where it can be used for coercion discovery, though. If it is desirable to keep the current lifetime implications for |
comment:2
It has also been discussed on sage-devel that the existence of a coercion from B to C would change over time. Namely:
I suppose that this phenomenon can not be avoided: We can not have a static coercion digraph (otherwise we would restrict ourselves to a fixed finite set of usable parents), and when we have a dynamic coercion graph, then it is, well, dynamic. However, one additional complication arises with the current implementation: The absence of a coercion is cached. Hence, if we asked for a coercion in 1., then in 3. you would still not get a coerce map, because the absence of a coercion has been cached in 1. Let Once this is done, we could change the backtracking so that it not only iterates over But what about the problem of caching coercions? We should carefully think if the current scenario ( How to make it possible to allow a coercion via a newly created parent? Perhaps the following would be feasible: If we do Note that clearing it in the sense of all cached coercions to So, would the suggestion make sense to you?
Hm. Probably this is not good enough. What if we have had Given this example, it seems to me that we could only solve the problem in a drastic way: Do not cache the absence of a coercion at all. But caching the absence of a coercion is essential for speed. So, the drastic solution would probably be correct, but highly infeasible. If nobody has a better suggestion, then I think we should restrict ourselves to just fix the transitivity problem; the cache problem (it seems to me) is not solvable without creating a drastic slow-down. |
comment:3
Replying to @simon-king-jena:
There would be an additional effect, by the way: If the coercion that does get discovered is stored as a composition (on C) then there is now a reference from C._coerce_from_hash to A. This reference lives in a MonoDict keyed by B, so this reference remains as long as B is alive. So we see that as long as B and C are alive, then A will be kept alive as well (thus, with monodicts we can have objects whose lifetime depends on two objects BOTH being alive) Note that if the composition gets computed in a smarter way (for instance, a composition of homomorphisms between finitely generated rings could be explicitly computed by computing the images of the generators and constructing a straight homomorphism from B to C out of that) then the resulting coercion map might not refer to A anymore. I am not saying that this is avoidable nor that we should consider this a memory leak: we're keeping A in memory for a good reason, even if the reason is not directly supplied by the user.
There are milder ways of invalidating caches: We could mark cached non-existence with a "coercion graph version number". When a non-existence is retrieved, one could check the current "coercion graph version number" and if the current graph is newer, we'd have to reinvestigate the "None". Frankly, I don't believe that would give acceptable performance either, but we could at least be much more lazy about invalidating "no coercion exists" findings. The reason why I think this would still not be good enough is because I expect that coercion graph mutations occur fairly frequently (basically with any parent creation) and I don't see a way to localize coercion graph versions, so any mutation would invalidate ALL non-existence caches.
Yes, something along those lines. In fact, since By then we should probably be storing ALL maps there in weakened form. The only function of So we would end up with (names up for choice):
In the present naming system, it wasn't immediately clear to me what purposes
Yes, I don't have any suggestions that I seriously believe would lead to acceptable performance. |
comment:4
I was thinking about "versioning" of the coercion graph as well. Perhaps it would be worth trying. The idea would be: We have one global variable, say, Instead of storing Would this really be soooo expensive? I don't think so. Of course, it depends on how often we create non-sink non-source nodes---and I doubt that the combined use of So, I'd say we simply try. |
comment:5
Replying to @simon-king-jena:
I don't think you can establish whether something is a non-sink, since A start might be to only version up on "embedding" creations. That might very well be usable. I expect "embeddings" to be relatively rare, and I think we can declare in our documentation they are not the preferred way of expressing relations (they are vary prone to creating non-commutative diamonds). |
comment:6
Replying to @nbruin:
Perhaps I have not been clear. A non-sink non-source is something that has both in-arrows (i.e., is codomain of a registered coercion or coerce embedding) and out-arrows (i.e., is domain of a registered coercion or coerce embedding). It is easy to keep track of this property while registering a coercion or coerce embedding. But I actually think that your idea is better anyway:
It seems to me that this versioning would be complete under reasonable assumptions, based on the following lemma. Lemma Assume for all parents Proof Proof be contradiction. We assume that there is a path from Hence, all arrows created between q.e.d.
We could express in the documentation that one should call |
Dependencies: #14711 |
comment:8
Replying to @nbruin:
Or shorter:
Probably.
I have introduced
Yes, and that's why I don't think we need |
comment:9
PS: We also have |
comment:10
With the branch that I have just attached, one gets
What has been done in the patch:
Missing: Tests and versioning. |
Branch: u/SimonKing/ticket/15303 |
Commit: |
comment:12
Replying to @simon-king-jena:
yes, conversion probably needs some attention too. However, since conversions exits between a lot more pairs of parents, Do we use backtracking to discover them? There are almost certainly loops: trivially, because ZZ->Z/nZ and Z/nZ->ZZ are valid conversions. If we want a memory leak-free implementation I suspect having conversion without lifetime implications in either direction is required. Definitely material for another ticket. Last 10 new commits:
|
Branch pushed to git repo; I updated commit sha1. New commits:
|
comment:14
I have pushed a new commit that implements version numbers for the coercion cache, and I added a doctest along the following lines:
Hence, I think that the current commit solves our problem. In spite of the following "todo" list, I put it to "needs review" (but I am sure that further commits will be pushed soon). TODO:
|
Author: Simon King |
comment:15
I already found that some test fails when |
comment:16
Looking at what happens for number fields, I have the impression that The only two classes I have been able to identify that actually install embeddings are numberfields (and not all of them) and AbelianGroupWithValues_class So, assuming that the version check itself is lightning-fast (and why shouldn't it be?) I expect that negatively affected examples would have to do a lot of number field or AbelianGroupWithValues creations interleaved with complicated coercion discovery that benefits a lot from knowing certain coercions do NOT exist. There's AdditiveAbelianGroupWrapper which does an There's also UniversalCyclotomicField which registers an embedding into QQbar upon init, so that shouldn't really affect versioning. |
comment:17
The following runs into an infinite loop:
|
Work Issues: analyse recursion error |
comment:18
Shorter:
Hence, we do have a map, but applying it will run into a loop. |
comment:113
Replying to @mezzarobba:
So, that's not it. However, as I said above: After the fetch command, I did However, how can I see your merge resolution? |
comment:114
Replying to @mezzarobba:
So, that's not it. However, as I said above: After the fetch command, I did However, how can I see your merge resolution? |
comment:115
Replying to @simon-king-jena:
All I did manually was to choose the version of
|
comment:116
Replying to @mezzarobba:
I see. So, you took the version of
I have not even been aware that this has changed. So, someone has made morphisms that are defined by generator images hashable? Nice!
Didn't you also do something with Anyway, the output of |
comment:117
Replying to @simon-king-jena:
Of
No, there was no conflict there either. |
comment:118
Replying to @mezzarobba:
Then I show you what commit 2712c53cb68d2668b47ccc923b5a77421ff04bbd
Merge: 528a035 0c6fcdf
Author: Marc Mezzarobba <[email protected]>
Date: Sat Nov 30 14:10:11 2013 +0100
Merge 'trac/master' into ticket/15303
Conflicts:
src/sage/misc/weak_dict.pyx
src/sage/schemes/generic/morphism.py
diff --cc src/sage/categories/morphism.pyx
index 74f600f,47aa188..a9f44c8
--- a/src/sage/categories/morphism.pyx
+++ b/src/sage/categories/morphism.pyx
@@@ -249,8 -214,58 +290,58 @@@ garbage collection. Please use a copy."
sage: ZZ(x)
-1
"""
- self.codomain().register_conversion(self)
+ self._codomain.register_conversion(self)
+ # You *must* override this method in all cython classes
+ # deriving from this class.
+ # If you are happy with this implementation (typically
+ # is your domain has generators), simply write:
+ # def __hash__(self):
+ # return Morphism.__hash__(self)
+ def __hash__(self):
+ """
+ Return a hash of this morphism.
+
+ It is the hash of the triple (domain, codomain, definition)
+ where ``definition`` is:
+
+ - a tuple consisting of the images of the generators
+ of the domain if domain has generators
+
+ - the string representation of this morphism otherwise
+
+ AUTHOR:
+
+ - Xavier Caruso (2012-07-09)
+ """
+ domain = self.domain()
+ codomain = self.codomain()
+ try:
+ gens = domain.gens()
+ definition = tuple([self(x) for x in gens])
+ except (AttributeError, NotImplementedError):
+ definition = self.__repr__()
+ return hash((domain, codomain, definition))
+
+ def __richcmp__(left, right, int op):
+ return (<Element>left)._richcmp(right, op)
+
+ cdef int _cmp_c_impl(left, Element right) except -2:
+ if left is right: return 0
+ domain = left.domain()
+ c = cmp(domain, right.domain())
+ if c: return c
+ c = cmp(left.codomain(), right.codomain())
+ if c: return c
+ try:
+ gens = domain.gens()
+ for x in gens:
+ c = cmp(left(x), right(x))
+ if c: return c
+ except (AttributeError, NotImplementedError):
+ raise NotImplementedError
+
+
cdef class FormalCoercionMorphism(Morphism):
def __init__(self, parent):
Morphism.__init__(self, parent)
diff --cc src/sage/combinat/ncsf_qsym/ncsf.py
index 6478d46,7d84562..febf995
--- a/src/sage/combinat/ncsf_qsym/ncsf.py
+++ b/src/sage/combinat/ncsf_qsym/ncsf.py
@@@ -238,12 -249,11 +249,12 @@@ class NonCommutativeSymmetricFunctions(
sage: elementary(ribbon[2,1,2,1])
L[1, 3, 2] - L[1, 5] - L[4, 2] + L[6]
- TODO: explain the other changes of bases!
+ .. TODO:: explain the other changes of bases!
- Here is how to fetch the conversion morphisms::
+ Here is how to fetch the coerce morphisms. Note that by :trac:`15303`, we
+ should use a copy of the maps being used in the coercion system::
- sage: f = complete.coerce_map_from(elementary); f
+ sage: f = copy(complete.coerce_map_from(elementary)); f
Generic morphism:
From: NCSF in the Elementary basis
To: NCSF in the Complete basis
diff --cc src/sage/schemes/generic/morphism.py
index 3a6c351,09c6bc3..ab59866
--- a/src/sage/schemes/generic/morphism.py
+++ b/src/sage/schemes/generic/morphism.py
@@@ -79,10 -75,12 +79,12 @@@ AUTHORS
#*****************************************************************************
-from sage.structure.element import AdditiveGroupElement, RingElement, Element, generic_power
+from sage.structure.element import AdditiveGroupElement, RingElement, Element, generic_power, parent
from sage.structure.sequence import Sequence
-from sage.categories.homset import Homset
+from sage.categories.homset import Homset, Hom
- from sage.rings.all import is_RingHomomorphism, is_CommutativeRing, Integer
+ from sage.rings.all import Integer
+ from sage.rings.commutative_ring import is_CommutativeRing
+ from sage.rings.morphism import is_RingHomomorphism
from point import is_SchemeTopologicalPoint
from sage.rings.infinity import infinity
import scheme
|
comment:119
Replying to @simon-king-jena:
Because |
comment:120
Good and bad news. The good news: I did not see a crash in permgroup.py, even though it used to always occur with
The number of failing tests indicates that it could be mostly harmless. I'll soon push the branch (even though it actually is your branch, but I guess it is ok. |
Changed work issues from Crash in permgroup.py to none |
comment:121
What is broken in qsym.py? I'm asking because I'm editing the file currently. |
comment:122
Oooops, what is that?
So, why is the slow toy implementation being used when coercion is changed? |
comment:123
Replying to @darijgr:
Here is the diff that I did to fix the failure: diff --git a/src/sage/combinat/ncsf_qsym/qsym.py b/src/sage/combinat/ncsf_qsym/qsym.py
index 583ca87..f127c19 100644
--- a/src/sage/combinat/ncsf_qsym/qsym.py
+++ b/src/sage/combinat/ncsf_qsym/qsym.py
@@ -2232,23 +2232,25 @@ class QuasiSymmetricFunctions(UniqueRepresentation, Parent):
def __init_extra__(self):
"""
Set up caches for the transition maps to and from the monomial
- basis, and register them as coercions.
+ basis, and register them as coercions. By :trac:`15303`, we need
+ to copy coerce maps before exposing them outside of the coercion
+ system.
TESTS::
sage: HWL = QuasiSymmetricFunctions(QQ).HazewinkelLambda()
sage: M = QuasiSymmetricFunctions(QQ).Monomial()
- sage: HWL.coerce_map_from(M)
+ sage: M2HWL = copy(HWL.coerce_map_from(M)); M2HWL
Generic morphism:
From: Quasisymmetric functions over the Rational Field in the Monomial basis
To: Quasisymmetric functions over the Rational Field in the HazewinkelLambda basis
- sage: M.coerce_map_from(HWL)
+ sage: HWL2M = copy(M.coerce_map_from(HWL)); HWL2M
Generic morphism:
From: Quasisymmetric functions over the Rational Field in the HazewinkelLambda basis
To: Quasisymmetric functions over the Rational Field in the Monomial basis
- sage: M.coerce_map_from(HWL)(HWL[2])
+ sage: HWL2M(HWL[2])
M[1, 1]
- sage: HWL.coerce_map_from(M)(M[2])
+ sage: M2HWL(M[2])
HWL[1, 1] - 2*HWL[2]
"""
M = self.realization_of().M() |
comment:124
Replying to @simon-king-jena:
Same problem in And I guess the other errors should rather be fixed at #14711, because they are about "weakened coerce maps". |
Branch pushed to git repo; I updated commit sha1. New commits:
|
comment:126
Since #14711 has changed, I have merged the new commits from there. And I have included Marc's master merge. Doctests pass for me. |
Work Issues: rebase (11 files conflicting) |
As found in #14711:comment:134, the following example shows that a combination of
register_embedding
andregister_coercion
can lead to a failure in transitivity for coercion discovery; also discussed on sage-devel:One workaround is simple: just don't use
register_embedding
. However, after #14711, there are different lifetime implications between usingregister_embedding
andregister_coercion
, so this workaround might not be desirable.Depends on #14711
Depends on #15329
Depends on #15331
CC: @simon-king-jena @robertwb @jpflori
Component: coercion
Work Issues: rebase (11 files conflicting)
Author: Simon King
Branch/Commit: u/SimonKing/ticket/15303 @
12e8055
Issue created by migration from https://trac.sagemath.org/ticket/15303
The text was updated successfully, but these errors were encountered: