Hi everyone,

I am trying to integrate polynomials on an n-sphere and implemented (see 
attached) the algorithm published in Alan Genz "Fully Symmetric 
Interpolatory Rules for Multiple Integrals over Hyper-Spherical Surfaces" 
Journal of Computational and Applied Mathematics 157: 187-195, 2003. On 
double precision, everything is fine; however, I lose precision in some 
integrals using more than double prec.

I am using randomized samples on the sphere which (by construction) should 
integrate the polynomials (that I tested) exactly. However, integrating all 
polynomials up to degree 6 on a 5-dimensional sphere with points that 
should integrate all polynomials up to degree 7 exactly, I obtain the 
following test results :

mp.prec = 1024 (aka mp.dps = 307)

polynomial  |  degree of polynomial  |  log_10 ( difference of two 
randomized integrations )  |  log_10 ( relative error of weights )  | 
 analytic value of integral

[0, 0, 0, 0, 0, 0]      0      -inf                     -306.962262     
 31.0062767
[1, 0, 0, 0, 0, 0]      1      -308.111506      -306.962262      0.0
[2, 0, 0, 0, 0, 0]      2      -14.519354        -306.962262      5.16771278
[1, 1, 0, 0, 0, 0]      2      -14.8787358      -306.962262      0.0
[3, 0, 0, 0, 0, 0]      3      -308.583548      -306.962262      0.0
[2, 1, 0, 0, 0, 0]      3      -309.157806      -306.962262      0.0
[1, 1, 1, 0, 0, 0]      3      -309.759866      -306.962262      0.0
[4, 0, 0, 0, 0, 0]      4      -14.6442927      -306.962262      1.93789229
[3, 1, 0, 0, 0, 0]      4      -15.3047045      -306.962262      0.0
[2, 2, 0, 0, 0, 0]      4      -15.0673319      -306.962262      0.645964098
[2, 1, 1, 0, 0, 0]      4      -16.0594155      -306.962262      0.0
[1, 1, 1, 1, 0, 0]      4      -31.8146065      -306.962262      0.0
[5, 0, 0, 0, 0, 0]      5      -309.37189        -306.962262      0.0
[4, 1, 0, 0, 0, 0]      5      -309.481766      -306.962262      0.0
[3, 2, 0, 0, 0, 0]      5      -309.739262      -306.962262      0.0
[3, 1, 1, 0, 0, 0]      5      -310.095075      -306.962262      0.0
[2, 2, 1, 0, 0, 0]      5      -310.474598      -306.962262      0.0
[2, 1, 1, 1, 0, 0]      5      -310.714154      -306.962262      0.0
[1, 1, 1, 1, 1, 0]      5      -311.46643        -306.962262      0.0
[6, 0, 0, 0, 0, 0]      6      -14.7692314      -306.962262      0.968946146
[5, 1, 0, 0, 0, 0]      6      -15.6057345      -306.962262      0.0
[4, 2, 0, 0, 0, 0]      6      -15.4314091      -306.962262      0.193789229
[3, 3, 0, 0, 0, 0]      6      -15.8275833      -306.962262      0.0
[4, 1, 1, 0, 0, 0]      6      -16.5822942      -306.962262      0.0
[3, 2, 1, 0, 0, 0]      6      -16.4753651      -306.962262      0.0
[2, 2, 2, 0, 0, 0]      6      -16.0997045      -306.962262     
 0.0645964098
[3, 1, 1, 1, 0, 0]      6      -32.3374853      -306.962262      0.0
[2, 2, 1, 1, 0, 0]      6      -17.3552924      -306.962262      0.0
[2, 1, 1, 1, 1, 0]      6      -33.4100353      -306.962262      0.0
[1, 1, 1, 1, 1, 1]      6      -49.1751847      -306.962262      0.0

It should be noted that the polynomial are written in the following form: a 
list p of length n represents the polynomial  

z_0^{p[0]}  z_1^{p[1]}  z_2^{p[2]}  ...  z_{n-1}^{p[n-1]}.

If we look at the second to last column, we can see that the weights add 
are exact with respect to mp.prec=1024 but do not add up to 1.0 exactly. 
Hence, the loss in precision is unlikely to be caused by the weights being 
only on double prec (and even if it is, then I don't understand why because 
they are calculated with mp.prec=1024, as well). 

