# HG changeset patch
# User Francis Clarke <F.Clarke@Swansea.ac.uk>
# Date 1260823202 0
# Node ID e839322b0afc60fb94d29d3e617f9ee50ee07c2c
# Parent  964c2f4ce74db0417a771de0b0cfc951b1fab73c
Improved composita

diff -r 964c2f4ce74d -r e839322b0afc sage/rings/number_field/number_field.py
--- a/sage/rings/number_field/number_field.py	Thu Dec 10 18:56:58 2009 -0800
+++ b/sage/rings/number_field/number_field.py	Mon Dec 14 20:40:02 2009 +0000
@@ -166,6 +166,8 @@
     from sage.categories.fields import Fields
     return codomain in Fields()
 
+from sage.rings.number_field.morphism import RelativeNumberFieldHomomorphism_from_abs
+
 def proof_flag(t):
     """
     Used for easily determining the correct proof flag to use.
@@ -2548,15 +2550,15 @@
     def composite_fields(self, other, names=None, both_maps=False, preserve_embedding=True):
         """
         List of all possible composite number fields formed from self and
-        other, as well as possibly embeddings into the compositum.  See the
-        documentation for both_maps below.
-
-        If preserve_embedding is ``True`` and if self and other *both* have
-        embeddings into the same ambient field, only compositums respecting
-        both embeddings are returned.  If one (or both) of self or other does
-        not have an embedding, or they do not have embeddings into the same
-        ambient field, or preserve_embedding is not ``True`` all possible
-        composite number fields are returned.
+        other, together with (optionally) embeddings into the compositum;
+        see the documentation for both_maps below.
+
+        If preserve_embedding is True and if self and other both have
+        embeddings into the same ambient field, or into fields which are
+        contained in a common field, only the compositum respecting 
+        both embeddings is returned.  If one (or both) of self or other
+        does not have an embedding or preserve_embedding is False,
+        all possible composite number fields are returned.
 
         INPUT:
             
@@ -2564,17 +2566,20 @@
 
         - ``names`` - generator name for composite fields
 
-        - ``both_maps`` - (default: False)  if True, return quadruples (F,
-          self_into_F, other_into_F, k) such that self_into_F maps self into
-          F, other_into_F maps other into F, and k is an integer such that
-          other_into_F(other.gen()) + k*self_into_F(self.gen()) == F.gen()
-
-        - ``preserve_embedding`` - (default: True)  return only compositums with
-          compatible ambient embeddings.
+        - ``both_maps`` - (default: False)  if True, return quadruples 
+          (F, self_into_F, other_into_F, k) such that self_into_F is an 
+          embedding of self in F, other_into_F is an embedding of in F,
+          and k is an integer such that F.gen() equals
+          other_into_F(other.gen()) + k*self_into_F(self.gen())
+          If both self and other have embeddings into an ambient field, then 
+          F will have an embedding with respect to which both self_into_F 
+          and other_into_F will be compatible with the ambient embeddings.
+
+        - ``preserve_embedding`` - (default: True) if self and other have 
+          ambient embeddings, then return only the compatible compositum.
         
         OUTPUT:
         
-        
         -  ``list`` - list of the composite fields, possibly with maps.
         
         
@@ -2582,49 +2587,52 @@
         
             sage: K.<a> = NumberField(x^4 - 2)
             sage: K.composite_fields(K)
