Кубический сплайн

Материал из Википедии — свободной энциклопедии
Это старая версия этой страницы, сохранённая 81.200.20.34 (обсуждение) в 15:28, 25 декабря 2008 (Реализация на языке Haskell). Она может серьёзно отличаться от текущей версии.
Перейти к навигации Перейти к поиску

Пусть некоторая функция f(x) задана на отрезке , разбитом на части , . Кубическим сплайном называется функция S(x), которая:

  • на каждом отрезке является полиномом третьей степени;
  • имеет непрерывную вторую производную на всём отрезке ;
  • в точках выполняется равенство ;
  • .

По построению сплайн S(x) интерполирует функцию f в точках .

Теорема: Существует ровно одна функция S(x), удовлетворяющая этим условиям.

Построение

Обозначим:

На каждом отрезке функция есть полином третьей степени , коэффициенты которого надо определить. Запишем для удобства в виде:

тогда

Условия непрерывности всех производных до второго порядка включительно записываются в виде



а условия интерполяции в виде 

Отсюда получаем формулы для вычисления коэффициентов сплайна:

Если учесть, что , то вычисление с можно провести с помощью метода прогонки для трехдиагональной матрицы.

Реализация на языке Haskell

<source lang="hs"> // Интерполирование функций естественными кубическими сплайнами // Разработчик: Назар Андриенко Email: nuzikprogrammer@gmail.com // Автор - хуй!

Data Structures ^ Algorithms

> module Main where

> import Array > import Char

The Sigma ^ Bracketing datatypes. Sigma is the type of the elements that can be multiplied together; Bracketing is the type of bracketings of lists of elements;

> data Sigma = A | B | C > deriving (Show, Read, Eq, Ord, Enum, Ix)

> data Bracketing = S Sigma | Br Bracketing Bracketing

For convenience, we make Bracketing an instance of the Show ^ Read typeclasses. You don't need to know how this works unless you're interested, ^ you won't be asked to modify this part of the numFactorsogram.

