source: trunk/anuga_core/source/anuga_parallel/distribute_mesh.py @ 8550

Last change on this file since 8550 was 8550, checked in by steve, 13 years ago

Trying to speed up distribute

File size: 45.4 KB
Line 
1#########################################################
2#
3#
4#  Read in a data file and subdivide the triangle list
5#
6#
7#  The final routine, pmesh_divide_metis, does automatic
8# grid partitioning. Once testing has finished on this
9# routine the others should be removed.
10#
11#  Authors: Linda Stals and Matthew Hardy, June 2005
12#  Modified: Linda Stals, Nov 2005
13#            Jack Kelly, Nov 2005
14#            Steve Roberts, Aug 2009 (updating to numpy)
15#
16#
17#########################################################
18
19
20import sys
21from os import sep
22from sys import path
23from math import floor
24
25import numpy as num
26
27from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
28from anuga import indent
29
30try:
31    import local_config as config
32except:
33    import anuga_parallel.config as config
34
35
36#########################################################
37#
38# If the triangles list is reordered, the quantities
39# assigned to the triangles must also be reorded.
40#
41# *) quantities contain the quantites in the old ordering
42# *) proc_sum[i] contains the number of triangles in
43# processor i
44# *) tri_index is a map from the old triangle ordering to
45# the new ordering, where the new number for triangle
46# i is proc_sum[tri_index[i][0]]+tri_index[i][1]
47#
48# -------------------------------------------------------
49#
50# *) The quantaties are returned in the new ordering
51#
52#########################################################
53
54def reorder(quantities, tri_index, proc_sum):
55
56    # Find the number triangles
57
58    N = len(tri_index)
59
60    # Temporary storage area
61
62    index = num.zeros(N, num.int)
63    q_reord = {}
64
65    # Find the new ordering of the triangles
66
67    for i in xrange(N):
68        bin = tri_index[i][0]
69        bin_off_set = tri_index[i][1]
70        index[i] = proc_sum[bin]+bin_off_set
71
72    # Reorder each quantity according to the new ordering
73
74    for k in quantities:
75        q_reord[k] = num.zeros((N, 3), num.float)
76        for i in range(N):
77            q_reord[k][index[i]]=quantities[k].vertex_values[i]
78    del index
79
80    return q_reord
81
82
83def reorder_new(quantities, epart_order, proc_sum):
84
85    # Find the number triangles
86
87    N = len(epart_order)
88
89    # Temporary storage area
90
91
92    q_reord = {}
93
94    # Reorder each quantity according to the new ordering
95
96    for k in quantities:
97        q_reord[k] = num.zeros((N, 3), num.float)
98        q_reord[k][:] = quantities[k].vertex_values[epart_order]
99       
100
101    return q_reord
102
103
104#########################################################
105#
106# Divide the mesh using a call to metis, through pymetis.
107#
108# -------------------------------------------------------
109#
110# *)  The nodes, triangles, boundary, and quantities are
111# returned. triangles_per_proc defines the subdivision.
112# The first triangles_per_proc[0] triangles are assigned
113# to processor 0, the next triangles_per_proc[1] are
114# assigned to processor 1 etc. The boundary and quantites
115# are ordered the same way as the triangles
116#
117#########################################################
118
119#path.append('..' + sep + 'pymetis')
120
121try:
122    from pymetis.metis import partMeshNodal
123except ImportError:
124    print "***************************************************"
125    print "         Metis is probably not compiled."
126    print "         Read \anuga_core\source\pymetis\README"
127    print "***************************************************"
128    raise ImportError
129
130def pmesh_divide_metis(domain, n_procs):
131    # Wrapper for old pmesh_divide_metis which does not return tri_index or r_tri_index
132    nodes, ttriangles, boundary, triangles_per_proc, quantities, tri_index, r_tri_index = pmesh_divide_metis_helper(domain, n_procs)
133    return nodes, ttriangles, boundary, triangles_per_proc, quantities
134
135def pmesh_divide_metis_with_map(domain, n_procs):
136    return pmesh_divide_metis_helper(domain, n_procs)
137
138def pmesh_divide_metis_helper(domain, n_procs):
139   
140    # Initialise the lists
141    # List, indexed by processor of # triangles.
142   
143    triangles_per_proc = []
144   
145    # List of lists, indexed by processor of vertex numbers
146   
147    tri_list = []
148
149    # Serial to Parallel and Parallel to Serial Triangle index maps
150    tri_index = {}
151    r_tri_index = {} # reverse tri index, parallel to serial triangle index mapping
152   
153    # List indexed by processor of cumulative total of triangles allocated.
154   
155    proc_sum = []
156    for i in xrange(n_procs):
157        tri_list.append([])
158        triangles_per_proc.append(0)
159        proc_sum.append([])
160
161    # Prepare variables for the metis call
162   
163    n_tri = len(domain.triangles)
164    if n_procs != 1: #Because metis chokes on it...
165        n_vert = domain.get_number_of_nodes()
166        t_list = domain.triangles.copy()
167        t_list = num.reshape(t_list, (-1,))
168   
169        # The 1 here is for triangular mesh elements.
170        edgecut, epart, npart = partMeshNodal(n_tri, n_vert, t_list, 1, n_procs)
171        # print edgecut
172        # print npart
173        #print epart
174        del edgecut
175        del npart
176
177        # Sometimes (usu. on x86_64), partMeshNodal returns an array of zero
178        # dimensional arrays. Correct this.
179        if type(epart[0]) == num.ndarray:
180            epart_new = num.zeros(len(epart), num.int)
181            epart_new[:] = epart[:][0]
182#            for i in xrange(len(epart)):
183#                epart_new[i] = epart[i][0]
184            epart = epart_new
185            del epart_new
186        # Assign triangles to processes, according to what metis told us.
187       
188        # tri_index maps triangle number -> processor, new triangle number
189        # (local to the processor)
190
191
192
193        #triangles = []       
194        #for i in xrange(n_tri):
195        #    #triangles_per_proc[epart[i]] = triangles_per_proc[epart[i]] + 1
196        #    tri_list[epart[i]].append(domain.triangles[i])
197        #    tri_index[i] = [epart[i], len(tri_list[epart[i]]) - 1]
198        #    r_tri_index[epart[i], len(tri_list[epart[i]]) - 1] = i
199
200
201        #for i in xrange(n_procs):
202        #    tri_list[i] = num.array(tri_list[i])
203
204        #for i in xrange(n_procs):
205        #    for t in tri_list[i]:
206        #        triangles.append(t)
207
208        triangles_per_proc = num.bincount(epart)
209        proc_sum = num.zeros(n_procs+1,num.int)
210        proc_sum[1:] = num.cumsum(triangles_per_proc)
211
212        #boundary = {}
213        #for b in domain.boundary:
214        #    t =  tri_index[b[0]]
215        #    #print t
216        #    boundary[proc_sum[t[0]]+t[1], b[1]] = domain.boundary[b]
217
218
219
220        #print epart
221        #print len(tri_list[epart[0]])
222
223        #print tri_list
224        #print
225        #print tri_index
226
227
228        epart_order = num.argsort(epart, kind='mergesort')
229        new_triangles = domain.triangles[epart_order]
230
231        #new_r_tri_index_flat = num.zeros((n_tri,3), num.int)
232        new_tri_index = num.zeros((n_tri,2), num.int)
233        for i in xrange(n_procs):
234            ids = num.arange(proc_sum[i],proc_sum[i+1])
235            eids = epart_order[ids]
236            nrange = num.reshape(num.arange(triangles_per_proc[i]), (-1,1))
237            nones = num.ones_like(nrange)
238            #print ids.shape
239            #print nrange.shape
240            new_tri_index[eids] = num.concatenate((i*nones, nrange), axis = 1)
241            #new_r_tri_index_flat[ids] = num.concatenate((i*nones, nrange, num.reshape(eids, (-1,1))), axis = 1)
242
243
244        #from pprint import pprint
245        #pprint(new_r_tri_index_flat[:,0:2])
246
247        #print 50*'='
248        new_boundary = {}
249        for b in domain.boundary:
250            t =  new_tri_index[b[0]]
251            #print t
252            new_boundary[proc_sum[t[0]]+t[1], b[1]] = domain.boundary[b]
253
254
255
256#        for i in range(n_tri):
257#            assert num.allclose(new_tri_index[i], tri_index[i])
258
259
260        #x = new_r_tri_index_flat
261        #print len(x)
262
263        #new_r_tri_index = {}
264        #for i in xrange(n_tri):
265        #    new_r_tri_index[x[i,0], x[i,1]] = x[i,2]
266
267
268        #assert new_r_tri_index == r_tri_index
269
270        #from pprint import pprint
271        #pprint(new_r_tri_index)
272        #pprint(r_tri_index)
273
274        #print len(new_tri_list[0])
275        #print new_tri_list
276        #print tri_list
277
278        #for i in xrange(n_procs):
279        #    assert num.allclose(tri_list[i], new_tri_list[i]), 'i %d\n'%i
280#        tri_list = new_tri_list
281#        triangles = new_triangles
282
283
284#        for i in xrange(n_tri):
285#            tri_index[i] = ([epart[i], len(tri_list[epart[i]]) - 1])
286#            r_tri_index[epart[i], len(tri_list[epart[i]]) - 1] = i
287
288        #assert num.allclose(triangles_per_proc, new_triangles_per_proc)
289       
290        # Order the triangle list so that all of the triangles belonging
291        # to processor i are listed before those belonging to processor
292        # i+1
293
294
295
296
297
298
299        #print new_triangles
300        #print num.array(triangles)
301
302        #assert num.allclose(new_triangles,num.array(triangles))
303           
304        # The boundary labels have to changed in accoradance with the
305        # new triangle ordering, proc_sum and tri_index help with this
306
307#        proc_sum[0] = 0
308#        for i in xrange(n_procs - 1):
309#            proc_sum[i+1]=proc_sum[i]+triangles_per_proc[i]
310
311
312
313
314
315        #assert num.allclose(new_proc_sum,proc_sum)
316
317        # Relabel the boundary elements to fit in with the new triangle
318        # ordering
319
320
321
322        #quantities = reorder(domain.quantities, tri_index, proc_sum)
323        new_quantities = reorder_new(domain.quantities, epart_order, proc_sum)
324
325    else:
326        new_boundary = domain.boundary.copy()
327        triangles_per_proc[0] = n_tri
328        new_triangles = domain.triangles.copy()
329        new_tri_index = []
330        epart_order = []
331       
332        # This is essentially the same as a chunk of code from reorder.
333       
334        new_quantities = {}
335        for k in domain.quantities:
336            new_quantities[k] = num.zeros((n_tri, 3), num.float)
337            for i in range(n_tri):
338                new_quantities[k][i] = domain.quantities[k].vertex_values[i]
339       
340    # Extract the node list
341   
342    new_nodes = domain.get_nodes().copy()
343   
344    # Convert the triangle datastructure to be an array type,
345    # this helps with the communication
346
347    ttriangles = num.zeros((len(new_triangles), 3), num.int)
348    for i in xrange(len(new_triangles)):
349        ttriangles[i] = new_triangles[i]
350
351    #return nodes, ttriangles, boundary, triangles_per_proc, quantities
352   
353    #return nodes, ttriangles, boundary, triangles_per_proc, quantities, tri_index, r_tri_index
354
355    return new_nodes, new_triangles, new_boundary, triangles_per_proc, new_quantities, new_tri_index, epart_order
356
357#########################################################
358#
359# Subdivide the domain. This module is primarily
360# responsible for building the ghost layer and
361# communication pattern
362#
363#
364#  Author: Linda Stals, June 2005
365#  Modified: Linda Stals, Nov 2005 (optimise python code)
366#            Steve Roberts, Aug 2009 (convert to numpy)
367#
368#
369#########################################################
370
371
372
373#########################################################
374#
375# Subdivide the triangles into non-overlapping domains.
376#
377#  *)  The subdivision is controlled by triangles_per_proc.
378# The first triangles_per_proc[0] triangles are assigned
379# to the first processor, the second triangles_per_proc[1]
380# are assigned to the second processor etc.
381#
382#  *) nodes, triangles and boundary contains all of the
383# nodes, triangles and boundary tag information for the
384# whole domain. The triangles should be orientated in the
385# correct way and the nodes number consecutively from 0.
386#
387# -------------------------------------------------------
388#
389#  *) A dictionary containing the full_nodes, full_triangles
390# and full_boundary information for each processor is
391# returned. The node information consists of
392# [global_id, x_coord, y_coord].
393#
394#########################################################
395
396def submesh_full(mesh, triangles_per_proc):
397
398    # Initialise
399
400
401    nodes = mesh.nodes
402    triangles = mesh.triangles
403    boundary = mesh.boundary
404
405    tlower = 0
406    nproc = len(triangles_per_proc)
407    nnodes = len(nodes)
408    node_list = []
409    triangle_list = []
410    boundary_list = []
411    submesh = {}
412
413#    node_range = num.reshape(num.arange(nnodes),(nnodes,1))
414#
415#    #print node_range
416#    tsubnodes = num.concatenate((node_range, nodes), 1)
417
418    #print node_range
419    #print nodes
420    # Loop over processors
421
422    for p in xrange(nproc):
423
424        # Find triangles on processor p
425
426        tupper = triangles_per_proc[p]+tlower
427        subtriangles = triangles[tlower:tupper]
428        triangle_list.append(subtriangles)
429
430        # Find the boundary edges on processor p
431
432        subboundary = {}
433        for k in boundary:
434            if (k[0] >=tlower and k[0] < tupper):
435                subboundary[k]=boundary[k]
436        boundary_list.append(subboundary)
437
438        # Find nodes in processor p
439
440#        nodemap = num.zeros(nnodes, 'i')
441#        for t in subtriangles:
442#            nodemap[t[0]]=1
443#            nodemap[t[1]]=1
444#            nodemap[t[2]]=1
445#
446#        y = tsubnodes.take(num.flatnonzero(nodemap),axis=0)
447
448        #node_list.append(y)
449
450        ids = num.unique(subtriangles.flat)
451        lnodes = nodes[ids]
452#        print nodes.shape
453#        print ids.shape
454#        print lnodes.shape
455        x = num.concatenate((num.reshape(ids, (-1,1)),lnodes ), 1)
456#        print x
457#        print y
458        node_list.append(x)
459        # Move to the next processor
460
461        tlower = tupper
462
463    # Put the results in a dictionary
464
465    submesh["full_nodes"] = node_list
466    submesh["full_triangles"] = triangle_list
467    submesh["full_boundary"] = boundary_list
468
469    # Clean up before exiting
470
471    #del (nodemap)
472
473    return submesh
474
475
476#########################################################
477#
478# Build the ghost layer of triangles
479#
480#  *) Given the triangle subpartion for the processor
481# build a ghost layer of triangles. The ghost layer
482# consists of two layers of neighbouring triangles.
483#
484#  *) The vertices in the ghost triangles must also
485# be added to the node list for the current processor
486#
487#
488# -------------------------------------------------------
489#
490#  *) The extra triangles and nodes are returned.
491#
492#  *)  The node information consists of
493# [global_id, x_coord, y_coord].
494#
495#  *) The triangle information consists of
496# [triangle number, t], where t = [v1, v2, v3].
497#
498#########################################################
499
500def ghost_layer(submesh, mesh, p, tupper, tlower, parameters = None):
501
502    ncoord = mesh.number_of_nodes
503    ntriangles = mesh.number_of_triangles
504
505    if parameters is None:
506        layer_width  = 2
507    else:
508        layer_width = parameters['ghost_layer_width']
509
510
511    trianglemap = num.zeros(ntriangles, num.int)
512
513    # Find the first layer of boundary triangles
514    for t in xrange(tlower, tupper):
515       
516        n = mesh.neighbours[t, 0]
517        if n >= 0:
518            if n < tlower or n >= tupper:
519                trianglemap[n] = 1
520
521        n = mesh.neighbours[t, 1]
522        if n >= 0:
523            if n < tlower or n >= tupper:
524                trianglemap[n] = 1
525
526        n = mesh.neighbours[t, 2]
527        if n >= 0:
528            if n < tlower or n >= tupper:
529                trianglemap[n] = 1
530
531
532    # Find the subsequent layers of ghost triangles
533    for i in range(layer_width-1):
534
535        for t in xrange(ntriangles):
536            if trianglemap[t]==i+1:
537
538                n = mesh.neighbours[t, 0]
539                if n >= 0:
540                    if (n < tlower or n >= tupper) and trianglemap[n] == 0:
541                        trianglemap[n] = i+2
542
543                n = mesh.neighbours[t, 1]
544                if n >= 0:
545                    if (n < tlower or n >= tupper) and trianglemap[n] == 0:
546                        trianglemap[n] = i+2
547
548                n = mesh.neighbours[t, 2]
549                if n >= 0:
550                    if (n < tlower or n >= tupper) and trianglemap[n] == 0:
551                        trianglemap[n] = i+2
552
553    # Build the triangle list and make note of the vertices
554
555
556    nodemap = num.zeros(ncoord, num.int)
557    fullnodes = submesh["full_nodes"][p]
558
559    subtriangles = []
560    for i in xrange(ntriangles):
561        if trianglemap[i] != 0:
562            t = list(mesh.triangles[i])
563            nodemap[t[0]] = 1
564            nodemap[t[1]] = 1
565            nodemap[t[2]] = 1
566
567    trilist = num.reshape(num.arange(ntriangles),(ntriangles,1))
568    tsubtriangles = num.concatenate((trilist, mesh.triangles), 1)
569    subtriangles = tsubtriangles.take(num.flatnonzero(trianglemap),axis=0)
570
571   
572    # Keep a record of the triangle vertices, if they are not already there
573
574    for n in fullnodes:
575        nodemap[int(n[0])] = 0
576
577    nodelist = num.reshape(num.arange(ncoord),(ncoord,1))
578    tsubnodes = num.concatenate((nodelist, mesh.get_nodes()), 1)
579    subnodes = tsubnodes.take(num.flatnonzero(nodemap),axis=0)
580
581    # Clean up before exiting
582
583    del (nodelist)
584    del (trilist)
585    del (tsubnodes)
586    del (nodemap)
587    del (trianglemap)
588
589    # Return the triangles and vertices sitting on the boundary layer
590
591    return subnodes, subtriangles, layer_width
592
593#########################################################
594#
595# Find the edges of the ghost trianlges that do not
596# have a neighbour in the current cell. These are
597# treated as a special type of boundary edge.
598#
599#  *) Given the ghost triangles in a particular
600# triangle, use the mesh to find its neigbours. If
601# the neighbour is not in the processor set it to
602# be a boundary edge
603#
604#  *) The vertices in the ghost triangles must also
605# be added to the node list for the current processor
606#
607#  *) The boundary edges for the ghost triangles are
608# ignored.
609#
610# -------------------------------------------------------
611#
612#  *) The type assigned to the ghost boundary edges is 'ghost'
613#
614#  *)  The boundary information is returned as a directorier
615# with the key = (triangle id, edge no) and the values
616# assigned to the key is 'ghost'
617#
618#
619#########################################################
620def is_in_processor(ghost_list, tlower, tupper, n):
621
622    return num.equal(ghost_list,n).any() or (tlower <= n and tupper > n)
623
624
625def ghost_bnd_layer(ghosttri, tlower, tupper, mesh, p):
626
627
628    boundary = mesh.boundary
629
630    ghost_list = []
631    subboundary = {}
632
633
634    # FIXME SR: For larger layers need to pass through the correct
635    # boundary tag!
636
637    for t in ghosttri:
638        ghost_list.append(t[0])
639   
640    for t in ghosttri:
641
642        n = mesh.neighbours[t[0], 0]
643        if not is_in_processor(ghost_list, tlower, tupper, n):
644            if boundary.has_key( (t[0], 0) ):
645                subboundary[t[0], 0] = boundary[t[0],0]
646            else:
647                subboundary[t[0], 0] = 'ghost'
648
649
650        n = mesh.neighbours[t[0], 1]
651        if not is_in_processor(ghost_list, tlower, tupper, n):
652            if boundary.has_key( (t[0], 1) ):
653                subboundary[t[0], 1] = boundary[t[0],1]
654            else:
655                subboundary[t[0], 1] = 'ghost'
656
657
658        n = mesh.neighbours[t[0], 2]
659        if not is_in_processor(ghost_list, tlower, tupper, n):
660            if boundary.has_key( (t[0], 2) ):
661                subboundary[t[0], 2] = boundary[t[0],2]
662            else:
663                subboundary[t[0], 2] = 'ghost'
664           
665    return subboundary
666
667#########################################################
668#
669# The ghost triangles on the current processor will need
670# to get updated information from the neighbouring
671# processor containing the corresponding full triangles.
672#
673#  *) The tri_per_proc is used to determine which
674# processor contains the full node copy.
675#
676# -------------------------------------------------------
677#
678#  *) The ghost communication pattern consists of
679# [global node number, neighbour processor number].
680#
681#########################################################
682
683def ghost_commun_pattern(subtri, p, tri_per_proc):
684
685    # Loop over the ghost triangles
686
687    ghost_commun = num.zeros((len(subtri), 2), num.int)
688
689    for i in xrange(len(subtri)):
690        global_no = subtri[i][0]
691
692        # Find which processor contains the full triangle
693
694        nproc = len(tri_per_proc)
695        neigh = nproc-1
696        sum = 0
697        for q in xrange(nproc-1):
698            if (global_no < sum+tri_per_proc[q]):
699                neigh = q
700                break
701            sum = sum+tri_per_proc[q]
702
703        # Keep a copy of the neighbour processor number
704
705        ghost_commun[i] = [global_no, neigh]
706
707    return ghost_commun
708
709#########################################################
710#
711# The full triangles in this processor must communicate
712# updated information to neighbouring processor that
713# contain ghost triangles
714#
715#  *) The ghost communication pattern for all of the
716# processor must be built before calling this processor.
717#
718#  *) The full communication pattern is found by looping
719# through the ghost communication pattern for all of the
720# processors. Recall that this information is stored in
721# the form [global node number, neighbour processor number].
722# The full communication for the neighbour processor is
723# then updated.
724#
725# -------------------------------------------------------
726#
727#  *) The full communication pattern consists of
728# [global id, [p1, p2, ...]], where p1, p2 etc contain
729# a ghost node copy of the triangle global id.
730#
731#########################################################
732
733def full_commun_pattern(submesh, tri_per_proc):
734    tlower = 0
735    nproc = len(tri_per_proc)
736    full_commun = []
737
738    # Loop over the processor
739
740    for p in xrange(nproc):
741
742        # Loop over the full triangles in the current processor
743        # and build an empty dictionary
744
745        fcommun = {}
746        tupper = tri_per_proc[p]+tlower
747        for i in xrange(tlower, tupper):
748            fcommun[i] = []
749        full_commun.append(fcommun)
750        tlower = tupper
751
752    # Loop over the processor again
753
754    for p in xrange(nproc):
755
756        # Loop over the ghost triangles in the current processor,
757        # find which processor contains the corresponding full copy
758        # and note that the processor must send updates to this
759        # processor
760
761        for g in submesh["ghost_commun"][p]:
762            neigh = g[1]
763            full_commun[neigh][g[0]].append(p)
764
765    return full_commun
766
767
768#########################################################
769#
770# Given the non-overlapping grid partition, an extra layer
771# of triangles are included to help with the computations.
772# The triangles in this extra layer are not updated by
773# the processor, their updated values must be sent by the
774# processor containing the original, full, copy of the
775# triangle. The communication pattern that controls these
776# updates must also be built.
777#
778#  *) Assumes that full triangles, nodes etc have already
779# been found and stored in submesh
780#
781#  *) See the documentation for ghost_layer,
782# ghost_commun_pattern and full_commun_pattern
783#
784# -------------------------------------------------------
785#
786#  *) The additional information is added to the submesh
787# dictionary. See the documentation for ghost_layer,
788# ghost_commun_pattern and full_commun_pattern
789#
790#  *) The ghost_triangles, ghost_nodes, ghost_boundary,
791# ghost_commun and full_commun is added to submesh
792#########################################################
793
794def submesh_ghost(submesh, mesh, triangles_per_proc, parameters = None):
795
796    nproc = len(triangles_per_proc)
797    tlower = 0
798    ghost_triangles = []
799    ghost_nodes = []
800    ghost_commun = []
801    ghost_bnd = []
802    ghost_layer_width = []
803
804    # Loop over the processors
805
806    for p in xrange(nproc):
807
808        # Find the full triangles in this processor
809
810        tupper = triangles_per_proc[p]+tlower
811
812        # Build the ghost boundary layer
813
814        [subnodes, subtri, layer_width] = \
815                   ghost_layer(submesh, mesh, p, tupper, tlower, parameters)
816        ghost_layer_width.append(layer_width)
817        ghost_triangles.append(subtri)
818        ghost_nodes.append(subnodes)
819
820
821        # Find the boundary layer formed by the ghost triangles
822       
823        subbnd = ghost_bnd_layer(subtri, tlower, tupper, mesh, p)
824        ghost_bnd.append(subbnd)
825       
826        # Build the communication pattern for the ghost nodes
827
828        gcommun = \
829                ghost_commun_pattern(subtri, p, triangles_per_proc)
830        ghost_commun.append(gcommun)
831
832        # Move to the next processor
833
834        tlower = tupper
835
836
837    # Record the ghost layer and communication pattern
838    submesh["ghost_layer_width"] = ghost_layer_width
839    submesh["ghost_nodes"] = ghost_nodes
840    submesh["ghost_triangles"] = ghost_triangles
841    submesh["ghost_commun"] = ghost_commun
842    submesh["ghost_boundary"] = ghost_bnd
843   
844    # Build the communication pattern for the full triangles
845
846    full_commun = full_commun_pattern(submesh, triangles_per_proc)
847    submesh["full_commun"] = full_commun
848
849    # Return the submesh
850
851    return submesh
852
853
854#########################################################
855#
856# Certain quantities may be assigned to the triangles,
857# these quantities must be subdivided in the same way
858# as the triangles
859#
860#  *) The quantities are ordered in the same way as the
861# triangles
862#
863# -------------------------------------------------------
864#
865#  *) The quantites attached to the full triangles are
866# stored in full_quan
867#
868#  *) The quantities attached to the ghost triangles are
869# stored in ghost_quan
870#########################################################
871
872def submesh_quantities(submesh, quantities, triangles_per_proc):
873
874    nproc = len(triangles_per_proc)
875
876    lower = 0
877
878    # Build an empty dictionary to hold the quantites
879
880    submesh["full_quan"] = {}
881    submesh["ghost_quan"] = {}
882    for k in quantities:
883        submesh["full_quan"][k] = []
884        submesh["ghost_quan"][k] = []
885
886    # Loop trough the subdomains
887
888    for p in xrange(nproc):
889        upper =   lower+triangles_per_proc[p]
890
891        # Find the global ID of the ghost triangles
892
893        global_id = []
894        M = len(submesh["ghost_triangles"][p])
895        for j in xrange(M):
896            global_id.append(submesh["ghost_triangles"][p][j][0])
897
898        # Use the global ID to extract the quantites information from
899        # the full domain
900
901        for k in quantities:
902            submesh["full_quan"][k].append(quantities[k][lower:upper])
903            submesh["ghost_quan"][k].append(num.zeros( (M,3) , num.float))
904            for j in range(M):
905                submesh["ghost_quan"][k][p][j] = \
906                                               quantities[k][global_id[j]]
907
908        lower = upper
909
910    return submesh
911
912#########################################################
913#
914# Build the grid partition on the host.
915#
916#  *) See the documentation for submesh_ghost and
917# submesh_full
918#
919# -------------------------------------------------------
920#
921#  *) A dictionary containing the full_triangles,
922# full_nodes, full_boundary, ghost_triangles, ghost_nodes,
923# ghost_boundary, ghost_commun and full_commun and true boundary polygon is returned.
924#
925#########################################################
926
927def build_submesh(mesh, quantities,
928                  triangles_per_proc, parameters = None):
929
930    # Temporarily build the mesh to find the neighbouring
931    # triangles and true boundary polygon
932
933    #mesh = Mesh(nodes, triangles, boundary)
934    boundary_polygon = mesh.get_boundary_polygon()
935   
936
937    # Subdivide into non-overlapping partitions
938
939    submeshf = submesh_full(mesh, triangles_per_proc)
940   
941    # Add any extra ghost boundary layer information
942
943    submeshg = submesh_ghost(submeshf, mesh, triangles_per_proc, parameters)
944
945    # Order the quantities information to be the same as the triangle
946    # information
947
948    submesh = submesh_quantities(submeshg, quantities, \
949                                 triangles_per_proc)
950
951    submesh["boundary_polygon"] = boundary_polygon
952    return submesh
953
954#########################################################
955#
956#  Given the subdivision of the grid assigned to the
957# current processor convert it into a form that is
958# appropriate for the GA datastructure.
959#
960#  The main function of these modules is to change the
961# node numbering. The GA datastructure assumes they
962# are numbered consecutively from 0.
963#
964#  The module also changes the communication pattern
965# datastructure into a form needed by parallel_advection
966#
967#  Authors: Linda Stals and Matthew Hardy, June 2005
968#  Modified: Linda Stals, Nov 2005 (optimise python code)
969#            Steve Roberts, Aug 2009 (updating to numpy)
970#
971#
972#########################################################
973
974 
975#########################################################
976# Convert the format of the data to that used by ANUGA
977#
978#
979# *) Change the nodes global ID's to an integer value,
980#starting from 0.
981#
982# *) The triangles and boundary edges must also be
983# updated accordingly.
984#
985# -------------------------------------------------------
986#
987# *) The nodes, triangles and boundary edges defined by
988# the new numbering scheme are returned
989#
990#########################################################
991
992def build_local_GA(nodes, triangles, boundaries, tri_map):
993
994    Nnodes =len(nodes)
995    Ntriangles = len(triangles)
996   
997    # Extract the nodes (using the local ID)
998   
999    GAnodes = num.take(nodes, (1, 2), 1)
1000
1001    # Build a global ID to local ID mapping
1002
1003    NGlobal = 0
1004    for i in xrange(Nnodes):
1005        if nodes[i][0] > NGlobal:
1006            NGlobal = nodes[i][0]
1007
1008    node_map = -1*num.ones(int(NGlobal)+1, num.int)
1009
1010    num.put(node_map, num.take(nodes, (0,), 1).astype(num.int), \
1011        num.arange(Nnodes))
1012       
1013    # Change the global IDs in the triangles to the local IDs
1014
1015    GAtriangles = num.zeros((Ntriangles, 3), num.int)
1016    GAtriangles[:,0] = num.take(node_map, triangles[:,0])
1017    GAtriangles[:,1] = num.take(node_map, triangles[:,1])
1018    GAtriangles[:,2] = num.take(node_map, triangles[:,2])
1019
1020    # Change the triangle numbering in the boundaries
1021
1022    GAboundaries = {}
1023    for b in boundaries:
1024        GAboundaries[tri_map[b[0]], b[1]] = boundaries[b]
1025       
1026   
1027    return GAnodes, GAtriangles, GAboundaries, node_map
1028
1029
1030#########################################################
1031# Change the communication format to that needed by the
1032# parallel advection file.
1033#
1034# *) The index contains [global triangle no,
1035# local triangle no.]
1036#
1037# -------------------------------------------------------
1038#
1039# *) The ghost_recv and full_send dictionaries are
1040# returned.
1041#
1042# *) ghost_recv dictionary is local id, global id, value
1043#
1044# *) full_recv dictionary is local id, global id, value
1045#
1046# *) The information is ordered by the global id. This
1047# means that the communication order is predetermined and
1048# local and global id do not need to be
1049# compared when the information is sent/received.
1050#
1051#########################################################
1052
1053def build_local_commun(tri_map, ghostc, fullc, nproc):
1054
1055    # Initialise
1056
1057    full_send = {}
1058    ghost_recv = {}
1059
1060    # Build the ghost_recv dictionary (sort the
1061    # information by the global numbering)
1062   
1063    ghostc = num.sort(ghostc, 0)
1064   
1065    for c in xrange(nproc):
1066        s = ghostc[:,0]
1067        d = num.compress(num.equal(ghostc[:,1],c), s)
1068        if len(d) > 0:
1069            ghost_recv[c] = [0, 0]
1070            ghost_recv[c][1] = d
1071            ghost_recv[c][0] = num.take(tri_map, d)
1072           
1073    # Build a temporary copy of the full_send dictionary
1074    # (this version allows the information to be stored
1075    # by the global numbering)
1076
1077    tmp_send = {}
1078    for global_id in fullc:
1079        for i in xrange(len(fullc[global_id])):
1080            neigh = fullc[global_id][i]
1081            if not tmp_send.has_key(neigh):
1082                tmp_send[neigh] = []
1083            tmp_send[neigh].append([global_id, \
1084                                    tri_map[global_id]])
1085
1086    # Extract the full send information and put it in the form
1087    # required for the full_send dictionary
1088
1089    for neigh in tmp_send:
1090        neigh_commun = num.sort(tmp_send[neigh], 0)
1091        full_send[neigh] = [0, 0]
1092        full_send[neigh][0] = neigh_commun[:,1]
1093        full_send[neigh][1] = neigh_commun[:,0]
1094
1095    return ghost_recv, full_send
1096
1097
1098#########################################################
1099# Convert the format of the data to that used by ANUGA
1100#
1101#
1102# *) Change the nodes global ID's to an integer value,
1103# starting from 0. The node numbering in the triangles
1104# must also be updated to take this into account.
1105#
1106# *) The triangle number will also change, which affects
1107# the boundary tag information and the communication
1108# pattern.
1109#
1110# -------------------------------------------------------
1111#
1112# *) The nodes, triangles, boundary edges and communication
1113# pattern defined by the new numbering scheme are returned
1114#
1115#########################################################
1116
1117def build_local_mesh(submesh, lower_t, upper_t, nproc):
1118
1119    # Combine the full nodes and ghost nodes
1120
1121    nodes = num.concatenate((submesh["full_nodes"], \
1122                         submesh["ghost_nodes"]))
1123
1124    ghost_layer_width = submesh["ghost_layer_width"]
1125   
1126    # Combine the full triangles and ghost triangles
1127
1128    gtri =  num.take(submesh["ghost_triangles"],(1, 2, 3),1)
1129    triangles = num.concatenate((submesh["full_triangles"], gtri))
1130
1131    # Combine the full boundaries and ghost boundaries
1132
1133    boundaries = submesh["full_boundary"]
1134    for b in submesh["ghost_boundary"]:
1135        boundaries[b]=submesh["ghost_boundary"][b]
1136
1137    # Make note of the new triangle numbers, including the ghost
1138    # triangles
1139
1140    NGlobal = upper_t
1141    for i in xrange(len(submesh["ghost_triangles"])):
1142        id = submesh["ghost_triangles"][i][0]
1143        if id > NGlobal:
1144            NGlobal = id
1145    #index = num.zeros(int(NGlobal)+1, num.int)
1146    tri_map = -1*num.ones(int(NGlobal)+1, num.int)
1147    tri_map[lower_t:upper_t]=num.arange(upper_t-lower_t)
1148    for i in xrange(len(submesh["ghost_triangles"])):
1149        tri_map[submesh["ghost_triangles"][i][0]] = i+upper_t-lower_t
1150   
1151    # Change the node numbering (and update the numbering in the
1152    # triangles)
1153
1154    [GAnodes, GAtriangles, GAboundary, node_map] = \
1155    build_local_GA(nodes, triangles, boundaries, tri_map)
1156
1157    # Extract the local quantities
1158   
1159    quantities ={}
1160    for k in submesh["full_quan"]:
1161        Nf = len(submesh["full_quan"][k])
1162        Ng = len(submesh["ghost_quan"][k])
1163        quantities[k] = num.zeros((Nf+Ng, 3), num.float)
1164        quantities[k][0:Nf] = submesh["full_quan"][k] 
1165        quantities[k][Nf:Nf+Ng] = submesh["ghost_quan"][k]
1166                             
1167    # Change the communication pattern into a form needed by
1168    # the parallel_adv
1169
1170    gcommun = submesh["ghost_commun"]
1171    fcommun = submesh["full_commun"]
1172    [ghost_rec, full_send] = \
1173                build_local_commun(tri_map, gcommun, fcommun, nproc)
1174
1175
1176    return GAnodes, GAtriangles, GAboundary, quantities, ghost_rec, \
1177           full_send, tri_map, node_map, ghost_layer_width
1178
1179
1180#########################################################
1181#
1182# Handle the communication between the host machine
1183# (processor 0) and the processors. The host machine is
1184# responsible for the doing the initial grid partitioning.
1185#
1186# The routines given below should be moved to the
1187# build_submesh.py and build_local.py file to allow
1188# overlapping of  communication and computation.
1189# This should be done after more debugging.
1190#
1191#
1192#  Author: Linda Stals, June 2005
1193#  Modified: Linda Stals, Nov 2005 (optimise python code)
1194#            Steve Roberts, Aug 2009 (update to numpy)
1195#
1196#
1197#########################################################
1198
1199
1200#########################################################
1201#
1202# Send the submesh to processor p.
1203#
1204# *) The order and form is strongly coupled with
1205# rec_submesh.
1206#
1207# -------------------------------------------------------
1208#
1209# *) All of the information has been sent to processor p.
1210#
1211#########################################################
1212
1213def send_submesh(submesh, triangles_per_proc, p, verbose=True):
1214
1215    import pypar
1216   
1217    myid = pypar.rank()
1218    nprocs = pypar.size()
1219   
1220    if verbose: print 'P%d: Sending submesh to P%d' %(myid, p)
1221   
1222    # build and send the tagmap for the boundary conditions
1223   
1224    tagmap = {}
1225    counter = 1
1226    for b in submesh["full_boundary"][p]:
1227         bkey = submesh["full_boundary"][p][b]
1228         if not tagmap.has_key(bkey):
1229             tagmap[bkey] = counter
1230             counter = counter+1
1231    for b in submesh["ghost_boundary"][p]:
1232         bkey = submesh["ghost_boundary"][p][b]
1233         if not tagmap.has_key(bkey):
1234             tagmap[bkey] = counter
1235             counter = counter+1
1236
1237
1238    # send boundary tags
1239    pypar.send(tagmap, p)
1240
1241    # send the quantities key information
1242    pypar.send(submesh["full_quan"].keys(), p)
1243
1244    # compress full_commun
1245    flat_full_commun = []
1246
1247    for c in submesh["full_commun"][p]:
1248        for i in range(len(submesh["full_commun"][p][c])):
1249            flat_full_commun.append([c,submesh["full_commun"][p][c][i]])
1250
1251    # send the array sizes so memory can be allocated
1252
1253    setup_array = num.zeros((9,),num.int)
1254    setup_array[0] = len(submesh["full_nodes"][p])
1255    setup_array[1] = len(submesh["ghost_nodes"][p])
1256    setup_array[2] = len(submesh["full_triangles"][p])
1257    setup_array[3] = len(submesh["ghost_triangles"][p])
1258    setup_array[4] = len(submesh["full_boundary"][p])
1259    setup_array[5] = len(submesh["ghost_boundary"][p])
1260    setup_array[6] = len(submesh["ghost_commun"][p])
1261    setup_array[7] = len(flat_full_commun)
1262    setup_array[8] = len(submesh["full_quan"])
1263
1264    x = num.array(setup_array, num.int)
1265    pypar.send(x, p, bypass=True)
1266
1267
1268    # ghost layer width
1269    x = num.array(submesh["ghost_layer_width"][p], num.int)
1270    pypar.send(x, p, bypass=True)
1271
1272
1273    # send the number of triangles per processor
1274    x = num.array(triangles_per_proc)
1275    pypar.send(x, p, bypass=True)
1276
1277    # send the nodes
1278    x = num.array(submesh["full_nodes"][p], num.float)
1279    pypar.send(x, p, bypass=True)
1280
1281    x = num.array(submesh["ghost_nodes"][p], num.float)
1282    pypar.send(x, p, bypass=True)
1283
1284    # send the triangles
1285    x = num.array(submesh["full_triangles"][p], num.int)
1286    pypar.send(x, p, bypass=True)
1287
1288    # send ghost triangles
1289    x = num.array(submesh["ghost_triangles"][p], num.int)
1290    pypar.send(x, p, bypass=True)
1291
1292
1293    # send the boundary
1294    bc = []
1295    for b in submesh["full_boundary"][p]:
1296        bc.append([b[0], b[1], tagmap[submesh["full_boundary"][p][b]]])
1297
1298    x = num.array(bc, num.int)
1299    pypar.send(x, p, bypass=True)
1300
1301
1302    bc = []
1303    for b in submesh["ghost_boundary"][p]:
1304        bc.append([b[0], b[1], tagmap[submesh["ghost_boundary"][p][b]]])
1305
1306    x = num.array(bc, num.int)
1307    pypar.send(x, p, bypass=True)
1308
1309
1310
1311    # send the communication pattern
1312    x = submesh["ghost_commun"][p]
1313    pypar.send(x, p, bypass=True)
1314
1315
1316    x = num.array(flat_full_commun, num.int)
1317    pypar.send(x, p, bypass=True)
1318
1319
1320    # send the quantities
1321    for k in submesh["full_quan"]:
1322        x = num.array(submesh["full_quan"][k][p], num.float)
1323        pypar.send(x, p, bypass=True)
1324       
1325    for k in submesh["ghost_quan"]:
1326        x = num.array(submesh["ghost_quan"][k][p], num.float)
1327        pypar.send(x,p, bypass=True)
1328       
1329
1330#########################################################
1331#
1332# Receive the submesh from processor p.
1333#
1334# *) The order and form is strongly coupled with
1335# send_submesh.
1336#
1337# -------------------------------------------------------
1338#
1339# *) All of the information has been received by the
1340# processor p and passed into build_local.
1341#
1342# *) The information is returned in a form needed by the
1343# GA datastructure.
1344#
1345#########################################################
1346
1347def rec_submesh_flat(p, verbose=True):
1348
1349    import pypar
1350   
1351    numprocs = pypar.size()
1352    myid = pypar.rank()
1353
1354    submesh_cell = {}
1355   
1356    if verbose: print indent+'P%d: Receiving submesh from P%d' %(myid, p)
1357
1358    # receive the tagmap for the boundary conditions
1359   
1360    tagmap = pypar.receive(p)
1361
1362    itagmap = {}
1363    for t in tagmap:
1364        itagmap[tagmap[t]]=t
1365
1366    # receive the quantities key information
1367    qkeys = pypar.receive(p)
1368
1369    # recieve information about the array sizes
1370    x = num.zeros((9,),num.int)
1371    pypar.receive(p, buffer=x,  bypass=True)
1372    setup_array = x
1373
1374    no_full_nodes      = setup_array[0]
1375    no_ghost_nodes     = setup_array[1]
1376    no_full_triangles  = setup_array[2]
1377    no_ghost_triangles = setup_array[3]
1378    no_full_boundary   = setup_array[4]
1379    no_ghost_boundary  = setup_array[5]
1380    no_ghost_commun    = setup_array[6]
1381    no_full_commun     = setup_array[7]
1382    no_quantities      = setup_array[8]
1383
1384   
1385    # ghost layer width
1386    x = num.zeros((1,),num.int)
1387    pypar.receive(p, buffer=x,  bypass=True)
1388    submesh_cell["ghost_layer_width"] = x[0]
1389
1390
1391    # receive the number of triangles per processor
1392    x = num.zeros((numprocs,),num.int)
1393    pypar.receive(p, buffer=x,  bypass=True)
1394    triangles_per_proc = x
1395
1396    # receive the full nodes
1397    x = num.zeros((no_full_nodes,3),num.float)
1398    pypar.receive(p, buffer=x,  bypass=True)
1399    submesh_cell["full_nodes"] = x
1400
1401
1402    # receive the ghost nodes
1403    x = num.zeros((no_ghost_nodes,3),num.float)
1404    pypar.receive(p, buffer=x,  bypass=True)
1405    submesh_cell["ghost_nodes"] = x
1406   
1407    # receive the full triangles
1408    x = num.zeros((no_full_triangles,3),num.int)
1409    pypar.receive(p, buffer=x,  bypass=True)
1410    submesh_cell["full_triangles"] = x
1411
1412   
1413    # receive the ghost triangles
1414    x = num.zeros((no_ghost_triangles,4),num.int)
1415    pypar.receive(p, buffer=x,  bypass=True)
1416    submesh_cell["ghost_triangles"] = x
1417
1418
1419
1420    # receive the full boundary
1421    x = num.zeros((no_full_boundary,3),num.int)
1422    pypar.receive(p, buffer=x,  bypass=True)
1423    bnd_c = x
1424
1425    submesh_cell["full_boundary"] = {}
1426    for b in bnd_c:
1427        submesh_cell["full_boundary"][b[0],b[1]]=itagmap[b[2]]
1428
1429
1430    # receive the ghost boundary
1431    x = num.zeros((no_ghost_boundary,3),num.int)
1432    pypar.receive(p, buffer=x,  bypass=True)
1433    bnd_c = x
1434
1435    submesh_cell["ghost_boundary"] = {}
1436    for b in bnd_c:
1437        submesh_cell["ghost_boundary"][b[0],b[1]]=itagmap[b[2]]
1438
1439    # receive the ghost communication pattern
1440    x = num.zeros((no_ghost_commun,2),num.int)
1441    pypar.receive(p, buffer=x,  bypass=True)
1442    submesh_cell["ghost_commun"] = x
1443   
1444    # receive the full communication pattern
1445    x = num.zeros((no_full_commun,2),num.int)
1446    pypar.receive(p, buffer=x,  bypass=True)
1447    full_commun = x
1448
1449    submesh_cell["full_commun"] = {}
1450    for c in full_commun:
1451        submesh_cell["full_commun"][c[0]] = []
1452    for c in full_commun:
1453        submesh_cell["full_commun"][c[0]].append(c[1])
1454
1455    # receive the quantities
1456
1457    submesh_cell["full_quan"]={}
1458    for i in range(no_quantities):
1459        x = num.zeros((no_full_triangles,3), num.float)
1460        pypar.receive(p, buffer=x, bypass=True)
1461        submesh_cell["full_quan"][qkeys[i]]= x
1462
1463    submesh_cell["ghost_quan"]={}
1464    for i in range(no_quantities):
1465        x = num.zeros((no_ghost_triangles,3), num.float)
1466        pypar.receive(p, buffer=x, bypass=True)
1467        submesh_cell["ghost_quan"][qkeys[i]]= x
1468   
1469    return submesh_cell, triangles_per_proc,\
1470           no_full_nodes, no_full_triangles
1471
1472
1473
1474#########################################################
1475#
1476# Receive the submesh from processor p.
1477#
1478# *) The order and form is strongly coupled with
1479# send_submesh.
1480#
1481# -------------------------------------------------------
1482#
1483# *) All of the information has been received by the
1484# processor p and passed into build_local.
1485#
1486# *) The information is returned in a form needed by the
1487# GA datastructure.
1488#
1489#########################################################
1490
1491def rec_submesh(p, verbose=True):
1492
1493    import pypar
1494   
1495    numproc = pypar.size()
1496    myid = pypar.rank()
1497
1498    [submesh_cell, triangles_per_proc,\
1499     number_of_full_nodes, number_of_full_triangles] = rec_submesh_flat(p,verbose)
1500   
1501    # find the full triangles assigned to this processor
1502
1503    lower_t = 0
1504    for i in range(myid):
1505        lower_t = lower_t+triangles_per_proc[i]
1506    upper_t = lower_t+triangles_per_proc[myid]
1507
1508    # convert the information into a form needed by the GA
1509    # datastructure
1510
1511    [GAnodes, GAtriangles, boundary, quantities, \
1512     ghost_rec, full_send, tri_map, node_map, ghost_layer_width] = \
1513              build_local_mesh(submesh_cell, lower_t, upper_t, \
1514                               numproc)
1515   
1516    return GAnodes, GAtriangles, boundary, quantities,\
1517           ghost_rec, full_send,\
1518           number_of_full_nodes, number_of_full_triangles, tri_map, node_map,\
1519           ghost_layer_width
1520         
1521
1522
1523#########################################################
1524#
1525# Extract the submesh that will belong to the
1526# "host processor" (i.e. processor zero)
1527#
1528#  *) See the documentation for build_submesh
1529#
1530# -------------------------------------------------------
1531#
1532#  *) A dictionary containing the full_triangles,
1533# full_nodes, full_boundary, ghost_triangles, ghost_nodes,
1534# ghost_boundary, ghost_commun and full_commun belonging
1535# to processor zero are returned.
1536#
1537#########################################################
1538def extract_hostmesh(submesh, triangles_per_proc):
1539
1540   
1541    submesh_cell = {}
1542    submesh_cell["ghost_layer_width"] = submesh["ghost_layer_width"][0]
1543    submesh_cell["full_nodes"] = submesh["full_nodes"][0]
1544    submesh_cell["ghost_nodes"] = submesh["ghost_nodes"][0]
1545    submesh_cell["full_triangles"] = submesh["full_triangles"][0]
1546    submesh_cell["ghost_triangles"] = submesh["ghost_triangles"][0]
1547    submesh_cell["full_boundary"] = submesh["full_boundary"][0]
1548    submesh_cell["ghost_boundary"] = submesh["ghost_boundary"][0]
1549    submesh_cell["ghost_commun"] = submesh["ghost_commun"][0]
1550    submesh_cell["full_commun"] = submesh["full_commun"][0]
1551    submesh_cell["full_quan"] ={}
1552    submesh_cell["ghost_quan"]={}
1553    for k in submesh["full_quan"]:
1554        submesh_cell["full_quan"][k] = submesh["full_quan"][k][0]
1555        submesh_cell["ghost_quan"][k] = submesh["ghost_quan"][k][0]
1556
1557    numprocs = len(triangles_per_proc)
1558    points, vertices, boundary, quantities, ghost_recv_dict, \
1559            full_send_dict, tri_map, node_map, ghost_layer_width = \
1560            build_local_mesh(submesh_cell, 0, triangles_per_proc[0], numprocs)
1561
1562
1563    return  points, vertices, boundary, quantities, ghost_recv_dict, \
1564           full_send_dict, tri_map, node_map, ghost_layer_width
1565           
1566
1567
1568
1569
Note: See TracBrowser for help on using the repository browser.