+            [Number Field in a0 with defining polynomial x^4 - 162, 
+             Number Field in a1 with defining polynomial x^8 + 28*x^4 + 2500]
+        
+        A particular compositum is selected, together with compatible maps 
+        into the compositum, if the fields are endowed with a real or
+        complex embedding:: 
+
+            sage: K1 = NumberField(x^4 - 2, 'a', embedding=RR(2^(1/4)))
+            sage: K2 = NumberField(x^4 - 2, 'a', embedding=RR(-2^(1/4)))
+            sage: K1.composite_fields(K2)
+            [Number Field in a0 with defining polynomial x^4 - 162]
+            sage: [F, f, g, k], = K1.composite_fields(K2, both_maps=True); F
+            Number Field in a0 with defining polynomial x^4 - 162
+            sage: f(K1.0), g(K2.0)
+            (-1/3*a0, 1/3*a0)
+
+        With preserve_embedding set to False, the embeddings are ignored::
+
+            sage: K1.composite_fields(K2, preserve_embedding=False)
             [Number Field in a0 with defining polynomial x^4 - 162,
-             Number Field in a1 with defining polynomial x^4 - 2,
-             Number Field in a2 with defining polynomial x^8 + 28*x^4 + 2500]
-            sage: k.<a> = NumberField(x^3 + 2)
-            sage: m.<b> = NumberField(x^3 + 2)
-            sage: k.composite_fields(m, 'c')
-            [Number Field in c0 with defining polynomial x^3 - 2,
-             Number Field in c1 with defining polynomial x^6 - 40*x^3 + 1372]
-
-        Let's get the maps as well::
-
-            sage: Q1.<a> = NumberField(x^2 + 2, 'b').extension(x^2 + 3, 'c').absolute_field()
-            sage: Q2.<b> = NumberField(x^2 + 3, 'a').extension(x^2 + 5, 'c').absolute_field()
-            sage: Q1.composite_fields(Q2)
-            [Number Field in ab0 with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600,
-             Number Field in ab1 with defining polynomial x^8 + 160*x^6 + 6472*x^4 + 74880*x^2 + 1296]
-
-            sage: F, Q1_into_F, Q2_into_F, k = Q1.composite_fields(Q2, both_maps=True)[0]
-            sage: F
-            Number Field in ab0 with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600
+             Number Field in a1 with defining polynomial x^8 + 28*x^4 + 2500]
+
+        Changing the embedding selects a different compositum::
+
+            sage: K3 = NumberField(x^4 - 2, 'a', embedding=CC(2^(1/4)*I))
+            sage: [F, f, g, k], = K1.composite_fields(K3, both_maps=True); F
+            Number Field in a0 with defining polynomial x^8 + 28*x^4 + 2500
+            sage: f(K1.0), g(K3.0)
+            (1/240*a0^5 - 41/120*a0, 1/120*a0^5 + 19/60*a0)
+
+        If no embeddings are specified, the maps into the composite are chosen arbitrarily::
+
+            sage: Q1.<a> = NumberField(x^4 + 10*x^2 + 1)
+            sage: Q2.<b> = NumberField(x^4 + 16*x^2 + 4)
+            sage: Q1.composite_fields(Q2, 'c')
+            [Number Field in c with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600]
+            sage: F, Q1_into_F, Q2_into_F, k = Q1.composite_fields(Q2, 'c', both_maps=True)[0]
             sage: Q1_into_F
             Ring morphism:
               From: Number Field in a with defining polynomial x^4 + 10*x^2 + 1
-              To:   Number Field in ab0 with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600
-              Defn: a |--> 19/14400*ab0^7 + 137/1800*ab0^5 + 2599/3600*ab0^3 + 8/15*ab0
-            sage: Q2_into_F
-            Ring morphism:
-              From: Number Field in b with defining polynomial x^4 + 16*x^2 + 4
-              To:   Number Field in ab0 with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600
-              Defn: b |--> 19/7200*ab0^7 + 137/900*ab0^5 + 2599/1800*ab0^3 + 31/15*ab0
-
-            sage: Q1_into_F.domain() is Q1
-            True
-            sage: Q2_into_F(b) + k*Q1_into_F(a) == F.gen()
-            True
-
-        Let's check something about the "other" composite field::
-
-            sage: F, Q1_into_F, Q2_into_F, k = Q1.composite_fields(Q2, both_maps=True)[1]
-            sage: Q1_into_F.domain() is Q1
-            True
-            sage: Q2_into_F(b) + k*Q1_into_F(a) == F.gen()
-            True
+              To:   Number Field in c with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600
+              Defn: a |--> 19/14400*c^7 + 137/1800*c^5 + 2599/3600*c^3 + 8/15*c
+
+        This is just one of four embeddings of Q1 into F::
+            sage: Hom(Q1, F).order()
+            4
 
         TESTS:
 
@@ -2634,90 +2642,47 @@
             sage: K0.<b> = CyclotomicField(7, 'a').subfields(3)[0][0].change_names()
             sage: K1.<a1> = K0.extension(x^2 - 2*b^2, 'a1').absolute_field()
             sage: K2.<a2> = K0.extension(x^2 - 3*b^2, 'a2').absolute_field()
