]> gitweb.michael.orlitzky.com - spline3.git/blob - src/Tetrahedron.hs
src/Tetrahedron.hs: use explicit FunctionValues(..) imports.
[spline3.git] / src / Tetrahedron.hs
1 -- The local "coefficient" function defined within the "c" function
2 -- pattern matches on a bunch of integers, but doesn't handle the
3 -- "otherwise" case for performance reasons.
4 {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
5 {-# LANGUAGE BangPatterns #-}
6
7 module Tetrahedron (
8 Tetrahedron(..),
9 b0, -- Cube test
10 b1, -- Cube test
11 b2, -- Cube test
12 b3, -- Cube test
13 barycenter,
14 c,
15 polynomial,
16 tetrahedron_properties,
17 tetrahedron_tests,
18 volume ) -- Cube test
19 where
20
21 import Data.Vector ( singleton, snoc )
22 import qualified Data.Vector as V ( sum )
23 import Test.Tasty ( TestTree, testGroup )
24 import Test.Tasty.HUnit ( Assertion, assertEqual, testCase )
25 import Test.Tasty.QuickCheck (
26 Arbitrary( arbitrary ),
27 Gen,
28 Property,
29 (==>),
30 testProperty )
31
32 import Comparisons ( (~=) )
33 import FunctionValues (
34 FunctionValues( front, back, left, right, top, down, front_left,
35 front_right, front_down, front_top, back_left, back_right,
36 back_down, back_top, left_down, left_top, right_down,
37 right_top, front_left_down, front_left_top,
38 front_right_down, front_right_top, interior ),
39 empty_values )
40 import Misc ( factorial )
41 import Point ( Point(Point), scale )
42 import RealFunction ( RealFunction, cmult, fexp )
43
44 data Tetrahedron =
45 Tetrahedron { function_values :: FunctionValues,
46 v0 :: !Point,
47 v1 :: !Point,
48 v2 :: !Point,
49 v3 :: !Point,
50 precomputed_volume :: !Double
51 }
52 deriving (Eq)
53
54
55 instance Arbitrary Tetrahedron where
56 arbitrary = do
57 rnd_v0 <- arbitrary :: Gen Point
58 rnd_v1 <- arbitrary :: Gen Point
59 rnd_v2 <- arbitrary :: Gen Point
60 rnd_v3 <- arbitrary :: Gen Point
61 rnd_fv <- arbitrary :: Gen FunctionValues
62
63 -- We can't assign an incorrect precomputed volume,
64 -- so we have to calculate the correct one here.
65 let t' = Tetrahedron rnd_fv rnd_v0 rnd_v1 rnd_v2 rnd_v3 0
66 let vol = volume t'
67 return (Tetrahedron rnd_fv rnd_v0 rnd_v1 rnd_v2 rnd_v3 vol)
68
69
70 instance Show Tetrahedron where
71 show t = "Tetrahedron:\n" ++
72 " function_values: " ++ (show (function_values t)) ++ "\n" ++
73 " v0: " ++ (show (v0 t)) ++ "\n" ++
74 " v1: " ++ (show (v1 t)) ++ "\n" ++
75 " v2: " ++ (show (v2 t)) ++ "\n" ++
76 " v3: " ++ (show (v3 t)) ++ "\n"
77
78
79 -- | Find the barycenter of the given tetrahedron.
80 -- We just average the four vertices.
81 barycenter :: Tetrahedron -> Point
82 barycenter (Tetrahedron _ v0' v1' v2' v3' _) =
83 (v0' + v1' + v2' + v3') `scale` (1/4)
84
85
86
87 {-# INLINE polynomial #-}
88 polynomial :: Tetrahedron -> (RealFunction Point)
89 polynomial t =
90 V.sum $ singleton ((c t 0 0 0 3) `cmult` (beta t 0 0 0 3)) `snoc`
91 ((c t 0 0 1 2) `cmult` (beta t 0 0 1 2)) `snoc`
92 ((c t 0 0 2 1) `cmult` (beta t 0 0 2 1)) `snoc`
93 ((c t 0 0 3 0) `cmult` (beta t 0 0 3 0)) `snoc`
94 ((c t 0 1 0 2) `cmult` (beta t 0 1 0 2)) `snoc`
95 ((c t 0 1 1 1) `cmult` (beta t 0 1 1 1)) `snoc`
96 ((c t 0 1 2 0) `cmult` (beta t 0 1 2 0)) `snoc`
97 ((c t 0 2 0 1) `cmult` (beta t 0 2 0 1)) `snoc`
98 ((c t 0 2 1 0) `cmult` (beta t 0 2 1 0)) `snoc`
99 ((c t 0 3 0 0) `cmult` (beta t 0 3 0 0)) `snoc`
100 ((c t 1 0 0 2) `cmult` (beta t 1 0 0 2)) `snoc`
101 ((c t 1 0 1 1) `cmult` (beta t 1 0 1 1)) `snoc`
102 ((c t 1 0 2 0) `cmult` (beta t 1 0 2 0)) `snoc`
103 ((c t 1 1 0 1) `cmult` (beta t 1 1 0 1)) `snoc`
104 ((c t 1 1 1 0) `cmult` (beta t 1 1 1 0)) `snoc`
105 ((c t 1 2 0 0) `cmult` (beta t 1 2 0 0)) `snoc`
106 ((c t 2 0 0 1) `cmult` (beta t 2 0 0 1)) `snoc`
107 ((c t 2 0 1 0) `cmult` (beta t 2 0 1 0)) `snoc`
108 ((c t 2 1 0 0) `cmult` (beta t 2 1 0 0)) `snoc`
109 ((c t 3 0 0 0) `cmult` (beta t 3 0 0 0))
110
111
112
113 -- | The Bernstein polynomial on t with indices i,j,k,l. Denoted by a
114 -- capital 'B' in the Sorokina/Zeilfelder paper.
115 beta :: Tetrahedron -> Int -> Int -> Int -> Int -> (RealFunction Point)
116 beta t i j k l =
117 coefficient `cmult` (b0_term * b1_term * b2_term * b3_term)
118 where
119 denominator = (factorial i)*(factorial j)*(factorial k)*(factorial l)
120 coefficient = (6 / (fromIntegral denominator)) :: Double
121 b0_term = (b0 t) `fexp` i
122 b1_term = (b1 t) `fexp` j
123 b2_term = (b2 t) `fexp` k
124 b3_term = (b3 t) `fexp` l
125
126
127 -- | The coefficient function. c t i j k l returns the coefficient
128 -- c_ijkl with respect to the tetrahedron t. The definition uses
129 -- pattern matching to mimic the definitions given in Sorokina and
130 -- Zeilfelder, pp. 84-86. If incorrect indices are supplied, the world
131 -- will end. This is for performance reasons.
132 c :: Tetrahedron -> Int -> Int -> Int -> Int -> Double
133 c !t !i !j !k !l =
134 coefficient i j k l
135 where
136 fvs = function_values t
137 f = front fvs
138 b = back fvs
139 r = right fvs
140 l' = left fvs
141 t' = top fvs
142 d = down fvs
143 fl = front_left fvs
144 fr = front_right fvs
145 fd = front_down fvs
146 ft = front_top fvs
147 bl = back_left fvs
148 br = back_right fvs
149 bd = back_down fvs
150 bt = back_top fvs
151 ld = left_down fvs
152 lt = left_top fvs
153 rd = right_down fvs
154 rt = right_top fvs
155 fld = front_left_down fvs
156 flt = front_left_top fvs
157 frd = front_right_down fvs
158 frt = front_right_top fvs
159 i' = interior fvs
160
161 coefficient :: Int -> Int -> Int -> Int -> Double
162 coefficient 0 0 3 0 =
163 (1/8) * (i' + f + l' + t' + lt + fl + ft + flt)
164
165 coefficient 0 0 0 3 =
166 (1/8) * (i' + f + r + t' + rt + fr + ft + frt)
167
168 coefficient 0 0 2 1 =
169 (5/24)*(i' + f + t' + ft) + (1/24)*(l' + fl + lt + flt)
170
171 coefficient 0 0 1 2 =
172 (5/24)*(i' + f + t' + ft) + (1/24)*(r + fr + rt + frt)
173
174 coefficient 0 1 2 0 =
175 (5/24)*(i' + f) + (1/8)*(l' + t' + fl + ft)
176 + (1/24)*(lt + flt)
177
178 coefficient 0 1 0 2 =
179 (5/24)*(i' + f) + (1/8)*(r + t' + fr + ft)
180 + (1/24)*(rt + frt)
181
182 coefficient 0 1 1 1 =
183 (13/48)*(i' + f) + (7/48)*(t' + ft)
184 + (1/32)*(l' + r + fl + fr)
185 + (1/96)*(lt + rt + flt + frt)
186
187 coefficient 0 2 1 0 =
188 (13/48)*(i' + f) + (17/192)*(l' + t' + fl + ft)
189 + (1/96)*(lt + flt)
190 + (1/64)*(r + d + fr + fd)
191 + (1/192)*(rt + ld + frt + fld)
192
193 coefficient 0 2 0 1 =
194 (13/48)*(i' + f) + (17/192)*(r + t' + fr + ft)
195 + (1/96)*(rt + frt)
196 + (1/64)*(l' + d + fl + fd)
197 + (1/192)*(rd + lt + flt + frd)
198
199 coefficient 0 3 0 0 =
200 (13/48)*(i' + f) + (5/96)*(l' + r + t' + d + fl + fr + ft + fd)
201 + (1/192)*(rt + rd + lt + ld + frt + frd + flt + fld)
202
203 coefficient 1 0 2 0 =
204 (1/4)*i' + (1/6)*(f + l' + t')
205 + (1/12)*(lt + fl + ft)
206
207 coefficient 1 0 0 2 =
208 (1/4)*i' + (1/6)*(f + r + t')
209 + (1/12)*(rt + fr + ft)
210
211 coefficient 1 0 1 1 =
212 (1/3)*i' + (5/24)*(f + t')
213 + (1/12)*ft
214 + (1/24)*(l' + r)
215 + (1/48)*(lt + rt + fl + fr)
216
217 coefficient 1 1 1 0 =
218 (1/3)*i' + (5/24)*f
219 + (1/8)*(l' + t')
220 + (5/96)*(fl + ft)
221 + (1/48)*(d + r + lt)
222 + (1/96)*(fd + ld + rt + fr)
223
224 coefficient 1 1 0 1 =
225 (1/3)*i' + (5/24)*f
226 + (1/8)*(r + t')
227 + (5/96)*(fr + ft)
228 + (1/48)*(d + l' + rt)
229 + (1/96)*(fd + lt + rd + fl)
230
231 coefficient 1 2 0 0 =
232 (1/3)*i' + (5/24)*f
233 + (7/96)*(l' + r + t' + d)
234 + (1/32)*(fl + fr + ft + fd)
235 + (1/96)*(rt + rd + lt + ld)
236
237 coefficient 2 0 1 0 =
238 (3/8)*i' + (7/48)*(f + t' + l')
239 + (1/48)*(r + d + b + lt + fl + ft)
240 + (1/96)*(rt + bt + fr + fd + ld + bl)
241
242 coefficient 2 0 0 1 =
243 (3/8)*i' + (7/48)*(f + t' + r)
244 + (1/48)*(l' + d + b + rt + fr + ft)
245 + (1/96)*(lt + bt + fl + fd + rd + br)
246
247 coefficient 2 1 0 0 =
248 (3/8)*i' + (1/12)*(t' + r + l' + d)
249 + (1/64)*(ft + fr + fl + fd)
250 + (7/48)*f
251 + (1/48)*b
252 + (1/96)*(rt + ld + lt + rd)
253 + (1/192)*(bt + br + bl + bd)
254
255 coefficient 3 0 0 0 =
256 (3/8)*i' + (1/12)*(t' + f + l' + r + d + b)
257 + (1/96)*(lt + fl + ft + rt + bt + fr)
258 + (1/96)*(fd + ld + bd + br + rd + bl)
259
260
261
262 -- | Compute the determinant of the 4x4 matrix,
263 --
264 -- [1]
265 -- [x]
266 -- [y]
267 -- [z]
268 --
269 -- where [1] = [1, 1, 1, 1],
270 -- [x] = [x1,x2,x3,x4],
271 --
272 -- et cetera.
273 --
274 -- The termX nonsense is an attempt to prevent Double overflow.
275 -- which has been observed to happen with large coordinates.
276 --
277 det :: Point -> Point -> Point -> Point -> Double
278 det p0 p1 p2 p3 =
279 term5 + term6
280 where
281 Point x1 y1 z1 = p0
282 Point x2 y2 z2 = p1
283 Point x3 y3 z3 = p2
284 Point x4 y4 z4 = p3
285 term1 = ((x2 - x4)*y1 - (x1 - x4)*y2 + (x1 - x2)*y4)*z3
286 term2 = ((x2 - x3)*y1 - (x1 - x3)*y2 + (x1 - x2)*y3)*z4
287 term3 = ((x3 - x4)*y2 - (x2 - x4)*y3 + (x2 - x3)*y4)*z1
288 term4 = ((x3 - x4)*y1 - (x1 - x4)*y3 + (x1 - x3)*y4)*z2
289 term5 = term1 - term2
290 term6 = term3 - term4
291
292
293 -- | Computed using the formula from Lai & Schumaker, Definition 15.4,
294 -- page 436.
295 {-# INLINE volume #-}
296 volume :: Tetrahedron -> Double
297 volume (Tetrahedron _ v0' v1' v2' v3' _) =
298 (1/6)*(det v0' v1' v2' v3')
299
300 -- | The barycentric coordinates of a point with respect to v0.
301 {-# INLINE b0 #-}
302 b0 :: Tetrahedron -> (RealFunction Point)
303 b0 t point = (volume inner_tetrahedron) / (precomputed_volume t)
304 where
305 inner_tetrahedron = t { v0 = point }
306
307
308 -- | The barycentric coordinates of a point with respect to v1.
309 {-# INLINE b1 #-}
310 b1 :: Tetrahedron -> (RealFunction Point)
311 b1 t point = (volume inner_tetrahedron) / (precomputed_volume t)
312 where
313 inner_tetrahedron = t { v1 = point }
314
315
316 -- | The barycentric coordinates of a point with respect to v2.
317 {-# INLINE b2 #-}
318 b2 :: Tetrahedron -> (RealFunction Point)
319 b2 t point = (volume inner_tetrahedron) / (precomputed_volume t)
320 where
321 inner_tetrahedron = t { v2 = point }
322
323
324 -- | The barycentric coordinates of a point with respect to v3.
325 {-# INLINE b3 #-}
326 b3 :: Tetrahedron -> (RealFunction Point)
327 b3 t point = (volume inner_tetrahedron) / (precomputed_volume t)
328 where
329 inner_tetrahedron = t { v3 = point }
330
331
332
333
334 -- | Check the volume of a particular tetrahedron (computed by hand)
335 -- Its vertices are in clockwise order, so the volume should be
336 -- negative.
337 tetrahedron1_geometry_tests :: TestTree
338 tetrahedron1_geometry_tests =
339 testGroup "tetrahedron1 geometry"
340 [ testCase "volume1" volume1 ]
341 where
342 p0 = Point 0 (-0.5) 0
343 p1 = Point 0 0.5 0
344 p2 = Point 2 0 0
345 p3 = Point 1 0 1
346 t = Tetrahedron { v0 = p0,
347 v1 = p1,
348 v2 = p2,
349 v3 = p3,
350 function_values = empty_values,
351 precomputed_volume = 0 }
352
353 volume1 :: Assertion
354 volume1 =
355 assertEqual "volume is correct" True (vol ~= (-1/3))
356 where
357 vol = volume t
358
359
360 -- | Check the volume of a particular tetrahedron (computed by hand)
361 -- Its vertices are in counter-clockwise order, so the volume should
362 -- be positive.
363 tetrahedron2_geometry_tests :: TestTree
364 tetrahedron2_geometry_tests =
365 testGroup "tetrahedron2 geometry"
366 [ testCase "volume1" volume1 ]
367 where
368 p0 = Point 0 (-0.5) 0
369 p1 = Point 2 0 0
370 p2 = Point 0 0.5 0
371 p3 = Point 1 0 1
372 t = Tetrahedron { v0 = p0,
373 v1 = p1,
374 v2 = p2,
375 v3 = p3,
376 function_values = empty_values,
377 precomputed_volume = 0 }
378
379 volume1 :: Assertion
380 volume1 = assertEqual "volume1 is correct" True (vol ~= (1/3))
381 where
382 vol = volume t
383
384
385
386 -- | The barycentric coordinate of v0 with respect to itself should
387 -- be one.
388 prop_b0_v0_always_unity :: Tetrahedron -> Property
389 prop_b0_v0_always_unity t =
390 (volume t) > 0 ==> (b0 t) (v0 t) ~= 1.0
391
392 -- | The barycentric coordinate of v1 with respect to v0 should
393 -- be zero.
394 prop_b0_v1_always_zero :: Tetrahedron -> Property
395 prop_b0_v1_always_zero t =
396 (volume t) > 0 ==> (b0 t) (v1 t) ~= 0
397
398 -- | The barycentric coordinate of v2 with respect to v0 should
399 -- be zero.
400 prop_b0_v2_always_zero :: Tetrahedron -> Property
401 prop_b0_v2_always_zero t =
402 (volume t) > 0 ==> (b0 t) (v2 t) ~= 0
403
404 -- | The barycentric coordinate of v3 with respect to v0 should
405 -- be zero.
406 prop_b0_v3_always_zero :: Tetrahedron -> Property
407 prop_b0_v3_always_zero t =
408 (volume t) > 0 ==> (b0 t) (v3 t) ~= 0
409
410 -- | The barycentric coordinate of v1 with respect to itself should
411 -- be one.
412 prop_b1_v1_always_unity :: Tetrahedron -> Property
413 prop_b1_v1_always_unity t =
414 (volume t) > 0 ==> (b1 t) (v1 t) ~= 1.0
415
416 -- | The barycentric coordinate of v0 with respect to v1 should
417 -- be zero.
418 prop_b1_v0_always_zero :: Tetrahedron -> Property
419 prop_b1_v0_always_zero t =
420 (volume t) > 0 ==> (b1 t) (v0 t) ~= 0
421
422 -- | The barycentric coordinate of v2 with respect to v1 should
423 -- be zero.
424 prop_b1_v2_always_zero :: Tetrahedron -> Property
425 prop_b1_v2_always_zero t =
426 (volume t) > 0 ==> (b1 t) (v2 t) ~= 0
427
428 -- | The barycentric coordinate of v3 with respect to v1 should
429 -- be zero.
430 prop_b1_v3_always_zero :: Tetrahedron -> Property
431 prop_b1_v3_always_zero t =
432 (volume t) > 0 ==> (b1 t) (v3 t) ~= 0
433
434 -- | The barycentric coordinate of v2 with respect to itself should
435 -- be one.
436 prop_b2_v2_always_unity :: Tetrahedron -> Property
437 prop_b2_v2_always_unity t =
438 (volume t) > 0 ==> (b2 t) (v2 t) ~= 1.0
439
440 -- | The barycentric coordinate of v0 with respect to v2 should
441 -- be zero.
442 prop_b2_v0_always_zero :: Tetrahedron -> Property
443 prop_b2_v0_always_zero t =
444 (volume t) > 0 ==> (b2 t) (v0 t) ~= 0
445
446 -- | The barycentric coordinate of v1 with respect to v2 should
447 -- be zero.
448 prop_b2_v1_always_zero :: Tetrahedron -> Property
449 prop_b2_v1_always_zero t =
450 (volume t) > 0 ==> (b2 t) (v1 t) ~= 0
451
452 -- | The barycentric coordinate of v3 with respect to v2 should
453 -- be zero.
454 prop_b2_v3_always_zero :: Tetrahedron -> Property
455 prop_b2_v3_always_zero t =
456 (volume t) > 0 ==> (b2 t) (v3 t) ~= 0
457
458 -- | The barycentric coordinate of v3 with respect to itself should
459 -- be one.
460 prop_b3_v3_always_unity :: Tetrahedron -> Property
461 prop_b3_v3_always_unity t =
462 (volume t) > 0 ==> (b3 t) (v3 t) ~= 1.0
463
464 -- | The barycentric coordinate of v0 with respect to v3 should
465 -- be zero.
466 prop_b3_v0_always_zero :: Tetrahedron -> Property
467 prop_b3_v0_always_zero t =
468 (volume t) > 0 ==> (b3 t) (v0 t) ~= 0
469
470 -- | The barycentric coordinate of v1 with respect to v3 should
471 -- be zero.
472 prop_b3_v1_always_zero :: Tetrahedron -> Property
473 prop_b3_v1_always_zero t =
474 (volume t) > 0 ==> (b3 t) (v1 t) ~= 0
475
476 -- | The barycentric coordinate of v2 with respect to v3 should
477 -- be zero.
478 prop_b3_v2_always_zero :: Tetrahedron -> Property
479 prop_b3_v2_always_zero t =
480 (volume t) > 0 ==> (b3 t) (v2 t) ~= 0
481
482
483 prop_swapping_vertices_doesnt_affect_coefficients1 :: Tetrahedron -> Bool
484 prop_swapping_vertices_doesnt_affect_coefficients1 t =
485 c t 0 0 1 2 == c t' 0 0 1 2
486 where
487 t' = t { v0 = (v1 t), v1 = (v0 t) }
488
489 prop_swapping_vertices_doesnt_affect_coefficients2 :: Tetrahedron -> Bool
490 prop_swapping_vertices_doesnt_affect_coefficients2 t =
491 c t 0 1 1 1 == c t' 0 1 1 1
492 where
493 t' = t { v2 = (v3 t), v3 = (v2 t) }
494
495 prop_swapping_vertices_doesnt_affect_coefficients3 :: Tetrahedron -> Bool
496 prop_swapping_vertices_doesnt_affect_coefficients3 t =
497 c t 2 1 0 0 == c t' 2 1 0 0
498 where
499 t' = t { v2 = (v3 t), v3 = (v2 t) }
500
501 prop_swapping_vertices_doesnt_affect_coefficients4 :: Tetrahedron -> Bool
502 prop_swapping_vertices_doesnt_affect_coefficients4 t =
503 c t 2 0 0 1 == c t' 2 0 0 1
504 where
505 t' = t { v0 = (v3 t), v3 = (v0 t) }
506
507
508
509
510 tetrahedron_tests :: TestTree
511 tetrahedron_tests =
512 testGroup "Tetrahedron tests" [
513 tetrahedron1_geometry_tests,
514 tetrahedron2_geometry_tests ]
515
516
517
518 p78_24_properties :: TestTree
519 p78_24_properties =
520 testGroup "p. 78, Section (2.4) properties" [
521 testProperty "c3000 identity" prop_c3000_identity,
522 testProperty "c2100 identity" prop_c2100_identity,
523 testProperty "c1110 identity" prop_c1110_identity]
524 where
525 -- | Returns the domain point of t with indices i,j,k,l.
526 domain_point :: Tetrahedron -> Int -> Int -> Int -> Int -> Point
527 domain_point t i j k l =
528 weighted_sum `scale` (1/3)
529 where
530 v0' = (v0 t) `scale` (fromIntegral i)
531 v1' = (v1 t) `scale` (fromIntegral j)
532 v2' = (v2 t) `scale` (fromIntegral k)
533 v3' = (v3 t) `scale` (fromIntegral l)
534 weighted_sum = v0' + v1' + v2' + v3'
535
536
537 -- | Used for convenience in the next few tests.
538 p :: Tetrahedron -> Int -> Int -> Int -> Int -> Double
539 p t i j k l = (polynomial t) (domain_point t i j k l)
540
541
542 -- | Given in Sorokina and Zeilfelder, p. 78.
543 prop_c3000_identity :: Tetrahedron -> Property
544 prop_c3000_identity t =
545 (volume t) > 0 ==>
546 c t 3 0 0 0 ~= p t 3 0 0 0
547
548 -- | Given in Sorokina and Zeilfelder, p. 78.
549 prop_c2100_identity :: Tetrahedron -> Property
550 prop_c2100_identity t =
551 (volume t) > 0 ==>
552 c t 2 1 0 0 ~= (term1 - term2 + term3 - term4)
553 where
554 term1 = (1/3)*(p t 0 3 0 0)
555 term2 = (5/6)*(p t 3 0 0 0)
556 term3 = 3*(p t 2 1 0 0)
557 term4 = (3/2)*(p t 1 2 0 0)
558
559 -- | Given in Sorokina and Zeilfelder, p. 78.
560 prop_c1110_identity :: Tetrahedron -> Property
561 prop_c1110_identity t =
562 (volume t) > 0 ==>
563 c t 1 1 1 0 ~= (term1 + term2 - term3 - term4)
564 where
565 term1 = (1/3)*((p t 3 0 0 0) + (p t 0 3 0 0) + (p t 0 0 3 0))
566 term2 = (9/2)*(p t 1 1 1 0)
567 term3 = (3/4)*((p t 2 1 0 0) + (p t 1 2 0 0) + (p t 2 0 1 0))
568 term4 = (3/4)*((p t 1 0 2 0) + (p t 0 2 1 0) + (p t 0 1 2 0))
569
570
571
572 tetrahedron_properties :: TestTree
573 tetrahedron_properties =
574 testGroup "Tetrahedron properties" [
575 p78_24_properties,
576 testProperty "b0_v0_always_unity" prop_b0_v0_always_unity,
577 testProperty "b0_v1_always_zero" prop_b0_v1_always_zero,
578 testProperty "b0_v2_always_zero" prop_b0_v2_always_zero,
579 testProperty "b0_v3_always_zero" prop_b0_v3_always_zero,
580 testProperty "b1_v1_always_unity" prop_b1_v1_always_unity,
581 testProperty "b1_v0_always_zero" prop_b1_v0_always_zero,
582 testProperty "b1_v2_always_zero" prop_b1_v2_always_zero,
583 testProperty "b1_v3_always_zero" prop_b1_v3_always_zero,
584 testProperty "b2_v2_always_unity" prop_b2_v2_always_unity,
585 testProperty "b2_v0_always_zero" prop_b2_v0_always_zero,
586 testProperty "b2_v1_always_zero" prop_b2_v1_always_zero,
587 testProperty "b2_v3_always_zero" prop_b2_v3_always_zero,
588 testProperty "b3_v3_always_unity" prop_b3_v3_always_unity,
589 testProperty "b3_v0_always_zero" prop_b3_v0_always_zero,
590 testProperty "b3_v1_always_zero" prop_b3_v1_always_zero,
591 testProperty "b3_v2_always_zero" prop_b3_v2_always_zero,
592 testProperty "swapping_vertices_doesnt_affect_coefficients1"
593 prop_swapping_vertices_doesnt_affect_coefficients1,
594 testProperty "swapping_vertices_doesnt_affect_coefficients2"
595 prop_swapping_vertices_doesnt_affect_coefficients2,
596 testProperty "swapping_vertices_doesnt_affect_coefficients3"
597 prop_swapping_vertices_doesnt_affect_coefficients3,
598 testProperty "swapping_vertices_doesnt_affect_coefficients4"
599 prop_swapping_vertices_doesnt_affect_coefficients4 ]