summaryrefslogtreecommitdiff
path: root/packages/sundials/src/Numeric/Sundials/CVode/ODE.hs
diff options
context:
space:
mode:
Diffstat (limited to 'packages/sundials/src/Numeric/Sundials/CVode/ODE.hs')
-rw-r--r--packages/sundials/src/Numeric/Sundials/CVode/ODE.hs471
1 files changed, 0 insertions, 471 deletions
diff --git a/packages/sundials/src/Numeric/Sundials/CVode/ODE.hs b/packages/sundials/src/Numeric/Sundials/CVode/ODE.hs
deleted file mode 100644
index ad7cf51..0000000
--- a/packages/sundials/src/Numeric/Sundials/CVode/ODE.hs
+++ /dev/null
@@ -1,471 +0,0 @@
1{-# OPTIONS_GHC -Wall #-}
2
3{-# LANGUAGE QuasiQuotes #-}
4{-# LANGUAGE TemplateHaskell #-}
5{-# LANGUAGE MultiWayIf #-}
6{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8
9-----------------------------------------------------------------------------
10-- |
11-- Module : Numeric.Sundials.CVode.ODE
12-- Copyright : Dominic Steinitz 2018,
13-- Novadiscovery 2018
14-- License : BSD
15-- Maintainer : Dominic Steinitz
16-- Stability : provisional
17--
18-- Solution of ordinary differential equation (ODE) initial value problems.
19--
20-- <https://computation.llnl.gov/projects/sundials/sundials-software>
21--
22-- A simple example:
23--
24-- <<diagrams/brusselator.png#diagram=brusselator&height=400&width=500>>
25--
26-- @
27-- import Numeric.Sundials.CVode.ODE
28-- import Numeric.LinearAlgebra
29--
30-- import Plots as P
31-- import qualified Diagrams.Prelude as D
32-- import Diagrams.Backend.Rasterific
33--
34-- brusselator :: Double -> [Double] -> [Double]
35-- brusselator _t x = [ a - (w + 1) * u + v * u * u
36-- , w * u - v * u * u
37-- , (b - w) / eps - w * u
38-- ]
39-- where
40-- a = 1.0
41-- b = 3.5
42-- eps = 5.0e-6
43-- u = x !! 0
44-- v = x !! 1
45-- w = x !! 2
46--
47-- lSaxis :: [[Double]] -> P.Axis B D.V2 Double
48-- lSaxis xs = P.r2Axis &~ do
49-- let ts = xs!!0
50-- us = xs!!1
51-- vs = xs!!2
52-- ws = xs!!3
53-- P.linePlot' $ zip ts us
54-- P.linePlot' $ zip ts vs
55-- P.linePlot' $ zip ts ws
56--
57-- main = do
58-- let res1 = odeSolve brusselator [1.2, 3.1, 3.0] (fromList [0.0, 0.1 .. 10.0])
59-- renderRasterific "diagrams/brusselator.png"
60-- (D.dims2D 500.0 500.0)
61-- (renderAxis $ lSaxis $ [0.0, 0.1 .. 10.0]:(toLists $ tr res1))
62-- @
63--
64-----------------------------------------------------------------------------
65module Numeric.Sundials.CVode.ODE ( odeSolve
66 , odeSolveV
67 , odeSolveVWith
68 , odeSolveVWith'
69 , ODEMethod(..)
70 , StepControl(..)
71 ) where
72
73import qualified Language.C.Inline as C
74import qualified Language.C.Inline.Unsafe as CU
75
76import Data.Monoid ((<>))
77import Data.Maybe (isJust)
78
79import Foreign.C.Types (CDouble, CInt, CLong)
80import Foreign.Ptr (Ptr)
81import Foreign.Storable (poke)
82
83import qualified Data.Vector.Storable as V
84
85import Data.Coerce (coerce)
86import System.IO.Unsafe (unsafePerformIO)
87
88import Numeric.LinearAlgebra.Devel (createVector)
89
90import Numeric.LinearAlgebra.HMatrix (Vector, Matrix, toList, rows,
91 cols, toLists, size, reshape)
92
93import Numeric.Sundials.Arkode (cV_ADAMS, cV_BDF,
94 getDataFromContents, putDataInContents)
95import qualified Numeric.Sundials.Arkode as T
96import Numeric.Sundials.ODEOpts (ODEOpts(..), Jacobian, SundialsDiagnostics(..))
97
98
99C.context (C.baseCtx <> C.vecCtx <> C.funCtx <> T.sunCtx)
100
101C.include "<stdlib.h>"
102C.include "<stdio.h>"
103C.include "<math.h>"
104C.include "<cvode/cvode.h>" -- prototypes for CVODE fcts., consts.
105C.include "<nvector/nvector_serial.h>" -- serial N_Vector types, fcts., macros
106C.include "<sunmatrix/sunmatrix_dense.h>" -- access to dense SUNMatrix
107C.include "<sunlinsol/sunlinsol_dense.h>" -- access to dense SUNLinearSolver
108C.include "<cvode/cvode_direct.h>" -- access to CVDls interface
109C.include "<sundials/sundials_types.h>" -- definition of type realtype
110C.include "<sundials/sundials_math.h>"
111C.include "../../../helpers.h"
112C.include "Numeric/Sundials/Arkode_hsc.h"
113
114
115-- | Stepping functions
116data ODEMethod = ADAMS
117 | BDF
118
119getMethod :: ODEMethod -> Int
120getMethod (ADAMS) = cV_ADAMS
121getMethod (BDF) = cV_BDF
122
123getJacobian :: ODEMethod -> Maybe Jacobian
124getJacobian _ = Nothing
125
126-- | A version of 'odeSolveVWith' with reasonable default step control.
127odeSolveV
128 :: ODEMethod
129 -> Maybe Double -- ^ initial step size - by default, CVode
130 -- estimates the initial step size to be the
131 -- solution \(h\) of the equation
132 -- \(\|\frac{h^2\ddot{y}}{2}\| = 1\), where
133 -- \(\ddot{y}\) is an estimated value of the
134 -- second derivative of the solution at \(t_0\)
135 -> Double -- ^ absolute tolerance for the state vector
136 -> Double -- ^ relative tolerance for the state vector
137 -> (Double -> Vector Double -> Vector Double) -- ^ The RHS of the system \(\dot{y} = f(t,y)\)
138 -> Vector Double -- ^ initial conditions
139 -> Vector Double -- ^ desired solution times
140 -> Matrix Double -- ^ solution
141odeSolveV meth hi epsAbs epsRel f y0 ts =
142 odeSolveVWith meth (X epsAbs epsRel) hi g y0 ts
143 where
144 g t x0 = coerce $ f t x0
145
146-- | A version of 'odeSolveV' with reasonable default parameters and
147-- system of equations defined using lists. FIXME: we should say
148-- something about the fact we could use the Jacobian but don't for
149-- compatibility with hmatrix-gsl.
150odeSolve :: (Double -> [Double] -> [Double]) -- ^ The RHS of the system \(\dot{y} = f(t,y)\)
151 -> [Double] -- ^ initial conditions
152 -> Vector Double -- ^ desired solution times
153 -> Matrix Double -- ^ solution
154odeSolve f y0 ts =
155 -- FIXME: These tolerances are different from the ones in GSL
156 odeSolveVWith BDF (XX' 1.0e-6 1.0e-10 1 1) Nothing g (V.fromList y0) (V.fromList $ toList ts)
157 where
158 g t x0 = V.fromList $ f t (V.toList x0)
159
160odeSolveVWith ::
161 ODEMethod
162 -> StepControl
163 -> Maybe Double -- ^ initial step size - by default, CVode
164 -- estimates the initial step size to be the
165 -- solution \(h\) of the equation
166 -- \(\|\frac{h^2\ddot{y}}{2}\| = 1\), where
167 -- \(\ddot{y}\) is an estimated value of the second
168 -- derivative of the solution at \(t_0\)
169 -> (Double -> V.Vector Double -> V.Vector Double) -- ^ The RHS of the system \(\dot{y} = f(t,y)\)
170 -> V.Vector Double -- ^ Initial conditions
171 -> V.Vector Double -- ^ Desired solution times
172 -> Matrix Double -- ^ Error code or solution
173odeSolveVWith method control initStepSize f y0 tt =
174 case odeSolveVWith' opts method control initStepSize f y0 tt of
175 Left (c, _v) -> error $ show c -- FIXME
176 Right (v, _d) -> v
177 where
178 opts = ODEOpts { maxNumSteps = 10000
179 , minStep = 1.0e-12
180 , relTol = error "relTol"
181 , absTols = error "absTol"
182 , initStep = error "initStep"
183 , maxFail = 10
184 }
185
186odeSolveVWith' ::
187 ODEOpts
188 -> ODEMethod
189 -> StepControl
190 -> Maybe Double -- ^ initial step size - by default, CVode
191 -- estimates the initial step size to be the
192 -- solution \(h\) of the equation
193 -- \(\|\frac{h^2\ddot{y}}{2}\| = 1\), where
194 -- \(\ddot{y}\) is an estimated value of the second
195 -- derivative of the solution at \(t_0\)
196 -> (Double -> V.Vector Double -> V.Vector Double) -- ^ The RHS of the system \(\dot{y} = f(t,y)\)
197 -> V.Vector Double -- ^ Initial conditions
198 -> V.Vector Double -- ^ Desired solution times
199 -> Either (Matrix Double, Int) (Matrix Double, SundialsDiagnostics) -- ^ Error code or solution
200odeSolveVWith' opts method control initStepSize f y0 tt =
201 case solveOdeC (fromIntegral $ maxFail opts)
202 (fromIntegral $ maxNumSteps opts) (coerce $ minStep opts)
203 (fromIntegral $ getMethod method) (coerce initStepSize) jacH (scise control)
204 (coerce f) (coerce y0) (coerce tt) of
205 Left (v, c) -> Left (reshape l (coerce v), fromIntegral c)
206 Right (v, d) -> Right (reshape l (coerce v), d)
207 where
208 l = size y0
209 scise (X aTol rTol) = coerce (V.replicate l aTol, rTol)
210 scise (X' aTol rTol) = coerce (V.replicate l aTol, rTol)
211 scise (XX' aTol rTol yScale _yDotScale) = coerce (V.replicate l aTol, yScale * rTol)
212 -- FIXME; Should we check that the length of ss is correct?
213 scise (ScXX' aTol rTol yScale _yDotScale ss) = coerce (V.map (* aTol) ss, yScale * rTol)
214 jacH = fmap (\g t v -> matrixToSunMatrix $ g (coerce t) (coerce v)) $
215 getJacobian method
216 matrixToSunMatrix m = T.SunMatrix { T.rows = nr, T.cols = nc, T.vals = vs }
217 where
218 nr = fromIntegral $ rows m
219 nc = fromIntegral $ cols m
220 -- FIXME: efficiency
221 vs = V.fromList $ map coerce $ concat $ toLists m
222
223solveOdeC ::
224 CInt ->
225 CLong ->
226 CDouble ->
227 CInt ->
228 Maybe CDouble ->
229 (Maybe (CDouble -> V.Vector CDouble -> T.SunMatrix)) ->
230 (V.Vector CDouble, CDouble) ->
231 (CDouble -> V.Vector CDouble -> V.Vector CDouble) -- ^ The RHS of the system \(\dot{y} = f(t,y)\)
232 -> V.Vector CDouble -- ^ Initial conditions
233 -> V.Vector CDouble -- ^ Desired solution times
234 -> Either (V.Vector CDouble, CInt) (V.Vector CDouble, SundialsDiagnostics) -- ^ Partial solution and error code or
235 -- solution and diagnostics
236solveOdeC maxErrTestFails maxNumSteps_ minStep_ method initStepSize
237 jacH (aTols, rTol) fun f0 ts =
238 unsafePerformIO $ do
239
240 let isInitStepSize :: CInt
241 isInitStepSize = fromIntegral $ fromEnum $ isJust initStepSize
242 ss :: CDouble
243 ss = case initStepSize of
244 -- It would be better to put an error message here but
245 -- inline-c seems to evaluate this even if it is never
246 -- used :(
247 Nothing -> 0.0
248 Just x -> x
249
250 let dim = V.length f0
251 nEq :: CLong
252 nEq = fromIntegral dim
253 nTs :: CInt
254 nTs = fromIntegral $ V.length ts
255 quasiMatrixRes <- createVector ((fromIntegral dim) * (fromIntegral nTs))
256 qMatMut <- V.thaw quasiMatrixRes
257 diagnostics :: V.Vector CLong <- createVector 10 -- FIXME
258 diagMut <- V.thaw diagnostics
259 -- We need the types that sundials expects. These are tied together
260 -- in 'CLangToHaskellTypes'. FIXME: The Haskell type is currently empty!
261 let funIO :: CDouble -> Ptr T.SunVector -> Ptr T.SunVector -> Ptr () -> IO CInt
262 funIO x y f _ptr = do
263 -- Convert the pointer we get from C (y) to a vector, and then
264 -- apply the user-supplied function.
265 fImm <- fun x <$> getDataFromContents dim y
266 -- Fill in the provided pointer with the resulting vector.
267 putDataInContents fImm dim f
268 -- FIXME: I don't understand what this comment means
269 -- Unsafe since the function will be called many times.
270 [CU.exp| int{ 0 } |]
271 let isJac :: CInt
272 isJac = fromIntegral $ fromEnum $ isJust jacH
273 jacIO :: CDouble -> Ptr T.SunVector -> Ptr T.SunVector -> Ptr T.SunMatrix ->
274 Ptr () -> Ptr T.SunVector -> Ptr T.SunVector -> Ptr T.SunVector ->
275 IO CInt
276 jacIO t y _fy jacS _ptr _tmp1 _tmp2 _tmp3 = do
277 case jacH of
278 Nothing -> error "Numeric.Sundials.CVode.ODE: Jacobian not defined"
279 Just jacI -> do j <- jacI t <$> getDataFromContents dim y
280 poke jacS j
281 -- FIXME: I don't understand what this comment means
282 -- Unsafe since the function will be called many times.
283 [CU.exp| int{ 0 } |]
284
285 res <- [C.block| int {
286 /* general problem variables */
287
288 int flag; /* reusable error-checking flag */
289 int i, j; /* reusable loop indices */
290 N_Vector y = NULL; /* empty vector for storing solution */
291 N_Vector tv = NULL; /* empty vector for storing absolute tolerances */
292
293 SUNMatrix A = NULL; /* empty matrix for linear solver */
294 SUNLinearSolver LS = NULL; /* empty linear solver object */
295 void *cvode_mem = NULL; /* empty CVODE memory structure */
296 realtype t;
297 long int nst, nfe, nsetups, nje, nfeLS, nni, ncfn, netf, nge;
298
299 /* general problem parameters */
300
301 realtype T0 = RCONST(($vec-ptr:(double *ts))[0]); /* initial time */
302 sunindextype NEQ = $(sunindextype nEq); /* number of dependent vars. */
303
304 /* Initialize data structures */
305
306 y = N_VNew_Serial(NEQ); /* Create serial vector for solution */
307 if (check_flag((void *)y, "N_VNew_Serial", 0)) return 1;
308 /* Specify initial condition */
309 for (i = 0; i < NEQ; i++) {
310 NV_Ith_S(y,i) = ($vec-ptr:(double *f0))[i];
311 };
312
313 cvode_mem = CVodeCreate($(int method), CV_NEWTON);
314 if (check_flag((void *)cvode_mem, "CVodeCreate", 0)) return(1);
315
316 /* Call CVodeInit to initialize the integrator memory and specify the
317 * user's right hand side function in y'=f(t,y), the inital time T0, and
318 * the initial dependent variable vector y. */
319 flag = CVodeInit(cvode_mem, $fun:(int (* funIO) (double t, SunVector y[], SunVector dydt[], void * params)), T0, y);
320 if (check_flag(&flag, "CVodeInit", 1)) return(1);
321
322 tv = N_VNew_Serial(NEQ); /* Create serial vector for absolute tolerances */
323 if (check_flag((void *)tv, "N_VNew_Serial", 0)) return 1;
324 /* Specify tolerances */
325 for (i = 0; i < NEQ; i++) {
326 NV_Ith_S(tv,i) = ($vec-ptr:(double *aTols))[i];
327 };
328
329 flag = CVodeSetMinStep(cvode_mem, $(double minStep_));
330 if (check_flag(&flag, "CVodeSetMinStep", 1)) return 1;
331 flag = CVodeSetMaxNumSteps(cvode_mem, $(long int maxNumSteps_));
332 if (check_flag(&flag, "CVodeSetMaxNumSteps", 1)) return 1;
333 flag = CVodeSetMaxErrTestFails(cvode_mem, $(int maxErrTestFails));
334 if (check_flag(&flag, "CVodeSetMaxErrTestFails", 1)) return 1;
335
336 /* Call CVodeSVtolerances to specify the scalar relative tolerance
337 * and vector absolute tolerances */
338 flag = CVodeSVtolerances(cvode_mem, $(double rTol), tv);
339 if (check_flag(&flag, "CVodeSVtolerances", 1)) return(1);
340
341 /* Initialize dense matrix data structure and solver */
342 A = SUNDenseMatrix(NEQ, NEQ);
343 if (check_flag((void *)A, "SUNDenseMatrix", 0)) return 1;
344 LS = SUNDenseLinearSolver(y, A);
345 if (check_flag((void *)LS, "SUNDenseLinearSolver", 0)) return 1;
346
347 /* Attach matrix and linear solver */
348 flag = CVDlsSetLinearSolver(cvode_mem, LS, A);
349 if (check_flag(&flag, "CVDlsSetLinearSolver", 1)) return 1;
350
351 /* Set the initial step size if there is one */
352 if ($(int isInitStepSize)) {
353 /* FIXME: We could check if the initial step size is 0 */
354 /* or even NaN and then throw an error */
355 flag = CVodeSetInitStep(cvode_mem, $(double ss));
356 if (check_flag(&flag, "CVodeSetInitStep", 1)) return 1;
357 }
358
359 /* Set the Jacobian if there is one */
360 if ($(int isJac)) {
361 flag = CVDlsSetJacFn(cvode_mem, $fun:(int (* jacIO) (double t, SunVector y[], SunVector fy[], SunMatrix Jac[], void * params, SunVector tmp1[], SunVector tmp2[], SunVector tmp3[])));
362 if (check_flag(&flag, "CVDlsSetJacFn", 1)) return 1;
363 }
364
365 /* Store initial conditions */
366 for (j = 0; j < NEQ; j++) {
367 ($vec-ptr:(double *qMatMut))[0 * $(int nTs) + j] = NV_Ith_S(y,j);
368 }
369
370 /* Main time-stepping loop: calls CVode to perform the integration */
371 /* Stops when the final time has been reached */
372 for (i = 1; i < $(int nTs); i++) {
373
374 flag = CVode(cvode_mem, ($vec-ptr:(double *ts))[i], y, &t, CV_NORMAL); /* call integrator */
375 if (check_flag(&flag, "CVode solver failure, stopping integration", 1)) return 1;
376
377 /* Store the results for Haskell */
378 for (j = 0; j < NEQ; j++) {
379 ($vec-ptr:(double *qMatMut))[i * NEQ + j] = NV_Ith_S(y,j);
380 }
381 }
382
383 /* Get some final statistics on how the solve progressed */
384
385 flag = CVodeGetNumSteps(cvode_mem, &nst);
386 check_flag(&flag, "CVodeGetNumSteps", 1);
387 ($vec-ptr:(long int *diagMut))[0] = nst;
388
389 /* FIXME */
390 ($vec-ptr:(long int *diagMut))[1] = 0;
391
392 flag = CVodeGetNumRhsEvals(cvode_mem, &nfe);
393 check_flag(&flag, "CVodeGetNumRhsEvals", 1);
394 ($vec-ptr:(long int *diagMut))[2] = nfe;
395 /* FIXME */
396 ($vec-ptr:(long int *diagMut))[3] = 0;
397
398 flag = CVodeGetNumLinSolvSetups(cvode_mem, &nsetups);
399 check_flag(&flag, "CVodeGetNumLinSolvSetups", 1);
400 ($vec-ptr:(long int *diagMut))[4] = nsetups;
401
402 flag = CVodeGetNumErrTestFails(cvode_mem, &netf);
403 check_flag(&flag, "CVodeGetNumErrTestFails", 1);
404 ($vec-ptr:(long int *diagMut))[5] = netf;
405
406 flag = CVodeGetNumNonlinSolvIters(cvode_mem, &nni);
407 check_flag(&flag, "CVodeGetNumNonlinSolvIters", 1);
408 ($vec-ptr:(long int *diagMut))[6] = nni;
409
410 flag = CVodeGetNumNonlinSolvConvFails(cvode_mem, &ncfn);
411 check_flag(&flag, "CVodeGetNumNonlinSolvConvFails", 1);
412 ($vec-ptr:(long int *diagMut))[7] = ncfn;
413
414 flag = CVDlsGetNumJacEvals(cvode_mem, &nje);
415 check_flag(&flag, "CVDlsGetNumJacEvals", 1);
416 ($vec-ptr:(long int *diagMut))[8] = ncfn;
417
418 flag = CVDlsGetNumRhsEvals(cvode_mem, &nfeLS);
419 check_flag(&flag, "CVDlsGetNumRhsEvals", 1);
420 ($vec-ptr:(long int *diagMut))[9] = ncfn;
421
422 /* Clean up and return */
423
424 N_VDestroy(y); /* Free y vector */
425 N_VDestroy(tv); /* Free tv vector */
426 CVodeFree(&cvode_mem); /* Free integrator memory */
427 SUNLinSolFree(LS); /* Free linear solver */
428 SUNMatDestroy(A); /* Free A matrix */
429
430 return flag;
431 } |]
432 preD <- V.freeze diagMut
433 let d = SundialsDiagnostics (fromIntegral $ preD V.!0)
434 (fromIntegral $ preD V.!1)
435 (fromIntegral $ preD V.!2)
436 (fromIntegral $ preD V.!3)
437 (fromIntegral $ preD V.!4)
438 (fromIntegral $ preD V.!5)
439 (fromIntegral $ preD V.!6)
440 (fromIntegral $ preD V.!7)
441 (fromIntegral $ preD V.!8)
442 (fromIntegral $ preD V.!9)
443 m <- V.freeze qMatMut
444 if res == 0
445 then do
446 return $ Right (m, d)
447 else do
448 return $ Left (m, res)
449
450-- | Adaptive step-size control
451-- functions.
452--
453-- [GSL](https://www.gnu.org/software/gsl/doc/html/ode-initval.html#adaptive-step-size-control)
454-- allows the user to control the step size adjustment using
455-- \(D_i = \epsilon^{abs}s_i + \epsilon^{rel}(a_{y} |y_i| + a_{dy/dt} h |\dot{y}_i|)\) where
456-- \(\epsilon^{abs}\) is the required absolute error, \(\epsilon^{rel}\)
457-- is the required relative error, \(s_i\) is a vector of scaling
458-- factors, \(a_{y}\) is a scaling factor for the solution \(y\) and
459-- \(a_{dydt}\) is a scaling factor for the derivative of the solution \(dy/dt\).
460--
461-- [ARKode](https://computation.llnl.gov/projects/sundials/arkode)
462-- allows the user to control the step size adjustment using
463-- \(\eta^{rel}|y_i| + \eta^{abs}_i\). For compatibility with
464-- [hmatrix-gsl](https://hackage.haskell.org/package/hmatrix-gsl),
465-- tolerances for \(y\) and \(\dot{y}\) can be specified but the latter have no
466-- effect.
467data StepControl = X Double Double -- ^ absolute and relative tolerance for \(y\); in GSL terms, \(a_{y} = 1\) and \(a_{dy/dt} = 0\); in ARKode terms, the \(\eta^{abs}_i\) are identical
468 | X' Double Double -- ^ absolute and relative tolerance for \(\dot{y}\); in GSL terms, \(a_{y} = 0\) and \(a_{dy/dt} = 1\); in ARKode terms, the latter is treated as the relative tolerance for \(y\) so this is the same as specifying 'X' which may be entirely incorrect for the given problem
469 | XX' Double Double Double Double -- ^ include both via relative tolerance
470 -- scaling factors \(a_y\), \(a_{{dy}/{dt}}\); in ARKode terms, the latter is ignored and \(\eta^{rel} = a_{y}\epsilon^{rel}\)
471 | ScXX' Double Double Double Double (Vector Double) -- ^ scale absolute tolerance of \(y_i\); in ARKode terms, \(a_{{dy}/{dt}}\) is ignored, \(\eta^{abs}_i = s_i \epsilon^{abs}\) and \(\eta^{rel} = a_{y}\epsilon^{rel}\)