-            sage: K1
-            Number Field in a1 with defining polynomial x^6 - 10*x^4 + 24*x^2 - 8
-            sage: K2
-            Number Field in a2 with defining polynomial x^6 - 15*x^4 + 54*x^2 - 27
-            sage: K1.is_isomorphic(K2)
-            False
 
         We need embeddings, so we redefine::
 
             sage: L1.<a1> = NumberField(K1.polynomial(), 'a1', embedding=CC.0)
-            sage: CDF(a1)
-            -0.629384245426
             sage: L2.<a2> = NumberField(K2.polynomial(), 'a2', embedding=CC.0)
-            sage: CDF(a2)
-            -0.77083512672
+            sage: [CDF(a1), CDF(a2)]
+            [-0.629384245426, -0.77083512672]
+
+        and we get the same embeddings via the compositum::
 
             sage: F, L1_into_F, L2_into_F, k = L1.composite_fields(L2, both_maps=True)[0]
-            sage: CDF(F.gen())
-            -0.141450881294
-
-        Both subfield generators have correct embeddings::
-
-            sage: CDF(L1_into_F(L1.gen())), CDF(L1.gen())
-            (-0.629384245426, -0.629384245426)
-            sage: CDF(L2_into_F(L2.gen())), CDF(L2.gen())
-            (-0.77083512672, -0.77083512672)
-
-        On the other hand, without embeddings, there are more composite fields::
-
-            sage: M1.<a1> = NumberField(L1.polynomial(), 'a1')
-            sage: M2.<a2> = NumberField(L2.polynomial(), 'a2')
-            sage: M1.composite_fields(M2)
-            [Number Field in a1a20 with defining polynomial x^12 - 50*x^10 + 613*x^8 - 1270*x^6 + 526*x^4 - 60*x^2 + 1,
-             Number Field in a1a21 with defining polynomial x^12 - 50*x^10 + 865*x^8 - 6730*x^6 + 24970*x^4 - 43152*x^2 + 27889,
-             Number Field in a1a22 with defining polynomial x^12 - 50*x^10 + 865*x^8 - 6310*x^6 + 18670*x^4 - 14928*x^2 + 1849]
-
-        Here's another example::
-
-            sage: Q1.<a> = NumberField(x^4 + 10*x^2 + 1, embedding=CC.0); Q1, CDF(a)
-            (Number Field in a with defining polynomial x^4 + 10*x^2 + 1, 0.317837245196*I)
-            sage: Q2.<b> = NumberField(x^4 + 16*x^2 + 4, embedding=CC.0); Q2, CDF(b)
-            (Number Field in b with defining polynomial x^4 + 16*x^2 + 4, 0.504017169931*I)
-
-            sage: len(Q1.composite_fields(Q2))
-            1
-            sage: F, Q1_into_F, Q2_into_F, k2 = Q1.composite_fields(Q2, both_maps=True)[0]
-            sage: F, CDF(F.gen())
-            (Number Field in ab1 with defining polynomial x^8 + 160*x^6 + 6472*x^4 + 74880*x^2 + 1296,
-             -0.131657320461*I)
-
-            sage: t = Q2_into_F(Q2.gen()) + k2*Q1_into_F(Q1.gen()); t, CDF(t)
-            (ab1, -0.131657320461*I)
-            sage: abs(t.minpoly()(CDF(t))) < 1e-8
-            True
-
-        Let's check that the preserve_embedding flag is respected::
-        
-            sage: len(Q1.composite_fields(Q2))
-            1
-            sage: len(Q1.composite_fields(Q2, preserve_embedding=False))
-            2
+            sage: [CDF(L1_into_F(L1.gen())), CDF(L2_into_F(L2.gen()))]
+            [-0.629384245426, -0.77083512672]
 
         Let's check that if only one field has an embedding, the resulting
