Changeset 8201:4e9b1bf4cf99


Ignore:
Timestamp:
11/28/07 08:24:54 (5 years ago)
Author:
dfdeshom@…
Branch:
default
Message:

Better random mpoly generation as proposed by malb and Steffen
<sreidt@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sage/rings/polynomial/multi_polynomial_ring_generic.pyx

    r6606 r8201  
    11include '../../ext/stdsage.pxi' 
     2 
    23 
    34from sage.structure.parent_gens cimport ParentWithGens 
    45import sage.misc.latex 
    5 import multi_polynomial_ideal 
     6import multi_polynomial_ideal  
    67from term_order import TermOrder 
    78from sage.rings.integer_ring import ZZ 
     
    910import multi_polynomial_element 
    1011import polynomial_ring 
     12 
    1113 
    1214def is_MPolynomialRing(x): 
     
    423425        return unpickle_MPolynomialRing_generic_v1,(base_ring, n, names, order) 
    424426 
    425  
    426     def random_element(self, degree=2, terms=5, *args, **kwds): 
    427         r""" 
    428         Return a random polynomial in this polynomial ring. 
    429          
     427    def _precomp_counts(self,n, d): 
     428        """ 
     429        Given a number of variable n and a degree d return a tuple (C,t) 
     430        such that C is a list of the cardinalities of the sets of 
     431        monomials up to degree d (including) in n variables and t is the 
     432        sum of these cardinalities. 
     433 
    430434        INPUT: 
    431             degree -- maximum total degree of resulting polynomial 
    432             terms  -- maximum number of terms to generate 
    433  
    434         OUTPUT: a random polynomial of total degree \code{degree} 
    435                 and with \code{term} terms in it. 
     435            n -- number of variables 
     436            d -- degree 
     437 
     438        EXAMPLE: 
     439        sage: C,t = _precomp_counts(10,2) 
     440        sage: C[0] 
     441        1 
     442        sage: C[1] 
     443        10 
     444        sage: C[2] 
     445        55 
     446        sage: t 
     447        66 
     448 
     449        TESTS: 
     450        sage: C,t = _precomp_counts(1,2) 
     451        sage: C[0] 
     452        1 
     453        sage: C[1] 
     454        1 
     455        sage: C[2] 
     456        1 
     457        sage: t 
     458        3 
     459         
     460        """ 
     461        from sage.rings.arith import binomial 
     462        C = [1]  #d = 0 
     463        for dbar in xrange(1, d+1): 
     464            C.append(binomial(n+dbar-1, dbar)) 
     465        t = sum(C) 
     466        return C, t 
     467             
     468    def _to_monomial(self,i, n, d): 
     469        """ 
     470        Given an index i, a number of variables n and a degree d return 
     471        the i-th monomial of degree d in n variables. 
     472         
     473        INPUT: 
     474            i -- index: 0 <= i < binom(n+d-1,n-1) 
     475            n -- number of variables 
     476            d -- degree 
     477 
     478        EXAMPLE: 
     479        sage: _to_monomial(0,10,2) 
     480        (0, 0, 0, 0, 0, 0, 0, 0, 0, 2) 
     481        sage: _to_monomial(8,10,2) 
     482        (0, 0, 0, 0, 0, 0, 1, 1, 0, 0) 
     483        sage: _to_monomial(54,10,2) 
     484        (2, 0, 0, 0, 0, 0, 0, 0, 0, 0) 
     485 
     486        NOTE: We do not check if the provided index/rank is within the 
     487        allowed range. If it is not an infinite loop will occure. 
     488        """ 
     489        from sage.combinat import choose_nk 
     490        comb = choose_nk.from_rank(i, n+d-1, n-1) 
     491        if comb == []: 
     492            return (d,) 
     493        monomial = [ comb[0] ] 
     494        res = [] 
     495        for j in range(n-2): 
     496            res.append(comb[j+1]-comb[j]-1) 
     497        monomial += res 
     498        monomial.append( n+d-1-comb[-1]-1 ) 
     499        return tuple(monomial) 
     500 
     501    def _random_monomial_upto_degree_class(self,n, degree, counts=None, total=None): 
     502        """ 
     503        Choose a random exponent tuple for $n$ variables with a random 
     504        degree $d$, i.e. choose the degree uniformly at random first 
     505        before choosing a random monomial. 
     506         
     507        INPUT: 
     508            n -- number of variables 
     509            degree -- degree of monomials 
     510            counts -- ignored 
     511            total -- ignored 
     512            """ 
     513        # bug: doesn't handle n=1 
     514        from sage.rings.arith import binomial 
     515        #Select random degree 
     516        d = ZZ.random_element(0,degree+1) 
     517        total = binomial(n+d-1, d) 
     518         
     519        #Select random monomial of degree d 
     520        random_index = ZZ.random_element(0, total-1) 
     521        #Generate the corresponding monomial 
     522        return self._to_monomial(random_index, n, d) 
     523 
     524    def _random_monomial_upto_degree_uniform(self,n, degree, counts=None, total=None): 
     525        """ 
     526        Choose a random exponent tuple for $n$ variables with a random 
     527        degree up to $d$, i.e. choose a random monomial uniformly random 
     528        from all monomials up to degree $d$. This discriminates against 
     529        smaller degrees because there are more monomials of bigger 
     530        degrees. 
     531     
     532        INPUT: 
     533            n -- number of variables 
     534            degree -- degree of monomials 
     535            counts -- ignored 
     536            total -- ignored 
     537            """ 
     538        if counts is not None or total is not None: 
     539            counts, total = self._precomp_counts(n, degree) 
     540             
     541        #Select a random one 
     542        random_index = ZZ.random_element(0, total-1) 
     543        #Figure out which degree it corresponds to 
     544        d = 0 
     545        while random_index >= counts[d]: 
     546            random_index -= counts[d] 
     547            d += 1 
     548        #Generate the corresponding monomial 
     549        return self._to_monomial(random_index, n, d) 
     550 
     551    def random_element(self, d, t, choose_degree=False,*args, **kwargs): 
     552        """ 
     553        Return a random polynomial of at most degree $d$ and at most $t$ 
     554        terms. 
     555 
     556        First monomials are chosen uniformly random from the set of all 
     557        possible monomials of degree up to $d$ (inclusive). This means 
     558        that it is more likely that a monomial of degree $d$ appears than 
     559        a monomial of degree $d-1$ because the former class is bigger. 
     560 
     561        Exactly $t$ \emph{distinct} monomials are chosen this way and each 
     562        one gets a random coefficient (possibly zero) from the base ring 
     563        assigned.  
     564 
     565        The returned polynomial is the sum of this list of terms. 
     566 
     567        INPUT: 
     568            d -- maximal degree (likely to be reached) 
     569            t -- number of terms requested 
     570            choose_degree -- choose degrees of monomials randomly first rather 
     571                             than monomials uniformly random. 
     572            **kwargs -- passed to the random element generator of the base ring 
    436573 
    437574        EXAMPLES: 
    438             sage: [QQ['x,y'].random_element() for _ in range(5)] 
    439             [-1/14*x*y + 1/2*x, x*y + x - y + 1, 3*x*y + x - 1/2, 1/3*x*y - 5*x + 1/2*y + 7/6, 2*x*y + 1/2*x + 1] 
    440             sage: R = MPolynomialRing(ZZ, 'x,y',2 ); 
    441             sage: R.random_element(2)          # random 
    442             -1*x*y + x + 15*y - 2 
    443             sage: R.random_element(12)         # random 
    444             x^4*y^5 + x^3*y^5 + 6*x^2*y^2 - x^2 
    445             sage: R.random_element(12,3)       # random 
    446             -3*x^4*y^2 - x^5 - x^4*y 
    447             sage: R.random_element(3)          # random 
    448             2*y*z + 2*x + 2*y 
    449  
    450             sage: R.<x,y> = MPolynomialRing(RR) 
    451             sage: R.random_element(2)          # random 
    452             -0.645358174399450*x*y + 0.572655401740132*x + 0.197478565033010 
    453  
    454             sage: R.random_element(41)         # random 
    455             -4*x^6*y^4*z^4*a^6*b^3*c^6*d^5 + 1/2*x^4*y^3*z^5*a^4*c^5*d^6 - 5*x^3*z^3*a^6*b^4*c*d^5 + 10*x^2*y*z^5*a^4*b^2*c^3*d^4 - 5*x^3*y^5*z*b^2*c^5*d 
    456  
    457         AUTHOR: 
    458             -- didier deshommes 
    459         """ 
    460         # General strategy: 
    461         # generate n-tuples of numbers with each element in the tuple 
    462         # not greater than  (degree/n) so that the degree  
    463         # (ie, the sum of the elements in the tuple) does not exceed 
    464         # their total degree 
    465          
    466         n = self.__ngens         # length of the n-tuple 
    467         max_deg = int(degree/n)  # max degree for each term 
    468         R = self.base_ring() 
    469          
    470         # restrict exponents to positive integers only 
    471         exponents = [ tuple([ZZ.random_element(0,max_deg+1) for _ in range(n)]) 
    472                        for _ in range(terms) ] 
    473         coeffs = [] 
    474         for _ in range(terms): 
    475             c = R.random_element(*args,**kwds) 
    476             while not c: 
    477                 c = R.random_element(*args,**kwds) 
    478             coeffs.append(c) # allow only nonzero coefficients 
    479  
    480         d = dict( zip(tuple(exponents), coeffs) )  
    481         return self(multi_polynomial_element.MPolynomial_polydict(self, PolyDict(d))) 
    482  
     575        sage: P.<x,y,z> = PolynomialRing(QQ) 
     576        sage: random_element(P, 2, 5) 
     577        1/2*y^2 + x*z - x + 2*y + 19/2*z 
     578 
     579        sage: random_element(P, 2, 5, choose_degree=True) 
     580        28*x*z + y*z - z^2 - 1/2*x - 44/39 
     581   
     582        sage: random_element(P, 2, 15) 
     583        Traceback (most recent call last): 
     584        ... 
     585        Cannot compute polynomial with more terms than exist. 
     586 
     587        sage: random_element(P, 0, 1) 
     588        1 
     589 
     590        sage: random_element(P, 2, 0) 
     591        0 
     592 
     593        """ 
     594        from sage.combinat.integer_vector import IntegerVectors 
     595        from sage.rings.arith import binomial 
     596         
     597        k = self.base_ring() 
     598        n = self.ngens() 
     599 
     600        #total or d is 0. Just return 
     601        if 0 == total or 0 == d: 
     602            return tuple([0]*n) 
     603 
     604        counts, total = self._precomp_counts(n, d) 
     605         
     606 
     607        if t < 0: 
     608            raise TypeError, "Cannot compute polynomial with a negative number of terms." 
     609 
     610        elif t < total/2: 
     611            # we choose random monomials if t < total/2 because then we 
     612            # expect the algorithm to be faster than generating all 
     613            # monomials and picking a random index from the list. if t == 
     614            # total/2 we expect every second random monomial to be a 
     615            # double such that our runtime is doubled in the worst case. 
     616            M = set() 
     617            if not choose_degree: 
     618                while t: 
     619                    m = self._random_monomial_upto_degree_uniform(n, d, counts, total) 
     620                    if not m in M: 
     621                        M.add(m) 
     622                        t -= 1 
     623            else: 
     624                while t: 
     625                    m = self._random_monomial_upto_degree_class(n, d) 
     626                    if not m in M: 
     627                        M.add(m) 
     628                        t -= 1 
     629        elif t <= total: 
     630            # generate a list of all monomials and choose among them 
     631            if not choose_degree: 
     632                M = sum([list(IntegerVectors(_d,n)) for _d in xrange(d+1)],[]) 
     633                for mi in xrange(total - t): # we throw away those we don't need 
     634                    M.pop( ZZ.random_element(0,len(M)-1) ) 
     635                M = map(tuple, M) 
     636            else: 
     637                M = [list(IntegerVectors(_d,n)) for _d in xrange(d+1)] 
     638                Mbar = [] 
     639                for mi in xrange(t):  
     640                    d = ZZ.random_element(0,len(M)) #choose degree at random 
     641                    m = ZZ.random_element(0,len(M[d])) # choose monomial at random 
     642                    Mbar.append( M[d].pop(m) ) # remove and insert 
     643                    if len(M[d]) == 0: 
     644                        M.pop(d) # bookkeeping 
     645                M = map(tuple, Mbar) 
     646                 
     647        else: 
     648            raise TypeError, "Cannot compute polynomial with more terms than exist." 
     649 
     650        C = [k.random_element(*args,**kwargs) for _ in range(len(M))] 
     651     
     652        return self(dict(zip(M,C))) 
     653         
    483654    def new_ring(self, names=None, order=None): 
    484655        """ 
Note: See TracChangeset for help on using the changeset viewer.