8.1.3. More on 1d integration

Now let us repeat the previous study with several changes:

  1. Try binning with variable widths as well as variable integration orders.

  2. Check that changing variables affects the integration.

  3. Use an alternative way of bin edges specification.

  4. Use an alternative way of binding the function output.

  5. Use an alternative integration method.

Here is the code:

  1import load
  2from gna.env import env
  3import gna.constructors as C
  4import numpy as np
  5from matplotlib import pyplot as plt
  6import matplotlib.gridspec as gridspec
  7from gna.bindings import common
  8import ROOT as R
  9
 10# Make a variable for global namespace
 11ns = env.globalns
 12
 13# Create a parameter in the global namespace
 14pa = ns.defparameter('a', central=1.0, free=True, label='weight 1')
 15pb = ns.defparameter('b', central=0.2, free=True, label='weight 2')
 16pk = ns.defparameter('k', central=8.0, free=True, label='argument scale')
 17
 18# Print the list of parameters
 19ns.printparameters(labels=True)
 20print()
 21
 22# Define binning and integration orders
 23nbins = 20
 24x_edges = -np.pi + 2.0*np.pi*np.linspace(0.0, 1.0, nbins+1, dtype='d')**0.6
 25x_widths = x_edges[1:]-x_edges[:-1]
 26x_width_ratio = x_widths/x_widths.min()
 27orders = np.ceil(x_width_ratio*4).astype('i')
 28
 29# Initialize histogram
 30hist = C.Histogram(x_edges)
 31
 32# Initialize integrator
 33integrator = R.IntegratorTrap(nbins, orders)
 34integrator.points.edges(hist.hist.hist)
 35int_points = integrator.points.x
 36
 37# Create integrable: a*sin(x) + b*cos(k*x)
 38cos_arg = C.WeightedSum( ['k'], [int_points] )
 39sin_t = R.Sin(int_points)
 40cos_t = R.Cos(cos_arg.sum.sum)
 41fcn   = C.WeightedSum(['a', 'b'], [sin_t.sin.result, cos_t.cos.result])
 42
 43integrator.add_input(fcn.sum.sum)
 44
 45integrator.print()
 46print()
 47
 48# Label transformations
 49hist.hist.setLabel('Input histogram\n(bins definition)')
 50integrator.points.setLabel('Sampler\n(trapezoidal)')
 51integrator.hist.setLabel('Integrator\n(convolution)')
 52cos_arg.sum.setLabel('kx')
 53sin_t.sin.setLabel('sin(x)')
 54cos_t.cos.setLabel('cos(kx)')
 55fcn.sum.setLabel('a sin(x) + b cos(kx)')
 56
 57# Compute the integral analytically as a cross check
 58# int| a*sin(x) + b*cos(k*x) = a*cos(x) - b/k*sin(kx) + C
 59a, b, k = (p.value() for p in (pa, pb, pk))
 60F = - a*np.cos(x_edges) + (b/k)*np.sin(k*x_edges)
 61integral_a = F[1:] - F[:-1]
 62bar_width = (x_edges[1:]-x_edges[:-1])*0.5
 63bar_left = x_edges[:-1]+bar_width
 64
 65integral_n = integrator.hist.hist.data()
 66
 67# Do some plotting
 68fig = plt.figure()
 69ax = plt.subplot(111)
 70ax.minorticks_on()
 71# ax.grid()
 72ax.set_ylabel( 'f(x)' )
 73ax.set_title(r'$a\,\sin(x)+b\,\sin(kx)$')
 74
 75# Draw the function and integrals
 76fcn.sum.sum.plot_vs(int_points, 'o-', label='function at integration points', alpha=0.5, markerfacecolor='none', markersize=4.0)
 77integrator.hist.hist.plot_bar(label='integral: numerical', divide=2, shift=0, alpha=0.6)
 78ax.bar(bar_left, integral_a, bar_width, label='integral: analytical', align='edge', alpha=0.6)
 79
 80# Freeze axis limits and draw bin edges
 81ax.autoscale(enable=True, axis='y')
 82ymin, ymax = ax.get_ylim()
 83ax.vlines(integrator.points.xedges.data(), ymin, ymax, linestyle='--', alpha=0.4, linewidth=0.5)
 84
 85ax.legend(loc='lower right')
 86
 87ymin, ymax = ax.get_ylim()
 88
 89# Save figure and graph as images
 90savefig(tutorial_image_name('png', suffix='1'))
 91
 92# Do more plotting
 93fig = plt.figure()
 94ax = plt.subplot(111)
 95ax.minorticks_on()
 96# ax.grid()
 97ax.set_ylabel( 'f(x)' )
 98ax.set_title(r'$a\,\sin(x)+b\,\sin(kx)$')
 99ax.autoscale(enable=True, axis='y')
