]> gitweb.michael.orlitzky.com - spline3.git/blob - src/FunctionValues.hs
src/{Cube,FunctionValues}.hs: add explicit Cardinal imports.
[spline3.git] / src / FunctionValues.hs
1 {-# LANGUAGE BangPatterns #-}
2
3 -- | The FunctionValues module contains the 'FunctionValues' type and
4 -- the functions used to manipulate it.
5 --
6 module FunctionValues (
7 FunctionValues(..),
8 empty_values,
9 eval,
10 make_values,
11 rotate,
12 function_values_tests,
13 function_values_properties,
14 value_at )
15 where
16
17 import Prelude hiding ( LT )
18 import Test.Tasty ( TestTree, testGroup )
19 import Test.Tasty.HUnit ( Assertion, testCase )
20 import Test.Tasty.QuickCheck ( Arbitrary( arbitrary ), choose, testProperty )
21
22 import Assertions ( assertTrue )
23 import Cardinal (
24 Cardinal(F, B, L, R, D, T, FL, FR, FD, FT, BL, BR, BD, BT, LD, LT, RD,
25 RT, FLD, FLT, FRD, FRT, BLD, BLT, BRD, BRT, I, Scalar, Sum,
26 Difference, Product, Quotient ),
27 cwx,
28 cwy,
29 cwz )
30 import Examples ( trilinear )
31 import Values ( Values3D, dims, idx )
32
33 -- | The FunctionValues type represents the value of our function f at
34 -- the 27 points surrounding (and including) the center of a
35 -- cube. Each value of f can be accessed by the name of its
36 -- direction.
37 --
38 data FunctionValues =
39 FunctionValues { front :: !Double,
40 back :: !Double,
41 left :: !Double,
42 right :: !Double,
43 top :: !Double,
44 down :: !Double,
45 front_left :: !Double,
46 front_right :: !Double,
47 front_down :: !Double,
48 front_top :: !Double,
49 back_left :: !Double,
50 back_right :: !Double,
51 back_down :: !Double,
52 back_top :: !Double,
53 left_down :: !Double,
54 left_top :: !Double,
55 right_down :: !Double,
56 right_top :: !Double,
57 front_left_down :: !Double,
58 front_left_top :: !Double,
59 front_right_down :: !Double,
60 front_right_top :: !Double,
61 back_left_down :: !Double,
62 back_left_top :: !Double,
63 back_right_down :: !Double,
64 back_right_top :: !Double,
65 interior :: !Double }
66 deriving (Eq, Show)
67
68
69 instance Arbitrary FunctionValues where
70 arbitrary = do
71 front' <- choose (min_double, max_double)
72 back' <- choose (min_double, max_double)
73 left' <- choose (min_double, max_double)
74 right' <- choose (min_double, max_double)
75 top' <- choose (min_double, max_double)
76 down' <- choose (min_double, max_double)
77 front_left' <- choose (min_double, max_double)
78 front_right' <- choose (min_double, max_double)
79 front_top' <- choose (min_double, max_double)
80 front_down' <- choose (min_double, max_double)
81 back_left' <- choose (min_double, max_double)
82 back_right' <- choose (min_double, max_double)
83 back_top' <- choose (min_double, max_double)
84 back_down' <- choose (min_double, max_double)
85 left_top' <- choose (min_double, max_double)
86 left_down' <- choose (min_double, max_double)
87 right_top' <- choose (min_double, max_double)
88 right_down' <- choose (min_double, max_double)
89 front_left_top' <- choose (min_double, max_double)
90 front_left_down' <- choose (min_double, max_double)
91 front_right_top' <- choose (min_double, max_double)
92 front_right_down' <- choose (min_double, max_double)
93 back_left_top' <- choose (min_double, max_double)
94 back_left_down' <- choose (min_double, max_double)
95 back_right_top' <- choose (min_double, max_double)
96 back_right_down' <- choose (min_double, max_double)
97 interior' <- choose (min_double, max_double)
98
99 return empty_values { front = front',
100 back = back',
101 left = left',
102 right = right',
103 top = top',
104 down = down',
105 front_left = front_left',
106 front_right = front_right',
107 front_top = front_top',
108 front_down = front_down',
109 back_left = back_left',
110 back_right = back_right',
111 back_top = back_top',
112 back_down = back_down',
113 left_top = left_top',
114 left_down = left_down',
115 right_top = right_top',
116 right_down = right_down',
117 front_left_top = front_left_top',
118 front_left_down = front_left_down',
119 front_right_top = front_right_top',
120 front_right_down = front_right_down',
121 back_left_top = back_left_top',
122 back_left_down = back_left_down',
123 back_right_top = back_right_top',
124 back_right_down = back_right_down',
125 interior = interior' }
126 where
127 -- | We perform addition with the function values contained in a
128 -- FunctionValues object. If we choose random doubles near the machine
129 -- min/max, we risk overflowing or underflowing the 'Double'. This
130 -- places a reasonably safe limit on the maximum size of our generated
131 -- 'Double' members.
132 max_double :: Double
133 max_double = 10000.0
134
135 -- | See 'max_double'.
136 min_double :: Double
137 min_double = (-1) * max_double
138
139
140 -- | Return a 'FunctionValues' with a bunch of zeros for data points.
141 empty_values :: FunctionValues
142 empty_values =
143 FunctionValues 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
144
145
146 -- | The eval function is where the magic happens for the
147 -- FunctionValues type. Given a 'Cardinal' direction and a
148 -- 'FunctionValues' object, eval will return the value of the
149 -- function f in that 'Cardinal' direction. Note that 'Cardinal' can
150 -- be a composite type; eval is what performs the \"arithmetic\" on
151 -- 'Cardinal' directions.
152 eval :: FunctionValues -> Cardinal -> Double
153 eval f F = front f
154 eval f B = back f
155 eval f L = left f
156 eval f R = right f
157 eval f T = top f
158 eval f D = down f
159 eval f FL = front_left f
160 eval f FR = front_right f
161 eval f FD = front_down f
162 eval f FT = front_top f
163 eval f BL = back_left f
164 eval f BR = back_right f
165 eval f BD = back_down f
166 eval f BT = back_top f
167 eval f LD = left_down f
168 eval f LT = left_top f
169 eval f RD = right_down f
170 eval f RT = right_top f
171 eval f FLD = front_left_down f
172 eval f FLT = front_left_top f
173 eval f FRD = front_right_down f
174 eval f FRT = front_right_top f
175 eval f BLD = back_left_down f
176 eval f BLT = back_left_top f
177 eval f BRD = back_right_down f
178 eval f BRT = back_right_top f
179 eval f I = interior f
180 eval _ (Scalar x) = x
181 eval f (Sum x y) = (eval f x) + (eval f y)
182 eval f (Difference x y) = (eval f x) - (eval f y)
183 eval f (Product x y) = (eval f x) * (eval f y)
184 eval f (Quotient x y) = (eval f x) / (eval f y)
185
186
187 -- | Takes a three-dimensional list of 'Double' and a set of 3D
188 -- coordinates (i,j,k), and returns the value at (i,j,k) in the
189 -- supplied list. If there is no such value, we calculate one
190 -- according to Sorokina and Zeilfelder, remark 7.3, p. 99.
191 --
192 -- We specifically do not consider values more than one unit away
193 -- from our grid.
194 --
195 -- Examples:
196 --
197 -- >>> value_at Examples.trilinear 0 0 0
198 -- 1.0
199 --
200 -- >>> value_at Examples.trilinear (-1) 0 0
201 -- 0.0
202 --
203 -- >>> value_at Examples.trilinear 0 0 4
204 -- 1.0
205 --
206 -- >>> value_at Examples.trilinear 1 3 0
207 -- 5.0
208 --
209 value_at :: Values3D -> Int -> Int -> Int -> Double
210 value_at v3d !i !j !k
211 -- Put the most common case first!
212 | (valid_i i) && (valid_j j) && (valid_k k) =
213 idx v3d i j k
214
215 -- The next three are from the first line in (7.3). Analogous cases
216 -- have been added where the indices are one-too-big. These are the
217 -- "one index is bad" cases.
218 | not (valid_i i) =
219 if (dim_i == 1)
220 then
221 -- We're one-dimensional in our first coordinate, so just
222 -- return the data point that we do have. If we try to use
223 -- the formula from remark 7.3, we go into an infinite loop.
224 value_at v3d 0 j k
225 else
226 if (i == -1)
227 then
228 2*(value_at v3d 0 j k) - (value_at v3d 1 j k)
229 else
230 2*(value_at v3d (i-1) j k) - (value_at v3d (i-2) j k)
231
232 | not (valid_j j) =
233 if (dim_j == 1)
234 then
235 -- We're one-dimensional in our second coordinate, so just
236 -- return the data point that we do have. If we try to use
237 -- the formula from remark 7.3, we go into an infinite loop.
238 value_at v3d i 0 k
239 else
240 if (j == -1)
241 then
242 2*(value_at v3d i 0 k) - (value_at v3d i 1 k)
243 else
244 2*(value_at v3d i (j-1) k) - (value_at v3d i (j-2) k)
245
246 | not (valid_k k) =
247 if (dim_k == 1)
248 then
249 -- We're one-dimensional in our third coordinate, so just
250 -- return the data point that we do have. If we try to use
251 -- the formula from remark 7.3, we go into an infinite loop.
252 value_at v3d i j 0
253 else
254 if (k == -1)
255 then
256 2*(value_at v3d i j 0) - (value_at v3d i j 1)
257 else
258 2*(value_at v3d i j (k-1)) - (value_at v3d i j (k-2))
259 where
260 (dim_i, dim_j, dim_k) = dims v3d
261
262 valid_i :: Int -> Bool
263 valid_i i' = (i' >= 0) && (i' < dim_i)
264
265 valid_j :: Int -> Bool
266 valid_j j' = (j' >= 0) && (j' < dim_j)
267
268 valid_k :: Int -> Bool
269 valid_k k' = (k' >= 0) && (k' < dim_k)
270
271
272
273 -- | Given a three-dimensional list of 'Double' and a set of 3D
274 -- coordinates (i,j,k), constructs and returns the 'FunctionValues'
275 -- object centered at (i,j,k)
276 make_values :: Values3D -> Int -> Int -> Int -> FunctionValues
277 make_values values !i !j !k =
278 empty_values { front = value_at values (i-1) j k,
279 back = value_at values (i+1) j k,
280 left = value_at values i (j-1) k,
281 right = value_at values i (j+1) k,
282 down = value_at values i j (k-1),
283 top = value_at values i j (k+1),
284 front_left = value_at values (i-1) (j-1) k,
285 front_right = value_at values (i-1) (j+1) k,
286 front_down =value_at values (i-1) j (k-1),
287 front_top = value_at values (i-1) j (k+1),
288 back_left = value_at values (i+1) (j-1) k,
289 back_right = value_at values (i+1) (j+1) k,
290 back_down = value_at values (i+1) j (k-1),
291 back_top = value_at values (i+1) j (k+1),
292 left_down = value_at values i (j-1) (k-1),
293 left_top = value_at values i (j-1) (k+1),
294 right_down = value_at values i (j+1) (k-1),
295 right_top = value_at values i (j+1) (k+1),
296 front_left_down = value_at values (i-1) (j-1) (k-1),
297 front_left_top = value_at values (i-1) (j-1) (k+1),
298 front_right_down = value_at values (i-1) (j+1) (k-1),
299 front_right_top = value_at values (i-1) (j+1) (k+1),
300 back_left_down = value_at values (i+1) (j-1) (k-1),
301 back_left_top = value_at values (i+1) (j-1) (k+1),
302 back_right_down = value_at values (i+1) (j+1) (k-1),
303 back_right_top = value_at values (i+1) (j+1) (k+1),
304 interior = value_at values i j k }
305
306 -- | Takes a 'FunctionValues' and a function that transforms one
307 -- 'Cardinal' to another (called a rotation). Then it applies the
308 -- rotation to each element of the 'FunctionValues' object, and
309 -- returns the result.
310 rotate :: (Cardinal -> Cardinal) -> FunctionValues -> FunctionValues
311 rotate rotation fv =
312 FunctionValues { front = eval fv (rotation F),
313 back = eval fv (rotation B),
314 left = eval fv (rotation L),
315 right = eval fv (rotation R),
316 down = eval fv (rotation D),
317 top = eval fv (rotation T),
318 front_left = eval fv (rotation FL),
319 front_right = eval fv (rotation FR),
320 front_down = eval fv (rotation FD),
321 front_top = eval fv (rotation FT),
322 back_left = eval fv (rotation BL),
323 back_right = eval fv (rotation BR),
324 back_down = eval fv (rotation BD),
325 back_top = eval fv (rotation BT),
326 left_down = eval fv (rotation LD),
327 left_top = eval fv (rotation LT),
328 right_down = eval fv (rotation RD),
329 right_top = eval fv (rotation RT),
330 front_left_down = eval fv (rotation FLD),
331 front_left_top = eval fv (rotation FLT),
332 front_right_down = eval fv (rotation FRD),
333 front_right_top = eval fv (rotation FRT),
334 back_left_down = eval fv (rotation BLD),
335 back_left_top = eval fv (rotation BLT),
336 back_right_down = eval fv (rotation BRD),
337 back_right_top = eval fv (rotation BRT),
338 interior = interior fv }
339
340
341
342 -- | Ensure that the trilinear values wind up where we think they
343 -- should.
344 test_directions :: Assertion
345 test_directions =
346 assertTrue "all direction functions work" (and equalities)
347 where
348 fvs = make_values trilinear 1 1 1
349 equalities = [ interior fvs == 4,
350 front fvs == 1,
351 back fvs == 7,
352 left fvs == 2,
353 right fvs == 6,
354 down fvs == 3,
355 top fvs == 5,
356 front_left fvs == 1,
357 front_right fvs == 1,
358 front_down fvs == 1,
359 front_top fvs == 1,
360 back_left fvs == 3,
361 back_right fvs == 11,
362 back_down fvs == 5,
363 back_top fvs == 9,
364 left_down fvs == 2,
365 left_top fvs == 2,
366 right_down fvs == 4,
367 right_top fvs == 8,
368 front_left_down fvs == 1,
369 front_left_top fvs == 1,
370 front_right_down fvs == 1,
371 front_right_top fvs == 1,
372 back_left_down fvs == 3,
373 back_left_top fvs == 3,
374 back_right_down fvs == 7,
375 back_right_top fvs == 15]
376
377
378 function_values_tests :: TestTree
379 function_values_tests =
380 testGroup "FunctionValues tests"
381 [ testCase "test directions" test_directions ]
382
383
384 prop_x_rotation_doesnt_affect_front :: FunctionValues -> Bool
385 prop_x_rotation_doesnt_affect_front fv0 =
386 expr1 == expr2
387 where
388 fv1 = rotate cwx fv0
389 expr1 = front fv0
390 expr2 = front fv1
391
392 prop_x_rotation_doesnt_affect_back :: FunctionValues -> Bool
393 prop_x_rotation_doesnt_affect_back fv0 =
394 expr1 == expr2
395 where
396 fv1 = rotate cwx fv0
397 expr1 = back fv0
398 expr2 = back fv1
399
400
401 prop_y_rotation_doesnt_affect_left :: FunctionValues -> Bool
402 prop_y_rotation_doesnt_affect_left fv0 =
403 expr1 == expr2
404 where
405 fv1 = rotate cwy fv0
406 expr1 = left fv0
407 expr2 = left fv1
408
409 prop_y_rotation_doesnt_affect_right :: FunctionValues -> Bool
410 prop_y_rotation_doesnt_affect_right fv0 =
411 expr1 == expr2
412 where
413 fv1 = rotate cwy fv0
414 expr1 = right fv0
415 expr2 = right fv1
416
417
418 prop_z_rotation_doesnt_affect_down :: FunctionValues -> Bool
419 prop_z_rotation_doesnt_affect_down fv0 =
420 expr1 == expr2
421 where
422 fv1 = rotate cwz fv0
423 expr1 = down fv0
424 expr2 = down fv1
425
426
427 prop_z_rotation_doesnt_affect_top :: FunctionValues -> Bool
428 prop_z_rotation_doesnt_affect_top fv0 =
429 expr1 == expr2
430 where
431 fv1 = rotate cwz fv0
432 expr1 = top fv0
433 expr2 = top fv1
434
435
436 function_values_properties :: TestTree
437 function_values_properties =
438 testGroup "FunctionValues properties" [
439 testProperty
440 "x rotation doesn't affect front"
441 prop_x_rotation_doesnt_affect_front,
442 testProperty
443 "x rotation doesn't affect back"
444 prop_x_rotation_doesnt_affect_back,
445 testProperty
446 "y rotation doesn't affect left"
447 prop_y_rotation_doesnt_affect_left,
448 testProperty
449 "y rotation doesn't affect right"
450 prop_y_rotation_doesnt_affect_right,
451 testProperty
452 "z rotation doesn't affect top"
453 prop_z_rotation_doesnt_affect_top,
454 testProperty
455 "z rotation doesn't affect down"
456 prop_z_rotation_doesnt_affect_down ]