The really interesting column is the middle one. Here, I took two 
randomized set of integration points (both should integrate all polynomials 
exactly up to machine error) and printed the decadic logarithm of the 
absolute value of their difference. In other words, we are expecting the 
middle column to be populated with numbers in the vicinity of -307 (just 
like the fourth column). However, we only get these values for odd degree 
polynomials (and the constant 1 but that is just the sum of weights, i.e., 
the randomization doesn't do anything). For even degree polynomials, we 
have significantly reduced precision. 

Does anyone have an idea what might be the reason for such behavior?

Thank you very much,
Tobias

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sympy.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/cf76f223-96a0-4789-8530-db224622f6bb%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
import sympy as smp
import numpy
from copy import deepcopy

mp = smp.mpmath

prec = 1024         # set precision of computation in bit
mp.mp.prec = prec   # do not set this parameter directly as it will be changed for output purposes and reset to prec above
    
# define function to cycle partitions 
def next_partition(old_part):                   
    new_part = deepcopy(old_part)
    part_sum = new_part[0]
    for i in range(1,len(old_part)):
        part_sum += new_part[i]
        if new_part[0] <= new_part[i]+1:
            new_part[i] = 0
        else:
            new_part[0] = part_sum - i * (new_part[i]+1)
            for k in range(1,i+1):
                new_part[k] = new_part[i]+1
            return new_part
    new_part[0] = part_sum + 1
    return new_part
       

# find all partitions 
def get_all_partitions(m,dim):
    part = [m]+[0]*(dim-1)
    while sum(part) == m:
        yield part
        part = deepcopy(next_partition(part))
        

# volume of S^{dim-1} - 2 \Gamma(1/2)^{dim} / \Gamma(dim/2)
def vol_factor(dim):
    return mp.mpmathify(2.)*mp.power(mp.gamma(.5),mp.mpmathify(dim))/mp.gamma(mp.mpmathify(dim)/2.)

# normalized weights
def get_weight(s,g,d):
    w_s = mp.mpmathify(0.)
    k = [0]*d
    while True:
        i_s = 0
        k_s = 0
        m_c = 0
        k_n = mp.mpmathify(1.)
        p_d = mp.mpmathify(1.)
        for i in range(d):
            i_s += 1
            if k[i] == 0:
                p_d *= mp.mpmathify(1.-i_s)
            else:
                p_d *= mp.mpmathify(d*k_n)/mp.mpmathify(s+k_s)
                k_s += 2
                k_n += 2
            if i_s == g[m_c]:
                m_c += 1
                i_s = 0
                k_n = 1
        w_s += p_d
        for i in range(d):
            k[i] += 1
            if k[i] < 2:
                break
            k[i] = 0
        if i == d-1 and k[d-1] == 0:
            break
    for i in range(s):
        for j in range(g[i]):
            w_s = w_s/mp.mpmathify( g[i]-j )

    c = 0
    j = 0
    while j < len(g) and g[j] > 0:
        c += 1
        j += 1
    return mp.power(mp.mpmathify(2.),mp.mpmathify(-c))*w_s
    
    
    
# calculate all permutations (including identical permutations, i.e., [1,1,0] will appear twice)
def permute(l,low=0):
    if low+1 >= len(l):
        yield l
    else:
        for l1 in permute(l,low+1):
            yield l1
        for i in range(low+1,len(l)):
            l[low],l[i]=l[i],l[low]
            for l1 in permute(l,low+1):
                yield l1
            l[low],l[i]=l[i],l[low]


# calculate all sign combinations of non-zero elements
def signs(p,low=0):
    if low >= len(p):
        yield p
    else:
        for p1 in signs(p,low+1):
            yield p1
        if not (p[low]==0.):
            p[low] *= -1.0
            for p1 in signs(p,low+1):
                yield p1
            p[low] *= -1.0 

# calculate a random orthogonal Matrix
def random_orthogonal(n): 
    O = numpy.zeros((n,n))
    while numpy.linalg.det(O) == 0:
        O = numpy.random.normal(size=(n,n))         #generate nxn matrix with normally distributed entries and det =/= 0
        
    Q,R = numpy.linalg.qr(O)                        # QR decomposition: Q is obtained by Gram Schmidt on the columns of O
    return Q

class spherical_rule:       # Genz's method for S^k with k > 2; for k = 2, use t-design
    
    def __init__(self,dim,deg,randomized=True,normalized = False):
        self.__dim = dim                                                        # dimension of space
        self.__m = max(int(deg/2),1)                                            # degree parameter m
        self.__deg = 2*self.__m+1                                               # exact up to degree self.__deg
        self.__random = randomized                                              # shall points be randomized? default = yes
        self.__normalized = normalized                                          # shall weights be normalized? default = no
        self.__vol_fact = vol_factor(dim)                                       # get volume of sphere
        self.__random_orthogonal = mp.matrix(random_orthogonal(dim))            # get a randomized orthogonal matrix
        
        self.points = []                                                        # calculate all points of the spherical rule
        for p in self.__generate_points():
            if p not in self.points:
                self.points.append(p)
    
    
    def __generate_points(self): 
        if self.__dim == 2:                                                     # for S^2 use the k-th unit roots as (k-1)-design
            phase = mp.mpmathify(1.)
            if self.__random:                                                   # if randomizied, add a randomly chosen phase shift
                phase = mp.exp(mp.mpmathify(2.j)*mp.pi*mp.rand())
                
            w = mp.mpmathify(1.)/mp.mpmathify(self.__deg + 1.)                  # weights of k-point t-design: 1/k
            if not self.__normalized:
                w *= self.__vol_fact                                            # normalized weights to be multiplied by volume of sphere for spherical integration
            for root in mp.unitroots(self.__deg + 1):                           # cycle all unit roots
                x = phase * root                                                # add phase shift
                yield [ mp.matrix([[x.real],[x.imag]]) , w ]                    # return point (vector) and weight in a list
                
        else:       # for all other dimensions, use Genz
            for partition in get_all_partitions(self.__m, self.__dim):          # generate all partitions of m in dim summands -> generates points
                w = get_weight(self.__dim, partition, self.__m)                 # calculate corresponding weight (normalized)
                if not self.__normalized:                                       # if spherical integration, multiply by volume of sphere
                    w *= self.__vol_fact
                
                # let g be the generator, then the component x[i] of the raw point associated with g is given by sqrt( g[i]/m )
                p_raw = []
                for i in range(len(partition)):
                    p_raw.append(mp.sqrt( mp.mpmathify(partition[i])/mp.mpmathify(self.__m) ))
                
                for p_permuted in permute(p_raw):                                   # raw point needs to be permuted
                    for p in signs(p_permuted):                                     # and signs need to be swapped
                        if self.__random:                                           # randomize if wanted by multiplying point with orthogonal matrix we initialized
                            yield [ self.__random_orthogonal*mp.matrix(p) , w ]     # return randomized point and weight
                        else:
                            yield [ mp.matrix(p) , w ]                              # return non-randomized point and weight otherwise
                                                    # forcing the program to terminate
            
#####################################
##### test polynomial exactness #####
#####################################

# calculate \int_{S^{n-1}} z_1^{a_1} ... z_n^{a_n} d\vol_{S^{n-1}}(z) 
# result is zero if at least one a_j is odd
# otherwise let b_j := (a_j+1)/2. then \int = 2 \Gamma(b_1) ... \Gamma(b_n) / \Gamma(b_1+...+b_n) = 2 / \Gamma(\sum_{j=1}^n b_j) * \prod_{j=1}^n\Gamma(b_j)
def analytical(powers):
    r = mp.mpmathify(2.)
    beta = mp.mpmathify(0.)
    for j in range(len(powers)):
        r *= mp.mpmathify((powers[j]+1)%2) * mp.gamma(mp.mpmathify(powers[j]+1.)/mp.mpmathify(2.))
        beta += mp.mpmathify(powers[j]+1.)/mp.mpmathify(2.)
    return r/mp.gamma(beta)


# run a test integrating all monomials of degree <= deg over S^{dim-1}
def test(dim,deg,rand):
    points = spherical_rule(dim,deg,rand,False).points                              # calculate points and weights
    for degree in range(deg+1):                                                     # run over all monomial degrees
        for p in get_all_partitions(degree,dim):                                    # get all monomials (ordered by highest power since everything is symmetric)
            integral = mp.mpmathify(0.)
            weights = mp.mpmathify(0.)
            for point in points:
                int_add = mp.mpmathify(1.)
                for i in range(dim):
                    int_add *= mp.power(point[0][i,0],p[i])                         # calculate product z_i^{p_i}
                integral += int_add * point[1]                                      # multiply with weight and sum up
                weights += point[1]                                                 # sum up weights
            """
            if analytical(p) == 0.:
                print "abs",p, mp.log(abs(integral-analytical(p)),10) #integral,analytical(p),abs(integral-analytical(p)) # divide by sum of weights and multiply with volume of the sphere - absolute error (analytic value is zero)
            else:
                print "rel",p, mp.log(abs(integral-analytical(p))/analytical(p),10) #integral,analytical(p),(integral-analytical(p))/analytical(p)  # divide by sum of weights and multiply with volume of the sphere - relative error (analytic non-zero)
            """
            yield p,integral,weights,analytical(p)                                  # return the list of powers, the integral, the sum of weights, and the analytical integral

                
if __name__ == "__main__":
    print mp.mp
    dim = 6
    deg = 6
    rand = True
    
    
    y = test(dim,deg,rand)
    for x in test(dim,deg,rand):                                                                # getting two randomized quadrature rules and run the test
        z = next(y)                                                                             # cycling the generator y
        a,b = mp.log(abs(x[1]-z[1]),10),mp.log(abs(x[2]-vol_factor(dim))/vol_factor(dim),10)    # get log (decadic) of difference of the two integations and of the relative error of the weights
        with mp.workprec(32):                                                                   # reduce precision to single (otherwise output in next line will be hard to read)
            print x[0],sum(x[0]),a,b,x[3]                                                       # print list of powers, degree of monomial, the two logarithms above, and the analytic value of the integral
    
    
    

    

Reply via email to