forked from MetOffice/lfric_core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathglobal_mesh_mod.F90
More file actions
1742 lines (1319 loc) · 60.4 KB
/
Copy pathglobal_mesh_mod.F90
File metadata and controls
1742 lines (1319 loc) · 60.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
!-----------------------------------------------------------------------------
! Copyright (c) 2017, Met Office, on behalf of HMSO and Queen's Printer
! For further details please refer to the file LICENCE which you
! should have received as part of this distribution.
!-----------------------------------------------------------------------------
!> @brief Describes the cell ordering within the global mesh.
!>
!> @details This object holds the connectivities that fully
!> describe the 2D topology of the global mesh.
!>
module global_mesh_mod
use constants_mod, only: r_def, i_def, str_max_filename, &
str_def, degrees_to_radians, &
rmdi, imdi, &
cmdi, l_def, emdi, &
str_longlong
use global_mesh_map_mod, only: global_mesh_map_type
use global_mesh_map_collection_mod, only: global_mesh_map_collection_type
use linked_list_data_mod, only: linked_list_data_type
use log_mod, only: log_event, log_scratch_space, &
LOG_LEVEL_ERROR, &
LOG_LEVEL_TRACE
implicit none
private
integer(i_def), parameter :: spherical_domain = 301
integer(i_def), parameter :: planar_domain = 302
integer(i_def), parameter :: non_periodic_domain = 401
integer(i_def), parameter :: channel_domain = 402
integer(i_def), parameter :: periodic_domain = 403
integer(i_def), parameter :: lon_lat_coords = 501
integer(i_def), parameter :: xyz_coords = 502
type, extends(linked_list_data_type), public :: global_mesh_type
private
!======================================================
! Mesh metadata information.
!======================================================
! Tag name of mesh.
character(str_def) :: mesh_name
! Flag to indicate if this a mesh represents coverage
! of a global model.
logical(l_def) :: global_model = .false.
! Domain surface geometry.
integer(i_def) :: geometry = emdi
! Domain boundaries topology.
integer(i_def) :: topology = emdi
! Co-ordinate system used to specify node locations.
integer(i_def) :: coord_sys = emdi
! Units used internal to this object.
character(str_def) :: coord_units_xy(2) = cmdi
! Configuration inputs used to generate this global mesh object
character(str_longlong) :: constructor_inputs = cmdi
! Principal coordinates that describe the domain shape.
! Units dependant on the geometry/coordinate system.
real(r_def) :: domain_extents(2,4) = rmdi
! Real-world location of mesh North Pole,
! only valid for spherical geometry on lon-lat cordinate-system.
real(r_def) :: north_pole(2) = rmdi
! Real-world location of mesh null island,
! only valid for spherical geometry on lon-lat cordinate-system.
real(r_def) :: null_island(2) = rmdi
! Latitude of equator of mesh following stretching
! only valid for spherical geometry and periodic topology
real(r_def) :: equatorial_latitude = rmdi
! Periodic in x/y-axes
logical(l_def) :: periodic_xy(2) = [.false.,.false.]
! Rim depth is only relevant for LBC meshes which will have a LAM
! mesh as the parent.
integer(i_def) :: rim_depth = imdi
!======================================================
! Mesh topology information.
!======================================================
! Horizontal coords of vertices in full domain.
real(r_def), allocatable :: vert_coords(:,:)
! Full domain cell to cell connectivities.
integer(i_def), allocatable :: cell_next_2d(:,:)
! Full domain vertices on a cell.
integer(i_def), allocatable :: vert_on_cell_2d(:,:)
! Full domain cells that surround a vertex.
integer(i_def), allocatable :: cell_on_vert_2d(:,:)
! Full domain edges on a cell.
integer(i_def), allocatable :: edge_on_cell_2d(:,:)
! Full domain cells either side of an edge.
integer(i_def), allocatable :: cell_on_edge_2d(:,:)
! Full domain list the cells that vertices are allocated to.
integer(i_def), allocatable :: vert_cell_owner(:)
! Full domain list the cells that edges are allocated to.
integer(i_def), allocatable :: edge_cell_owner(:)
! Total number of vertices in the full domain.
integer(i_def) :: nverts = imdi
! Total number of edges in the full domain.
integer(i_def) :: nedges = imdi
! total number of cells in full domain.
integer(i_def) :: ncells = imdi
! number of vertices on each cell.
integer(i_def) :: nverts_per_cell
! number of vertices on each edge.
integer(i_def) :: nverts_per_edge
! number of edges on each cell.
integer(i_def) :: nedges_per_cell
! maximum number of cells around a vertex.
integer(i_def) :: max_cells_per_vertex
! Number of panels on the mesh to be constructed.
integer(i_def) :: npanels
! ID used for invalid cells in connectivity.
integer(i_def) :: void_cell
!======================================================
! Intergrid map(s) information.
!======================================================
! Number of intergrid mesh maps with this mesh as source.
integer(i_def) :: ntarget_meshes
! Target of global mesh name.
character(str_def), allocatable :: target_global_mesh_names(:)
! Collection of global mesh maps in global cell IDs
type(global_mesh_map_collection_type), allocatable :: global_mesh_maps
!-------------------------------------------------------------
! Supplementary data: Only held for outputting to file.
! These may not actually be used in the main model though may
! be required for output to the mesh file for supporting
! applications, e.g. LFRic Inputs, XIOS. May be relocated
! in future tickets.
! Horizontal coords of cells in full domain.
real(r_def), allocatable :: cell_coords(:,:)
! Node IDs attached to edges.
integer(i_def), allocatable :: vert_on_edge_2d(:,:)
contains
procedure, public :: get_mesh_name
procedure, public :: get_npanels
procedure, public :: get_mesh_periodicity
procedure, public :: get_cell_id
procedure, public :: get_cell_on_vert
procedure, public :: get_cell_on_edge
procedure, public :: get_void_cell
procedure, public :: get_nverts
procedure, public :: get_nedges
procedure, public :: get_ncells
procedure, public :: get_max_cells_per_vertex
procedure, public :: get_edge_on_cell
procedure, public :: get_vert_on_cell
procedure, public :: get_edge_on_all_cells
procedure, public :: get_vert_on_all_cells
procedure, public :: get_nverts_per_cell
procedure, public :: get_nverts_per_edge
procedure, public :: get_nedges_per_cell
procedure, public :: get_cell_next
procedure, public :: get_all_cells_next
procedure, public :: get_a_vert_coords
procedure, public :: get_all_vert_coords
generic, public :: get_vert_coords => get_a_vert_coords, &
get_all_vert_coords
procedure, public :: get_a_cell_coords
procedure, public :: get_all_cell_coords
generic, public :: get_cell_coords => get_a_cell_coords, &
get_all_cell_coords
procedure, public :: get_vert_cell_owner
procedure, public :: get_edge_cell_owner
procedure, public :: add_global_mesh_map
procedure, public :: get_global_mesh_map
procedure, public :: get_target_mesh_names
procedure, public :: get_nmaps
procedure, public :: get_mesh_maps
procedure, public :: is_geometry_spherical
procedure, public :: is_geometry_planar
procedure, public :: is_topology_non_periodic
procedure, public :: is_topology_channel
procedure, public :: is_topology_periodic
procedure, public :: is_coord_sys_xyz
procedure, public :: is_coord_sys_ll
procedure, public :: get_rim_depth
procedure, public :: get_domain_extents
procedure, public :: get_north_pole
procedure, public :: get_null_island
procedure, public :: get_equatorial_latitude
procedure, public :: get_vert_on_edge
procedure, public :: get_coord_units
procedure, public :: get_constructor_inputs
procedure, public :: clear
final :: global_mesh_destructor
end type global_mesh_type
interface global_mesh_type
module procedure global_mesh_constructor
module procedure global_mesh_constructor_unit_test_data
end interface
! -------------------------------------------------------------------------
! Module parameters.
! -------------------------------------------------------------------------
! Counter variable to keep track of the next mesh id number to uniquely
! identify each different mesh.
integer(i_def), save :: global_mesh_id_counter = 0
contains
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Constructs a global mesh object from mesh file and reference
!> element.
!>
!> This global mesh object holds the connectivities which fully describe
!> the 2D topology of the mesh.
!>
!> @param[in] filename Filename for global 2D mesh(es) ugrid file.
!> @param[in] global_mesh_name Name of ugrid mesh topology to create
!> global mesh object from.
!>
!> @return Freshly minted global_mesh_type object.
!>
function global_mesh_constructor( ugrid_mesh_data ) result(self)
use ugrid_mesh_data_mod, only: ugrid_mesh_data_type
implicit none
type(ugrid_mesh_data_type), intent(in) :: ugrid_mesh_data
type(global_mesh_type) :: self
character(str_def) :: geometry
character(str_def) :: topology
character(str_def) :: coord_sys
! loop counter over entities (vertices or edges).
integer(i_def) :: ientity
call ugrid_mesh_data%get_data( self%mesh_name, &
geometry, &
topology, &
coord_sys, &
self%npanels, &
self%nverts, &
self%nedges, &
self%ncells, &
self%nverts_per_cell, &
self%nverts_per_edge, &
self%nedges_per_cell, &
self%max_cells_per_vertex, &
self%periodic_xy, &
self%ntarget_meshes, &
self%target_global_mesh_names, &
self%vert_coords, &
self%cell_coords, &
self%coord_units_xy, &
self%north_pole, &
self%null_island, &
self%equatorial_latitude, &
self%constructor_inputs, &
self%rim_depth, &
self%domain_extents, &
self%void_cell, &
self%cell_next_2d, &
self%vert_on_cell_2d, &
self%edge_on_cell_2d, &
self%vert_on_edge_2d )
global_mesh_id_counter = global_mesh_id_counter + 1
call self%set_id(global_mesh_id_counter)
select case (trim(geometry))
case ('spherical')
self%geometry = spherical_domain
case ('planar')
self%geometry = planar_domain
end select
select case (trim(topology))
case ('non_periodic')
self%topology = non_periodic_domain
case ('channel')
self%topology = channel_domain
case ('periodic')
self%topology = periodic_domain
end select
select case (trim(coord_sys))
case ('ll')
self%coord_sys = lon_lat_coords
case ('xyz')
self%coord_sys = xyz_coords
end select
! CF Standard for longitude/latitude is in degrees
! though many functions assume radians. Convert
! coords to radians before going any further into
! code.
if (self%coord_sys == lon_lat_coords) then
if (trim(self%coord_units_xy(1)) == 'degrees_east' .and. &
trim(self%coord_units_xy(2)) == 'degrees_north') then
self%vert_coords(:,:) = degrees_to_radians * self%vert_coords(:,:)
self%cell_coords(:,:) = degrees_to_radians * self%cell_coords(:,:)
self%north_pole(:) = degrees_to_radians * self%north_pole(:)
self%null_island(:) = degrees_to_radians * self%null_island(:)
self%equatorial_latitude = degrees_to_radians * self%equatorial_latitude
self%domain_extents(:,:) = degrees_to_radians * self%domain_extents(:,:)
self%coord_units_xy(:) = 'radians'
end if
end if
allocate( self%cell_on_vert_2d( self%max_cells_per_vertex, self%nverts ) )
call calc_cell_on_vertex( self%vert_on_cell_2d, &
self%nverts_per_cell, &
self%ncells, &
self%cell_on_vert_2d, &
self%max_cells_per_vertex, &
self%nverts, &
self%void_cell )
! Populate cells either side of each edge.
! There can only ever be 2 cells incident on an edge (whatever the
! topography!)
allocate( self%cell_on_edge_2d(2,self%nedges) )
call calc_cell_on_edge( self%edge_on_cell_2d, &
self%nedges_per_cell, &
self%ncells, &
self%cell_on_edge_2d, &
self%nedges, &
self%void_cell )
! Allocate each vertex to the cell with the highest global cell index
! of the cells neighbouring the vertex.
allocate( self%vert_cell_owner(self%nverts) )
do ientity=1,self%nverts
self%vert_cell_owner(ientity)=maxval( self%cell_on_vert_2d(:,ientity) )
end do
! Allocate each edge to the cell with the highest global cell index
! of the cells neighbouring the edge.
allocate( self%edge_cell_owner(self%nedges) )
do ientity=1,self%nedges
self%edge_cell_owner(ientity)=maxval( self%cell_on_edge_2d(:,ientity) )
end do
! Initialise values in this objects global mesh maps collection.
if (.not. allocated(self%global_mesh_maps) ) &
allocate ( self%global_mesh_maps, &
source = global_mesh_map_collection_type() )
end function global_mesh_constructor
!===========================================================================
!> @brief Constructs a small example mesh for unit testing purposes.
!>
!> @return 2D global mesh object based on a 9-cell global mesh. 3x3 cell
!> arrangement of quadrilateral cells.
!>
function global_mesh_constructor_unit_test_data() result (self)
implicit none
type(global_mesh_type) :: self
integer(i_def), parameter :: void = -9999
global_mesh_id_counter = global_mesh_id_counter + 1
call self%set_id(global_mesh_id_counter)
! The feigner in the mesh unit test sets geometry to spherical,
! so make the global mesh used in the mesh unit tests consistent.
self%geometry = spherical_domain
self%topology = non_periodic_domain
self%coord_sys = lon_lat_coords
self%mesh_name = 'planar:unit-test'
self%nverts_per_cell = 4
self%nedges_per_cell = 4
self%max_cells_per_vertex = 4
! Returns global_mesh_object of size 3x3 quad reference cell.
! As per reference cell, direction of numbering is anti-clockwise.
! Starting point is
! Vertices: Bottom Left (south-west)
! Edges: Left (west)
! Faces: Left (west)
self%ncells = 9
self%nverts = 16
self%nedges = 24
self%npanels = 1
allocate( self%cell_next_2d (self%nedges_per_cell, self%ncells) )
allocate( self%vert_on_cell_2d (self%nverts_per_cell, self%ncells) )
allocate( self%edge_on_cell_2d (self%nedges_per_cell, self%ncells) )
allocate( self%cell_on_vert_2d (self%max_cells_per_vertex, self%nverts) )
allocate( self%cell_on_edge_2d (2, self%nedges) )
allocate( self%vert_on_edge_2d (2, self%nedges) )
allocate( self%vert_coords (2, self%nverts) )
allocate( self%cell_coords (2, self%ncells) )
allocate( self%vert_cell_owner (self%nverts) )
allocate( self%edge_cell_owner (self%nedges) )
self%void_cell = void
self%ntarget_meshes = 0
self%domain_extents(:,1) = [ -2.0_r_def, -2.0_r_def ]
self%domain_extents(:,2) = [ 2.0_r_def, -2.0_r_def ]
self%domain_extents(:,3) = [ 2.0_r_def, 2.0_r_def ]
self%domain_extents(:,4) = [ -2.0_r_def, 2.0_r_def ]
! Note: These test coordinates are in [Long, Lat] in units of radians.
self%coord_units_xy(:) = 'radians'
self%vert_coords(1:2,1) = [-2.0_r_def, -2.0_r_def]
self%vert_coords(1:2,2) = [-1.0_r_def, -2.0_r_def]
self%vert_coords(1:2,3) = [ 1.0_r_def, -2.0_r_def]
self%vert_coords(1:2,4) = [ 2.0_r_def, -2.0_r_def]
self%vert_coords(1:2,5) = [-2.0_r_def, -1.0_r_def]
self%vert_coords(1:2,6) = [-1.0_r_def, -1.0_r_def]
self%vert_coords(1:2,7) = [ 1.0_r_def, -1.0_r_def]
self%vert_coords(1:2,8) = [ 2.0_r_def, -1.0_r_def]
self%vert_coords(1:2,9) = [-2.0_r_def, 1.0_r_def]
self%vert_coords(1:2,10) = [-1.0_r_def, 1.0_r_def]
self%vert_coords(1:2,11) = [ 1.0_r_def, 1.0_r_def]
self%vert_coords(1:2,12) = [ 2.0_r_def, 1.0_r_def]
self%vert_coords(1:2,13) = [-2.0_r_def, 2.0_r_def]
self%vert_coords(1:2,14) = [-1.0_r_def, 2.0_r_def]
self%vert_coords(1:2,15) = [ 1.0_r_def, 2.0_r_def]
self%vert_coords(1:2,16) = [ 2.0_r_def, 2.0_r_def]
! Note: These test coordinates are in [Long, Lat] in units of radians.
self%cell_coords(1:2,1) = [-1.5_r_def, -1.5_r_def]
self%cell_coords(1:2,2) = [ 0.0_r_def, -1.5_r_def]
self%cell_coords(1:2,3) = [ 1.5_r_def, -1.5_r_def]
self%cell_coords(1:2,4) = [-1.5_r_def, 0.0_r_def]
self%cell_coords(1:2,5) = [ 0.0_r_def, 0.0_r_def]
self%cell_coords(1:2,6) = [ 1.5_r_def, 0.0_r_def]
self%cell_coords(1:2,7) = [-1.5_r_def, 1.5_r_def]
self%cell_coords(1:2,8) = [ 0.0_r_def, 1.5_r_def]
self%cell_coords(1:2,9) = [ 1.5_r_def, 1.5_r_def]
self%cell_next_2d(:,1) = [void, void, 2, 4]
self%cell_next_2d(:,2) = [ 1, void, 3, 5]
self%cell_next_2d(:,3) = [ 2, void, void, 6]
self%cell_next_2d(:,4) = [void, 1, 5, 7]
self%cell_next_2d(:,5) = [ 4, 2, 6, 8]
self%cell_next_2d(:,6) = [ 5, 3, void, 9]
self%cell_next_2d(:,7) = [void, 4, 8, void]
self%cell_next_2d(:,8) = [ 7, 5, 9, void]
self%cell_next_2d(:,9) = [ 8, 6, void, void]
self%vert_on_cell_2d(:,1) = [ 1, 2, 6, 5]
self%vert_on_cell_2d(:,2) = [ 2, 3, 7, 6]
self%vert_on_cell_2d(:,3) = [ 3, 4, 8, 7]
self%vert_on_cell_2d(:,4) = [ 5, 6, 10, 9]
self%vert_on_cell_2d(:,5) = [ 6, 7, 11, 10]
self%vert_on_cell_2d(:,6) = [ 7, 8, 12, 11]
self%vert_on_cell_2d(:,7) = [ 9, 10, 14, 13]
self%vert_on_cell_2d(:,8) = [10, 11, 15, 14]
self%vert_on_cell_2d(:,9) = [11, 12, 16, 15]
self%edge_on_cell_2d(:,1) = [4, 1, 5, 8]
self%edge_on_cell_2d(:,2) = [5, 2, 6, 9]
self%edge_on_cell_2d(:,3) = [6, 3, 7, 10]
self%edge_on_cell_2d(:,4) = [11, 8, 12, 15]
self%edge_on_cell_2d(:,5) = [12, 9, 13, 16]
self%edge_on_cell_2d(:,6) = [13, 10, 14, 17]
self%edge_on_cell_2d(:,7) = [18, 15, 19, 22]
self%edge_on_cell_2d(:,8) = [19, 16, 20, 23]
self%edge_on_cell_2d(:,9) = [20, 17, 21, 24]
self%cell_on_vert_2d(:,1) = [void, void, 1, void]
self%cell_on_vert_2d(:,2) = [void, void, 2, 1]
self%cell_on_vert_2d(:,3) = [void, void, 3, 2]
self%cell_on_vert_2d(:,4) = [void, void, void, 3]
self%cell_on_vert_2d(:,5) = [void, 1, 4, void]
self%cell_on_vert_2d(:,6) = [ 1, 2, 5, 4]
self%cell_on_vert_2d(:,7) = [ 2, 3, 6, 5]
self%cell_on_vert_2d(:,8) = [ 3, void, void, 6]
self%cell_on_vert_2d(:,9) = [void, 4, 7, void]
self%cell_on_vert_2d(:,10) = [ 4, 5, 8, 7]
self%cell_on_vert_2d(:,11) = [ 5, 6, 9, 8]
self%cell_on_vert_2d(:,12) = [ 6, void, void, 9]
self%cell_on_vert_2d(:,13) = [void, 7, void, void]
self%cell_on_vert_2d(:,14) = [ 7, 8, void, void]
self%cell_on_vert_2d(:,15) = [ 8, 9, void, void]
self%cell_on_vert_2d(:,16) = [ 9, void, void, void]
self%cell_on_edge_2d(:,1) = [void, 1]
self%cell_on_edge_2d(:,2) = [void, 2]
self%cell_on_edge_2d(:,3) = [void, 3]
self%cell_on_edge_2d(:,4) = [ 1, void]
self%cell_on_edge_2d(:,5) = [ 2, 1]
self%cell_on_edge_2d(:,6) = [ 3, 2]
self%cell_on_edge_2d(:,7) = [void, 3]
self%cell_on_edge_2d(:,8) = [ 1, 4]
self%cell_on_edge_2d(:,9) = [ 2, 5]
self%cell_on_edge_2d(:,10) = [ 3, 6]
self%cell_on_edge_2d(:,11) = [ 4, void]
self%cell_on_edge_2d(:,12) = [ 5, 4]
self%cell_on_edge_2d(:,13) = [ 6, 5]
self%cell_on_edge_2d(:,14) = [void, 6]
self%cell_on_edge_2d(:,15) = [ 4, 7]
self%cell_on_edge_2d(:,16) = [ 5, 8]
self%cell_on_edge_2d(:,17) = [ 6, 9]
self%cell_on_edge_2d(:,18) = [ 7, void]
self%cell_on_edge_2d(:,19) = [ 8, 7]
self%cell_on_edge_2d(:,20) = [ 9, 8]
self%cell_on_edge_2d(:,21) = [void, 9]
self%cell_on_edge_2d(:,22) = [ 7, void]
self%cell_on_edge_2d(:,23) = [ 8, void]
self%cell_on_edge_2d(:,24) = [ 9, void]
self%vert_on_edge_2d(:,1) = [1, 2]
self%vert_on_edge_2d(:,2) = [2, 3]
self%vert_on_edge_2d(:,3) = [3, 4]
self%vert_on_edge_2d(:,4) = [1, 5]
self%vert_on_edge_2d(:,5) = [2, 6]
self%vert_on_edge_2d(:,6) = [3, 7]
self%vert_on_edge_2d(:,7) = [4, 8]
self%vert_on_edge_2d(:,8) = [5, 6]
self%vert_on_edge_2d(:,9) = [6, 7]
self%vert_on_edge_2d(:,10) = [7, 8]
self%vert_on_edge_2d(:,11) = [5, 9]
self%vert_on_edge_2d(:,12) = [6,10]
self%vert_on_edge_2d(:,13) = [7,11]
self%vert_on_edge_2d(:,14) = [8,12]
self%vert_on_edge_2d(:,15) = [9,10]
self%vert_on_edge_2d(:,16) = [10,11]
self%vert_on_edge_2d(:,17) = [11,12]
self%vert_on_edge_2d(:,18) = [9, 13]
self%vert_on_edge_2d(:,19) = [10,14]
self%vert_on_edge_2d(:,20) = [11,15]
self%vert_on_edge_2d(:,21) = [12,16]
self%vert_on_edge_2d(:,22) = [13,14]
self%vert_on_edge_2d(:,23) = [14,15]
self%vert_on_edge_2d(:,24) = [15,16]
self%vert_cell_owner(:) = [1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9, 9, 7, 8, 9, 9]
self%edge_cell_owner(:) = [1, 2, 3, 1, 2, 3, 3, 4, 5, 6, 4, 5, 6, 6, 7, 8, &
9, 7, 8, 9, 9, 7, 8, 9]
! Initialise values in this objects global mesh maps collection.
if (.not. allocated(self%global_mesh_maps) ) then
allocate( self%global_mesh_maps, &
source=global_mesh_map_collection_type() )
end if
end function global_mesh_constructor_unit_test_data
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Destroys a global_mesh object when it is finished with.
!>
subroutine global_mesh_destructor(self)
implicit none
type (global_mesh_type), intent(inout) :: self
!> @todo Is there a reason why this is a separate function? It seems
!> like a global mesh should be immutable.
call self%clear()
end subroutine global_mesh_destructor
!-------------------------------------------------------------------------------
! Returns the cells on vertices. PRIVATE subroutine.
!-------------------------------------------------------------------------------
! Details: Calculates the cells that are incident on a vertex by looping through
! the vertices on all cells (which we store) and filling an array based
! on vertex number with the cells around it.
! Input: vert_on_cell Array with indices of vertices on cells.
! verts_per_cell Number of vertices per cell.
! ncell Number of cells.
! cells_per_vert Number of cells per vertex.
! nvert Number of vertices.
! void_cell Cell ID to use for null connectivity.
! Output: cell_on_vert Array with indices of cells on vertices.
!-------------------------------------------------------------------------------
subroutine calc_cell_on_vertex( vert_on_cell, &
verts_per_cell, &
ncell, &
cell_on_vert, &
cells_per_vert, &
nvert, &
void_cell )
implicit none
integer(i_def), intent(in) :: verts_per_cell, ncell
integer(i_def), intent(in) :: vert_on_cell(verts_per_cell, ncell)
integer(i_def), intent(in) :: cells_per_vert, nvert, void_cell
integer(i_def), intent(out) :: cell_on_vert(cells_per_vert, nvert)
integer(i_def) :: cell
integer(i_def) :: vertno
integer(i_def) :: cellno
integer(i_def) :: vert
cell_on_vert = void_cell
! There is no order to how the cell IDs are listed around the vertex
! Some may have 4 or 3 (i.e. vertices on corners of panels).
do cell=1, ncell
do vertno=1, verts_per_cell
vert = vert_on_cell(vertno,cell)
do cellno=1, cells_per_vert
if (cell_on_vert(cellno,vert) == cell) exit
if (cell_on_vert(cellno,vert) == void_cell) then
cell_on_vert(cellno,vert) = cell
exit
end if
end do
end do
end do
end subroutine calc_cell_on_vertex
!-------------------------------------------------------------------------------
! Returns the cells on edges. PRIVATE subroutine.
!-------------------------------------------------------------------------------
! Details: Calculates the cells that are either side of an edge by looping
! through the edges on all cells (which we store) and filling an array
! based on edge number with the cells around it.
! Input: edge_on_cell Array with indices of edges on cells.
! edges_per_cell Number of edges per cell.
! ncell Number of cells.
! nedge Number of edges.
! void_cell Cell ID to use for null connectivity.
! Output: cell_on_edge Array with indices of cells on edges.
!-------------------------------------------------------------------------------
subroutine calc_cell_on_edge( edge_on_cell, &
edges_per_cell, &
ncell, &
cell_on_edge, &
nedge, &
void_cell )
implicit none
integer(i_def), intent(in) :: edges_per_cell, ncell
integer(i_def), intent(in) :: edge_on_cell(edges_per_cell, ncell)
integer(i_def), intent(in) :: nedge, void_cell
integer(i_def), intent(out) :: cell_on_edge(2, nedge)
integer(i_def) :: cell
integer(i_def) :: edgeno
integer(i_def) :: cellno
integer(i_def) :: edge
cell_on_edge = void_cell
do cell=1,ncell
do edgeno=1,edges_per_cell
edge=edge_on_cell(edgeno,cell)
do cellno=1, 2
if ( cell_on_edge(cellno,edge) == cell ) exit
if ( cell_on_edge(cellno,edge) == void_cell ) then
cell_on_edge(cellno,edge)=cell
exit
end if
end do
end do
end do
end subroutine calc_cell_on_edge
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns the number of panels in the mesh.
!>
!> @details Returns the number of panels in the mesh -
!> cubedsphere = 6,
!> planar = 1
!>
!> @return npanels The number of panels in the mesh.
!>
function get_npanels( self ) result ( npanels )
implicit none
class(global_mesh_type), intent(in) :: self
integer(i_def) :: npanels
npanels = self%npanels
end function get_npanels
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries the ID value used for void cells. These are invalid cells
!> in the cell-cell connectivity.
!> @return cell_id The ID value used for null cell-connectivity.
!>
function get_void_cell( self ) result ( cell_id )
implicit none
class(global_mesh_type), intent(in) :: self
integer(i_def) :: cell_id
cell_id = self%void_cell
end function get_void_cell
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh domain geometry is spherical.
!> @return answer .true. for a spherical domain surface.
!>
function is_geometry_spherical( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%geometry == spherical_domain )
end function is_geometry_spherical
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh domain geometry is a flat surface.
!> @return answer .true. for a flat domain surface.
!>
function is_geometry_planar( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%geometry == planar_domain )
end function is_geometry_planar
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh topology specifies a domain where all the
!> boundaries are closed.
!> @return answer .true. for a domain which is non-periodic.
!>
function is_topology_non_periodic( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%topology == non_periodic_domain )
end function is_topology_non_periodic
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh topology specifies a domain where
!> there is a single pair of periodic boundaries.
!> @return answer .true. for a channel domain.
!>
function is_topology_channel( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%topology == channel_domain )
end function is_topology_channel
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh topology specifies a domain where all
!> domain boundaries are open with periodicity.
!> @return answer .true. for a fully periodic domain.
!>
function is_topology_periodic( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%topology == periodic_domain )
end function is_topology_periodic
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh nodes are specified using Cartesian
!> co-ordinates (x,y,z).
!> @return answer .true. if nodes are specified in Cartesian co-ordinates.
!>
function is_coord_sys_xyz( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%coord_sys == xyz_coords )
end function is_coord_sys_xyz
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Queries if the global mesh nodes are specified using Spherical
!> co-ordinates (longitude, latitude).
!> @return answer .true. if nodes are specified in spherical co-ordinates.
!>
function is_coord_sys_ll( self ) result ( answer )
implicit none
class(global_mesh_type), intent(in) :: self
logical (l_def) :: answer
answer = ( self%coord_sys == lon_lat_coords )
end function is_coord_sys_ll
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns mesh tag name.
!> @return mesh_name Tag name of mesh that identifies it in the
!> UGRID file that it was read in from.
!>
function get_mesh_name( self ) result ( mesh_name )
implicit none
class(global_mesh_type), intent(in) :: self
character(str_def) :: mesh_name
mesh_name = self%mesh_name
end function get_mesh_name
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns values for the X and Y periodicity.
!>
!> @details periodic_xy as read from the mesh file are returned.
!> This functions is currently only used during the
!> partitioning of planar meshes for determining mesh extents.
!> The values are obtained from the header of the mesh files
!> and are set to "true" for cubedsphere meshes (due to mesh
!> IO function requirements) and to a user defined value for
!> planar meshes.
!>
!> @return periodic_xy Mesh domain periodicity in x/y-axes.
!>
function get_mesh_periodicity( self ) result ( periodic_xy )
implicit none
class(global_mesh_type), intent(in) :: self
logical(l_def) :: periodic_xy(2)
periodic_xy = self%periodic_xy
end function get_mesh_periodicity
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns the input arguments to constructor.
!> @details The returned string records the arguments used to generate the
!> global mesh for output to mesh input files. Principally so
!> meshes can be regenerated at later code revisions if a user only
!> possesses the input mesh file.
!>
!> @return string A string listing the arguments that were passed to the
!> global mesh constructor function.
!>
function get_constructor_inputs( self ) result ( constructor_inputs )
implicit none
class(global_mesh_type), intent(in) :: self
character(str_longlong) :: constructor_inputs
constructor_inputs = self%constructor_inputs
end function get_constructor_inputs
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns the north pole location of mesh.
!> @details A mesh may have been transformed before being written to file.
!> This returns the location of the north pole (in real world lon-lat)
!> referenced by co-ordinates in this mesh. Only valid for spherical
!> geometries with lon-lat coordinate system.
!> @return real(2), units (radians).
!>
function get_north_pole( self ) result( north_pole )
implicit none
class(global_mesh_type), intent(in) :: self
real(r_def) :: north_pole(2)
north_pole = self%north_pole
end function get_north_pole
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns the null island location of mesh.
!> @details A mesh may have been transformed before being written to file.
!> This returns the location of the null island (in real world lon-lat)
!> referenced by co-ordinates in this mesh. Only valid for spherical
!> geometries with lon-lat coordinate system.
!> @return null_island [lon,lat] real-world coordinates in radians.
!>
function get_null_island( self ) result( null_island )
implicit none
class(global_mesh_type), intent(in) :: self
real(r_def) :: null_island(2)
null_island = self%null_island
end function get_null_island
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!> @brief Returns the latitude of the equator of the mesh.
!> @details A mesh may have been transformed before being written to file.
!! This returns the latitde of the equator used for this mesh.
!! Only valid for cubed-sphere meshes.