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

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

small changes to parallel code

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