On 4/27/07, Robert Bradshaw <[EMAIL PROTECTED]> wrote: > > A couple of points on square root. First, sqrt() and square_root() > > can have different behavior.
After getting extensive user feedback, especially from younger people, I've decided that having square_root and sqrt do different things is very very confusing. >> I think there are three possible > > behaviours one might want: > > 1. Return a real approximation. The main plus on this option is > > that this is what many users will want if they ask for sqrt(2). It really depends. I would say that most "normal" users want something symbolic when they type "sqrt(2)" and most engineers want sqrt(2) as a floating point number. > > 2. Return an element of a quadratic extension. We can then have > > one of the completions (or one of the pair of complex embeddings) > > be distinguished. This just means that there's an automatic > > coercion from QQ[sqrt(2)] to RR given by choosing the positive > > square root. While not really canonical mathematically, it's > > sufficiently standard that we should put it in. Users who want the > > other embedding will be able to get it fairly easily. Good. > > 3. Return an integer (or rational) if n is a perfect square or > > throw and error otherwise. This kind of behavior currently exists > > for finite fields for example. > > Exactly what we were thinking. > > > My inclination would be to have sqrt do the first, and square_root > > the second. The fact we are even having this discussion illustrates that square_root versus sqrt being different is confusing. Instead we should have something like: sqrt --- does number 2 above, or whatever the analogue is in general, i.e., it tries to compute the square root as exactly as possible. For lazy p-adics, it would be lazy, for integers it would probably start by giving something symbolic, then two weeks later, when number fields improve, it would return a number field element exactly as you suggest. sqrt_exact -- tries to compute sqrt and raises an exception on failure sqrt_approx -- always returns a floating point square root. This should be pushed throughout the system, and there should also be functional notation for calling the above three. What do you think? >> Since 2 makes sense for pretty much every integral > > domain (return x in the ring R[x]/(x^2 - a) if a does not have a > > square root in R), maybe have square_root do that, and sqrt() > > return an element of an algebraic completion for fields, and have > > behavior 3 for rings? Yes to the first in most cases. I don't know what "algebraic completion" means. > In terms of square roots (or nth roots, though (2) gets a bit more > complicated), sqrt != square_root is probably going away (I guess too > many other users found this confusing). Yes, it is definitely going away. >I think the square_root > function should take a parameter to distinguish between the three > types (with default 2? One can always do sqrt(2.0) or RR(sqrt(2)), > etc. and I don't think causal users would be confused by \sqrt{2} as > an answer). One could have actual functions for the types too. E.g. > sqrt_approx, sqrt_extend, sqrt_exact(?). Yes. That's almost exactly what I proposed above. I prefer different function names, since the return results are so different, and since there are three options. If it were just one could have def sqrt(self, exact=True, extend=True): ... but it gets complicated because if exact=False, you also need to be able to pass in an optional precision parameter. I think I prefer three separate functions: sqrt_approx, sqrt, sqrt_exact. > >> Essentially, what we came up with is that (extra) implicit coercion > >> morphisms could be specified at ring (object) creation time, which > >> would be extra data attached to that specific ring, and the coercion > >> model would detect and use those. > > > > I think I like using commutative diagrams better. > > I think they're complimentary. For natural (or conventional) > morphisms I prefer commutative diagrams, but I think this would be > useful for creating, say, a number field with a specific embedding > into C. The commutative diagram itself should be immutable (in the > sense that adjacent edges are only added (deleted) as rings are > created (garbage collected). Our idea was more that this diagram gets created at runtime as references between objects. Having a separate object (derived from graph) is more powerful though, since you can compute the transitive closure, etc., and ensure that various axiom are satisfied. > As a specific example, what should sqrt(2) return? If the result lies > in a number field (or Z[\sqrt(2] or whatever) we would want to equip > this field with the extra information that the returned generator, by > convention, gets sent to the positive square root of two (for use if > one coerces into the symbolic ring or reals). The generic quadratic > extension should not come with this kind of a map. sqrt(2) could be an element of the number field K=Q[x]/(x^2-2) *equipped* with an embedding map K --> SR that sends x to the symbolic ring element sqrt(2). We could also equip K with maps to RR, CC, etc., by choosing a root to some precision (which would choose a root to any bigger precision automatically). More generally, at creation time one could equip any ParentWithGens object with a list of embedding morphisms that should be used by the coercion system automatically. This list is would be immutable. So there would be a new number field class NumberField_with_embedding(s). > >> Also, one could implement a > >> ambient_parent() method who would (if possible) return a new parent > >> together with injections from each of the original rings. The lattice > >> of number fields stuff would be used here. > > > > Yeah: an ambient_parent method could make sense in a > > CoercionEnvironment (the minimal element among things mapped to > > from both, if such exists, or error if there this element doesn't > > exist or is not unique). > > Possibly one could be created too (like your number field examples). > > >> I like the idea of being able to use python contexts. I don't know > >> how efficient one could make it though. > > > > In terms of efficiency, for elements with the same parent, there > > will be no performance hit. After that, I don't think a context > > would slow stuff down much if you did it right. > > After looking into the python context stuff more, I think I agree. > All the expensive stuff could be handled at context enter/exit. All > the (cached) information in the enclosing environment should be > merged into the current environment to avoid traveling up the tree > (as most coercions will probably still be defined at the outermost > level, and every hashtable lookup can be expensive compared to the > arithmetic itself.) > > I think I understand your proposal below. What I'm not clear on is > how the entry/exit points do more than connect each separate category > graph into one large graph with edges for "each (natural(ish)) > homomorphism A -> B in some suitably refined (wrt A and B) category." > This may not be well defined, but I think it is the best a computer > algebra system can do without forcing the user to do explicit > coercions all over. > > (There is one advantage I see, namely that it favors coercions that > remain within the category, but I believe this could be done anyway > if there are several paths.) > One can always extract the subgraph > corresponding to a specific category, but we're already mixing > categories with our objects (e.g. there is one object QQ that is used > for as an object in Rings and Fields and Groups and Q-bar/Q modules, > etc.) > > - Robert > > > I'd like to center the discussion on categories for a bit. Since > > implicit coercion maps are supposed to be morphisms, they properly > > have a category attached to them. So really, we should be asking: > > If R and S are objects of a category C, we want to pick a > > distinguished element of $Hom_C(R, S)$. So, given two parents R > > and S, we choose a category C with a forgetful functor to the > > category of parents and return a morphism in $Hom_C(R, S)$. So how > > do we implement CoercionEnvironments? > > > > Every parent object implements a function called > > _coercion_categories() which returns a list of categories that > > parent belongs to. It doesn't have to be all categories that that > > parent belongs to (clearly), but these categories will be the only > > ones considered by the coercion code. The default implementation > > is to just return the category of Parents (is this the same > > category as the category Sets? I'm not sure...), and every > > implementation should return Parents as one of the members of the > > list. > > > > By a descendant of a category C I mean a category D such that there > > is a forgetful functor from D to C. By an entry point of a > > commutative diagram L in a category D I mean a morphism f in Hom_C > > (A, B) where B is an object in L and D is a descendant of C. Exit > > point is defined analogously. By an extended commutative diagram I > > mean a commutative diagram with a list of entry and exit points. > > > > Each CoercionEnvironment has the following fields: > > * an enclosing environment "encl" (another CoercionEnvironment) > > * a dictionary "diagrams", with keys some collection of > > categories, and diagrams[cat] an extended commutative diagram in > > the category cat. > > * a dictionary "coerce_maps" so that we don't have to find > > coercion maps more than once. > > and a function find_coerce_map(R, S) that does the following: > > (note that the code below doesn't correctly handle cycles in diagrams) > > def find_coerce_map(self, R, S): > > """ > > Returns a coercion morphism from R to S, or None if none exists > > in the current environment. > > > > Tests first to see if R and S are already cached in the > > dictionary of known maps. > > Next, checks to see if R and S both belong to one of the > > diagrams and there's a path between them. If this is true for only > > one of the diagrams, returns that map. If true for more than one, > > raises an error. > > Next, checks to see if R belongs to a diagram with exit points > > and S to a diagram with entry points. If so, recursively tries to > > find a coercion map from the codomain of an exit point of a diagram > > to which R belongs to the domain of an entry point of a diagram to > > which S belongs. > > Finally, checks to see if there is an enclosing environment, > > and if there is a coercion map in the enclosing environment. > > """ > > if self.coerce_maps.has_key((R, S)): > > return self.coerce_maps [(R, S)] > > f = None > > for cat in intersection(R._coercion_categories(), > > S._coercion_categories): > > if self.diagrams.has_key(cat) and R in self.diagrams[cat] > > and S in self.diagrams[cat]: > > path = self.diagrams[cat].path(R, S) > > if path is not None: > > if f is None: > > f = path.compose() > > else: > > raise ValueError, "more than one coercion map > > found" > > if f is None: > > for diag1 in self.diagrams.values() if R in diag1: > > for diag2 in self.diagrams.values() if S in diag2: > > for g in diag1.exit_points() if g.domain() is not R: > > pathR = diag1.path(R, g.domain()) > > if pathR is not None: > > for h in diag2.entry_points() if h.codomain > > () is not S: > > pathS = diag2.path(h.codomain(), S) > > if pathS is not None: > > e = self.find_coerce_map(g.codomain > > (), h.domain()) > > if e is not None: > > if f is None: > > f = pathS.compose() * h * > > e * g * pathR.compose() > > else: > > raise ValueError, "more > > than one coercion map found" > > if f is None and self.encl is not None: > > f = self.encl.find_coerce_map(R, S) > > self.coerce_maps[(R, S)] = f > > return f > > > > Suggestions are welcome (especially thoughts on the giant nested > > for loops). They strike fear into my heart. > > As an example, in the default environment, the diagrams[Rings] > > diagram would include the ring morphism from ZZ to QQ sending 1 to > > 1. Whenever you create a polynomial ring over a ring R it would > > add a morphism from R to the polynomial ring embedding R as > > constant polynomials. Whenever you create a quotient ring it would > > add a morphism to the default diagram. Etc. > > > > When you create a finite field of prime order, diagrams > > [FiniteFields] will add an entry point from ZZ to the prime field. > > diagrams[FiniteFields] will then build a lattice of compatibly > > embedded fields as you create new extension fields of that prime > > field. Note that the diagrams can be disjoint unions of other > > diagrams (in practice one might use a list of diagrams > > instead...). I don't know yet how to make weakrefs work with this > > whole scheme. > > > > David -- William Stein Associate Professor of Mathematics University of Washington http://www.williamstein.org --~--~---------~--~----~------------~-------~--~----~ To post to this group, send email to sage-devel@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sage-devel URLs: http://sage.scipy.org/sage/ and http://modular.math.washington.edu/sage/ -~----------~----~----~----~------~----~------~--~---