-        fields do not have an embedding::
-        
-            sage: Q2.<b> = NumberField(x^4 + 16*x^2 + 4)
-            sage: Q2.coerce_embedding() is None
-            True
-
-            sage: Q1.composite_fields(Q2)
-            [Number Field in ab0 with defining polynomial x^8 + 64*x^6 + 904*x^4 + 3840*x^2 + 3600,
-             Number Field in ab1 with defining polynomial x^8 + 160*x^6 + 6472*x^4 + 74880*x^2 + 1296]
-            sage: Q1.composite_fields(Q2)[0].coerce_embedding() is None
-            True
-        """
-        if names is None:
-            sv = self.variable_name(); ov = other.variable_name()
-            names = sv + (ov if ov != sv else "")
+        fields do not have embeddings::
+        
+            sage: L1.composite_fields(K2)[0].coerce_embedding() is None
+            True
+            sage: L2.composite_fields(K1)[0].coerce_embedding() is None
+            True
+
+        We check that other can be a relative number field::
+        
+            sage: L.<a, b> = NumberField([x^3 - 5, x^2 + 3])        
+            sage: CyclotomicField(3, 'w').composite_fields(L, both_maps=True)
+            [(Number Field in wa with defining polynomial x^6 - 3*x^5 + 6*x^4 - 17*x^3 + 21*x^2 + 12*x + 16, Ring morphism:
+              From: Cyclotomic Field of order 3 and degree 2
+              To:   Number Field in wa with defining polynomial x^6 - 3*x^5 + 6*x^4 - 17*x^3 + 21*x^2 + 12*x + 16
+              Defn: w |--> -1/36*wa^5 + 5/36*wa^4 - 5/18*wa^3 + 25/36*wa^2 - 35/36*wa - 5/9, Relative number field morphism: 
+              From: Number Field in a with defining polynomial x^3 - 5 over its base field
+              To:   Number Field in wa with defining polynomial x^6 - 3*x^5 + 6*x^4 - 17*x^3 + 21*x^2 + 12*x + 16
+              Defn: a |--> 1/36*wa^5 - 5/36*wa^4 + 5/18*wa^3 - 25/36*wa^2 + 71/36*wa - 4/9
+                    b |--> 1/18*wa^5 - 5/18*wa^4 + 5/9*wa^3 - 25/18*wa^2 + 35/18*wa + 1/9, -1)]
+        """
         if not isinstance(other, NumberField_generic):
             raise TypeError, "other must be a number field."
-        f = self.pari_polynomial()
-        g = other.pari_polynomial()
-
-        R = self.absolute_polynomial().parent()
+
+        sv = self.variable_name(); ov = other.variable_name()
+        if names is None:
+            names = sv + (ov if ov != sv else "")
         name = sage.structure.parent_gens.normalize_names(1, names)[0]
 
         # should we try to preserve embeddings?
@@ -2727,47 +2692,95 @@
         if other.coerce_embedding() is None:
             subfields_have_embeddings = False
         if subfields_have_embeddings:
-            if self.coerce_embedding().codomain() is not other.coerce_embedding().codomain():
+            try:
+                from sage.categories.pushout import pushout
+                ambient_field = pushout(self.coerce_embedding().codomain(), other.coerce_embedding().codomain())
+            except CoercionException:
+                ambient_field = None
+            if ambient_field is None:
                 subfields_have_embeddings = False
 
+        f = self.pari_polynomial()
+        g = other.pari_polynomial()
+        R = self.absolute_polynomial().parent()
+
         if not both_maps and not subfields_have_embeddings:
             # short cut!
-            C = map(R, f.polcompositum(g))
-            return [ NumberField(C[i], name + str(i)) for i in range(len(C)) ]
-
-        # If flag = 1, outputs a vector of 4-component vectors [R, a, b,
-        # k], where R ranges through the list of all possible compositums
+            # eliminate duplicates from the fields given by polcompositum
+            # and return the resulting number fields.  There is no need to
+            # check that the polynomials are irreducible.
+            C = []
+            for r in f.polcompositum(g):
+                if not any(r.nfisisom(s) for s in C):
+                    C.append(r)
+            C = map(R, C)
+
+            if len(C) == 1 and name != sv and name != ov:
+                names =[name]
+            else:
+                names = [name + str(i) for i in range(len(C))]
+
+            return [ NumberField(r, names[i], check=False) for i, r in enumerate(C) ]
+            
+        # If flag = 1, polcompositum outputs a vector of 4-component vectors 
+        # [R, a, b, k], where R ranges through the list of all possible compositums
         # as above, and a (resp. b) expresses the root of P (resp. Q) as
         # an element of Q(X )/(R). Finally, k is a small integer such that
