--- /dev/null
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Finite Element Method (FEM) to solve the problem,
+--
+-- -(A(x)*u'(x))' + c(x)*u*(x) = f(x) on (a,b)
+--
+-- with one of the two types of boundary conditions,
+--
+-- * Dirichlet: u(a) = u(b) = 0
+--
+-- * A(a)u'(a) = alpha, A(b)u'(b) = beta
+--
+-- if c(x) = 0, then it is assumed that the boundary conditions are
+-- Dirichlet.
+--
+module FEM.R1
+where
+
+import qualified Algebra.Algebraic as Algebraic ( C )
+import qualified Algebra.Field as Field ( C )
+import qualified Algebra.RealField as RealField ( C )
+import Data.Vector.Fixed ( Arity, S )
+import NumericPrelude
+import qualified Prelude as P
+
+import Linear.Matrix (
+ Col,
+ Mat(..),
+ (!!!),
+ construct,
+ nrows )
+import Polynomials.Orthogonal ( legendre )
+
+-- | Dirichlet boundary conditions. Since u(a)=u(b)=0 are fixed,
+-- there's no additional information conveyed by this type.
+data Dirichlet = Dirichlet
+
+-- | Neumann boundary conditions. @alpha@ specifies A(a)u'(b) and
+-- @beta@ specifies A(b)u'(b).
+data Neumann a = Neumann { alpha :: a, beta :: a }
+
+-- | Boundary conditions can be either Dirichlet or Neumann.
+type BoundaryConditions a = Either Dirichlet (Neumann a)
+
+type Interval a = (a,a)
+
+-- | The data for the PDE that we are attempting to solve.
+data PDE a =
+ PDE {
+ -- | A(x)
+ big_A :: (a -> a),
+ -- | c(x)
+ c :: (a -> a),
+ -- | f(x)
+ f :: (a -> a),
+ -- | The domain in R^1 as an interval
+ domain :: Interval a,
+ bdy :: BoundaryConditions a }
+
+
+
+-- | Non-PDE parameters for the finite element method. The additional
+-- type parameter @n@ should be a type-level representation of the
+-- largest element in @max_degrees@. It needs to be known statically
+-- for the dimensions of the pointer matrix.
+data Params m n a =
+ Params {
+ -- | A partition of the domain.
+ mesh :: Col m (Interval a),
+
+ -- | A list of the highest-degree polynomial that we will use over
+ -- each interval in our mesh.
+ max_degrees :: Col m Int }
+
+
+-- | The pointer matrix mapping local basis elements to global
+-- ones. The (i,j)th entry of the matrix contains the index of the
+-- global basis function corresponding to the jth local basis
+-- function over element i.
+--
+-- TODO: This works for Dirichlet boundary conditions only.
+--
+-- Note that the number of columns in the matrix depends on the
+--
+-- Examples:
+--
+-- >>> import Data.Vector.Fixed ( N5, N6 )
+-- >>> import Linear.Matrix ( Col5, fromList )
+--
+-- >>> let p = fromList [[3],[3],[5],[4],[5]] :: Col5 Int
+-- >>> let mesh = undefined :: Col5 (Int,Int)
+-- >>> let params = Params mesh p :: Params N5 N5 Int
+-- >>> let row1 = [0,1,5,6,0,0] :: [Int]
+-- >>> let row2 = [1,2,7,8,0,0] :: [Int]
+-- >>> let row3 = [2,3,9,10,11,12] :: [Int]
+-- >>> let row4 = [3,4,13,14,15,0] :: [Int]
+-- >>> let row5 = [4,0,16,17,18,19] :: [Int]
+-- >>> let expected = fromList [row1,row2,row3,row4,row5] :: Mat N5 N6 Int
+-- >>> pointer params == expected
+-- True
+--
+pointer :: (Arity m, Arity n) => Params m n a -> Mat m (S n) Int
+pointer params =
+ construct lambda
+ where
+ -- | The number of polynomials in the local basis for element i.
+ numpolys :: Int -> Int
+ numpolys i = ((max_degrees params) !!! (i,0)) + 1
+
+ lambda i j
+
+ -- The first two (linear) basis functions are easy to do.
+ | j < 2 = if i + j >= (nrows $ mesh params) then 0 else i + j
+
+ -- No local basis function exists for this j.
+ | j >= (numpolys i) = 0
+
+ -- The first row we handle as a special case.
+ | i == 0 = (nrows $ mesh params) + j - 2
+
+ -- The first entry for this row not corresponding to one of the
+ -- linear polynomials (which we did in the first guard). This
+ -- grabs the biggest number in the previous row and begins counting
+ -- from there.
+ | j == 2 = (lambda (i-1) (numpolys (i-1) - 1)) + 1
+
+ -- If none of the other cases apply, we can compute our value by
+ -- looking to the left in the matrix.
+ | otherwise = (lambda i (j-1)) + 1
+
+
+
+-- | An affine transform taking the interval [x1,x2] into [-1,1].
+--
+-- Examples:
+--
+-- >>> let phi = affine (-6,9)
+-- >>> phi (-6)
+-- -1.0
+-- >>> phi (9)
+-- 1.0
+--
+affine :: Field.C a => (a,a) -> (a -> a)
+affine (x1,x2) x = (fromInteger 2)*(x - x1)/(x2 - x1) - (fromInteger 1)
+
+
+-- | Orthonormal basis functions over [-1,1]. The test case below
+-- comes from Sage where the orthogonality of the polynomials'
+-- derivatives can easily be tested.
+--
+-- Examples:
+--
+-- >>> import qualified Algebra.Absolute as Absolute ( abs )
+--
+-- >>> let expected = 6.33910180790284
+-- >>> let actual = big_N 3 1.5 :: Double
+-- >>> Absolute.abs (actual - expected) < 1e-12
+-- True
+--
+big_N :: forall a. (Algebraic.C a, RealField.C a) => Integer -> a -> a
+big_N k x
+ | k < 0 = error "requested a negative basis function"
+ | otherwise =
+ coeff * ( legendre (k+2) x - legendre k x )
+ where
+ four = fromInteger 4
+ six = fromInteger 6
+ coeff = one / (sqrt (four*(fromInteger k) + six)) :: a