]> gitweb.michael.orlitzky.com - numerical-analysis.git/commitdiff
Implement Gaussian quadrature and test it.
authorMichael Orlitzky <michael@orlitzky.com>
Wed, 5 Feb 2014 20:30:47 +0000 (15:30 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Wed, 5 Feb 2014 20:30:47 +0000 (15:30 -0500)
src/Integration/Gaussian.hs

index 5d683f6d45fb0f08dd5caa3c8777e91aa95fe206..8fcca4f710d4fd918e061f475196ad3c9a2af1e5 100644 (file)
@@ -5,25 +5,34 @@
 -- | Gaussian quadrature over [-1, 1] using the Legendre polynomials.
 --
 module Integration.Gaussian (
+  gaussian,
+  gaussian',
   jacobi_matrix,
   nodes_and_weights )
 where
 
 import Algebra.Absolute ( abs )
+import qualified Algebra.Absolute as Absolute ( C )
 import qualified Algebra.Algebraic as Algebraic ( C )
+import qualified Algebra.Field as Field ( C )
 import qualified Algebra.Ring as Ring ( C )
+import qualified Algebra.ToRational as ToRational ( C )
 import Data.Vector.Fixed ( Arity, S )
 import NumericPrelude hiding ( abs )
 
 import Linear.Matrix (
   Col,
+  Col10,
   Mat(..),
+  colzipwith,
   construct,
+  fromList,
   identity_matrix,
   matmap,
   row',
   transpose )
 import Linear.QR ( eigenvectors_symmetric )
+import Normed ( Normed(..) )
 
 
 -- | Compute the Jacobi matrix for the Legendre polynomials over
@@ -37,7 +46,7 @@ import Linear.QR ( eigenvectors_symmetric )
 --
 --   Examples:
 --
---   >>> import Linear.Matrix ( Mat2, frobenius_norm, fromList )
+--   >>> import Linear.Matrix ( Mat2, frobenius_norm )
 --
 --   >>> let c = 1/(sqrt 3)
 --   >>> let expected = fromList [[0,c],[c, 0]] :: Mat2 Double
@@ -75,7 +84,7 @@ jacobi_matrix =
 --
 --   Examples:
 --
---   >>> import Linear.Matrix ( Col2, frobenius_norm, fromList )
+--   >>> import Linear.Matrix ( Col2, frobenius_norm )
 --
 --   >>> let (ns,ws) = nodes_and_weights 1000 :: (Col2 Double, Col2 Double)
 --   >>> let c = 1/(sqrt 3)
@@ -117,3 +126,91 @@ nodes_and_weights iterations =
     weights_row = matmap (\x -> (fromInteger 2)*x^2) first_components
 
     weights = transpose $ weights_row
+
+
+-- | Integrate a function over [-1,1] using Gaussian quadrature. This
+--   is the \"simple\" version of the 'gaussian'' function; here the ten
+--   nodes and weights are fixed.
+--
+--   Examples:
+--
+--   >>> let f x = x * (sin x)
+--   >>> let actual = gaussian f :: Double
+--   >>> let expected = 0.6023373578795136
+--   >>> abs (actual - expected) < 1e-12
+--   True
+--
+gaussian :: forall a.
+            (Absolute.C a, Algebraic.C a, ToRational.C a, Field.C a)
+         => (a -> a) -- ^ The function to integrate.
+         -> a
+gaussian f =
+  gaussian' f nodes weights
+  where
+    coerce = fromRational' . toRational
+
+    nodes :: Col10 a
+    nodes = fromList $ map (map coerce) node_list
+
+    node_list :: [[Double]]
+    node_list = [[-0.9739065285171726],
+                 [-0.8650633666889886],
+                 [-0.6794095682990235],
+                 [-0.43339539412924943],
+                 [-0.14887433898163027],
+                 [0.14887433898163094],
+                 [0.4333953941292481],
+                 [0.6794095682990247],
+                 [0.8650633666889846],
+                 [0.9739065285171717]]
+
+    weights :: Col10 a
+    weights = fromList $ map (map coerce) weight_list
+
+    weight_list :: [[Double]]
+    weight_list =  [[0.06667134430868775],
+                    [0.14945134915057925],
+                    [0.21908636251598282],
+                    [0.26926671930999607],
+                    [0.295524224714752],
+                    [0.295524224714754],
+                    [0.26926671930999635],
+                    [0.219086362515982],
+                    [0.14945134915058048],
+                    [0.06667134430868814]]
+
+
+
+-- | Integrate a function over [-1,1] using Gaussian quadrature. The
+--   nodes and the weights must be given (due to the design of the
+--   library, the number of nodes/weights must be requested at the
+--   type level so this function couldn't just compute e.g. @n@ of
+--   them for you).
+--
+--   The class constraints on @a@ could be loosened significantly if
+--   we implemented column sum outside of the 1-norm.
+--
+--   Examples:
+--
+--   >>> import Linear.Matrix ( Col5 )
+--
+--   >>> let (ns, ws) = nodes_and_weights 100 :: (Col5 Double, Col5 Double)
+--   >>> let f x = x * (sin x)
+--   >>> let actual = gaussian' f ns ws
+--   >>> let expected = 0.6023373578795136
+--   >>> abs (actual - expected) < 1e-8
+--   True
+--
+gaussian' :: forall m a.
+             (Arity m, Absolute.C a, Algebraic.C a, ToRational.C a, Ring.C a)
+          => (a -> a)    -- ^ The function @f@ to integrate.
+          -> Col (S m) a -- ^ Column matrix of nodes
+          -> Col (S m) a -- ^ Column matrix of weights
+          -> a
+gaussian' f nodes weights =
+  -- The one norm is just the sum of the entries, which is what we
+  -- want.
+  norm_p (1::Int) weighted_values
+  where
+    function_values = matmap f nodes
+    weighted_values = colzipwith (*) weights function_values