100ax.set_ylim(ymin, ymax)
101ax.axhline(0.0, linestyle='--', color='black', linewidth=1.0, alpha=0.5)
102
103def plot_sample():
104    label = 'a={}, b={}, k={}'.format(*(p.value() for p in (pa,pb,pk)))
105    lines = fcn.sum.sum.plot_vs(int_points, '--', alpha=0.5, linewidth=1.0)
106    color = lines[0].get_color()
107    integrator.hist.hist.plot_hist(alpha=0.6, color=color, label=label)
108
109plot_sample()
110pa.set(-0.5)
111pk.set(16)
112plot_sample()
113pa.set(0)
114pb.set(0.1)
115plot_sample()
116
117# Freeze axis limits and draw bin edges
118ymin, ymax = ax.get_ylim()
119ax.vlines(integrator.points.xedges.data(), ymin, ymax, linestyle='--', alpha=0.4, linewidth=0.5)
120ax.legend(loc='lower right')
121
122plt.show()

First of all, instead of passing the bin edges as an argument we make a histogram first.

hist = C.Histogram(x_edges)

Usually we would like to have only one bins definition for the computational chain, thus, if several integrations occur, it is more convenient to provide bin edges via input.

integrator = R.IntegratorTrap(nbins, orders)
integrator.points.edges(hist.hist.hist)

Note that the integrator is created without bins passed as an argument. Only in this case the points.edges input is created. The variable orders now contains a numpy array with integers. Instead of Gauss-Legendre in this example we will use the trapezoidal integration.

Then we use the similar function as we have used in the previous example. Instead of binding it directly we use a method add_input(output). This method may be used several times. On each execution it will create and bind a new input. A new output is created for each input. Thus for the same bins and orders the transformation may calculate several integrals within one execution.

integrator.add_input(fcn.sum.sum)

The method returns the corresponding output.

Note, that because of the taintflag propagation, even in case only one input is tainted, all the integrals will be recalculated. Multiple transformations should be used in case such a behaviour is not desired.

The code produces the following computational chain:

../_images/02_integral1d_again_graph.png

Computatinal graph used to integrate function \(f(x)=a\sin(x)+b\cos(kx)\). The bin edges are passed via input.

Then we repeat the same procedure with cross checking the calculation. The plot below illustrates the variable bin widths as well as variable integration orders.

../_images/02_integral1d_again_1.png

The trapezoidal quadrature application to the function (1). Variable bin width and different integration orders per bin.

In order to illustrate the impact of the parameters on the results let us create a function, that plots function (1), its integrals for the current values of the parameters:

def plot_sample():
    label = 'a={}, b={}, k={}'.format(*(p.value() for p in (pa,pb,pk)))
    lines = fcn.sum.sum.plot_vs(int_points, '--', alpha=0.5, linewidth=1.0)
    color = lines[0].get_color()
    integrator.hist.hist.plot_hist(alpha=0.6, color=color, label=label)

Now we make three plots for different values of the parameters:

plot_sample()
pa.set(-0.5)
pk.set(16)
plot_sample()
pa.set(0)
pb.set(0.1)
plot_sample()

The result is shown on the figure below:

../_images/02_integral1d_again_2.png

The trapezoidal quadrature application to the function (1) for different values of the parameters \(a\), \(b\) and \(k\).