-        # b + ka = X modulo R.
-        C = f.polcompositum(g, 1)
-        rets = []
-
-        embedding = None
+        # b + ka = X modulo R.  
+        # In this case duplicates must only be eliminated if embeddings are going 
+        # to be preserved.
+        C = []
+        for v in f.polcompositum(g, 1):
+            if subfields_have_embeddings or not any(v[0].nfisisom(u[0]) for u in C):
+                C.append(v)
+        
         a = self.gen()
         b = other.gen()
-        for i in range(len(C)):
-            r, a_in_F, b_in_F, k = C[i]
+
+        # If both subfields are provided with embeddings, then we must select
+        # the compositum which corresponds to these embeddings.  We do this by
+        # evaluating the given polynomials at the corresponding embedded values.
+        # For the case we want the result will be zero, but rounding errors are 
+        # difficult to predict, so we just take the field which yields the 
+        # mimumum value.
+        if subfields_have_embeddings:
+            poly_vals = []
+            for r, _, _, k in C:
+	        r = R(r)
+	        k = ZZ(k) # essential
+                embedding = other.coerce_embedding()(b) + k*self.coerce_embedding()(a)
+                poly_vals.append(sage.rings.complex_double.CDF(r(embedding)).abs()) 
+            i = poly_vals.index(min(poly_vals))
+            C = [C[i]]
+
+        if len(C) == 1 and name != sv and name != ov:
+            names =[name]
+        else:
+            names = [name + str(i) for i in range(len(C))]
+
+        if both_maps and not other.is_absolute():
+            other_abs = other.absolute_field('z')
+            _, to_other_abs = other_abs.structure()
+
+        embedding = None
+        rets = []
+        for i, [r, a_in_F, b_in_F, k] in enumerate(C):
             r = R(r)
-            k = ZZ(k)
-
+            k = ZZ(k) # essential
             if subfields_have_embeddings:
                 embedding = other.coerce_embedding()(b) + k*self.coerce_embedding()(a)
-                if r(embedding) > 1e-30: # XXX how to do this more generally?
-                    continue
-
-            F = NumberField(r, name + str(i), embedding=embedding)
-            a_in_F = F(a_in_F)
-            b_in_F = F(b_in_F)
-            into_F1 = self.hom ([a_in_F], check=True)
-            into_F2 = other.hom([b_in_F], check=True)
-            assert into_F2(b) + k*into_F1(a) == F.gen()
-            rets.append( (F, into_F1, into_F2, k) )
-
-        if not both_maps:
-            return [ F for F, _, _, _ in rets ]
-        else:
-            return rets
+            F = NumberField(r, names[i], check=False, embedding=embedding)
+            if both_maps:
+                self_to_F = self.hom ([F(a_in_F)])
+                if other.is_absolute():
+                    other_to_F = other.hom([F(b_in_F)])
+                else:
+                    other_abs_to_F = other_abs.hom([F(b_in_F)])
+                    other_to_F = RelativeNumberFieldHomomorphism_from_abs(other.Hom(F), other_abs_to_F*to_other_abs)
+                rets.append( (F, self_to_F, other_to_F, k) )
+            else:
+                rets.append(F)
+        return rets
 
     def absolute_degree(self):
         """
diff -r 964c2f4ce74d -r e839322b0afc sage/rings/number_field/number_field_rel.py
--- a/sage/rings/number_field/number_field_rel.py	Thu Dec 10 18:56:58 2009 -0800
+++ b/sage/rings/number_field/number_field_rel.py	Mon Dec 14 20:40:02 2009 +0000
@@ -511,10 +511,83 @@
             sage: K.<a,b> = NumberField([x^4 + 3, x^2 + 2]); K
             Number Field in a with defining polynomial x^4 + 3 over its base field
             sage: K.galois_closure('c')
-            Number Field in c with defining polynomial x^16 + 144*x^14 + 8988*x^12 + 329616*x^10 + 7824006*x^8 + 113989680*x^6 + 1360354716*x^4 + 3470308272*x^2 + 9407642049
+            Number Field in c with defining polynomial x^16 + 16*x^14 + 28*x^12 + 784*x^10 + 19846*x^8 - 595280*x^6 + 2744476*x^4 + 3212848*x^2 + 29953729
         """
         return self.absolute_field('a').galois_closure(names=names)
 
