I wrote a basic adaptive quadrature that uses the difference between trapezoidal and Simpson rule to estimate the error on each intervall and makes the mesh finer if needed:
def adaptQuad(f, Meshes, tol):
M = Meshes[-1]
h = np.diff(M) #Calculate intervall lengths h_i
mp = (M[1:]+M[:-1])/2 #calculate midpoints
fx = f(M); fm = f(mp) #evaluate f(x)
trp = h*(fx[:-1]+2*fm+fx[1:])/4 #evaluate trapezoidal (on each interval)
simp = h*(fx[:-1]+4*fm+fx[1:])/6 #evaluate Simpson (on each interval)
err = abs(trp-simp) #Difference between the rules
if sum(err) > tol: #are we done?
ref = np.nonzero(err > sum(err)/len(err))[0] #Refine mesh where needed
Meshes.append(np.sort(np.append(M,mp[ref])))
return adaptQuad(f, Meshes, tol) #recursion
return sum(simp), Meshes #return composite simpson
I wanted to compare it to the composite simpson rule:
def compSimpson(f,a,b,N):
x,h = np.linspace(a,b,N+1,retstep=True)
midP = (x[1:]+x[:-1])/2
x = x[1:-1]
return h/6 *(f(a) + 2*sum(f(x))+4*sum(f(midP))+f(b))
But when I tried it on:
f = lambda x: 1/(1+25*x**2)
with the following:
Q,M = adaptQuad(f,[np.linspace(-1,1,5)],0.001)
Q_simp = compSimpson(f,-1,1,len(M[-1])-1) #Try Comp. Simpson with as many intervals
I = integrate.quad(f,-1,1)[0]
print("Error composite Simpson: ", abs(Q_simp-I))
print("Error adaptive quad: ", abs(Q-I))
I get: Error composite Simpson: 5.274740111360643e-08 Error adaptive quad: 1.2452016172281866e-05
I tried some different total tolerances and most of the time the composite simpson rule outperforms the adaptive quadrature? This shouldn't be possible but I can't find my error in the implementation either, I'd be glad for some help:)
Edit: Here an illustration of how the Mesh evolves over time (the nodes are the dots, each line of same coloured dots shows one Meshes[i])

As commented, your refinement strategy is skewed. It prefers small intervals over large, or in other words, it is too eager to split large intervals.
What you really should want is that each interval contributes to the error according to their length, that is, a constant error density. For that purpose I would change your code to
This equalizes the results somewhat,
Both are far below the desired error bound
1e-3. But already fortol=1e-4there is again the marked difference towards the composite method,The second point to consider is a little more subtle, the adaptive strategy produces a subdivision for the error of the trapezoidal method, but returns the result for the Simpson method. While the result is better, the error bound
tol*interval_lengthis not valid or very pessimistic.Another effect is that a greater density of small segments is required for the trapezoidal method in "stiff" regions than for the Simpson method. This means that the adaptive strategy has unnecessary computations in these stiff regions, while the equidistant composite Simpson still has sufficient segment density there, and greater density in more flat regions, thus giving an overall better result.
Or in other words, if you want a fair comparison, take the composite trapezoidal method with the same number of function evaluations.
See Saving nodes in iterative Adaptive Simpson quadrature method (Matlab) for another approach where the recursion actually splits the interval and the requested error bound.