1 """Local function space representation."""
2
3 from __future__ import division
4
5 __copyright__ = "Copyright (C) 2007 Andreas Kloeckner"
6
7 __license__ = """
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see U{http://www.gnu.org/licenses/}.
20 """
21
22
23
24
25 import numpy
26 import numpy.linalg as la
27 import pyublas
28 from hedge.tools import AffineMap
29 import hedge._internal
30 from math import sqrt
31 from pytools import memoize_method
32 import hedge.mesh
33
34
35
36
37 __all__ = ["TriangularElement", "TetrahedralElement"]
43 """Calculator for Warburton's warp factor.
44
45 See T. Warburton,
46 "An explicit construction of interpolation nodes on the simplex"
47 Journal of Engineering Mathematics Vol 56, No 3, p. 247-262, 2006
48 """
49
59
61 if abs(x) > 1-1e-10:
62 return 0
63 else:
64 return self.int_f(x)/(1-x**2)
65
73 return repr(self.value)
74
80 self.alpha = alpha
81 self.warp = WarpFactorCalculator(order)
82
83 cls = TriangularElement
84
85 from pytools import wandering_element
86 from tools import normalize
87
88 vertices = [cls.barycentric_to_equilateral(bary)
89 for bary in wandering_element(cls.dimensions+1)]
90 all_vertex_indices = range(cls.dimensions+1)
91 face_vertex_indices = cls.geometry \
92 .face_vertices(all_vertex_indices)
93 faces_vertices = cls.geometry.face_vertices(vertices)
94
95 edgedirs = [normalize(v2-v1) for v1, v2 in faces_vertices]
96 opp_vertex_indices = [
97 (set(all_vertex_indices) - set(fvi)).__iter__().next()
98 for fvi in face_vertex_indices]
99
100 self.loop_info = zip(
101 face_vertex_indices,
102 edgedirs,
103 opp_vertex_indices)
104
106 shifts = []
107
108 from operator import add, mul
109
110 for fvi, edgedir, opp_vertex_index in self.loop_info:
111 blend = 4*reduce(mul, (bp[i] for i in fvi))
112 warp_amount = blend*self.warp(bp[fvi[1]]-bp[fvi[0]]) \
113 * (1 + (self.alpha*bp[opp_vertex_index])**2)
114 shifts.append(warp_amount*edgedir)
115
116 return reduce(add, shifts)
117
118
119
120
121 TriangleBasisFunction = hedge._internal.TriangleBasisFunction
122 GradTriangleBasisFunction = hedge._internal.GradTriangleBasisFunction
123 TetrahedronBasisFunction = hedge._internal.TetrahedronBasisFunction
124 GradTetrahedronBasisFunction = hedge._internal.GradTetrahedronBasisFunction
125
126
127
128
129 -class Element(object):
131 """Get a sequence of functions that form a basis of the approximation space."""
132 raise NotImplementedError
133
135 """Get the gradient functions of the basis_functions(), in the same order."""
136 raise NotImplementedError
137
138
139 @memoize_method
146
147 @memoize_method
149 """Return the inverse of the mass matrix of the unit element
150 with respect to the nodal coefficients. Divide by the Jacobian
151 to obtain the global mass matrix.
152 """
153
154
155 v = self.vandermonde()
156 return numpy.dot(v, v.T)
157
158 @memoize_method
160 """Return the mass matrix of the unit element with respect
161 to the nodal coefficients. Multiply by the Jacobian to obtain
162 the global mass matrix.
163 """
164
165 return numpy.asarray(la.inv(self.inverse_mass_matrix()), order="C")
166
167 @memoize_method
177
178 @memoize_method
180 """Return matrices that map the nodal values of a function
181 to the nodal values of its derivative in each of the unit
182 coordinate directions.
183 """
184
185 from hedge.tools import leftsolve
186
187 v = self.vandermonde()
188 return [leftsolve(v, vdiff) for vdiff in self.grad_vandermonde()]
189
190 @memoize_method
192 """Return a matrix that combines the effect of multiple face
193 mass matrices applied to a vector of the shape::
194
195 [face_1_dofs]+[face_2_dofs]+...+[face_n_dofs]
196
197 Observe that this automatically maps this vector to a volume
198 contribution.
199 """
200 fnc = self.face_node_count()
201
202 result = numpy.zeros(
203 (self.node_count(), self.face_count()*fnc),
204 dtype=numpy.float)
205
206 fmm = self.face_mass_matrix()
207
208 for i_face, f_indices in enumerate(self.face_indices()):
209 for i_dof, f_index in enumerate(f_indices):
210 result[f_index, i_face*fnc:(i_face+1)*fnc] = fmm[i_dof]
211
212 return result
213
214 @memoize_method
216 """Return a matrix that combines the effect of the inverse
217 mass matrix applied after the multi-face mass matrix to a vector
218 of the shape::
219
220 [face_1_dofs]+[face_2_dofs]+...+[face_n_dofs]
221
222 Observe that this automatically maps this vector to a volume
223 contribution.
224 """
225 return numpy.dot(self.inverse_mass_matrix(),
226 self.multi_face_mass_matrix())
227
229 """Find a permuation C{p} such that::
230
231 diff_mats = self.differentiation_matrices()
232 diff_mats[0][p][:,p] == diff_mats[target_idx]
233
234 The permutation is returned as a numpy array of type intp.
235 """
236
237 node_tups = self.node_tuples()
238 d = self.dimensions
239
240 from pytools import get_read_from_map_from_permutation
241
242 def transpose(tup, i, j):
243 l = list(tup)
244 l[i], l[j] = l[j], l[i]
245 return tuple(l)
246
247 p = numpy.array(get_read_from_map_from_permutation(
248 node_tups,
249 [transpose(nt, 0, target_idx) for nt in node_tups]),
250 dtype=numpy.intp)
251
252 dmats = self.differentiation_matrices()
253 assert la.norm(dmats[0][p][:,p]-dmats[target_idx]) < 1e-12
254
255 return p
256
263
264 @property
266 return self.order > 0
267
268
269 @memoize_method
271 """Return the number of interpolation nodes in this element."""
272 d = self.dimensions
273 o = self.order
274 from operator import mul
275 from pytools import factorial
276 return int(reduce(mul, (o+1+i for i in range(d)))/factorial(d))
277
278 @memoize_method
281
282 @memoize_method
285
286 @memoize_method
288 """Return the list of the vertices' node indices."""
289 from pytools import wandering_element
290
291 result = []
292
293 node_tup_to_idx = dict(
294 (ituple, idx)
295 for idx, ituple in enumerate(self.node_tuples()))
296
297 vertex_tuples = [self.dimensions * (0,)] \
298 + list(wandering_element(self.dimensions, wanderer=self.order))
299
300 return [node_tup_to_idx[vt] for vt in vertex_tuples]
301
302 @memoize_method
304 """Return a list of face index lists. Each face index list contains
305 the local node numbers of the nodes on that face.
306 """
307
308 node_tup_to_idx = dict(
309 (ituple, idx)
310 for idx, ituple in enumerate(self.node_tuples()))
311
312 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most
313
314 enum_order_nodes_gen = generate_nonnegative_integer_tuples_summing_to_at_most(
315 self.order, self.dimensions)
316
317 faces = [[] for i in range(self.dimensions+1)]
318
319 for node_tup in enum_order_nodes_gen:
320 for face_idx in self.faces_for_node_tuple(node_tup):
321 faces[face_idx].append(node_tup_to_idx[node_tup])
322
323 return [tuple(fi) for fi in faces]
324
325
327 """Generate equidistant nodes in barycentric coordinates."""
328 for indices in self.node_tuples():
329 divided = tuple(i/self.order for i in indices)
330 yield (1-sum(divided),) + divided
331
337
343
344 @memoize_method
349
350
352 """Generate a hashable objects identifying each basis function, in order.
353
354 The output from this function is required to be in the same order
355 as that of L{basis_functions} and L{grad_basis_functions}, and thereby
356 also from L{vandermonde}.
357 """
358 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most
359
360 return generate_nonnegative_integer_tuples_summing_to_at_most(
361 self.order, self.dimensions)
362
363
374
379 dimensions = 1
380 has_local_jacobians = False
381 geometry = hedge.mesh.Interval
382
383
384 @memoize_method
386 """Generate tuples enumerating the node indices present
387 in this element. Each tuple has a length equal to the dimension
388 of the element. The tuples constituents are non-negative integers
389 whose sum is less than or equal to the order of the element.
390
391 The order in which these nodes are generated dictates the local
392 node numbering.
393 """
394 return [(i,) for i in range(self.order+1)]
395
397 """Return the list of face indices of faces on which the node
398 represented by C{node_idx} lies.
399 """
400
401 if node_idx == (0,):
402 if self.order == 0:
403 return [0, 1]
404 else:
405 return [0]
406 elif node_idx == (self.order,):
407 return [1]
408 else:
409 return []
410
411
412 @memoize_method
414 """Return a list of tuples of indices into the node list that
415 generate a tesselation of the reference element."""
416
417 return [(i,i+1) for i in range(self.order)]
418
419
420 @memoize_method
422 return numpy.array([[1]], dtype=float)
423
424 @staticmethod
426 if set(face_1_vertices) != set(face_2_vertices):
427 raise FaceVertexMismatch("face vertices do not match")
428
429 class IntervalFaceIndexShuffle:
430 def __hash__(self):
431 return 0x3472477
432
433 def __eq__(self, other):
434 return True
435
436 def __call__(self, indices):
437 return indices
438
439 return IntervalFaceIndexShuffle()
440
446 """An arbitrary-order polynomial finite interval element.
447
448 Coordinate systems used:
449 ========================
450
451 unit coordinates (r)::
452
453 ---[--------0--------]--->
454 -1 1
455 """
456
459
460
469
470 equilateral_nodes = nodes
471 unit_nodes = nodes
472
473
474 @memoize_method
476 """Get a sequence of functions that form a basis of the approximation space.
477
478 The approximation space is spanned by the polynomials:::
479
480 r**i for i <= N
481 """
482 from hedge.polynomial import LegendreFunction
483
484 class VectorLF:
485 def __init__(self, n):
486 self.lf = LegendreFunction(n)
487
488 def __call__(self, x):
489 return self.lf(x[0])
490
491 return [VectorLF(idx[0]) for idx in self.generate_mode_identifiers()]
492
500
501 def __call__(self, x):
502 return numpy.array([self.dlf(x[0])])
503
504 return [DiffVectorLF(idx[0])
505 for idx in self.generate_mode_identifiers()]
506
507
509 if self.order == 0:
510 return 1
511 else:
512 unodes = self.unit_nodes()
513 return la.norm(unodes[0] - unodes[1]) * 0.85
514
517
522 """An arbitrary-order triangular finite element.
523
524 Coordinate systems used:
525 ========================
526
527 unit coordinates (r,s)::
528
529 C
530 |\\
531 | \\
532 | O
533 | \\
534 | \\
535 A-----B
536
537 Points in unit coordinates::
538
539 O = (0,0)
540 A = (-1,-1)
541 B = (1,-1)
542 C = (-1,1)
543
544 equilateral coordinates (x,y)::
545
546 C
547 / \\
548 / \\
549 / \\
550 / O \\
551 / \\
552 A-----------B
553
554 Points in equilateral coordinates::
555
556 O = (0,0)
557 A = (-1,-1/sqrt(3))
558 B = (1,-1/sqrt(3))
559 C = (0,2/sqrt(3))
560
561 When global vertices are passed in, they are mapped to the
562 reference vertices A, B, C in order.
563
564 Faces are always ordered AB, BC, AC.
565 """
566
567
568
569
570
571 dimensions = 2
572 has_local_jacobians = False
573 geometry = hedge.mesh.Triangle
574
575 - def __init__(self, order, fancy_node_ordering=True):
576 self.order = order
577 self.fancy_node_ordering = fancy_node_ordering
578
579
580 @memoize_method
582 """Generate tuples enumerating the node indices present
583 in this element. Each tuple has a length equal to the dimension
584 of the element. The tuples constituents are non-negative integers
585 whose sum is less than or equal to the order of the element.
586
587 The order in which these nodes are generated dictates the local
588 node numbering.
589 """
590 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most
591 node_tups = list(generate_nonnegative_integer_tuples_summing_to_at_most(
592 self.order, self.dimensions))
593
594 if not self.fancy_node_ordering:
595 return node_tups
596
597 faces_to_nodes = {}
598 for node_tup in node_tups:
599 faces_to_nodes.setdefault(
600 frozenset(self.faces_for_node_tuple(node_tup)),
601 []).append(node_tup)
602
603 result = []
604 def add_face_nodes(faces):
605 result.extend(faces_to_nodes.get(frozenset(faces), []))
606
607 add_face_nodes([])
608 add_face_nodes([0])
609 add_face_nodes([0,1])
610 add_face_nodes([1])
611 add_face_nodes([1,2])
612 add_face_nodes([2])
613 add_face_nodes([0,2])
614
615 assert set(result) == set(node_tups)
616 assert len(result) == len(node_tups)
617
618 return result
619
621 """Return the list of face indices of faces on which the node
622 represented by C{node_tuple} lies.
623 """
624 m, n = node_tuple
625
626 result = []
627 if n == 0:
628 result.append(0)
629 if n+m == self.order:
630 result.append(1)
631 if m == 0:
632 result.append(2)
633 return result
634
635
636 @staticmethod
638 """Return the equilateral (x,y) coordinate corresponding
639 to the barycentric coordinates (lambda1..lambdaN)."""
640
641
642 return numpy.array([
643 (-lambda1 +lambda2 ),
644 (-lambda1 -lambda2 +2*lambda3)/sqrt(3.0)])
645
646
647 equilateral_to_unit = AffineMap(
648 numpy.array([[1,-1/sqrt(3)], [0,2/sqrt(3)]]),
649 numpy.array([-1/3,-1/3]))
650
652 """Generate warped nodes in equilateral coordinates (x,y)."""
653
654
655
656
657
658
659
660
661
662
663 alpha_opt = [0.0000, 0.0000, 1.4152, 0.1001, 0.2751, 0.9800, 1.0999,
664 1.2832, 1.3648, 1.4773, 1.4959, 1.5743, 1.5770, 1.6223, 1.6258]
665
666 try:
667 alpha = alpha_opt[self.order-1]
668 except IndexError:
669 alpha = 5/3
670
671 warp = TriangleWarper(alpha, self.order)
672
673 for bp in self.equidistant_barycentric_nodes():
674 yield self.barycentric_to_equilateral(bp) + warp(bp)
675
676 @memoize_method
678 """Return a list of tuples of indices into the node list that
679 generate a tesselation of the reference element."""
680
681 node_dict = dict(
682 (ituple, idx)
683 for idx, ituple in enumerate(self.node_tuples()))
684
685 result = []
686 for i, j in self.node_tuples():
687 if i+j < self.order:
688 result.append(
689 (node_dict[i,j], node_dict[i+1,j], node_dict[i,j+1]))
690 if i+j < self.order-1:
691 result.append(
692 (node_dict[i+1,j+1], node_dict[i,j+1], node_dict[i+1,j]))
693 return result
694
695
696 @memoize_method
698 """Get a sequence of functions that form a basis of the approximation space.
699
700 The approximation space is spanned by the polynomials:::
701
702 r**i * s**j for i+j <= N
703 """
704 return [TriangleBasisFunction(*idx) for idx in
705 self.generate_mode_identifiers()]
706
711
712
713 @memoize_method
725
726 @staticmethod
728 if set(face_1_vertices) != set(face_2_vertices):
729 raise FaceVertexMismatch("face vertices do not match")
730
731 class TriangleFaceIndexShuffle:
732 def __init__(self, operations):
733 self.operations = operations
734
735 def __hash__(self):
736 return hash(self.operations)
737
738 def __eq__(self, other):
739 return self.operations == other.operations
740
741 def __call__(self, indices):
742 for op in self.operations:
743 if op == "flip":
744 indices = indices[::-1]
745 else:
746 raise RuntimeError, "invalid operation"
747 return indices
748
749
750 if face_1_vertices != face_2_vertices:
751 assert face_1_vertices[::-1] == face_2_vertices
752 return TriangleFaceIndexShuffle(("flip",))
753 else:
754 return TriangleFaceIndexShuffle(())
755
756
758 area = abs(2*el.map.jacobian())
759 semiperimeter = sum(la.norm(vertices[vi1]-vertices[vi2])
760 for vi1, vi2 in [(0,1), (1,2), (2,0)])/2
761 return area/semiperimeter
762
767 """An arbitrary-order tetrahedral finite element.
768
769 Coordinate systems used:
770 ========================
771
772 unit coordinates (r,s,t)::
773
774 ^ s
775 |
776 C
777 /|\\
778 / | \\
779 / | \\
780 / | \\
781 / O| \\
782 / __A-----B---> r
783 /_--^ ___--^^
784 ,D--^^^
785 t L
786
787 (squint, and it might start making sense...)
788
789 Points in unit coordinates::
790
791 O=( 0, 0, 0)
792 A=(-1,-1,-1)
793 B=(+1,-1,-1)
794 C=(-1,+1,-1)
795 D=(-1,-1,+1)
796
797 Points in equilateral coordinates (x,y,z)::
798
799 O = (0,0)
800 A = (-1,-1/sqrt(3),-1/sqrt(6))
801 B = ( 1,-1/sqrt(3),-1/sqrt(6))
802 C = ( 0, 2/sqrt(3),-1/sqrt(6))
803 D = ( 0, 0, 3/sqrt(6))
804
805 When global vertices are passed in, they are mapped to the
806 reference vertices A, B, C, D in order.
807
808 Faces are always ordered ABC, ABD, ACD, BCD.
809 """
810
811
812
813
814
815 dimensions = 3
816 has_local_jacobians = False
817 geometry = hedge.mesh.Tetrahedron
818
821
822
823 @memoize_method
825 """Generate tuples enumerating the node indices present
826 in this element. Each tuple has a length equal to the dimension
827 of the element. The tuples constituents are non-negative integers
828 whose sum is less than or equal to the order of the element.
829
830 The order in which these nodes are generated dictates the local
831 node numbering.
832 """
833 from pytools import generate_nonnegative_integer_tuples_summing_to_at_most
834 node_tups = list(generate_nonnegative_integer_tuples_summing_to_at_most(
835 self.order, self.dimensions))
836
837 if False:
838
839 faces_to_nodes = {}
840 for node_tup in node_tups:
841 faces_to_nodes.setdefault(
842 frozenset(self.faces_for_node_tuple(node_tup)),
843 []).append(node_tup)
844
845 result = []
846 def add_face_nodes(faces):
847 result.extend(faces_to_nodes.get(frozenset(faces), []))
848
849 add_face_nodes([0,3])
850 add_face_nodes([0])
851 add_face_nodes([0,2])
852 add_face_nodes([0,1])
853 add_face_nodes([0,1,2])
854 add_face_nodes([0,1,3])
855 add_face_nodes([0,2,3])
856 add_face_nodes([1])
857 add_face_nodes([1,2])
858 add_face_nodes([1,2,3])
859 add_face_nodes([1,3])
860 add_face_nodes([2])
861 add_face_nodes([2,3])
862 add_face_nodes([3])
863 add_face_nodes([])
864
865 assert set(result) == set(node_tups)
866 assert len(result) == len(node_tups)
867
868 if True:
869
870 from pytools import average
871
872 def order_number_for_node_tuple(nt):
873 faces = self.faces_for_node_tuple(nt)
874 if not faces:
875 return -1
876 elif len(faces) >= 3:
877 return 1000
878 else:
879 return average(faces)
880
881 def cmp_node_tuples(nt1, nt2):
882 return cmp(
883 order_number_for_node_tuple(nt1),
884 order_number_for_node_tuple(nt2))
885
886 result = node_tups
887
888
889 for i, nt in enumerate(result):
890 fnt = self.faces_for_node_tuple(nt)
891
892
893 return result
894
896 """Return the list of face indices of faces on which the node
897 represented by C{node_tuple} lies.
898 """
899 m,n,o = node_tuple
900 result = []
901
902 if o == 0:
903 result.append(0)
904 if n == 0:
905 result.append(1)
906 if m == 0:
907 result.append(2)
908 if n+m+o == self.order:
909 result.append(3)
910
911 return result
912
913
914 @staticmethod
916 """Return the equilateral (x,y) coordinate corresponding
917 to the barycentric coordinates (lambda1..lambdaN)."""
918
919
920 return numpy.array([
921 (-lambda1 +lambda2 ),
922 (-lambda1 -lambda2 +2*lambda3 )/sqrt(3.0),
923 (-lambda1 -lambda2 - lambda3 +3*lambda4)/sqrt(6.0),
924 ])
925
926
927 equilateral_to_unit = AffineMap(
928 numpy.array([
929 [1,-1/sqrt(3),-1/sqrt(6)],
930 [0, 2/sqrt(3),-1/sqrt(6)],
931 [0, 0, sqrt(6)/2]
932 ]),
933 numpy.array([-1/2,-1/2,-1/2]))
934
936 """Generate warped nodes in equilateral coordinates (x,y)."""
937
938
939
940
941 alpha_opt = [0,0,0,0.1002, 1.1332,1.5608,1.3413,1.2577,1.1603,
942 1.10153,0.6080,0.4523,0.8856,0.8717,0.9655]
943
944 try:
945 alpha = alpha_opt[self.order-1]
946 except IndexError:
947 alpha = 1
948
949 from pytools import wandering_element
950
951 vertices = [self.barycentric_to_equilateral(bary)
952 for bary in wandering_element(self.dimensions+1)]
953 all_vertex_indices = range(self.dimensions+1)
954 face_vertex_indices = self.geometry \
955 .face_vertices(all_vertex_indices)
956 faces_vertices = self.geometry \
957 .face_vertices(vertices)
958
959 bary_points = list(self.equidistant_barycentric_nodes())
960 equi_points = [self.barycentric_to_equilateral(bp)
961 for bp in bary_points]
962
963 from tools import normalize
964 from operator import add, mul
965
966 tri_warp = TriangleWarper(alpha, self.order)
967
968 for fvi, (v1, v2, v3) in zip(face_vertex_indices, faces_vertices):
969
970 directions = [normalize(v2-v1), normalize((v3)-(v1+v2)/2)]
971
972
973 assert abs(numpy.dot(directions[0],directions[1])) < 1e-16
974
975
976 opp_vertex_index = (set(all_vertex_indices) - set(fvi)).__iter__().next()
977
978 shifted = []
979 for bp, ep in zip(bary_points, equi_points):
980 face_bp = [bp[i] for i in fvi]
981
982 blend = reduce(mul, face_bp) * (1+alpha*bp[opp_vertex_index])**2
983
984 for i in fvi:
985 denom = bp[i] + 0.5*bp[opp_vertex_index]
986 if abs(denom) > 1e-12:
987 blend /= denom
988 else:
989 blend = 0.5
990 break
991
992 shifted.append(ep + blend*reduce(add,
993 (tw*dir for tw, dir in zip(tri_warp(face_bp), directions))))
994
995 equi_points = shifted
996
997 return equi_points
998
999 @memoize_method
1001 """Return a list of tuples of indices into the node list that
1002 generate a tesselation of the reference element."""
1003
1004 node_dict = dict(
1005 (ituple, idx)
1006 for idx, ituple in enumerate(self.node_tuples()))
1007
1008 def add_tuples(a, b):
1009 return tuple(ac+bc for ac, bc in zip(a,b))
1010
1011 def try_add_tet(d1, d2, d3, d4):
1012 try:
1013 result.append((
1014 node_dict[add_tuples(current, d1)],
1015 node_dict[add_tuples(current, d2)],
1016 node_dict[add_tuples(current, d3)],
1017 node_dict[add_tuples(current, d4)],
1018 ))
1019 except KeyError, e:
1020 pass
1021
1022 result = []
1023 for current in self.node_tuples():
1024
1025
1026
1027
1028 try_add_tet((0,0,0), (1,0,0), (0,1,0), (0,0,1))
1029 try_add_tet((1,0,1), (1,0,0), (0,0,1), (0,1,0))
1030 try_add_tet((1,0,1), (0,1,1), (0,1,0), (0,0,1))
1031
1032 try_add_tet((1,0,0), (0,1,0), (1,0,1), (1,1,0))
1033 try_add_tet((0,1,1), (0,1,0), (1,1,0), (1,0,1))
1034 try_add_tet((0,1,1), (1,1,1), (1,0,1), (1,1,0))
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044 return result
1045
1046
1047 @memoize_method
1049 """Get a sequence of functions that form a basis of the approximation space.
1050
1051 The approximation space is spanned by the polynomials::
1052
1053 r**i * s**j * t**k for i+j+k <= order
1054 """
1055 return [TetrahedronBasisFunction(*idx) for idx in
1056 self.generate_mode_identifiers()]
1057
1064
1065
1066 @memoize_method
1088
1090 (a,b,c) = face_1_vertices
1091 f2_tuple = face_2_vertices
1092
1093 try:
1094 idx_map = self._shuffle_face_idx_map
1095 except AttributeError:
1096 idx_map = self._shuffle_face_idx_map = {}
1097 idx = 0
1098 for j in range(0, self.order+1):
1099 for i in range(0, self.order+1-j):
1100 self._shuffle_face_idx_map[i,j] = idx
1101 idx += 1
1102
1103 order = self.order
1104
1105 class TetrahedronFaceIndexShuffle:
1106 def __init__(self, operations):
1107 self.operations = operations
1108
1109 def __hash__(self):
1110 return hash(self.operations)
1111
1112 def __eq__(self, other):
1113 return self.operations == other.operations
1114
1115 def __call__(self, indices):
1116 for op in self.operations:
1117 if op == "flip":
1118 indices = self.flip(indices)
1119 elif op == "shift_left":
1120 indices = self.shift_left(indices)
1121 else:
1122 raise RuntimeError, "invalid operation"
1123 return indices
1124
1125
1126 def flip(self, indices):
1127 """Flip the indices along the unit hypotenuse."""
1128 result = []
1129 for j in range(0, order+1):
1130 for i in range(0, order+1-j):
1131 result.append(indices[idx_map[j,i]])
1132 return tuple(result)
1133
1134 def shift_left(self, indices):
1135 """Rotate all edges to the left."""
1136 result = len(indices)*[0]
1137 idx = 0
1138 for j in range(0, order+1):
1139 for i in range(0, order+1-j):
1140 result[idx_map[j, order-i-j]] = indices[idx]
1141 idx += 1
1142 return tuple(result)
1143
1144
1145 if f2_tuple == (a,b,c):
1146
1147 return TetrahedronFaceIndexShuffle(())
1148 elif f2_tuple == (a,c,b):
1149
1150 return TetrahedronFaceIndexShuffle(("flip",))
1151 elif f2_tuple == (b,c,a):
1152
1153
1154 return TetrahedronFaceIndexShuffle(("shift_left", "shift_left"))
1155 elif f2_tuple == (b,a,c):
1156
1157
1158 return TetrahedronFaceIndexShuffle(("shift_left", "flip"))
1159 elif f2_tuple == (c,a,b):
1160
1161
1162 return TetrahedronFaceIndexShuffle(("shift_left",))
1163 elif f2_tuple == (c,b,a):
1164
1165
1166 return TetrahedronFaceIndexShuffle(("flip", "shift_left"))
1167 else:
1168 raise FaceVertexMismatch("face vertices do not match")
1169
1170
1172 result = abs(el.map.jacobian())/max(abs(fj) for fj in el.face_jacobians)
1173 if self.order in [1, 2]:
1174 from warnings import warn
1175 warn("cowardly halving timestep for order 1 and 2 tets to avoid CFL issues")
1176 result /= 2
1177
1178 return result
1179
1180
1181
1182
1183 ELEMENTS = [IntervalElement, TriangularElement, TetrahedralElement]
1184