+    def composite_fields(self, other, names=None, both_maps=False, preserve_embedding=True):
+        """
+        List of all possible composite number fields formed from self and
+        other, together with (optionally) embeddings into the compositum;
+        see the documentation for both_maps below.
+
+        Since relative fields do not have ambient embeddings,
+        preserve_embedding has no effect.  In every case all possible
+        composite number fields are returned.
+
+        INPUT:
+            
+        - ``other`` - a number field
+
+        - ``names`` - generator name for composite fields
+
+        - ``both_maps`` - (default: False)  if True, return quadruples 
+          (F, self_into_F, other_into_F, k) such that self_into_F maps self into
+          F, other_into_F maps other into F, and k is an integer such that
+          other_into_F(other.gen()) + k*self_into_F(self.gen()) == F.gen()
+          If both self and other have embeddings into an ambient field, then 
+          F will have an embedding with respect to which both self_into_F 
+          and other_into_F will be compatible with the ambient embeddings.
+
+        - ``preserve_embedding`` - (default: True) has no effect, but is kept
+          for compatibility with the absolute version of this function.  In every
+          case the list of all possible compositums is returned.
+        
+        OUTPUT:
+        
+        -  ``list`` - list of the composite fields, possibly with maps.
+        
+        
+        EXAMPLES::
+        
+            sage: K.<a, b> = NumberField([x^2 + 5, x^2 - 2])
+            sage: L.<c, d> = NumberField([x^2 + 5, x^2 - 3])
+            sage: K.composite_fields(L)
+            [Number Field in ac with defining polynomial x^8 - 24*x^6 + 464*x^4 + 3840*x^2 + 25600]
+            sage: K.composite_fields(L, both_maps=True)
+            [[Number Field in ac with defining polynomial x^8 - 24*x^6 + 464*x^4 + 3840*x^2 + 25600,
+              Relative number field morphism:
+              From: Number Field in a with defining polynomial x^2 + 5 over its base field
+             To:   Number Field in ac with defining polynomial x^8 - 24*x^6 + 464*x^4 + 3840*x^2 + 25600
+              Defn: a |--> -9/66560*ac^7 + 11/4160*ac^5 - 241/4160*ac^3 - 101/104*ac
+                    b |--> -21/166400*ac^7 + 73/20800*ac^5 - 779/10400*ac^3 + 7/260*ac,
+              Relative number field morphism:
+              From: Number Field in c with defining polynomial x^2 + 5 over its base field
+              To:   Number Field in ac with defining polynomial x^8 - 24*x^6 + 464*x^4 + 3840*x^2 + 25600
+              Defn: c |--> -9/66560*ac^7 + 11/4160*ac^5 - 241/4160*ac^3 - 101/104*ac
+                    d |--> -3/25600*ac^7 + 7/1600*ac^5 - 147/1600*ac^3 + 1/40*ac,
+              -2]]
+        """
+        if not isinstance(other, NumberField_generic):
+            raise TypeError, "other must be a number field."
+        if names is None:
+            sv = self.variable_name(); ov = other.variable_name()
+            names = sv + (ov if ov != sv else "")
+
+        self_abs = self.absolute_field('w')
+        abs_composites = self_abs.composite_fields(other, names=names, both_maps=both_maps)
+
+        if not both_maps:
+            return abs_composites
+
+        _, to_self_abs = self_abs.structure()
+
+        rets = []
+        for F, self_abs_to_F, other_to_F, k in abs_composites:
+            self_to_F = RelativeNumberFieldHomomorphism_from_abs(self.Hom(F), self_abs_to_F*to_self_abs)
+            rets.append([F, self_to_F, other_to_F, k])
+        return rets
+
     def absolute_degree(self):
         """
         The degree of this relative number field over the rational field.
