Search code examples
pythonnumpymatlabscipynumerical-integration

dblquad in scipy vs. matlab giving different results


I want to double integrate a function. But I get different results when using dblquad over scipy.integrate and matlab. A python implementation of my function to double integrate is like this:

###Python implementation##
import numpy as np
from scipy.integrate import dblquad

def InitialCondition(x_b, y_b, m10, m20, N0):

    IC = np.zeros((len(x_b)-1,len(y_b)-1))
    for i in xrange(len(x_b) - 1):
        for j in xrange(len(y_b) - 1):
            IC[i,j], abserr =  dblquad(ExponenIC, x_b[i], x_b[i + 1], lambda x: y_b[j], lambda x: y_b[j+1], args=(m10, m20, N0), epsabs=1.49e-15, epsrel=1.49e-15)
    return IC

def ExponenIC(x, y, m10, m20, N0):

    retVal = (16 * N0) / (m10 * m20) * (x / m10)* (y / m20) * np.exp(-2 * (x / m10) - 2 * (y / m20))

    return retVal


if __name__=='__main__':
    x_min, x_max  = 0.0004, 20.0676
    x_b = np.exp(np.linspace(np.log(x_min), np.log(x_max), 4))
    y_b = np.copy(x_b)
    m10, m20, N0 = 0.04, 0.04, 1
    print InitialCondition(x_b, y_b, m10, m20, N0)

But if I repeat the same in matlab, with equivalent implementation and same input as shown below:

%%%Matlab equivalent%%%
function IC = test(x_b, y_b, m10, m20, N0)
for i = 1:length(x_b)-1
      for j = 1:length(y_b)-1
          IC(i, j) = dblquad(@ExponenIC, x_b(i), x_b(i+1), y_b(j), y_b(j+1), 1e-6, @quad, m10, m20, N0);
      end
end
return

function retVal = ExponenIC(x, y, m10, m20, N0)

 retVal = (16 * N0) / (m10*m20) * (x / m10) .* (y / m20) .* exp(-2*(x/m10) - 2 * (y/m20));  

return

% for calling
x_min = 0.0004;
x_max = 20.0676;
x_b  =  exp(linspace(log(x_min), log(x_max), 4));
y_b = x_b;
m10 =  0.04;
m20  =  0.04;
N0 = 1;
I = test(x_b, y_b, m10, m20, N0)

Scipy dblquad returns:

[[  2.84900512e-02   1.40266599e-01   7.34019842e-12]
 [  1.40266599e-01   6.90582083e-01   3.61383932e-11]
 [  7.28723691e-12   3.58776449e-11   1.89113430e-21]] 

and Matlab dblquad returns:

IC =
    28.4901e-003   140.2666e-003   144.9328e-012
   140.2666e-003   690.5820e-003   690.9716e-012
   144.9328e-012   690.9716e-012   737.2926e-021

I have tried to change tolerances and order of input but two solutions remained always different. Thus, I am not able to understand which one is accurate and I would like to have it correct in python. Can someone suggest if this is a bug in either of the dblquadsolver or in my code somewhere?


Solution

  • From a glance at the results, the repetition of 690 in the Matlab output (in places where Python has different results) casts a doubt on Matlab's performance.

    One of problems with using the (deprecated) function dblquad in Matlab is that the tolerance you specify to it is absolute (to my understanding). This is why when you specify 1e-6, the integrals of order 1e-11 come out wrong. When you replace it by 1e-12, the computation takes a lot longer (because the larger integrals must now be computed to a great precision), yet the smallest of integrals, of size 1e-21, is still wrong.

    Hence, you should use a routine that supports relative error tolerance, such as integral2.

    Replacing the Matlab line with dblquad by

    IC(i, j) = integral2(@(x,y) ExponenIC(x,y, m10, m20, N0), x_b(i), x_b(i+1), y_b(j), y_b(j+1), 'RelTol', 1e-12);
    

    I get

    0.0284900512006556     0.14026659933722     7.10653215130477e-12
    0.14026659933722       0.690582082532588    3.51109000906259e-11 
    7.10653215130476e-12   3.5110900090626e-11  1.78512164747727e-21 
    

    which roughly agrees with Python output. Still, there remains a substantial difference. To settle the matter definitely, I calculated the integrals analytically. The exact result is

     0.0284900512006717     0.140266599337199     7.28723691243472e-12 
     0.140266599337199      0.690582082532677     3.58776449039036e-11 
     7.28723691243472e-12   3.58776449039036e-11  1.86394265998016e-21
    

    Neither package achieved the desired accuracy, but Python/scipy was much closer.


    For completeness, the loop that outputs the analytic solution:

    function IC = test(x_b, y_b, m10, m20, N0)
    F = @(x,a)  -0.25*exp(-2*x/a)*(2*x+a);
    for i = 1:length(x_b)-1
          for j = 1:length(y_b)-1
              IC(i,j) = (16 * N0) / (m10*m20) *(F(x_b(i+1),m10)-F(x_b(i),m10)) * (F(y_b(j+1),m20)-F(y_b(j),m20));
          end
    end
    end