HORIZON HASKELLDocslts/ghc-10.0.x8c2f8e82026-10-19Search names, modules, packages, or :: a typeCtrl K

GHC 10.0.0.20260917 · lts/ghc-10.0.x · 8c2f8e8 · 2026-10-19

Modulemassiv-1.0.5.0Haskell2010

Data.Massiv.Array.Numeric.Integral

  • 10 values
  • Packagemassiv-1.0.5.0
  • Exports10
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceIntegral.hs

Inspiration for the code in this module was taken from Paul Dawkins Online Notes. In particular the page on Integral Approximation, so if you need to brush up on some theory it is a great place to start.

Implementation-wise, integral approximation here relies heavily on stencils with stride, because such computation is fast and is automatically parallelizable.

Here are some examples of where this can be useful:

Integral of a function on a region

Say we have a gaussian f(x) = e^(x^2) on interval [0, 2] (as in Paul's tutorial above). For this we define a function f, an array with equally spaced (dx) sample input values and apply the function to that array, which will give us an array of n + 1 sample points, or looking from a different angle n intervals.

Example3 expressions
import Data.Massiv.Arrayf x = exp ( x ^ (2 :: Int) ) :: FloatfromFunction Seq (\ scale x -> f (scale x)) 0 2 (Sz1 1) 4Array D Seq (Sz1 5)  [ 1.0, 1.2840254, 2.7182817, 9.487736, 54.59815 ]

Once we have that array of sample points ready, we could use integralApprox and one of the stencils to compute an integral, but there are already functions that will do both steps for you:

Example1 expression
simpsonsRule Seq U (\ scale x -> f (scale x)) 0 2 (Sz1 1) 4Array D Seq (Sz1 1)  [ 17.353626 ]

scale is the function that will change an array index into equally spaced and appropriately shifted values of x, y, ... before they can get applied to f(x, y, ...)

Accurate values of a function

Another very useful place where integral approximation can be used is when a more accurate representation of a non-linear function is desired. Consider the same gaussian function applied to equally spaced values, with zero being in the middle of the vector:

Example3 expressions
xArr = makeArrayR D Seq (Sz1 4) $ \ i -> fromIntegral i - 1.5 :: FloatxArrArray D Seq (Sz1 4)  [ -1.5, -0.5, 0.5, 1.5 ]fmap f xArrArray D Seq (Sz1 4)  [ 9.487736, 1.2840254, 1.2840254, 9.487736 ]

The problem with above example is that computed values do not accurately represent the total value contained within each vector cell. For that reason if your were to later use it for example as convolution stencil, approximation would be very poor. The way to solve it is to approximate an integral across each cell of vector by drastically blowing up the xArr and then reducing it to a smaller array by using one of the approximation rules:

Example8 expressions
startValue = -2 :: FloatdistPerCell = 1 :: FloatdesiredSize = Sz1 4 :: Sz1numSamples = 4 :: IntxArrX4 = fromFunction Seq ($) startValue distPerCell desiredSize numSamplesxArrX4Array D Seq (Sz1 17)  [ -2.0, -1.75, -1.5, -1.25, -1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0 ]yArrX4 = computeAs U $ fmap f xArrX4integralApprox trapezoidStencil distPerCell desiredSize numSamples yArrX4Array D Seq (Sz1 4)  [ 16.074406, 1.4906789, 1.4906789, 16.074408 ]

We can clearly see the difference is huge, but it doesn't mean it is much better than our previous estimate. In order to get more accurate results we can use a better Simpson's rule for approximation and many more sample points. There is no need to create individual arrays xArrX4 and yArrX4, there are functions like simpsonsRule that will take care of it for us:

Example1 expression
simpsonsRule Seq U (\ scale i -> f (scale i)) startValue distPerCell desiredSize 128Array D Seq (Sz1 4)  [ 14.989977, 1.4626511, 1.4626517, 14.989977 ]
valuemidpointRule
  1. :: (Fractional e, StrideLoad DW ix e, Manifest r e)
  2. => Comp

    Computation strategy.

  3. -> r

    Intermediate array representation.

  4. -> ((Int -> e) -> ix -> e)

    f(x,y,...) - Function to integrate

  5. -> e

    a - Starting value point.

  6. -> e

    d - Distance per matrix cell.

  7. -> Sz ix

    sz - Result matrix size.

  8. -> Int

    n - Number of sample points per cell in each direction.

  9. -> Array D ix e
#

Use midpoint rule to approximate an integral.

valuemidpointStencil
  1. :: (Fractional e, Index ix)
  2. => e

    Δx - distance between sample points

  3. -> Dim

    Dimension along which to integrate

  4. -> Int

    n - number of sample points.

  5. -> Stencil ix e e
#

Midpoint Rule

\int_{{\,a}}^{{\,b}}{{f\left( x \right)\,dx}} \approx \Delta x \cdot \,f\left( {x_1 + \frac{\Delta x}{2}} \right) + \Delta x \cdot \,f\left( {x_2 + \frac{\Delta x}{2}} \right) + \cdots + \Delta x \cdot \,f\left( {x_n + \frac{\Delta x}{2}} \right)

Trapezoid Rule

valuetrapezoidRule
  1. :: (Fractional e, StrideLoad DW ix e, Manifest r e)
  2. => Comp

    Computation strategy

  3. -> r

    Intermediate array representation

  4. -> ((Int -> e) -> ix -> e)

    f(x,y,...) - function to integrate

  5. -> e

    a - Starting value point.

  6. -> e

    d - Distance per matrix cell.

  7. -> Sz ix

    sz - Result matrix size.

  8. -> Int

    n - Number of sample points per cell in each direction.

  9. -> Array D ix e
#

Use trapezoid rule to approximate an integral.

valuetrapezoidStencil
  1. :: (Fractional e, Index ix)
  2. => e

    Δx - distance between sample points

  3. -> Dim

    Dimension along which to integrate

  4. -> Int

    n - number of sample points.

  5. -> Stencil ix e e
#

Trapezoid Rule

\int_{{\,a}}^{{\,b}}{{f\left( x \right)\,dx}} \approx \frac{{\Delta x}}{2}\cdot\left( {f\left( {{x_0}} \right) + f\left( {{x_1}} \right)} \right) + \frac{{\Delta x}}{2}\cdot\left( {f\left( {{x_1}} \right) + f\left( {{x_2}} \right)} \right) + \cdots + \frac{{\Delta x}}{2}\cdot\left( {f\left( {{x_{n - 1}}} \right) + f\left( {{x_n}} \right)} \right)

Simpson's Rule

valuesimpsonsRule
  1. :: (Fractional e, StrideLoad DW ix e, Manifest r e)
  2. => Comp

    Computation strategy

  3. -> r

    Intermediate array representation

  4. -> ((Int -> e) -> ix -> e)

    f(x,y,...) - Function to integrate

  5. -> e

    a - Starting value point.

  6. -> e

    d - Distance per matrix cell.

  7. -> Sz ix

    sz - Result matrix size.

  8. -> Int

    n - Number of sample points per cell in each direction. This value must be even, otherwise error.

  9. -> Array D ix e
#

Use Simpson's rule to approximate an integral.

valuesimpsonsStencil
  1. :: (Fractional e, Index ix)
  2. => e

    Δx - distance between sample points

  3. -> Dim

    Dimension along which to integrate

  4. -> Int

    n - Number of sample points. This value should be even, otherwise error.

  5. -> Stencil ix e e
#

Simpson's Rule

\int_{{\,a}}^{{\,b}}{{f\left( x \right)\,dx}} \approx \frac{{\Delta x}}{3}\cdot\left( {f\left( {{x_0}} \right) + 4\cdot f\left( {{x_1}} \right) + f\left( {{x_2}} \right)} \right) + \frac{{\Delta x}}{3}\cdot\left( {f\left( {{x_2}} \right) + 4\cdot f\left( {{x_3}} \right) + f\left( {{x_4}} \right)} \right) + \cdots + \frac{{\Delta x}}{3}\cdot\left( {f\left( {{x_{n - 2}}} \right) + 4\cdot f\left( {{x_{n - 1}}} \right) + f\left( {{x_n}} \right)} \right)

General Integral approximation

2 declarations
valueintegralApprox
  1. :: (Fractional e, StrideLoad DW ix e, Manifest r e)
  2. => (e -> Dim -> Int -> Stencil ix e e)

    Integration Stencil

  3. -> e

    d - Length of interval per cell

  4. -> Sz ix

    sz - Result size of the matrix

  5. -> Int

    n - Number of samples

  6. -> Array r ix e

    Array with values of f(x,y,..) that will be used as source for integration.

  7. -> Array D ix e
#

Compute an approximation of integral using a supplied rule in a form of Stencil.

From functions

0 declarations

Sampled at the edge

valuefromFunction
  1. :: (Index ix, Fractional a)
  2. => Comp

    Computation strategy

  3. -> ((Int -> a) -> ix -> e)

    A function that will produce elements of scaled up array. First argument is a scaling function that should be applied to individual indicies.

  4. -> a

    a - Starting point

  5. -> a

    d - Distance per cell

  6. -> Sz ix

    sz - Size of the desired array

  7. -> Int

    n - Scaling factor, i.e. number of sample points per cell.

  8. -> Array D ix e
#

Create an array from a function with sample points at the edges

Example1 expression
fromFunction Seq (\ scale (i :. j) -> scale i + scale j :: Double) (-2) 1 (Sz 4) 2Array D Seq (Sz (9 :. 9))  [ [ -4.0, -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0 ]  , [ -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5 ]  , [ -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0 ]  , [ -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5 ]  , [ -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0 ]  , [ -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 ]  , [ -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0 ]  , [ -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5 ]  , [ 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 ]  ]

Sampled at the midpoint

valuefromFunctionMidpoint
  1. :: (Index ix, Fractional a)
  2. => Comp
  3. -> (Int -> a) -> ix -> e
  4. -> a
  5. -> a
  6. -> Sz ix
  7. -> Int
  8. -> Array D ix e
#

Similar to fromFunction, but will create an array from a function with sample points in the middle of cells.

Example1 expression
fromFunctionMidpoint Seq (\ scale (i :. j) -> scale i + scale j :: Double) (-2) 1 (Sz 4) 2Array D Seq (Sz (8 :. 8))  [ [ -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0 ]  , [ -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5 ]  , [ -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0 ]  , [ -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5 ]  , [ -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0 ]  , [ -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 ]  , [ -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0 ]  , [ 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5 ]  ]