> instance Show Bracketing where > showsPrec n (Br a b) > = ('(':) . showsPrec n a . showsPrec n b . (')':) > showsPrec n (S x) > = showsPrec n x > instance Read Bracketing where > readsPrec n xs = term ("(" ++ filter (not . isSpace) xs ++ ")") > where term ('(':xs) > = [(x, xs') | (x, ')':xs') <- term xs] ++ > [(Br a b, xs) | (a, xs' ) <- term xs, > (b, xs) <- term ('(':xs')] > term ('A':xs) = [(S A, xs)] > term ('B':xs) = [(S B, xs)] > term ('C':xs) = [(S C, xs)] > term _ = []


Some utility functions that you'll need to use to answer the questions.

> multiply A A = B > multiply A B = B > multiply A C = A > multiply B A = C > multiply B B = B > multiply B C = A > multiply C A = A > multiply C B = C > multiply C C = C

> factors :: Sigma -> [(Sigma,Sigma)] > factors A = [(A,C),(B,C),(C,A)] > factors B = [(A,A),(A,B),(B,B)] > factors C = [(B,A),(C,B),(C,C)]

> bracketings :: [Sigma] -> [Bracketing] > bracketings [] = [] > bracketings [z] = [S z] > bracketings w = [ Br ub vb | > (u,v) <- splits w, > ub <- bracketings u, vb <- bracketings v ]

> evaluate :: Bracketing -> Sigma > evaluate (S c) = c > evaluate (Br b1 b2) = multiply (evaluate b1) (evaluate b2)

> splits :: [a] -> [([a], [a])] > splits [x] = [] > splits (x:xs) = ([x], xs):[(x:us, vs) | (us, vs) <- splits xs]

Test cases

> testcases :: Sigma > testcases = [[A], [B], [C], [A,C], [B,A], > [A,B,A], [B,B,A], [A,B,B,A], [A,B,A,C,A,B], [A,B,B,C,A,C,B], > [B,B,C,A,B,C,A], [A,B,B,C,A,B,C], [B,B,B,B,B,B,B,B,B,B], > [A,B,B,B,B,B,B,B,B,B,B,B,B,C], > [A,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B,C]]

> testcase :: Int -> [Sigma] > testcase i = if (i <= length testcases && i>0) > then testcases !! (i-1) > else error ("There is no testcase " ++ show i)

> testbbb :: Int -> [Sigma] > testbbb i = take i (repeat B)

> teststring :: String -> [Sigma] > teststring = map char2sigma > where char2sigma 'A' = A > char2sigma 'B' = B > char2sigma 'C' = C > char2sigma x = error ("Character "++ [x] ++ " not in Sigma")

Specification for the num problems

> possible_spec :: Sigma -> [Sigma] -> Bool > possible_spec z w = elem z (map evaluate (bracketings w))

> numbers_spec :: Sigma -> [Sigma] -> Integer > numbers_spec z w = toInteger$length (filter (== z) > (map evaluate (bracketings w)))

> brackets_spec :: Sigma -> [Sigma] -> Maybe Bracketing > brackets_spec z w = if possible_spec z w > then Just (snd$head ( filter ((== z).fst) > (zip (map evaluate wbs) wbs))) > else Nothing > where wbs = bracketings w

Task 1 *****************************************************************

Recursive solution to the first problem

 the base case: we've got all possible facotisations of z and a sing splitting of the string

> processFactors :: [(Sigma,Sigma)]->([Sigma],[Sigma])->Sigma->Bool > processFactors [] splitting z = False > processFactors (pair:pairs) splitting z = if ((possible_rec x u == True) && > (possible_rec y v == True)) > then True > else processFactors pairs splitting z > where u=fst (splitting) > v=snd(splitting) > x=fst (pair) > y=snd (pair)

 we process each pair consecutively:
                                              

> processPairs :: [([Sigma],[Sigma])]->Sigma->Bool > processPairs [] z = False > processPairs (splitting:splittings) z = if ((processFactors pairs splitting z) == True) then True > else processPairs splittings z > where pairs=factors(z)

 we apply ProcessPairs to a string w, where splittings represent all possible ways
 we can split the intial pair into two

> possible_rec :: Sigma -> [Sigma] -> Bool > possible_rec z [] = False > possible_rec z [w] = if (z==w) then True > else False > possible_rec z w = processPairs splittings z > where splittings = splits w


Task 2 *****************************************************************

Runtime analysis

Writing out a reccurence tree for T(n) we'll get (n-1) edges going out from the root (which symbolise the (n-1) possible ways of splitting the initial string). From each node there'll be 2 edges, which symbolise the way we split the string (1/n-1, 2/n-2,...,n-1/1). From each of those nodes we'll get 3 edges which symbolise the 3 ways we could get x or y. Notice there are 2 nodes of each number(symmetric in the tree): 2 nodes of 1, 2 nodes of 2,..., 2 nodes of (n-1). Thus our reccurence will look like this:

T(1)=3 T(n)=2*3T(1) + 2*3T(2) + ... + 2*3T(n-1)

OR

T(1)=3 T(n)=6*(T(1)+T(2)+...+T(n-1))

Notice T(1)=3;T(2)=6T(1)=6*3;T(3)=6T(1)+6T(2)=T(2)+6T(2)=7*T(2)=7*6T(1)=7*6*3;

      T(4)=..=7*7*6*3;...;T(n-1)=6*(7^n-3)*3

Thus, T(n) = 3(1+6+6*7+...+6*7^(n-3))=3 + 3*(6(7^(n-4)-1)/2)= 3 + 9*7^(n-4) - 9=

          = 9*7^(n-4) - 6 = O(7^n)


So the total running time is T(n)=O(7^n)

For testing the running-time on PC, it's better to use such case as possible_rec A [B,B,...,B], rather than the test-cases providing in the initial code as the running time ofthis test-case is quiet close to the worst one.

The results are:

T (possible_rec (testbbb 10)) = 10.36 secs T (possible_rec (testbbb 12)) = 163.68 secs

We can see that the running time of the algorithm grows in exponential time and the exponential factor doesn't go higher than 7.

Task 3 *****************************************************************

Recursive solution to the second problem

 possible_num is just a slight modification of possible_rec
 the key idea is that when we split a string w into u and v
 we have to multiply the number of ways we can factorize u to x
 and the number of ways we can factorize v to y to get the proper result

> possible_num :: Sigma -> [Sigma] -> Int > possible_num z [] = 0 > possible_num z [w] = if (z==w) then 1 > else 0 > possible_num z w = numPairs splittings z > where splittings = splits w

> numPairs :: [([Sigma],[Sigma])]->Sigma->Int > numPairs [] z = 0 > numPairs (splitting:splittings) z = numFactors pairs splitting z + numPairs splittings z > where pairs=factors(z)

> numFactors :: [(Sigma,Sigma)]->([Sigma],[Sigma])->Sigma->Int > numFactors [] splitting z = 0 > numFactors (pair:pairs) splitting z = if ((n1 > 0) && (n2 > 0)) > then (n1 * n2) + numFactors pairs splitting z > else numFactors pairs splitting z > where u=fst (splitting) > v=snd (splitting) > x=fst (pair) > y=snd (pair) > n1 = possible_num x u > n2 = possible_num y v


Task 4 *****************************************************************

Dynamic programming solution to the first problem

We are only interested in the cases when j>=i


> possible_dp :: Sigma -> [Sigma] -> Bool > possible_dp z w = table3 ! z ! 1 ! n > where n = length w > table3 = array (A,C) [(z, table z) | z <- [A .. C]] > table z = array (1,n) [(i, column z i) | i <- [1..n]] > column z i = array (1,n) [(j, poss z i j) | j <- [1..n]] > rec = array (1,n) [(i,head (drop (i-1) w)) | i<-[1..n]] > poss z i j = if (j>=i) then > if (j==i) then > if (z == rec ! i) then True > else False > else if any (True==) [((table3 ! fst(pair) ! i  ! k == True) && > (table3  ! snd(pair) ! (k+1) ! j == True)) > | pair <- pairs, k<-[i..j-1]] > then True > else False > else False > where pairs = factors z

Task 5 *****************************************************************

Dynamic programming solution to the second problem

numbers_dp is just a slight modification to possible_dp

> numbers_dp :: Sigma -> [Sigma] -> Integer > numbers_dp z w = table3 ! z ! 1 ! n > where n = length w > table3 = array (A,C) [(z, table z) | z <- [A .. C]] > table z = array (1,n) [(i, column z i) | i <- [1..n]] > column z i = array (1,n) [(j, poss z i j) | j <- [1..n]] > rec = array (1,n) [(i,head (drop (i-1) w)) | i<-[1..n]] > poss z i j = if (j>=i) then > if (j==i) then > if (z == rec ! i) then 1 > else 0 > else sum [(table3 ! fst(pair) ! i ! k) * (table3 ! snd(pair) ! (k+1) ! j) > | pair <- pairs,k<-[i..j-1]] > else 0 > where pairs = factors z


Task 6 *****************************************************************

Runtime analysis discussion

We have to process, n singleton lists, comparing each to A,B and C; than we have to process (n-1) lists of size 2 ([a1,a2],[a2,a3],...,[a(n-1),a(n)]), looking up if they parenthesise to A,B or C; keeping this process going we finally have to process 2 lists of size (n-1) and than 1 list of size n.

So our reccurence-formula will look like this: T(n)=2*3((n-1)*1+(n-2)*2+(n-3)*3+...3*(n-3)+2*(n-2)+1*(n-1))=

   =n+2n+3n+ ... +(n-1)*n - (1+4+9+...+(n-1)^2)=
   =n(1+2+3+...+(n-1)) - (n*(n-1)*(2n-1))/6=
   =n*(n*(n-1)/2) - (n*(n-1)*(2n-1))/6=
   = (2*n^3-6*n^2-n)/6=O(n^3)  

Thus the total runing time for possible_dp and numbers_dp is T(n)=O(n^3)

The actual running time of numbers_dp is smaller than the running time of possible_dp, because multipliing numbers is much more expensive than evaluating booleans.

This statement can be confirmed with following tests:



The test cases show that T(possible_rec)<T(possible_spec)<T(possible_dp):

T (possible_rec A (testbbb 12)) = 163.68 secs T (possible_spec A (testbbb 12)) = 10.83 secs T (possible_dp A (testbbb 12)) = 0.12 secs

T (possible_num A (testbbb 12)) = 1181.39 secs T (numbers_spec A (testbbb 12)) = 10.87 secs T (numbers_dp A (testbbb 12)) = 0.24 secs


Task 7 (optional) ******************************************************

Dynamic programming solution to the last problem

> brackets_dp :: Sigma -> [Sigma] -> Maybe Bracketing > brackets_dp z w = table3 ! z ! 1 ! n > where n = length w > table3 = array (A,C) [(z, table z) | z <- [A .. C]] > table z = array (1,n) [(i, column z i) | i <- [1..n]] > column z i = array (1,n) [(j, brcks z i j) | j <- [1..n]] > brcks z i j = error "'brcks' not implemented yet"