Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ElmerCSC
GitHub Repository: ElmerCSC/elmerfem
Path: blob/devel/fem/src/CutFEMUtils.F90
3203 views
1
!/*****************************************************************************/
2
! *
3
! * Elmer, A Finite Element Software for Multiphysical Problems
4
! *
5
! * Copyright 1st April 1995 - , CSC - IT Center for Science Ltd., Finland
6
! *
7
! * This library is free software; you can redistribute it and/or
8
! * modify it under the terms of the GNU Lesser General Public
9
! * License as published by the Free Software Foundation; either
10
! * version 2.1 of the License, or (at your option) any later version.
11
! *
12
! * This library is distributed in the hope that it will be useful,
13
! * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
! * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
! * Lesser General Public License for more details.
16
! *
17
! * You should have received a copy of the GNU Lesser General Public
18
! * License along with this library (in file ../LGPL-2.1); if not, write
19
! * to the Free Software Foundation, Inc., 51 Franklin Street,
20
! * Fifth Floor, Boston, MA 02110-1301 USA
21
! *
22
! *****************************************************************************/
23
!
24
!/******************************************************************************
25
! *
26
! * Authors: Juha Ruokolainen, Peter RÃ¥back
27
! * Email: [email protected]
28
! * Web: http://www.csc.fi/elmer
29
! * Address: CSC - IT Center for Science Ltd.
30
! * Keilaranta 14
31
! * 02101 Espoo, Finland
32
! *
33
! * Original Date: 01 Oct 1996
34
! *
35
! ****************************************************************************/
36
37
!> \ingroup ElmerLib
38
!> \{
39
40
!------------------------------------------------------------------------------
41
!> Module containing utilities for CutFEM style of strategies.
42
!------------------------------------------------------------------------------
43
44
MODULE CutFemUtils
45
USE Types
46
USE Lists
47
USE ElementUtils, ONLY : FreeMatrix
48
USE Interpolation, ONLY : CopyElementNodesFromMesh
49
USE ElementDescription
50
USE MatrixAssembly
51
USE MeshUtils, ONLY : AllocateMesh, FindMeshEdges, MeshStabParams
52
USE ModelDescription, ONLY : FreeMesh
53
USE SolverUtils, ONLY : GaussPointsAdapt, SolveLinearSystem, VectorValuesRange
54
USE ParallelUtils
55
56
IMPLICIT NONE
57
58
PRIVATE
59
60
LOGICAL :: CutExtend, CutExtrapolate
61
LOGICAL, ALLOCATABLE :: CutDof(:)
62
INTEGER, POINTER :: ExtendPerm(:) => NULL(), OrigMeshPerm(:) => NULL(), &
63
CutPerm(:) => NULL(), PhiPerm(:) => NULL()
64
REAL(KIND=dp), POINTER :: OrigMeshValues(:) => NULL(), CutValues(:) => NULL(), &
65
ExtendValues(:) => NULL(), PhiValues(:) => NULL(), &
66
OrigPrevMeshValues(:,:) => NULL(), PrevCutValues(:,:) => NULL()
67
INTEGER, POINTER :: OrigActiveElements(:), AddActiveElements(:), UnsplitActiveElements(:)
68
REAL(KIND=dp), ALLOCATABLE, TARGET :: CutInterp(:)
69
TYPE(Matrix_t), POINTER :: NodeMatrix
70
INTEGER :: CutFemBody
71
CHARACTER(:), ALLOCATABLE :: CutStr
72
INTEGER :: CutDofs = 0
73
INTEGER :: nCase(20)
74
75
#define DEBUG_ORIENT 0
76
#if DEBUG_ORIENT
77
REAL(KIND=dp) :: CutFEMCenter(3)
78
#endif
79
80
81
PUBLIC :: CreateCutFEMMatrix, CreateCutFEMMesh, CreateCutFEMPerm, CreateCutFEMAddMesh, &
82
CutFEMVariableFinalize, CutFEMSetOrigMesh, CutFEMSetAddMesh, LevelSetUpdate, &
83
CutInterfaceBC, CutInterfaceBulk, CutInterfaceCheck
84
85
PUBLIC :: CutInterp
86
87
TYPE(Mesh_t), POINTER :: CutFEMOrigMesh => NULL(), CutFEMAddMesh => NULL()
88
89
90
CONTAINS
91
92
93
! Given a levelset function create a permutation that tells which
94
! edges and which nodes are being cut by the zero levelset.
95
! Optionally also create a permutation for the outside mesh.
96
!------------------------------------------------------------------
97
SUBROUTINE CreateCutFEMPerm(Solver,UpdateCoords)
98
TYPE(Solver_t) :: Solver
99
LOGICAL :: UpdateCoords
100
101
TYPE(Mesh_t), POINTER :: Mesh
102
TYPE(ValueList_t), POINTER :: Params
103
104
INTEGER :: i,j,k,nn,ne,body_in,body_out,body_cut,InsideCnt(3),dofs
105
REAL(KIND=dp) :: h1,h2,hprod,Eps,r,MaxRat
106
INTEGER, POINTER :: NodeIndexes(:)
107
TYPE(Variable_t), POINTER :: Var, PhiVar
108
TYPE(Element_t), POINTER :: Element, pElement
109
CHARACTER(:), ALLOCATABLE :: str
110
LOGICAL :: Found, PassiveInside, PassiveOutside, isCut, isMore, UseAbsEps, Hit
111
REAL(KIND=dp), POINTER :: xtmp(:)
112
LOGICAL :: UpdateOrigCoords
113
CHARACTER(*), PARAMETER :: Caller = 'CreateCutFEMPerm'
114
115
116
Params => Solver % Values
117
Mesh => Solver % Mesh
118
119
! Memorize original nodal variable and matrix.
120
OrigMeshValues => NULL()
121
OrigPrevMeshValues => NULL()
122
OrigMeshPerm => NULL()
123
IF(ASSOCIATED(Solver % Variable ) ) THEN
124
IF(ASSOCIATED(Solver % Variable % Perm ) ) THEN
125
OrigMeshValues => Solver % Variable % Values
126
OrigMeshPerm => Solver % Variable % Perm
127
OrigPrevMeshValues => Solver % Variable % PrevValues
128
END IF
129
CutDofs = Solver % Variable % dofs
130
dofs = CutDofs
131
END IF
132
133
CutFEMOrigMesh => Solver % Mesh
134
OrigActiveElements => Solver % ActiveElements
135
136
NodeMatrix => Solver % Matrix
137
138
CutExtend = ListGetLogical( Params,'CutFEM extend',Found )
139
CutExtrapolate = ListGetLogical( Params,'CutFEM extrapolate',Found )
140
UpdateOrigCoords = ListGetLogical( Params,'CutFEM bodyfitted',Found )
141
142
! We always need mesh edges since the new dofs are created in intersections of levelset and edge.
143
IF(ASSOCIATED(Mesh % edges)) THEN
144
CALL Info(Caller,'Mesh edges already created!',Level=12)
145
ELSE
146
CALL Info(Caller,'Create element edges',Level=10)
147
CALL FindMeshEdges( Mesh )
148
149
! We need global numbering for the edges that we use for the unique numbering of new nodes
150
IF( ParEnv % PEs > 1 ) THEN
151
CALL Info(Caller,'Numbering Mesh edges in parallel')
152
CALL SParEdgeNumbering(Mesh)
153
END IF
154
END IF
155
156
nn = Mesh % NumberOfNodes
157
ne = Mesh % NumberOfEdges
158
159
IF( UpdateCoords ) THEN
160
i = SIZE(Mesh % Nodes % x)
161
IF(i < nn + ne ) THEN
162
CALL Info(Caller,'Enlarging node coordinates for edge cuts from '&
163
//I2S(i)//' to '//I2S(nn+ne),Level=7)
164
ALLOCATE(xtmp(nn))
165
xtmp = Mesh % Nodes % x(1:nn)
166
DEALLOCATE(Mesh % Nodes % x)
167
ALLOCATE(Mesh % Nodes % x(nn+ne))
168
Mesh % Nodes % x(1:nn) = xtmp
169
170
xtmp = Mesh % Nodes % y(1:nn)
171
DEALLOCATE(Mesh % Nodes % y)
172
ALLOCATE(Mesh % Nodes % y(nn+ne))
173
Mesh % Nodes % y(1:nn) = xtmp
174
175
xtmp = Mesh % Nodes % z(1:nn)
176
DEALLOCATE(Mesh % Nodes % z)
177
ALLOCATE(Mesh % Nodes % z(nn+ne))
178
Mesh % Nodes % z(1:nn) = xtmp
179
DEALLOCATE(xtmp)
180
END IF
181
END IF
182
183
IF(.NOT. ALLOCATED(CutDof) ) THEN
184
CALL Info(Caller,'Allocating "CutDof" field to indicate levelset cuts!',Level=20)
185
ALLOCATE( CutDof(nn+ne) )
186
END IF
187
CutDof = .FALSE.
188
189
! We store the cut for future interpolation.
190
IF(.NOT. ALLOCATED(CutInterp)) THEN
191
CALL Info(Caller,'Allocating "CutInterp" for edge related interpolation!',Level=20)
192
ALLOCATE(CutInterp(ne))
193
END IF
194
CutInterp = 0.0_dp
195
196
CutStr = ListGetString( Params,'Levelset Variable', Found)
197
IF( .NOT. Found ) CutStr = "surface"
198
199
PhiVar => VariableGet(Mesh % Variables, CutStr, ThisOnly=.TRUE.)
200
IF(.NOT. ASSOCIATED(PhiVar) ) THEN
201
CALL Fatal(Caller,'"Levelset Variable" not available: '//TRIM(CutStr))
202
END IF
203
PhiValues => PhiVar % Values
204
PhiPerm => PhiVar % Perm
205
206
body_in = ListGetInteger( Params,'CutFEm Inside Body',Found )
207
IF(.NOT. Found) body_in = CurrentModel % NumberOfBodies
208
body_out = ListGetInteger( Params,'CutFem Outside Body',Found )
209
IF(.NOT. Found) body_out = body_in+1
210
body_cut = MAX(body_in,body_out)
211
212
! This is a little dirty, we set the interface elements so we recognize them.
213
IF(CutExtend) body_cut = body_cut + 1
214
215
Eps = ListGetCReal(Params,'CutFem Epsilon',Found )
216
IF(.NOT. Found) Eps = 1.0e-3
217
UseAbsEps = ListGetLogical(Params,'CutFEM Epsilon Absolute',Found )
218
219
220
! First mark the cutted nodes.
221
! These could maybe be part of the same loop as well but I separated when testing something.
222
DO i=1, Mesh % NumberOfEdges
223
NodeIndexes => Mesh % Edges(i) % NodeIndexes
224
IF(ANY(PhiPerm(NodeIndexes) == 0)) CYCLE
225
h1 = PhiValues(PhiPerm(NodeIndexes(1)))
226
h2 = PhiValues(PhiPerm(NodeIndexes(2)))
227
hprod = h1*h2
228
IF( hprod < 0.0_dp ) THEN
229
r = ABS(h2)/(ABS(h1)+ABS(h2))
230
Hit = .FALSE.
231
IF( UseAbsEps ) THEN
232
IF(ABS(h2) < Eps ) THEN
233
CutDof(NodeIndexes(2)) = .TRUE.
234
Hit = .TRUE.
235
END IF
236
IF(ABS(h1) < Eps ) THEN
237
CutDof(NodeIndexes(1)) = .TRUE.
238
Hit = .TRUE.
239
END IF
240
ELSE
241
IF( r <= Eps ) THEN
242
CutDof(NodeIndexes(2)) = .TRUE.
243
Hit = .TRUE.
244
END IF
245
IF((1.0-r < Eps) ) THEN
246
CutDof(NodeIndexes(1)) = .TRUE.
247
Hit = .TRUE.
248
END IF
249
END IF
250
ELSE IF( ABS(hprod) < 1.0d-20 ) THEN
251
IF(ABS(h1) < 1.0e-20) CutDof(NodeIndexes(1)) = .TRUE.
252
IF(ABS(h2) < 1.0e-20) CutDof(NodeIndexes(2)) = .TRUE.
253
END IF
254
END DO
255
256
257
IF(ParEnv % PEs > 1 ) THEN
258
BLOCK
259
INTEGER, POINTER :: Perm(:)
260
INTEGER :: ni
261
REAL(KIND=dp), POINTER :: CutDofR(:)
262
263
ni = COUNT( CutDof(1:nn) .AND. Mesh % ParallelInfo % GInterface(1:nn) )
264
ni = ParallelReduction( ni )
265
266
IF( ni > 0 ) THEN
267
ALLOCATE(CutDofR(nn),Perm(nn))
268
CutDofR = 0.0_dp
269
DO i=1,nn
270
Perm(i) = i
271
END DO
272
273
WHERE( CutDof(1:nn) )
274
CutDofR = 1.0_dp
275
END WHERE
276
CALL ExchangeNodalVec( Mesh % ParallelInfo, Perm, CutDofR, op = 2)
277
DO i=1,nn
278
IF(CutDofR(i) > 0.5_dp ) CutDof(i) = .TRUE.
279
END DO
280
DEALLOCATE(CutDofR, Perm )
281
END IF
282
END BLOCK
283
END IF
284
285
286
! Then mark the edges trying to avoid nearby cuts.
287
InsideCnt = 0
288
j = 0
289
290
! This is an add'hoc value that represents the maximum aspect ratio of elements in the mesh.
291
MaxRat = 2.0
292
293
DO i=1, Mesh % NumberOfEdges
294
NodeIndexes => Mesh % Edges(i) % NodeIndexes
295
IF(ANY(PhiPerm(NodeIndexes)==0)) CYCLE
296
h1 = PhiValues(PhiPerm(NodeIndexes(1)))
297
h2 = PhiValues(PhiPerm(NodeIndexes(2)))
298
hprod = h1*h2
299
IF( hprod < 0.0_dp ) THEN
300
r = ABS(h2)/(ABS(h1)+ABS(h2))
301
Hit = .FALSE.
302
303
! We may have a sloppier rule if the dof is already cut?
304
! If the rule is exactly the same then no need for separate loop.
305
IF( r <= MaxRat * Eps ) THEN
306
IF(CutDof(NodeIndexes(2))) CYCLE
307
ELSE IF((1.0-r < MaxRat * Eps) ) THEN
308
IF(CutDof(NodeIndexes(1))) CYCLE
309
END IF
310
311
j = j+1
312
CutDof(nn+i) = .TRUE.
313
314
! The iterpolation weight should always be [0,1]
315
IF(r < 0.0 .OR. r > 1.0) THEN
316
PRINT *,'Invalid cutinterp:',i,j,r
317
END IF
318
319
CutInterp(i) = r
320
321
! We update nodes so that the element on-the-fly can point to then using NodeIndexes.
322
IF( UpdateCoords ) THEN
323
Mesh % Nodes % x(nn+i) = (1-r) * Mesh % Nodes % x(NodeIndexes(2)) + &
324
r * Mesh % Nodes % x(NodeIndexes(1))
325
Mesh % Nodes % y(nn+i) = (1-r) * Mesh % Nodes % y(NodeIndexes(2)) + &
326
r * Mesh % Nodes % y(NodeIndexes(1))
327
Mesh % Nodes % z(nn+i) = (1-r) * Mesh % Nodes % z(NodeIndexes(2)) + &
328
r * Mesh % Nodes % z(NodeIndexes(1))
329
END IF
330
END IF
331
END DO
332
333
! Should we update the original coords for nodes which closely match the levelset but not exactly.
334
! This would be the case if we want to follow the body fitted shape of a object as closely as possible.
335
! We would not want to do in transient cases
336
IF(UpdateOrigCoords) THEN
337
BLOCK
338
LOGICAL, ALLOCATABLE :: MovedNode(:)
339
REAL(KIND=dp), ALLOCATABLE :: TmpCoords(:,:)
340
ALLOCATE(MovedNode(nn), TmpCoords(nn,3))
341
MovedNode = .FALSE.
342
TmpCoords = 0.0_dp
343
DO i=1, Mesh % NumberOfEdges
344
NodeIndexes => Mesh % Edges(i) % NodeIndexes
345
IF(.NOT. ANY(CutDOF(NodeIndexes))) CYCLE
346
347
h1 = PhiValues(PhiPerm(NodeIndexes(1)))
348
h2 = PhiValues(PhiPerm(NodeIndexes(2)))
349
hprod = h1*h2
350
IF( hprod >= 0.0_dp ) CYCLE
351
352
r = ABS(h2)/(ABS(h1)+ABS(h2))
353
IF( r <= Eps ) THEN
354
j = 2
355
ELSE IF((1.0-r < Eps) ) THEN
356
j = 1
357
ELSE
358
CYCLE
359
END IF
360
361
k = NodeIndexes(j)
362
IF(.NOT. CutDof(k)) CYCLE
363
IF(MovedNode(k)) CYCLE
364
365
TmpCoords(k,1) = (1-r) * Mesh % Nodes % x(NodeIndexes(2)) + &
366
r * Mesh % Nodes % x(NodeIndexes(1))
367
TmpCoords(k,2) = (1-r) * Mesh % Nodes % y(NodeIndexes(2)) + &
368
r * Mesh % Nodes % y(NodeIndexes(1))
369
TmpCoords(k,3) = (1-r) * Mesh % Nodes % z(NodeIndexes(2)) + &
370
r * Mesh % Nodes % z(NodeIndexes(1))
371
MovedNode(k) = .TRUE.
372
END DO
373
k = COUNT(MovedNode)
374
CALL Info(Caller,'Moved cut nodes to be exactly at zero levelset!')
375
376
WHERE(MovedNode(1:nn))
377
Mesh % Nodes % x(1:nn) = TmpCoords(1:nn,1)
378
Mesh % Nodes % y(1:nn) = TmpCoords(1:nn,2)
379
Mesh % Nodes % z(1:nn) = TmpCoords(1:nn,3)
380
END WHERE
381
DEALLOCATE(MovedNode, TmpCoords)
382
END BLOCK
383
END IF
384
385
386
IF(InfoActive(25)) THEN
387
PRINT *,'CutInterp interval:',MINVAL(CutInterp),MAXVAL(CutInterp)
388
PRINT *,'Nodes split',COUNT(CutDof(1:nn))
389
PRINT *,'Edges cut',COUNT(CutDof(nn+1:nn+ne))
390
END IF
391
392
! Set the material for inside/outside or interface material.
393
! This way we do not need to have too complicated material sections.
394
CutFEMBody = 0
395
DO i=1,Mesh % NumberOfBulkElements
396
Element => Mesh % Elements(i)
397
398
NodeIndexes => Element % NodeIndexes
399
IF(ANY(PhiPerm(NodeIndexes) == 0)) CYCLE
400
401
! So far we assume that there is only one body index used to define the CutFEM region.
402
! We are tampering with the index, so we need to store it.
403
IF(CutFEMBody == 0) THEN
404
CutFEMBody = Element % BodyId
405
ELSE
406
IF(CutFemBody /= Element % BodyId ) THEN
407
CALL Fatal(Caller,'Modify code to deal with several bodies!')
408
END IF
409
END IF
410
411
j = -1
412
IF(ANY(CutDof(nn + Element % EdgeIndexes)) ) THEN
413
! Some edge is split => interface element
414
j = body_cut
415
InsideCnt(3) = InsideCnt(3)+1
416
ELSE
417
! Also at interface element if we have diagonal split in a quad.
418
IF(Element % TYPE % ElementCode / 100 == 4 ) THEN
419
IF(ALL(CutDof(NodeIndexes([1,3])))) THEN
420
j = body_cut
421
ELSE IF(ALL(CutDof(NodeIndexes([2,4])))) THEN
422
j = body_cut
423
END IF
424
END IF
425
426
! Ok, no interface. Use the min/max value to indicate whether this is inside/outside.
427
IF(j<0) THEN
428
h1 = MAXVAL(PhiValues(PhiPerm(NodeIndexes)))
429
h2 = MINVAL(PhiValues(PhiPerm(NodeIndexes)))
430
IF(h1 > -h2) THEN
431
InsideCnt(2) = InsideCnt(2)+1
432
j = body_out
433
ELSE
434
InsideCnt(1) = InsideCnt(1)+1
435
j = body_in
436
END IF
437
ELSE
438
InsideCnt(3) = InsideCnt(3)+1
439
END IF
440
END IF
441
442
Element % BodyId = j
443
END DO
444
445
IF(InfoActive(25)) THEN
446
PRINT *,'Inside/Outside count:',InsideCnt
447
END IF
448
449
! CutPerm is the reordered dofs for the CutFEM mesh.
450
IF(.NOT. ASSOCIATED(CutPerm)) THEN
451
ALLOCATE(CutPerm(nn+ne))
452
CALL info(Caller,'Allocated CutPerm of size: '//I2S(nn+ne),Level=20)
453
END IF
454
CutPerm = 0
455
456
PassiveOutside = ListGetLogical( Params,'CutFEM Passive Outside',Found )
457
IF(.NOT. Found ) PassiveOutside = (body_out == 0)
458
PassiveInside = ListGetLogical( Params,'CutFEM Passive Inside',Found )
459
IF(.NOT. Found) PassiveInside = (body_in == 0)
460
461
! Set all cut dofs to exist.
462
WHERE(CutDof)
463
CutPerm = 1
464
END WHERE
465
IF( PassiveOutside ) THEN
466
DO i=1,nn
467
j = PhiPerm(i)
468
IF(j==0) CYCLE
469
IF(PhiValues(j) < 0) CutPerm(i) = 1
470
END DO
471
ELSE IF( PassiveInside ) THEN
472
DO i=1,nn
473
j = PhiPerm(i)
474
IF(j==0) CYCLE
475
IF(PhiValues(j) > 0) CutPerm(i) = 1
476
END DO
477
ELSE
478
! We both inside and outside exist.
479
CutPerm(1:nn) = 1
480
END IF
481
482
j = 0
483
DO i=1,nn+ne
484
IF(CutPerm(i) > 0) THEN
485
j = j+1
486
CutPerm(i) = j
487
END IF
488
END DO
489
k = COUNT(CutPerm(1:nn)>0)
490
CALL Info(Caller,'CutFEM number of nodes: '//I2S(j)//' (original '//I2S(k)//')',Level=7)
491
492
493
! If there is a primary variable associated to the original mesh copy that to the new mesh.
494
IF(ASSOCIATED(OrigMeshValues)) THEN
495
IF(ASSOCIATED(CutValues)) DEALLOCATE(CutValues)
496
ALLOCATE(CutValues(dofs*j))
497
CutValues = 0.0_dp
498
499
DO i=1,dofs
500
WHERE(CutPerm(1:nn) > 0 )
501
CutValues(dofs*(CutPerm-1)+i) = OrigMeshValues(dofs*(OrigMeshPerm-1)+i)
502
END WHERE
503
END DO
504
505
! Point the permutation and values to the newly allocated vectors.
506
! This way
507
Solver % Variable % Perm => CutPerm
508
Solver % Variable % Values => CutValues
509
510
! For transient problems do the same for PrevValues
511
IF(ASSOCIATED(OrigPrevMeshValues)) THEN
512
IF(ASSOCIATED(PrevCutValues)) DEALLOCATE(PrevCutValues)
513
i = SIZE(OrigPrevMeshValues,2)
514
ALLOCATE(PrevCutValues(dofs*j,i))
515
PrevCutValues = 0.0_dp
516
517
! Copy nodal values as initial guess to cut fem values.
518
#if 0
519
! fix this
520
DO l=1,dofs
521
DO i=1,nn
522
j = CutPerm(i)
523
k = OrigMeshPerm(i)
524
IF(j==0 .OR. k==0) CYCLE
525
OrigMeshValues(dofs*(k-1)+l) = CutValues(dofs*(j-1)+l)
526
END DO
527
END DO
528
#endif
529
530
DO i=1,SIZE(OrigPrevMeshValues,2)
531
DO j=1,dofs
532
WHERE(CutPerm(1:nn) > 0 )
533
PrevCutValues(dofs*(CutPerm(1:nn)-1)+j,i) = &
534
OrigPrevMeshValues(dofs*(OrigMeshPerm(1:nn)-1)+j,i)
535
END WHERE
536
END DO
537
END DO
538
Solver % Variable % PrevValues => PrevCutValues
539
END IF
540
END IF
541
542
! This in an optional routine if we want to extend the field values outside
543
! active domain. The reason might be to provide better initial values for the new territory.
544
IF(CutExtend) THEN
545
CALL Info(Caller,'Extending field outside the active domain!',Level=20)
546
r = ListGetCReal( Params,'CutFEM extend width',Found )
547
548
IF(.NOT. ASSOCIATED(ExtendPerm)) THEN
549
ALLOCATE(ExtendPerm(nn+ne))
550
END IF
551
ExtendPerm = 0
552
553
r = ListGetCReal( Params,'CutFEM extend width',Found )
554
555
! Set the material for inside/outside.
556
DO i=1,Mesh % NumberOfBulkElements
557
Element => Mesh % Elements(i)
558
IF(ANY(PhiPerm(Element % NodeIndexes) == 0)) CYCLE
559
560
IF( Element % BodyId == body_cut ) THEN
561
! Mark dofs to extend on elements which lack CutFEM dofs.
562
10 pElement => CutInterfaceBulk(Element,isCut,isMore)
563
IF(ANY(CutPerm(pElement % NodeIndexes) == 0) ) THEN
564
ExtendPerm( pElement % NodeIndexes ) = 1
565
END IF
566
IF(IsMore) GOTO 10
567
! Ok, revert the dirty flag.
568
Element % BodyId = body_cut-1
569
ELSE
570
IF( ALL( CutPerm( Element % NodeIndexes ) == 0) ) THEN
571
IF( Found ) THEN
572
! Mark all dofs within a defined width.
573
IF(MINVAL(ABS(PhiVar % Values(PhiVar % Perm(Element % NodeIndexes )))) < r ) THEN
574
ExtendPerm( Element % NodeIndexes ) = 1
575
END IF
576
ELSE
577
! Mark all elements in the outside region.
578
ExtendPerm( Element % NodeIndexes ) = 1
579
END IF
580
END IF
581
END IF
582
END DO
583
584
j = 0
585
DO i=1,nn+ne
586
IF(ExtendPerm(i) == 0) CYCLE
587
j = j+1
588
ExtendPerm(i) = j
589
END DO
590
591
k = COUNT(ExtendPerm > 0 .AND. CutPerm > 0 )
592
CALL Info(Caller,'Interface dofs '//I2S(j)//' (shared '//I2S(k)//')')
593
END IF
594
595
596
! This is a dirty way to halt the progress when levelset goes beyond the planned
597
! outer boundaries.
598
BLOCK
599
TYPE(ValueList_t), POINTER :: BC
600
INTEGER :: bc_id
601
k = Mesh % NumberOfBulkElements
602
DO i=1,Mesh % NumberOfBoundaryElements
603
Element => Mesh % Elements(k+i)
604
NodeIndexes => Element % NodeIndexes
605
606
DO bc_id=1,CurrentModel % NumberOfBCs
607
IF ( Element % BoundaryInfo % Constraint == CurrentModel % BCs(bc_id) % Tag ) EXIT
608
END DO
609
IF ( bc_id > CurrentModel % NumberOfBCs ) CYCLE
610
611
BC => CurrentModel % BCs(bc_id) % Values
612
IF(ListGetLogical(BC,'CutFem Forbidden Boundary',Found ) ) THEN
613
IF(ANY(CutPerm(nn+Element % EdgeIndexes)>0)) THEN
614
CALL Fatal(Caller,'CutFEM extends beyond forbidden boundaries!')
615
END IF
616
END IF
617
END DO
618
END BLOCK
619
620
#if DEBUG_ORIENT
621
r = HUGE(r)
622
DO i=1,Mesh % NumberOfNodes
623
j = PhiPerm(i)
624
IF(j==0) CYCLE
625
IF(PhiValues(j) < r) THEN
626
r = PhiValues(j)
627
CutFEMCenter(1) = Mesh % Nodes % x(i)
628
CutFEMCenter(2) = Mesh % Nodes % y(i)
629
CutFEMCenter(3) = Mesh % Nodes % z(i)
630
END IF
631
END DO
632
PRINT *,'CutFEMCenter:',CutFEMCenter
633
#endif
634
635
! This is just counter for different split cases while developing the code.
636
nCase = 0
637
638
Solver % CutInterp => CutInterp
639
640
END SUBROUTINE CreateCutFEMPerm
641
642
643
! Given a permutation, create a matrix. We assume simple nodal elements.
644
! Some extra dofs are created since at the interface we assume that
645
! there can be all possible connections.
646
!-----------------------------------------------------------------------
647
FUNCTION CreateCutFemMatrix(Solver,Perm,MimicMat) RESULT ( A )
648
TYPE(Solver_t) :: Solver
649
INTEGER :: Perm(:)
650
TYPE(Matrix_t), POINTER :: A
651
TYPE(Matrix_t), POINTER, OPTIONAL :: MimicMat
652
653
TYPE(Mesh_t), POINTER :: Mesh
654
INTEGER :: i,j,k,l,t,m,n,dofs,nn,active
655
INTEGER, ALLOCATABLE :: BlockInds(:),DofInds(:)
656
TYPE(Element_t), POINTER :: Element
657
INTEGER, SAVE :: AllocVecs(3)
658
CHARACTER(*), PARAMETER :: Caller = 'CreateCutFemMatrix'
659
660
Mesh => Solver % Mesh
661
CutDofs = Solver % Variable % Dofs
662
dofs = CutDofs
663
664
! Create new matrix
665
A => AllocateMatrix()
666
A % FORMAT = MATRIX_LIST
667
668
! Add extreme entry since list matrix likes to be allocated at once.
669
n = dofs * MAXVAL(Perm)
670
IF(n==0) THEN
671
CALL Warn(Caller,'CutFEM matrix size is zero?')
672
A % NumberOfRows = 0
673
RETURN
674
END IF
675
676
CALL Info(Caller,'Size of CutFEM matrix with '//I2S(dofs)//' dofs is: '//I2S(n),Level=10)
677
678
679
CALL List_AddToMatrixElement(A % ListMatrix, n, n, 0.0_dp )
680
681
n = 2*Mesh % MaxElementNodes
682
ALLOCATE(BlockInds(n),DofInds(n*dofs))
683
BlockInds = 0
684
685
nn = Mesh % NumberOfNodes
686
687
DO t=1,Mesh % NumberOfBulkElements
688
Element => Mesh % Elements(t)
689
IF(ANY(PhiPerm(Element % NodeIndexes) == 0)) CYCLE
690
691
! Add active node indexes.
692
m = 0
693
n = Element % TYPE % NumberOfNodes
694
DO i=1,n
695
j = Perm(Element % NodeIndexes(i))
696
IF(j==0) CYCLE
697
m = m+1
698
BlockInds(m) = j
699
END DO
700
701
! Add active edge indexes after node indexes.
702
n = Element % TYPE % NumberOfEdges
703
DO i=1,n
704
j = Perm(nn + Element % EdgeIndexes(i))
705
IF(j==0) CYCLE
706
m = m+1
707
BlockInds(m) = j
708
END DO
709
710
! For vector valued problems add the number of dof indeces.
711
IF( dofs == 1 ) THEN
712
DofInds(1:m) = BlockInds(1:m)
713
ELSE
714
DO i=0,dofs-1
715
DofInds(m*i+1:(m+1)*i) = dofs*(BlockInds(1:m)-1)+(i+1)
716
END DO
717
m = m*dofs
718
END IF
719
720
! Add locations to matrix. We add zeros since we are only creating the topology, not assembling.
721
DO i=1,m
722
DO j=1,m
723
CALL List_AddToMatrixElement(A % ListMatrix,DofInds(i),DofInds(j),0.0_dp)
724
END DO
725
END DO
726
END DO
727
728
! Make a CRS matrix that has now a topology to account for all entries coming from cutfem.
729
CALL List_toCRSMatrix(A)
730
CALL CRS_SortMatrix(A,.FALSE.)
731
732
IF(.NOT. ASSOCIATED(A % rhs)) THEN
733
ALLOCATE(A % rhs(A % NumberOfRows))
734
END IF
735
A % rhs = 0.0_dp
736
737
738
! MimicMat is the matrix which we should replace. So if it is transient, it will have MassValues etc.
739
IF(PRESENT(MimicMat)) THEN
740
! If the matrix does not exist, do not update.
741
IF(ASSOCIATED(MimicMat)) THEN
742
AllocVecs = 0
743
IF(ASSOCIATED(MimicMat % MassValues)) AllocVecs(1) = 1
744
IF(ASSOCIATED(MimicMat % DampValues)) AllocVecs(2) = 1
745
IF(ASSOCIATED(MimicMat % Force)) AllocVecs(3) = SIZE(MimicMat % Force,2)
746
END IF
747
IF(AllocVecs(1) > 0 ) THEN
748
ALLOCATE(A % MassValues(SIZE(A % Values)))
749
A % MassValues = 0.0_dp
750
END IF
751
IF(AllocVecs(2) > 0) THEN
752
ALLOCATE(A % DampValues(SIZE(A % Values)))
753
A % DampValues = 0.0_dp
754
END IF
755
n = AllocVecs(3)
756
IF(n > 0 ) THEN
757
ALLOCATE(A % Force(A % NumberOfRows,n))
758
A % Force = 0.0_dp
759
END IF
760
END IF
761
762
END FUNCTION CreateCutFemMatrix
763
764
765
! This is a routine that just checks whether an element is cut.
766
!----------------------------------------------------------------
767
SUBROUTINE CutInterfaceCheck( Element, IsCut, IsActive, ExtPerm )
768
TYPE(Element_t), POINTER :: Element
769
LOGICAL :: IsCut, IsActive
770
INTEGER, POINTER, OPTIONAL :: ExtPerm(:)
771
772
INTEGER, SAVE :: n_split, n_cut, n_act
773
INTEGER :: j,j2,j3,nn,ne
774
LOGICAL :: Visited = .FALSE.
775
INTEGER, POINTER :: Perm(:)
776
TYPE(Mesh_t), POINTER :: Mesh
777
CHARACTER(*), PARAMETER :: Caller = 'CutInterfaceCheck'
778
779
SAVE Visited, Mesh, nn
780
781
IF(.NOT. Visited) THEN
782
Mesh => CurrentModel % Solver % Mesh
783
nn = Mesh % NumberOfNodes
784
Visited = .TRUE.
785
END IF
786
787
IF( PRESENT(ExtPerm) ) THEN
788
Perm => ExtPerm
789
ELSE
790
Perm => CurrentModel % Solver % Variable % Perm
791
END IF
792
793
n_split = COUNT( CutDof(nn + Element % EdgeIndexes) )
794
n_cut = COUNT( CutDof(Element % NodeIndexes) )
795
796
n_act = COUNT( Perm(Element % NodeIndexes) > 0 )
797
798
IsCut = ( n_split > 0 .OR. n_cut > 1 )
799
IsActive = (n_act == Element % TYPE % numberOfNodes ) .OR. IsCut
800
801
END SUBROUTINE CutInterfaceCheck
802
803
804
! Given Element, levelset function and the CutDof field return information whether the element
805
! is cut and if it, should we call the routine again for the next split.
806
!----------------------------------------------------------------------------------------------
807
FUNCTION CutInterfaceBulk( Element, IsCut, IsMore ) RESULT ( pElement )
808
TYPE(Element_t), POINTER :: Element, pElement
809
LOGICAL :: IsCut
810
LOGICAL :: IsMore
811
812
TYPE(Element_t), TARGET :: Elem303, Elem404, Elem706, Elem808
813
TYPE(Element_t), POINTER :: prevElement
814
INTEGER :: SgnNode, i, n, nn, ElemType, body_out, body_in, CutCnt
815
LOGICAL :: Found
816
REAL(KIND=dp), POINTER :: x(:), y(:), z(:)
817
CHARACTER(:), ALLOCATABLE :: str
818
TYPE(Variable_t), POINTER :: PhiVar !Var
819
TYPE(Mesh_t), POINTER :: Mesh
820
TYPE(Solver_t), POINTER :: Solver => NULL()
821
CHARACTER(*), PARAMETER :: Caller = 'CutInterfaceBulk'
822
TYPE(Nodes_t) :: ElemNodes
823
INTEGER, ALLOCATABLE :: LocalInds(:), ElemInds(:)
824
LOGICAL, ALLOCATABLE :: ElemCut(:)
825
826
827
SAVE Mesh, Solver, x, y, z, Elem303, Elem404, body_in, body_out, &
828
nn, CutCnt, PhiVar, ElemNodes, ElemInds, ElemCut, ElemType, LocalInds, &
829
prevElement
830
831
IF(.NOT. ASSOCIATED( Solver, CurrentModel % Solver ) ) THEN
832
Mesh => CurrentModel % Solver % Mesh
833
Solver => CurrentModel % Solver
834
835
IF(.NOT. ASSOCIATED(ElemNodes % x)) THEN
836
n = 8
837
ALLOCATE( ElemNodes % x(n), ElemNodes % y(n), ElemNodes % z(n), ElemInds(n), &
838
ElemCut(n), LocalInds(4))
839
END IF
840
841
nn = Mesh % NumberOfNodes
842
x => Mesh % Nodes % x
843
y => Mesh % Nodes % y
844
z => Mesh % Nodes % z
845
846
! Create empty element skeletons that are filled when splitting elements.
847
Elem303 % TYPE => GetElementType(303)
848
ALLOCATE(Elem303 % NodeIndexes(3))
849
Elem303 % NodeIndexes = 0
850
851
Elem404 % TYPE => GetElementType(404)
852
ALLOCATE(Elem404 % NodeIndexes(4))
853
Elem404 % NodeIndexes = 0
854
855
PhiVar => VariableGet(Mesh % Variables, CutStr, ThisOnly=.TRUE.)
856
IF(.NOT. ASSOCIATED(PhiVar) ) THEN
857
CALL Fatal(Caller,'"Levelset Variable" not available: '//TRIM(CutStr))
858
END IF
859
860
body_in = ListGetInteger( Solver % Values,'CutFEm Inside Body',Found )
861
IF(.NOT. Found) body_in = CurrentModel % NumberOfBodies
862
body_out = ListGetInteger( Solver % Values,'CutFem Outside Body',Found )
863
IF(.NOT. Found) body_out = body_in+1
864
END IF
865
866
867
! This is the counter for splitting.
868
IF(.NOT. ASSOCIATED(prevElement,Element)) THEN
869
CutCnt = 1
870
prevElement => Element
871
ElemType = Element % Type % ElementCode
872
n = ElemType / 100
873
874
! For triangles & quads these are true, not for others...
875
ElemInds(1:n) = Element % NodeIndexes(1:n)
876
ElemInds(n+1:2*n) = nn + Element % EdgeIndexes(1:n)
877
878
ElemCut(1:2*n) = CutDof(ElemInds(1:2*n))
879
880
ElemNodes % x(1:2*n) = x(ElemInds(1:2*n))
881
ElemNodes % y(1:2*n) = y(ElemInds(1:2*n))
882
ElemNodes % z(1:2*n) = z(ElemInds(1:2*n))
883
ELSE
884
CutCnt = CutCnt+1
885
END IF
886
887
pElement => Element
888
CALL SplitSingleElement(Element, ElemCut, ElemNodes, CutCnt, &
889
IsCut, IsMore, LocalInds, SgnNode )
890
IF(.NOT. IsCut) RETURN
891
892
i = COUNT(LocalInds > 0)
893
SELECT CASE(i)
894
CASE(3)
895
pElement => Elem303
896
CASE(4)
897
pElement => Elem404
898
CASE DEFAULT
899
CALL Fatal('CutInterfaceBulk','Impossible number of nodes!')
900
END SELECT
901
pElement % NodeIndexes(1:i) = ElemInds(LocalInds(1:i))
902
903
! This circumwents some rare case when node is cut.
904
IF( body_out == 0 ) THEN
905
IF( ALL( CutPerm(pElement % NodeIndexes) > 0) ) THEN
906
pElement % BodyId = body_in
907
ELSE
908
pElement % BodyId = body_out
909
END IF
910
ELSE
911
i = PhiVar % Perm(pElement % NodeIndexes(sgnNode))
912
IF( PhiVar % Values(i) > 0.0_dp ) THEN
913
pElement % BodyId = body_out
914
ELSE
915
pElement % BodyId = body_in
916
END IF
917
END IF
918
919
END FUNCTION CutInterfaceBulk
920
921
922
923
! Given Element, levelset function and the CutDof field return the elements created at the interface.
924
! In 2D and also for straigth cuts in 3D we should expect to have just one element but it could
925
! be changed. This includes also wedges even if the above does not just to model how they could
926
! work in 3D.
927
!----------------------------------------------------------------------------------------------
928
FUNCTION CutInterfaceBC( Element, IsCut, IsMore ) RESULT ( pElement )
929
TYPE(Element_t), POINTER :: Element, pElement
930
LOGICAL :: IsCut, IsMore
931
932
TYPE(Element_t), TARGET :: Elem202, Elem303, Elem404
933
TYPE(Element_t), POINTER, SAVE :: prevElement
934
INTEGER :: m, n, n_split, n_cut, i, j, j2, j3, j4, nn, SplitCase
935
INTEGER, POINTER :: nIndexes(:), eIndexes(:)
936
TYPE(Mesh_t), POINTER :: Mesh
937
LOGICAL :: Visited = .FALSE., Found, VerticalCut
938
REAL(KIND=dp), POINTER :: x(:), y(:), z(:)
939
TYPE(Solver_t), POINTER :: Solver
940
CHARACTER(*), PARAMETER :: Caller = 'CutInterfaceBC'
941
942
SAVE Visited, Mesh, Solver, nn, x, y, z, n_split, n_cut, &
943
Elem202, Elem303, Elem404, VerticalCut, m
944
945
946
IF(.NOT. Visited) THEN
947
Solver => CurrentModel % Solver
948
Mesh => Solver % Mesh
949
nn = Mesh % NumberOfNodes
950
x => Mesh % Nodes % x
951
y => Mesh % Nodes % y
952
z => Mesh % Nodes % z
953
954
n = ListGetInteger( Solver % Values,'CutFem Interface BC',Found )
955
956
IF( Mesh % MeshDim == 3 ) THEN
957
Elem303 % TYPE => GetElementType(303)
958
ALLOCATE(Elem303 % NodeIndexes(3))
959
Elem303 % NodeIndexes = 0
960
ALLOCATE( Elem303 % BoundaryInfo )
961
Elem303 % BoundaryInfo % Constraint = n
962
963
Elem404 % TYPE => GetElementType(404)
964
ALLOCATE(Elem404 % NodeIndexes(4))
965
Elem404 % NodeIndexes = 0
966
ALLOCATE( Elem404 % BoundaryInfo )
967
Elem404 % BoundaryInfo % Constraint = n
968
969
VerticalCut = ListGetLogical(Solver % Values,'CutFEM vertical cut',Found )
970
ELSE
971
Elem202 % TYPE => GetElementType(202)
972
ALLOCATE(Elem202 % NodeIndexes(2))
973
Elem202 % NodeIndexes = 0
974
ALLOCATE( Elem202 % BoundaryInfo )
975
Elem202 % BoundaryInfo % Constraint = n
976
977
VerticalCut = .FALSE.
978
END IF
979
980
Visited = .TRUE.
981
END IF
982
983
nIndexes => Element % NodeIndexes
984
eIndexes => Element % EdgeIndexes
985
986
987
! This is the counter for splitting.
988
IF(.NOT. ASSOCIATED(prevElement,Element)) THEN
989
m = 1
990
prevElement => Element
991
IF( VerticalCut ) THEN
992
n = SIZE(eIndexes) / 2
993
n_split = COUNT( CutDof(nn + eIndexes(1:n)) )
994
n = SIZE(nIndexes) / 2
995
n_cut = COUNT( CutDof(nIndexes(1:n)) )
996
ELSE
997
n_split = COUNT( CutDof(nn + eIndexes) )
998
n_cut = COUNT( CutDof(nIndexes) )
999
END IF
1000
ELSE
1001
m = m+1
1002
END IF
1003
1004
IsMore = .FALSE.
1005
1006
IF( n_split == 0 .AND. n_cut <= 1 ) THEN
1007
isCut = .FALSE.
1008
pElement => NULL()
1009
RETURN
1010
END IF
1011
1012
IsCut = .TRUE.
1013
1014
SELECT CASE( Element % TYPE % ElementCode )
1015
CASE( 808 )
1016
pElement => Elem303
1017
CASE( 706 )
1018
pElement => Elem404
1019
CASE( 504 )
1020
pElement => Elem303
1021
CASE( 303, 404 )
1022
pElement => Elem202
1023
CASE DEFAULT
1024
CALL Fatal(Caller,'Unknown element type to split: '//I2S(Element % TYPE % ElementCode)//'!')
1025
END SELECT
1026
pElement % NodeIndexes = 0
1027
1028
1029
! This allows use case to deal with element types, edge splits and node splits at the same time.
1030
SplitCase = 100 * Element % TYPE % ElementCode + 10 * n_split + n_cut
1031
1032
1033
SELECT CASE( SplitCase )
1034
1035
CASE( 30320 )
1036
! Find the both cut edges
1037
DO j=1,3
1038
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1039
END DO
1040
DO j2=1,3
1041
IF(j2==j) CYCLE
1042
IF( CutDof( nn + eIndexes(j2) ) ) EXIT
1043
END DO
1044
pElement % NodeIndexes(1) = nn + eIndexes(j)
1045
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1046
1047
CASE( 30321 )
1048
IF(m==1) THEN
1049
DO j=1,3
1050
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1051
END DO
1052
DO j2=1,3
1053
IF(j2==j) CYCLE
1054
IF( CutDof( nn + eIndexes(j2) ) ) EXIT
1055
END DO
1056
pElement % NodeIndexes(1) = nn + eIndexes(j)
1057
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1058
IsMore = .TRUE.
1059
ELSE
1060
DO j=1,3
1061
IF(CutDof(nIndexes(j))) EXIT
1062
END DO
1063
j2 = j
1064
IF( .NOT. CutDof(nn + eIndexes(j2) ) ) THEN
1065
j2 = MODULO(j-2,3)+1
1066
IF(.NOT. CutDof(nn + eIndexes(j2))) THEN
1067
CALL Fatal('Caller','Could not imagine this 303 case!')
1068
END IF
1069
END IF
1070
pElement % NodeIndexes(1) = nIndexes(j)
1071
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1072
IsMore = .FALSE.
1073
END IF
1074
1075
CASE( 30311 )
1076
! Find the edge and node that is cut
1077
DO j=1,3
1078
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1079
END DO
1080
DO j2=1,3
1081
IF( CutDof( nIndexes(j2) ) ) EXIT
1082
END DO
1083
pElement % NodeIndexes(1) = nn + eIndexes(j)
1084
pElement % NodeIndexes(2) = nIndexes(j2)
1085
1086
CASE( 30302 )
1087
! Find the two nodes that are cut.
1088
DO j=1,3
1089
IF( CutDof( nIndexes(j) ) ) EXIT
1090
END DO
1091
DO j2=1,3
1092
IF(j2==j) CYCLE
1093
IF( CutDof( nIndexes(j2) ) ) EXIT
1094
END DO
1095
pElement % NodeIndexes(1) = nIndexes(j)
1096
pElement % NodeIndexes(2) = nIndexes(j2)
1097
1098
CASE( 40420 )
1099
DO j=1,4
1100
IF(CutDof( nn + eIndexes(j))) EXIT
1101
END DO
1102
DO j2=1,4
1103
IF(j2==j) CYCLE
1104
IF(CutDof( nn + eIndexes(j2))) EXIT
1105
END DO
1106
pElement % NodeIndexes(1) = nn + eIndexes(j)
1107
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1108
1109
CASE( 40421 )
1110
1111
IF(m==1) THEN
1112
DO j=1,4
1113
IF(CutDof( nn + eIndexes(j))) EXIT
1114
END DO
1115
DO j2=1,4
1116
IF(j2==j) CYCLE
1117
IF(CutDof( nn + eIndexes(j2))) EXIT
1118
END DO
1119
pElement % NodeIndexes(1) = nn + eIndexes(j)
1120
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1121
IsMore = .TRUE.
1122
ELSE
1123
DO j=1,4
1124
IF(CutDof(nIndexes(j))) EXIT
1125
END DO
1126
j2 = j
1127
IF( .NOT. CutDof(nn + eIndexes(j2) ) ) THEN
1128
j2 = MODULO(j-2,4)+1
1129
IF(.NOT. CutDof(nn + eIndexes(j2))) THEN
1130
CALL Fatal('Caller','Could not imagine this 404 case!')
1131
END IF
1132
END IF
1133
pElement % NodeIndexes(1) = nIndexes(j)
1134
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1135
IsMore = .FALSE.
1136
END IF
1137
1138
CASE( 40411 )
1139
DO j=1,4
1140
IF(CutDof( nn + eIndexes(j))) EXIT
1141
END DO
1142
DO j2=1,4
1143
IF(CutDof( nIndexes(j2))) EXIT
1144
END DO
1145
pElement % NodeIndexes(1) = nn + eIndexes(j)
1146
pElement % NodeIndexes(2) = nIndexes(j2)
1147
1148
CASE( 40402 )
1149
DO j=1,4
1150
IF(CutDof( nIndexes(j))) EXIT
1151
END DO
1152
DO j2=1,4
1153
IF(j2==j) CYCLE
1154
IF(CutDof( nIndexes(j2))) EXIT
1155
END DO
1156
pElement % NodeIndexes(1) = nIndexes(j)
1157
pElement % NodeIndexes(2) = nIndexes(j2)
1158
1159
CASE( 50440 )
1160
DO j=1,6
1161
IF(CutDof( nn + eIndexes(j))) EXIT
1162
END DO
1163
DO j2=1,6
1164
IF(j2==j) CYCLE
1165
IF(CutDof( nn + eIndexes(j2))) EXIT
1166
END DO
1167
DO j3=1,6
1168
IF(j3==j .OR. j3==j2) CYCLE
1169
IF(CutDof( nn + eIndexes(j3))) EXIT
1170
END DO
1171
DO j4=1,6
1172
IF(j4==j .OR. j4==j2 .OR. j4==j3) CYCLE
1173
IF(CutDof( nn + eIndexes(j4))) EXIT
1174
END DO
1175
1176
IF(m==1) THEN
1177
pElement % NodeIndexes(1) = nn + eIndexes(j)
1178
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1179
pElement % NodeIndexes(3) = nn + eIndexes(j3)
1180
IsMore = .TRUE.
1181
ELSE
1182
pElement % NodeIndexes(1) = nn + eIndexes(j)
1183
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1184
pElement % NodeIndexes(3) = nn + eIndexes(j4)
1185
IsMore = .FALSE.
1186
END IF
1187
1188
CASE( 50430 )
1189
DO j=1,6
1190
IF(CutDof( nn + eIndexes(j))) EXIT
1191
END DO
1192
DO j2=1,6
1193
IF(j2==j) CYCLE
1194
IF(CutDof( nn + eIndexes(j2))) EXIT
1195
END DO
1196
DO j3=1,6
1197
IF(j3==j .OR. j3==j2) CYCLE
1198
IF(CutDof( nn + eIndexes(j3))) EXIT
1199
END DO
1200
pElement % NodeIndexes(1) = nn + eIndexes(j)
1201
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1202
pElement % NodeIndexes(3) = nn + eIndexes(j3)
1203
1204
CASE( 50421 )
1205
DO j=1,6
1206
IF(CutDof( nn + eIndexes(j))) EXIT
1207
END DO
1208
DO j2=1,6
1209
IF(j2==j) CYCLE
1210
IF(CutDof( nn + eIndexes(j2))) EXIT
1211
END DO
1212
DO j3=1,4
1213
IF(CutDof( nIndexes(j3))) EXIT
1214
END DO
1215
pElement % NodeIndexes(1) = nn + eIndexes(j)
1216
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1217
pElement % NodeIndexes(3) = nIndexes(j3)
1218
1219
CASE( 50412 )
1220
DO j=1,6
1221
IF(CutDof( nn + eIndexes(j))) EXIT
1222
END DO
1223
DO j2=1,4
1224
IF(CutDof( nIndexes(j2))) EXIT
1225
END DO
1226
DO j3=1,4
1227
IF(j3==j2) CYCLE
1228
IF(CutDof( nIndexes(j3))) EXIT
1229
END DO
1230
pElement % NodeIndexes(1) = nn + eIndexes(j)
1231
pElement % NodeIndexes(2) = nIndexes(j2)
1232
pElement % NodeIndexes(3) = nIndexes(j3)
1233
1234
CASE( 50403 )
1235
i = 0
1236
DO j=1,4
1237
! We cut all other edges expect "j"
1238
IF(.NOT. CutDof( nIndexes(j))) EXIT
1239
i = i+1
1240
pElement % NodeIndexes(i) = nIndexes(j)
1241
END DO
1242
1243
1244
CASE( 70620 )
1245
! For prisms we currently assumes that the field is cut vertically such that
1246
! we can split the bottom triangle and copy the same split in 3D.
1247
DO j=1,3
1248
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1249
END DO
1250
DO j2=1,3
1251
IF(j2==j) CYCLE
1252
IF( CutDof( nn + eIndexes(j2) ) ) EXIT
1253
END DO
1254
pElement % NodeIndexes(1) = nn + eIndexes(j)
1255
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1256
pElement % NodeIndexes(3) = nn + eIndexes(3+j2)
1257
pElement % NodeIndexes(4) = nn + eIndexes(3+j)
1258
1259
CASE( 70611 )
1260
! Find the edge and node that is cut
1261
DO j=1,3
1262
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1263
END DO
1264
DO j2=1,3
1265
IF( CutDof( nIndexes(j2) ) ) EXIT
1266
END DO
1267
pElement % NodeIndexes(1) = nn + eIndexes(j)
1268
pElement % NodeIndexes(2) = nIndexes(j2)
1269
pElement % NodeIndexes(3) = nIndexes(j2+3)
1270
pElement % NodeIndexes(4) = nn + eIndexes(j+3)
1271
1272
CASE( 70602 )
1273
! Find the two nodes that are cut.
1274
DO j=1,3
1275
IF( CutDof( nIndexes(j) ) ) EXIT
1276
END DO
1277
DO j2=1,3
1278
IF(j2==j) CYCLE
1279
IF( CutDof( nIndexes(j2) ) ) EXIT
1280
END DO
1281
pElement % NodeIndexes(1) = nIndexes(j)
1282
pElement % NodeIndexes(2) = nIndexes(j2)
1283
pElement % NodeIndexes(2) = nIndexes(j2+3)
1284
pElement % NodeIndexes(1) = nIndexes(j+3)
1285
1286
CASE( 80830 )
1287
DO j=1,12
1288
IF( CutDof( nn + eIndexes(j) ) ) EXIT
1289
END DO
1290
DO j2=1,12
1291
IF(j2==j) CYCLE
1292
IF( CutDof( nn + eIndexes(j2) ) ) EXIT
1293
END DO
1294
DO j3=1,12
1295
IF(j3==j .OR. j3==j2) CYCLE
1296
IF( CutDof( nn + eIndexes(j3) ) ) EXIT
1297
END DO
1298
pElement % NodeIndexes(1) = nn + eIndexes(j)
1299
pElement % NodeIndexes(2) = nn + eIndexes(j2)
1300
pElement % NodeIndexes(3) = nn + eIndexes(j3)
1301
1302
CASE DEFAULT
1303
PRINT *,'Unknown SplitCase:',SplitCase
1304
PRINT *,'EdgeCut:',CutDof(nn + Element % EdgeIndexes)
1305
PRINT *,'NodeCut:',CutDof(Element % NodeIndexes)
1306
PRINT *,'Phi:',PhiValues(PhiPerm(Element % NodeIndexes))
1307
CALL Fatal(Caller,'Unknown split case in bc element divisions: '//I2S(SplitCase))
1308
END SELECT
1309
1310
1311
! This is just a tentative routine where we orient the nodes of the segment such that
1312
! inside/outside is always consistently on left/right.
1313
IF(pElement % TYPE % ElementCode == 202 ) THEN
1314
BLOCK
1315
REAL(KIND=dp) :: pmax, p, x0, x1, xp, y0, y1, yp, dir1, dir2
1316
INTEGER :: i,j,imax
1317
1318
! The most trustworthy point to define the sign of the levelset is the one with extreme value.
1319
pmax = 0.0_dp
1320
imax = 0
1321
DO i=1,Element % TYPE % NumberOfNodes
1322
j = PhiPerm(Element % NodeIndexes(i))
1323
IF(j==0) CYCLE
1324
p = PhiValues(j)
1325
IF(ABS(p) > ABS(pmax)) THEN
1326
pmax = p
1327
imax = Element % NodeIndexes(i)
1328
END IF
1329
END DO
1330
1331
! Dir is an indicator one which side of the line segment the point lies.
1332
x0 = Mesh % Nodes % x(pElement % NodeIndexes(1))
1333
y0 = Mesh % Nodes % y(pElement % NodeIndexes(1))
1334
x1 = Mesh % Nodes % x(pElement % NodeIndexes(2))
1335
y1 = Mesh % Nodes % y(pElement % NodeIndexes(2))
1336
xp = Mesh % Nodes % x(imax)
1337
yp = Mesh % Nodes % y(imax)
1338
1339
dir1 = (x1 - x0) * (yp - y0) - (y1 - y0) * (xp - x0)
1340
1341
! Switch the signs so that if the point was found from left/right side of the
1342
! line segment the sign stays the same as previously.
1343
IF(dir1 * pmax < 0.0_dp) THEN
1344
j = pElement % NodeIndexes(1)
1345
pElement % NodeIndexes(1) = pElement % NodeIndexes(2)
1346
pElement % NodeIndexes(2) = j
1347
END IF
1348
1349
#if DEBUG_ORIENT
1350
! Here we can check that the orientation of the edges is consistent.
1351
! This check only applies to convex geometries where the centermost node
1352
! can be used to check the orientation.
1353
x0 = Mesh % Nodes % x(pElement % NodeIndexes(1))
1354
y0 = Mesh % Nodes % y(pElement % NodeIndexes(1))
1355
x1 = Mesh % Nodes % x(pElement % NodeIndexes(2))
1356
y1 = Mesh % Nodes % y(pElement % NodeIndexes(2))
1357
dir2 = (x1 - x0) * (CutFEMCenter(2) - y0) - (y1 - y0) * (CutFEMCenter(1) - x0)
1358
1359
IF( dir2 > 0.0 ) THEN
1360
PRINT *,'WrongDirection:',SplitCase,m,x0,x1,y0,y1
1361
PRINT *,'WrongDirIndexes:',CutDof(nIndexes),'e',CutDof(nn+eIndexes)
1362
PRINT *,'WrongDirPhi:',PhiValues(PhiPerm(nIndexes))
1363
PRINT *,'WrongDirX:',Mesh % Nodes % x(nIndexes)
1364
PRINT *,'WrongDirY:',Mesh % Nodes % y(nIndexes)
1365
PRINT *,'WrongDirImax:',imax,pmax
1366
1367
STOP
1368
j = pElement % NodeIndexes(1)
1369
pElement % NodeIndexes(1) = pElement % NodeIndexes(2)
1370
pElement % NodeIndexes(2) = j
1371
END IF
1372
#endif
1373
1374
END BLOCK
1375
END IF
1376
1377
pElement % BoundaryInfo % Left => NULL() ! Element
1378
pElement % BodyId = 0
1379
1380
END FUNCTION CutInterfaceBC
1381
1382
1383
! This is currently not used.
1384
!-------------------------------------------------------
1385
SUBROUTINE CutFEMElementCount(Solver, Perm, nBulk, nBC )
1386
TYPE(Solver_t) :: Solver
1387
INTEGER, POINTER :: Perm(:)
1388
INTEGER :: nBulk, nBC
1389
1390
TYPE(Mesh_t), POINTER :: Mesh
1391
INTEGER :: Active, t, n, nBulk0, nBC0, t0
1392
TYPE(Element_t), POINTER :: Element, pElement
1393
LOGICAL :: isCut, isMore, isActive
1394
1395
nBulk = 0
1396
nBulk0 = 0
1397
nBC = 0
1398
nBC0 = 0
1399
Mesh => Solver % Mesh
1400
1401
DO t=1,Mesh % NumberOfBulkElements
1402
Element => Mesh % Elements(t)
1403
IF(ANY(PhiPerm(Element % NodeIndexes)==0)) CYCLE
1404
CALL CutInterfaceCheck( Element, IsCut, IsActive, Perm )
1405
IF(.NOT. IsActive) CYCLE
1406
IF(IsCut) THEN
1407
10 pElement => CutInterfaceBulk(Element,isCut,isMore)
1408
IF(ALL(Perm(pElement % NodeIndexes) > 0) ) nBulk = nBulk + 1
1409
IF(IsMore) GOTO 10
1410
ELSE
1411
nBulk0 = nBulk0 + 1
1412
END IF
1413
END DO
1414
1415
1416
! Additional BC elements created on the interface.
1417
DO t=1,Mesh % NumberOfBulkElements
1418
Element => Mesh % Elements(t)
1419
IF(ANY(PhiPerm(Element % NodeIndexes)==0)) CYCLE
1420
CALL CutInterfaceCheck( Element, IsCut, IsActive, Perm )
1421
IF(.NOT. IsActive) CYCLE
1422
20 pElement => CutInterfaceBC(Element,isCut,isMore)
1423
IF(ASSOCIATED(pElement)) THEN
1424
IF(ALL(Perm(pElement % NodeIndexes) > 0) ) nBC = nBC + 1
1425
IF(IsMore) GOTO 20
1426
END IF
1427
END DO
1428
1429
! Remaining original boundary element.
1430
t0 = Mesh % NumberOfBulkElements
1431
DO t=1,Mesh % NumberOfBoundaryElements
1432
Element => Mesh % Elements(t0+t)
1433
IF(ANY(PhiPerm(Element % NodeIndexes)==0)) CYCLE
1434
IF(ALL(Perm(Element % NodeIndexes) > 0) ) nBC0 = nBC0 + 1
1435
END DO
1436
1437
CALL Info('CutFEMElementCount','Bulk elements remaining '//I2S(nBulk0)//' & splitted '//I2S(nBulk),Level=7)
1438
CALL Info('CutFEMElementCount','BC elements remaining '//I2S(nBC0)//' & splitted '//I2S(nBC),Level=7)
1439
1440
nBC = nBC0 + nBC
1441
nBulk = nBulk0 + nBulk
1442
1443
END SUBROUTINE CutFEMElementCount
1444
1445
1446
SUBROUTINE CreateCutFEMAddMesh(Solver)
1447
TYPE(Solver_t) :: Solver
1448
1449
INTEGER :: Sweep, t, n, i
1450
LOGICAL :: IsActive, IsCut
1451
TYPE(Element_t), POINTER :: Element
1452
1453
CALL Info('CreateCutFEMAddMesh','Creating interface mesh from split element',Level=10)
1454
1455
DO Sweep = 0,1
1456
n = 0
1457
DO t=1,CutFEMOrigMesh % NumberOfBulkElements
1458
Element => CutFEMOrigMesh % Elements(t)
1459
IF(ANY(PhiPerm(Element % NodeIndexes)==0)) CYCLE
1460
CALL CutInterfaceCheck( Element, IsCut, IsActive, CutPerm )
1461
IF(IsActive .AND. .NOT. IsCut) THEN
1462
n = n+1
1463
IF(Sweep==1) UnsplitActiveElements(n) = t
1464
END IF
1465
END DO
1466
IF(Sweep == 0) THEN
1467
ALLOCATE(UnsplitActiveElements(n))
1468
END IF
1469
END DO
1470
1471
IF(ASSOCIATED(CutFEMAddMesh)) THEN
1472
NULLIFY(CutFEMAddMesh % Nodes % x)
1473
NULLIFY(CutFEMAddMesh % Nodes % y)
1474
NULLIFY(CutFEMAddMesh % Nodes % z)
1475
CALL FreeMesh(CutFEMAddMesh)
1476
END IF
1477
CutFEMAddMesh => CreateCutFEMMesh(Solver,CutFEMOrigMesh,CutPerm,&
1478
.TRUE.,.TRUE.,.TRUE.,Solver % Values,'dummy variable')
1479
1480
CALL MeshStabParams( CutFEMAddMesh )
1481
1482
n = CutFEMAddMesh % NumberOfBulkElements
1483
ALLOCATE(AddActiveElements(n))
1484
DO i=1,n
1485
AddActiveElements(i) = i
1486
END DO
1487
1488
Solver % ActiveElements => UnsplitActiveElements
1489
Solver % NumberOfActiveElements = SIZE(UnsplitActiveElements)
1490
1491
CALL Info('CreateCutFEMAddMesh','Add mesh created with '//I2S(i)//' active elements!',Level=10)
1492
1493
END SUBROUTINE CreateCutFEMAddMesh
1494
1495
SUBROUTINE CutFEMSetAddMesh(Solver)
1496
TYPE(Solver_t) :: Solver
1497
1498
Solver % Mesh => CutFEMAddMesh
1499
CurrentModel % Mesh => CutFEMAddMesh
1500
Solver % ActiveElements => AddActiveElements
1501
Solver % NumberOfActiveElements = SIZE(Solver % ActiveElements)
1502
Solver % Mesh % Edges => CutFemOrigMesh % Edges
1503
1504
CALL Info('CutFEMSetAddMesh','Swapping CutFEM original mesh to interface mesh!',Level=10)
1505
1506
END SUBROUTINE CutFEMSetAddMesh
1507
1508
SUBROUTINE CutFEMSetOrigMesh(Solver)
1509
TYPE(Solver_t) :: Solver
1510
1511
Solver % Mesh => CutFEMOrigMesh
1512
CurrentModel % Mesh => CutFEMOrigMesh
1513
Solver % ActiveElements => UnsplitActiveElements
1514
Solver % NumberOfActiveElements = SIZE(Solver % ActiveElements)
1515
1516
NULLIFY(CutFEMAddMesh % Edges)
1517
1518
CALL Info('CutFEMSetOrigMesh','Swapping CutFEM interface mesh to original mesh!',Level=10)
1519
1520
END SUBROUTINE CutFEMSetOrigMesh
1521
1522
1523
1524
1525
! Assembly a matrix for extrapolating values outside the active domain.
1526
! Currently this is just diffusion matrix. We could perhaps use convection also.
1527
!------------------------------------------------------------------------------
1528
SUBROUTINE LocalFitMatrix( Element, n )
1529
!------------------------------------------------------------------------------
1530
INTEGER :: n
1531
TYPE(Element_t), POINTER :: Element
1532
!------------------------------------------------------------------------------
1533
REAL(KIND=dp) :: weight, dcoeff
1534
REAL(KIND=dp) :: Basis(n),dBasisdx(n,3),DetJ
1535
REAL(KIND=dp) :: STIFF(n,n), FORCE(n), LOAD(n)
1536
LOGICAL :: Stat,Found,CutElem
1537
INTEGER :: i,j,t,p,q
1538
TYPE(GaussIntegrationPoints_t) :: IP
1539
TYPE(Nodes_t) :: Nodes
1540
1541
SAVE Nodes
1542
!------------------------------------------------------------------------------
1543
1544
! CALL GetElementNodes( Nodes, Element )
1545
CALL CopyElementNodesFromMesh( Nodes, CurrentModel % Solver % Mesh, n, Element % NodeIndexes)
1546
1547
STIFF = 0._dp
1548
FORCE = 0._dp
1549
LOAD = 0.0_dp
1550
1551
dcoeff = 1.0_dp
1552
1553
! Numerical integration:
1554
!-----------------------
1555
IP = GaussPointsAdapt( Element )
1556
DO t=1,IP % n
1557
stat = ElementInfo( Element, Nodes, IP % U(t), IP % V(t), &
1558
IP % W(t), detJ, Basis, dBasisdx )
1559
IF(.NOT. Stat) CYCLE
1560
Weight = IP % s(t) * DetJ
1561
1562
STIFF(1:n,1:n) = STIFF(1:n,1:n) + dcoeff * Weight * &
1563
MATMUL( dBasisdx, TRANSPOSE( dBasisdx ) )
1564
END DO
1565
1566
CutElem = .TRUE.
1567
! CALL DefaultUpdateEquations(STIFF,FORCE,CutElem,Element)
1568
1569
!------------------------------------------------------------------------------
1570
END SUBROUTINE LocalFitMatrix
1571
!------------------------------------------------------------------------------
1572
1573
1574
1575
! This takes a CutFEM variable and either extrapolates it using a FE equation
1576
! (for many element layers) or just extends it by extrapolating on the cut edges.
1577
!---------------------------------------------------------------------------------
1578
SUBROUTINE CutFEMVariableFinalize( Solver )
1579
TYPE(Solver_t) :: Solver
1580
1581
TYPE(Matrix_t), POINTER :: B
1582
TYPE(Mesh_t), POINTER :: Mesh
1583
TYPE(Element_t), POINTER :: pElement, Element
1584
INTEGER :: i,j,k,l,n,t,active,nn,ne,i1,i2,dofs
1585
LOGICAL :: IsCut, IsMore, Found
1586
REAL(KIND=dp) :: s, r, dval, norm
1587
REAL(KIND=dp), ALLOCATABLE :: NodeWeigth(:)
1588
1589
1590
Mesh => Solver % Mesh
1591
nn = Mesh % NumberOfNodes
1592
ne = Mesh % NumberOfEdges
1593
dofs = CutDofs
1594
1595
! If we solve some other equation in between store the original norm.
1596
Norm = Solver % Variable % Norm
1597
1598
! Set values at shared nodes that have been computed.
1599
CALL Info('CutFEMVariableFinalize','Copying values at shared nodes to the original mesh!',Level=10)
1600
DO l=1,dofs
1601
DO i=1,nn
1602
j = CutPerm(i)
1603
k = OrigMeshPerm(i)
1604
IF(j==0 .OR. k==0) CYCLE
1605
OrigMeshValues(dofs*(k-1)+l) = CutValues(dofs*(j-1)+l)
1606
END DO
1607
END DO
1608
1609
1610
! We can only extrapolate using the edges that are cut since they have also one
1611
! known nodal value.
1612
IF(CutExtrapolate) THEN
1613
CALL Info('CutFEMVariableFinalize','Extrapolating values with split elements!',Level=10)
1614
1615
! Extrapolated nodes may have more than one hit. Hence use weigted average.
1616
! This is the weight.
1617
ALLOCATE(NodeWeigth(nn))
1618
NodeWeigth = 0.0_dp
1619
1620
k = 0
1621
DO i=1,Solver % Mesh % NumberOfEdges
1622
j = CutPerm(nn+i)
1623
IF(j==0) CYCLE
1624
r = CutInterp(i)
1625
1626
i1 = Mesh % Edges(i) % NodeIndexes(1)
1627
i2 = Mesh % Edges(i) % NodeIndexes(2)
1628
1629
IF(CutPerm(i1) > 0 .AND. CutPerm(i2) == 0 ) THEN
1630
s = (1-r)
1631
DO k=1,dofs
1632
OrigMeshValues(dofs*(OrigMeshPerm(i2)-1)+k) = OrigMeshValues(dofs*(OrigMeshPerm(i2)-1)+k) + &
1633
s*CutValues(dofs*(CutPerm(i1)-1)+k) + (CutValues(dofs*(j-1)+k)-CutValues(dofs*(CutPerm(i1)-1)+k))
1634
END DO
1635
NodeWeigth(OrigMeshPerm(i2)) = NodeWeigth(OrigMeshPerm(i2)) + s
1636
ELSE IF(CutPerm(i1) == 0 .AND. CutPerm(i2) > 0) THEN
1637
s = r
1638
DO k=1,dofs
1639
OrigMeshValues(dofs*(OrigMeshPerm(i1)-1)+k) = OrigMeshValues(dofs*(OrigMeshPerm(i1)-1)+k) + &
1640
s*CutValues(dofs*(CutPerm(i2)-1)+k) + (CutValues(dofs*(j-1)+k)-CutValues(dofs*(CutPerm(i2)-1)+k))
1641
END DO
1642
NodeWeigth(OrigMeshPerm(i1)) = NodeWeigth(OrigMeshPerm(i1)) + s
1643
END IF
1644
END DO
1645
1646
DO k=1,dofs
1647
WHERE( NodeWeigth(1:nn) > EPSILON(s))
1648
OrigMeshValues(k::dofs) = OrigMeshValues(k::dofs) / NodeWeigth(1:nn)
1649
END WHERE
1650
END DO
1651
END IF
1652
1653
1654
! Entend values using FEM strategies beyond value set above.
1655
! We can extrapolate much but the extrapolation method is nonhysical.
1656
IF( CutExtend ) THEN
1657
CALL Info('CutFEMVariableFinalize','Extending values from inside to outside using FEM!',Level=10)
1658
IF(dofs > 1) THEN
1659
CALL Fatal('CutFEMVariableFinalize','Extending values only coded for one dofs!')
1660
END IF
1661
1662
B => CreateCutFEMMatrix(Solver,ExtendPerm)
1663
ALLOCATE(ExtendValues(B % NumberOfRows))
1664
ExtendValues = 0.0_dp
1665
Solver % Matrix => B
1666
Solver % Variable % Values => ExtendValues
1667
Solver % Variable % Perm => ExtendPerm
1668
1669
DO t=1,Mesh % NumberOfBulkElements
1670
Element => Mesh % Elements(t)
1671
IF(ANY(PhiPerm(Element % NodeIndexes)==0)) CYCLE
1672
1673
n = Element % Type % NumberOfNodes
1674
1675
30 pElement => CutInterfaceBulk(Element,isCut,isMore)
1676
IF(isCut) THEN
1677
n = pElement % Type % NumberOfNodes
1678
IF(ALL(ExtendPerm(pElement % NodeIndexes) > 0) ) THEN
1679
CALL LocalFitMatrix( pElement, n )
1680
END IF
1681
IF(IsMore) GOTO 30
1682
ELSE
1683
IF(ALL(ExtendPerm(Element % NodeIndexes) > 0) ) THEN
1684
CALL LocalFitMatrix( Element, n )
1685
END IF
1686
END IF
1687
END DO
1688
1689
1690
! On the shared nodes of the "inside" and "outside" regions.
1691
! Set Dirichlet conditions in somewhat dirty way for now.
1692
DO i=1,Mesh % NumberOfNodes + Mesh % NumberOfEdges
1693
j = CutPerm(i)
1694
k = ExtendPerm(i)
1695
IF(j==0 .OR. k==0) CYCLE
1696
1697
dval = CutValues(j)
1698
s = B % Values(B % diag(k))
1699
CALL ZeroRow(B, k)
1700
CALL SetMatrixElement(B,k,k,s)
1701
B % rhs(k) = s * dval
1702
END DO
1703
1704
CALL SolveLinearSystem( B, B % rhs, ExtendValues, norm, Solver % Variable % dofs, Solver )
1705
1706
CALL FreeMatrix(B)
1707
Solver % Matrix => NULL()
1708
1709
DO i=1,nn
1710
j = ExtendPerm(i)
1711
k = OrigMeshPerm(i)
1712
IF(j==0 .OR. k==0) CYCLE
1713
OrigMeshValues(k) = ExtendValues(j)
1714
END DO
1715
END IF
1716
1717
! Revert to the original field that is present everywhere.
1718
Solver % Variable % Values => OrigMeshValues
1719
Solver % Variable % Perm => OrigMeshPerm
1720
Solver % Variable % PrevValues => OrigPrevMeshValues
1721
Solver % Variable % Norm = Norm
1722
1723
! Revert to original body id's.
1724
! If we don't do this then ActiveElements is spoiled.
1725
DO t=1,Mesh % NumberOfBulkElements
1726
Element => Mesh % Elements(t)
1727
IF(ALL(PhiPerm(Element % NodeIndexes)>0)) THEN
1728
Element % BodyId = CutFemBody
1729
END IF
1730
END DO
1731
1732
END SUBROUTINE CutFEMVariableFinalize
1733
1734
1735
!------------------------------------------------------------------------------
1736
!> Split a mesh at zero levelset by adding new nodes at the interface.
1737
!> The idea is to be able to better represent shapes that are not initially
1738
!> presented by body fitted finite element mesh. This is a modifieid version
1739
!> of similar routine in MeshUtils that utilizes the CutInterface* routines.
1740
!------------------------------------------------------------------------------
1741
FUNCTION CreateCutFEMMesh(Solver,Mesh,Perm,CreateBC,CreateBulk,&
1742
AddMeshMode, Vlist,ProjectPrefix) RESULT( NewMesh )
1743
!------------------------------------------------------------------------------
1744
TYPE(Solver_t) :: Solver
1745
TYPE(Mesh_t) :: Mesh
1746
INTEGER, POINTER :: Perm(:)
1747
LOGICAL :: CreateBC, CreateBulk, AddMeshMode
1748
TYPE(ValueList_t), POINTER :: Vlist
1749
CHARACTER(*) :: ProjectPrefix
1750
TYPE(Mesh_t), POINTER :: NewMesh
1751
!------------------------------------------------------------------------------
1752
INTEGER :: i, j, k, n
1753
INTEGER :: NodeCnt
1754
INTEGER :: nn, ne, nBC, nBulk, t, ntot, Sweep, InterfaceBC
1755
LOGICAL :: Found, isActive, isMore, isCut
1756
TYPE(Element_t), POINTER :: pElement,Element
1757
REAL(KIND=dp) :: r
1758
INTEGER, POINTER :: MeshPerm(:) => NULL()
1759
REAL(KIND=dp), POINTER :: Values(:)
1760
CHARACTER(:), ALLOCATABLE :: VarName
1761
CHARACTER(*), PARAMETER :: Caller = 'CreateCutFEMMesh'
1762
1763
SAVE MeshPerm
1764
1765
!------------------------------------------------------------------------------
1766
IF(.NOT. (CreateBC .OR. CreateBulk)) THEN
1767
CALL Info(Caller,'Nothing to do!?')
1768
RETURN
1769
END IF
1770
1771
IF( AddMeshMode ) THEN
1772
CALL Info( Caller, 'Creating mesh including splitted elements only!')
1773
ELSE IF(.NOT. CreateBulk ) THEN
1774
CALL Info( Caller, 'Creating mesh including isoline boundary elements only!')
1775
ELSE
1776
CALL Info( Caller, 'Creating actual mesh splitted by zero levelset!')
1777
END IF
1778
1779
1780
CALL ResetTimer(Caller)
1781
1782
! Define the nodes to be included in the new mesh.
1783
!----------------------------------
1784
nn = Mesh % NumberOfNodes
1785
ne = Mesh % NumberOfEdges
1786
1787
IF(.NOT. ( CreateBulk .OR. AddMeshMode ) ) THEN
1788
ALLOCATE(MeshPerm(nn+ne))
1789
MeshPerm = 0
1790
j = 0
1791
DO i=1,nn+ne
1792
IF(CutDof(i)) THEN
1793
IF(AddMeshMode) THEN
1794
MeshPerm(i) = i
1795
ELSE
1796
j=j+1
1797
MeshPerm(i) = j
1798
END IF
1799
END IF
1800
END DO
1801
ELSE
1802
MeshPerm => Perm
1803
END IF
1804
1805
NewMesh => AllocateMesh()
1806
NewMesh % SingleMesh = Mesh % SingleMesh
1807
NewMesh % MaxNDofs = Mesh % MaxNDofs
1808
NewMesh % MeshDim = Mesh % MeshDim
1809
NewMesh % MaxElementNodes = Mesh % MaxElementNodes
1810
1811
IF( AddMeshMode ) THEN
1812
! In add mesh mode we retain the nodes and coordinates of the original mesh
1813
! and just create the elements and their topologies.
1814
! This saves work and memory.
1815
NewMesh % Name = TRIM(Mesh % Name)//'-addmesh'
1816
NodeCnt = Mesh % NumberOfNodes
1817
NewMesh % Nodes % x => Mesh % Nodes % x
1818
NewMesh % Nodes % y => Mesh % Nodes % y
1819
NewMesh % Nodes % z => Mesh % Nodes % z
1820
ELSE
1821
! This mode is intended for the isoline that is created at the levelset.
1822
NewMesh % Name = TRIM(Mesh % Name)//'-cutfem'
1823
NodeCnt = MAXVAL(MeshPerm)
1824
NewMesh % OutputActive = .TRUE.
1825
1826
CALL AllocateVector( NewMesh % Nodes % x, NodeCnt )
1827
CALL AllocateVector( NewMesh % Nodes % y, NodeCnt )
1828
CALL AllocateVector( NewMesh % Nodes % z, NodeCnt )
1829
1830
! This includes already the nodes created at the intersections.
1831
DO i=1,nn+ne
1832
j = MeshPerm(i)
1833
IF(j==0) CYCLE
1834
NewMesh % Nodes % x(j) = Mesh % Nodes % x(i)
1835
NewMesh % Nodes % y(j) = Mesh % Nodes % y(i)
1836
NewMesh % Nodes % z(j) = Mesh % Nodes % z(i)
1837
END DO
1838
END IF
1839
1840
CALL Info(Caller,'Number of nodes in CutFEM mesh: '//I2S(NodeCnt),Level=6)
1841
1842
NewMesh % NumberOfNodes = NodeCnt
1843
NewMesh % Nodes % NumberOfNodes = NodeCnt
1844
1845
InterfaceBC = ListGetInteger( Solver % Values,'CutFEM Interface BC',Found )
1846
1847
! The 1st cycle just compute the number of elements.
1848
! In between allocate the mesh elements.
1849
! The 2nd cycle add the detected elements to the list.
1850
!------------------------------------------------------
1851
DO Sweep=0,1
1852
nBulk = 0
1853
nBC = 0
1854
1855
IF(CreateBulk) THEN
1856
DO t=1, Mesh % NumberOfBulkElements
1857
Element => Mesh % Elements(t)
1858
CALL CutInterfaceCheck( Element, IsCut, IsActive, Perm )
1859
!IF(.NOT. IsActive) CYCLE
1860
IF(IsCut) THEN
1861
10 pElement => CutInterfaceBulk(Element,isCut,isMore)
1862
IF(ALL(Perm(pElement % NodeIndexes) > 0) ) THEN
1863
nBulk = nBulk + 1
1864
IF(Sweep==1) CALL AddElementData(pElement,nBulk)
1865
END IF
1866
IF(IsMore) GOTO 10
1867
ELSE IF(.NOT. AddMeshMode ) THEN
1868
! We we create only interface then the standard bulk elements are not included!
1869
IF(ANY(Perm(Element % NodeIndexes) == 0) ) CYCLE
1870
nBulk = nBulk + 1
1871
IF(Sweep==1) CALL AddElementData(Element,nBulk)
1872
END IF
1873
END DO
1874
END IF
1875
1876
IF(CreateBC) THEN
1877
DO t=1,Mesh % NumberOfBulkElements
1878
Element => Mesh % Elements(t)
1879
!CALL CutInterfaceCheck( Element, IsCut, IsActive, Perm )
1880
!IF(.NOT. IsActive) CYCLE
1881
20 pElement => CutInterfaceBC(Element,isCut,isMore)
1882
IF(isCut) THEN
1883
IF(ASSOCIATED(pElement)) THEN
1884
IF(ASSOCIATED(Perm)) THEN
1885
IF(ALL(Perm(pElement % NodeIndexes) > 0) ) THEN
1886
nBC = nBC + 1
1887
IF(Sweep==1) CALL AddElementData(pElement,nBulk+nBC,InterfaceBC)
1888
END IF
1889
END IF
1890
IF(IsMore) GOTO 20
1891
END IF
1892
END IF
1893
END DO
1894
END IF
1895
1896
1897
IF( Sweep == 0 ) THEN
1898
IF( CreateBulk ) THEN
1899
NewMesh % NumberOfBulkElements = nBulk
1900
NewMesh % NumberOfBoundaryElements = nBC
1901
ELSE
1902
NewMesh % NumberOfBulkElements = nBC
1903
NewMesh % NumberOfBoundaryElements = 0
1904
END IF
1905
1906
IF(InfoActive(25)) THEN
1907
PRINT *,'Old Element Counts:',Mesh % NumberOfBulkElements, Mesh % NumberOfBoundaryElements
1908
PRINT *,'New Element Counts:',nBulk, nBC
1909
END IF
1910
1911
CALL AllocateVector( NewMesh % Elements, nBulk+nBC )
1912
CALL Info(Caller,'New mesh allocated for '//I2S(nBulk+nBc)//' elements', Level=10 )
1913
END IF
1914
1915
END DO
1916
1917
#if 0
1918
IF( ParEnv % PEs > 1 ) CALL CutFEMParallelMesh()
1919
#endif
1920
1921
1922
! If we create interface only then we have original numbering and may use
1923
IF(.NOT. AddMeshMode ) THEN
1924
CALL InterpolateLevelsetVariables()
1925
END IF
1926
1927
IF(.NOT. ( CreateBulk .OR. AddMeshMode) ) DEALLOCATE(MeshPerm)
1928
1929
CALL CheckTimer(Caller,Delete=.TRUE.)
1930
CALL Info(Caller,'Zero levelset mesh was created',Level=8)
1931
1932
CONTAINS
1933
1934
#if 0
1935
! We do not need to update the Mesh % ParallelInfo, only Matrix % ParallelInfo!
1936
1937
SUBROUTINE CutFEMParallelMesh()
1938
1939
INTEGER :: istat,n0,n
1940
1941
CALL Info(Caller,'Creating ParallelInfo for CutFEM mesh structures!',Level=10)
1942
IF(.NOT. ASSOCIATED(Mesh % ParallelInfo % GlobalDOFS) ) THEN
1943
CALL Fatal(Caller,'Original mesh has no GlobalDOFs numbering!')
1944
END IF
1945
IF(.NOT. ASSOCIATED(Mesh % Edges) ) THEN
1946
CALL Fatal(Caller,'Original mesh requires edges!')
1947
END IF
1948
1949
! Use maximum nodal index as the offset for nodes defined on cut edges.
1950
n0 = MAXVAL( Mesh % ParallelInfo % GlobalDOFs )
1951
n0 = ParallelReduction(n0,2)
1952
1953
n = NewMesh % NumberOfNodes
1954
CALL Info(Caller,'Allocating parallel structures for '//I2S(n)//' nodes',Level=10)
1955
1956
ALLOCATE(NewMesh % ParallelInfo % GlobalDOFs(n), STAT=istat )
1957
IF ( istat /= 0 ) &
1958
CALL Fatal( Caller, 'Unable to allocate NewMesh % ParallelInfo % NeighbourList' )
1959
NewMesh % ParallelInfo % GlobalDOFs = 0
1960
ALLOCATE(NewMesh % ParallelInfo % GInterface(n), STAT=istat )
1961
IF ( istat /= 0 ) &
1962
CALL Fatal( Caller, 'Unable to allocate NewMesh % ParallelInfo % NeighbourList' )
1963
NewMesh % ParallelInfo % GInterface = .FALSE.
1964
1965
ALLOCATE(NewMesh % ParallelInfo % NeighbourList(n), STAT=istat )
1966
IF ( istat /= 0 ) &
1967
CALL Fatal( Caller, 'Unable to allocate NewMesh % ParallelInfo % NeighbourList' )
1968
DO i=1,n
1969
NULLIFY(NewMesh % ParallelInfo % NeighbourList(i) % Neighbours)
1970
END DO
1971
1972
DO i=1,nn+ne
1973
j = MeshPerm(i)
1974
IF(j<=0) CYCLE
1975
1976
IF(i<=nn) THEN
1977
NewMesh % ParallelInfo % GInterface(j) = Mesh % ParallelInfo % GInterface(i)
1978
NewMesh % ParallelInfo % GlobalDOFs(j) = Mesh % ParallelInfo % GlobalDOFs(i)
1979
k = SIZE(Mesh % ParallelInfo % NeighbourList(i) % Neighbours)
1980
ALLOCATE(NewMesh % ParallelInfo % NeighbourList(j) % Neighbours(k))
1981
NewMesh % ParallelInfo % NeighbourList(j) % Neighbours = &
1982
Mesh % ParallelInfo % NeighbourList(i) % Neighbours
1983
ELSE
1984
NewMesh % ParallelInfo % GInterface(j) = Mesh % ParallelInfo % EdgeInterface(i-nn)
1985
NewMesh % ParallelInfo % GlobalDOFs(j) = n0 + Mesh % Edges(i-nn) % GElementIndex
1986
1987
k = SIZE(Mesh % ParallelInfo % EdgeNeighbourList(i-nn) % Neighbours)
1988
PRINT *,'ass1 vals:',ParEnv % MyPe, k,j,Mesh % ParallelInfo % EdgeNeighbourList(i-nn) % Neighbours
1989
ALLOCATE(NewMesh % ParallelInfo % NeighbourList(j) % Neighbours(k))
1990
NewMesh % ParallelInfo % NeighbourList(j) % Neighbours = &
1991
Mesh % ParallelInfo % EdgeNeighbourList(i-nn) % Neighbours
1992
END IF
1993
END DO
1994
1995
1996
DO i = 1, NewMesh % NumberOfNodes
1997
IF(.NOT. ASSOCIATED(NewMesh % ParallelInfo % NeighbourList(i) % Neighbours)) THEN
1998
PRINT *,'nn:',nn,ne, MAXVAL(MeshPerm), NewMesh % NumberOfNodes, &
1999
SIZE(MeshPerm)
2000
CALL Fatal('CutFEMParallelMesh','Neighbours not associated: '//I2S(i))
2001
END IF
2002
END DO
2003
2004
END SUBROUTINE CutFEMParallelMesh
2005
2006
#endif
2007
2008
2009
! We can easily interpolate any variable on the new nodes created on the edge.
2010
! if we know values on both nodes.
2011
!-----------------------------------------------------------------------------
2012
SUBROUTINE InterpolateLevelsetVariables()
2013
2014
INTEGER :: iVar, dofs, l
2015
TYPE(Variable_t), POINTER :: Var
2016
REAL(KIND=dp), POINTER :: Values(:)
2017
INTEGER, POINTER :: Perm(:)
2018
LOGICAL :: IsCutVar
2019
2020
DO iVar = -1,100
2021
2022
IF(iVar == -1) THEN
2023
! We want to always interpolate the primary variable!
2024
! This is the only variable living in the "CutFEM" universe.
2025
Var => Solver % Variable
2026
VarName = Solver % Variable % name
2027
IsCutVar = .TRUE.
2028
ELSE
2029
IF(iVar == 0) THEN
2030
! We also want to interpolate the levelset variable.
2031
VarName = CutStr
2032
ELSE
2033
VarName = ListGetString( Vlist,TRIM(ProjectPrefix)//' '//I2S(iVar), Found )
2034
IF(.NOT. Found ) EXIT
2035
2036
! These are cases "-1" and "-0" that are always done!
2037
IF(VarName == Solver % Variable % Name ) CYCLE
2038
IF(VarName == CutStr ) CYCLE
2039
END IF
2040
2041
Var => VariableGet( Mesh % Variables, VarName, ThisOnly = .TRUE. )
2042
IF(.NOT. ASSOCIATED(Var)) THEN
2043
CALL Fatal('InterpolateLevelsetVariable','Could not find variable in 2D mesh: '//TRIM(VarName))
2044
END IF
2045
IsCutVar = .FALSE.
2046
END IF
2047
2048
CALL Info('InterpolateLevelsetVariable','Doing field: '//TRIM(Var % Name))
2049
2050
dofs = Var % dofs
2051
NULLIFY(Values)
2052
ALLOCATE(Values(dofs*NodeCnt))
2053
Values = 0.0_dp
2054
2055
! Create unity permutation vector.
2056
NULLIFY(Perm)
2057
ALLOCATE(Perm(NodeCnt))
2058
DO i=1,NodeCnt
2059
Perm(i) = i
2060
END DO
2061
2062
! If the size of permutation is nn+ne it is a sign that the field is associated
2063
! to the CutFEM field.
2064
IF(IsCutVar) THEN
2065
ntot = nn + ne
2066
ELSE
2067
ntot = nn
2068
END IF
2069
2070
DO i=1,ntot
2071
j = Var % Perm(i)
2072
k = MeshPerm(i)
2073
IF(j==0 .OR. k==0) CYCLE
2074
DO l=1,dofs
2075
Values(dofs*(k-1)+l) = Var % Values(dofs*(j-1)+l)
2076
END DO
2077
END DO
2078
2079
! We do not want to interpolate the cut var that has been computed exactly to the new virtual nodes.
2080
! The interpolated values would be worse than the computed ones.
2081
IF(.NOT. IsCutVar ) THEN
2082
CALL Info(Caller,'Interpolating values: '//TRIM(VarName),Level=10)
2083
DO i=1,ne
2084
k = MeshPerm(nn+i)
2085
IF(k==0) CYCLE
2086
r = CutInterp(i)
2087
2088
Values(k) = 0.0_dp
2089
2090
j = Var % Perm(Mesh % Edges(i) % NodeIndexes(1))
2091
IF(j>0) THEN
2092
DO l=1,dofs
2093
Values(dofs*(k-1)+l) = r*Var % Values(dofs*(j-1)+l)
2094
END DO
2095
END IF
2096
2097
j = Var % Perm(Mesh % Edges(i) % NodeIndexes(2))
2098
IF(j>0) THEN
2099
DO l=1,dofs
2100
Values(dofs*(k-1)+l) = Values(dofs*(k-1)+l) + (1-r)*Var % Values(dofs*(j-1)+l)
2101
END DO
2102
END IF
2103
END DO
2104
END IF
2105
2106
CALL Info(Caller,'Projected variable: '//TRIM(VarName),Level=10)
2107
CALL VariableAddVector( NewMesh % Variables, NewMesh, Solver, VarName, Var % Dofs, Values, Perm )
2108
2109
IF(InfoActive(25)) THEN
2110
PRINT *,'Range:',MINVAL(Values),MAXVAL(Values),SIZE(Values), Var % Dofs, SIZE(Values)
2111
END IF
2112
END DO
2113
2114
END SUBROUTINE InterpolateLevelsetVariables
2115
2116
2117
! Here we just add some data to the new mesh.
2118
!--------------------------------------------
2119
SUBROUTINE AddelementData(pElement,ElemInd,BCtag)
2120
TYPE(Element_t), POINTER :: pElement
2121
INTEGER :: ElemInd
2122
INTEGER, OPTIONAL :: BCTag
2123
2124
TYPE(Element_t), POINTER :: Enew
2125
INTEGER :: n
2126
2127
Enew => NewMesh % Elements(ElemInd)
2128
Enew % PartIndex = pElement % PartIndex
2129
Enew % BodyId = pElement % BodyId
2130
Enew % ElementIndex = ElemInd
2131
2132
n = pElement % TYPE % NumberOfNodes
2133
Enew % TYPE => GetElementType(pElement % TYPE % ElementCode)
2134
2135
CALL AllocateVector( ENew % NodeIndexes, n)
2136
IF( AddMeshMode ) THEN
2137
Enew % NodeIndexes(1:n) = pElement % NodeIndexes(1:n)
2138
ELSE
2139
Enew % NodeIndexes(1:n) = MeshPerm(pElement % NodeIndexes(1:n))
2140
END IF
2141
Enew % NDOFs = n
2142
Enew % EdgeIndexes => NULL()
2143
Enew % FaceIndexes => NULL()
2144
2145
IF(PRESENT(BCTag)) THEN
2146
ALLOCATE(Enew % BoundaryInfo)
2147
Enew % BoundaryInfo % Constraint = BCTag
2148
END IF
2149
2150
! This effects only parallel runs, but testing parallel costs more...
2151
Enew % PartIndex = ParEnv % MyPe
2152
2153
END SUBROUTINE AddelementData
2154
2155
END FUNCTION CreateCutFEMMesh
2156
!------------------------------------------------------------------------------
2157
2158
2159
2160
! Here we create a 1D mesh on the zero level-set and convent it to new location,
2161
! and compute new signed distance.
2162
!--------------------------------------------
2163
SUBROUTINE LevelSetUpdate(Solver,Mesh)
2164
2165
TYPE(Solver_t) :: Solver
2166
TYPE(Mesh_t) :: Mesh
2167
2168
TYPE(Variable_t), POINTER :: PhiVar1D, PhiVar2D, pVar
2169
TYPE(Mesh_t), POINTER :: IsoMesh => NULL()
2170
REAL(KIND=dp), POINTER :: x(:), y(:)
2171
REAL(KIND=dp) :: val, Vx, Vy, dt, VPhi, PhiMax, BW
2172
CHARACTER(:), ALLOCATABLE :: str
2173
LOGICAL :: Found, Nonzero, MovingLevelset
2174
INTEGER :: nVar,i,j,iAvoid,iSolver
2175
2176
TYPE PolylineData_t
2177
INTEGER :: nLines = 0, nNodes = 0
2178
REAL(KIND=dp), ALLOCATABLE :: Vals(:,:)
2179
REAL(KIND=dp) :: IsoLineBB(4), MeshBB(4)
2180
END TYPE PolylineData_t
2181
TYPE(PolylineData_t), ALLOCATABLE, TARGET, SAVE :: PolylineData(:)
2182
2183
2184
SAVE IsoMesh
2185
2186
IsoMesh => CreateCutFEMMesh(Solver,Mesh,Solver % Variable % Perm,&
2187
.TRUE.,.FALSE.,.FALSE.,Solver % Values,'isoline variable')
2188
IsoMesh % Name = TRIM(Mesh % Name)//'-isomesh'
2189
2190
pVar => VariableGet( Mesh % Variables,'timestep size' )
2191
dt = pVar % Values(1)
2192
2193
phiVar1D => VariableGet( IsoMesh % Variables, CutStr, ThisOnly = .TRUE.)
2194
IF(.NOT. ASSOCIATED(PhiVar1D)) THEN
2195
CALL Fatal('LevelSetUpdate','Levelset function ("'//TRIM(CutStr)//'") needed in 1D mesh!')
2196
END IF
2197
2198
2199
! This should be ok by construction but some testing does not hurt...
2200
DO i=1,SIZE(phiVar1D % Perm)
2201
IF(i /= PhiVar1D % Perm(i) ) THEN
2202
CALL Fatal('LevelSetUpdate','PhiVar1D permutation not unity map')
2203
END IF
2204
END DO
2205
2206
phiVar2D => VariableGet( Mesh % Variables, CutStr, ThisOnly = .TRUE.)
2207
IF(.NOT. ASSOCIATED(PhiVar2D)) THEN
2208
CALL Fatal('LevelSetUpdate','Levelset function needed in 2D mesh!')
2209
END IF
2210
2211
x => Isomesh % Nodes % x
2212
y => Isomesh % Nodes % y
2213
2214
MovingLevelset = .FALSE.
2215
2216
! This assumes constant levelset convection. Mainly for testing.
2217
Vx = ListGetCReal( Solver % Values,'Levelset Velocity 1',Found )
2218
IF(Found) THEN
2219
IF(ABS(Vx) > EPSILON(Vx)) MovingLevelset = .TRUE.
2220
x = x + Vx * dt
2221
END IF
2222
2223
Vy = ListGetCReal( Solver % Values,'Levelset Velocity 2',Found )
2224
IF(Found) THEN
2225
IF(ABS(Vy) > EPSILON(Vy)) MovingLevelset = .TRUE.
2226
y = y + Vy * dt
2227
END IF
2228
2229
! This assumes constant calving speed. Mainly for testing.
2230
VPhi = ListGetCReal( Solver % Values,'Levelset Calving',Found )
2231
IF(Found) THEN
2232
MovingLevelset = .TRUE.
2233
PhiVar1D % Values = PhiVar1D % Values + VPhi * dt
2234
END IF
2235
2236
Nonzero = ListGetLogical( Solver % Values,'CutFEM signed distance nonzero',Found )
2237
PRINT *,'Move:',Vx,Vy,VPhi,dt,Nonzero
2238
2239
! Position dependent levelset velocity & calving speed.
2240
str = ListGetString( Solver % Values,'Levelset Velocity Variable',Found )
2241
IF(Found) THEN
2242
pVar => VariableGet( Mesh % Variables,TRIM(str)//' 1',UnfoundFatal=.TRUE.)
2243
x = x + pVar % Values * dt
2244
pVar => VariableGet( Mesh % Variables,TRIM(str)//' 2',UnfoundFatal=.TRUE.)
2245
y = y + pVar % Values * dt
2246
MovingLevelset = .TRUE.
2247
END IF
2248
2249
str = ListGetString( Solver % Values,'Levelset Calving Variable',Found )
2250
IF(Found) THEN
2251
pVar => VariableGet( Mesh % Variables,TRIM(str),UnfoundFatal=.TRUE.)
2252
PhiVar1D % Values = PhiVar1D % Values + pVar % Values * dt
2253
END IF
2254
2255
PhiMax = MAXVAL(ABS(PhiVar1D % Values))
2256
PhiMax = 1.01 * ( PhiMax + SQRT(Vx**2+Vy**2)*dt )
2257
2258
IF(.NOT. ALLOCATED(PolylineData)) THEN
2259
ALLOCATE(PolylineData(ParEnv % PEs))
2260
END IF
2261
CALL PopulatePolyline()
2262
2263
2264
DO i=1, Mesh % NumberOfNodes
2265
j = PhiVar2D % Perm(i)
2266
IF(j==0) CYCLE
2267
val = PhiVar2D % Values(j)
2268
#if 0
2269
IF(val > BW ) THEN
2270
val = val - BW
2271
ELSE IF(val < -BW ) THEN
2272
val = val + BW
2273
ELSE
2274
val = SignedDistance(i)
2275
END IF
2276
#else
2277
val = SignedDistance(i)
2278
#endif
2279
2280
IF(MovingLevelset) THEN
2281
PhiVar2D % Values(j) = val
2282
END IF
2283
END DO
2284
2285
! Deallocate data, next time this will be different.
2286
DO i=1,ParEnv % PEs
2287
IF(PolylineData(i) % nLines > 0) THEN
2288
DEALLOCATE(PolylineData(i) % Vals)
2289
END IF
2290
END DO
2291
2292
Solver % Mesh % Next => IsoMesh
2293
2294
CONTAINS
2295
2296
!------------------------------------------------------------------------------
2297
!> Computes the signed distance to zero levelset.
2298
!------------------------------------------------------------------------------
2299
SUBROUTINE PopulatePolyline()
2300
!------------------------------------------------------------------------------
2301
REAL(KIND=dp) :: x0,y0,x1,y1,ss,TotLineLen
2302
INTEGER :: i,j,k,n,m,i0,i1,nCol,dofs,k2
2303
TYPE(Variable_t), POINTER :: Var1D
2304
INTEGER :: iVar, MyPe, PEs, Phase
2305
!------------------------------------------------------------------------------
2306
2307
nCol = 6
2308
2309
nVar = 0
2310
iAvoid = 0
2311
iSolver = 0
2312
TotLineLen = 0.0_dp
2313
2314
DO k = 1,100
2315
str = ListGetString( Solver % Values,'isoline variable '//I2S(k), Found )
2316
IF(.NOT. Found ) EXIT
2317
2318
! The levelset is really computed, do not interpolate it.
2319
IF(str == CutStr) iAvoid = k
2320
IF(str == Solver % Variable % Name) iSolver = k
2321
2322
Var1D => VariableGet( IsoMesh % Variables, str, ThisOnly = .TRUE. )
2323
IF(.NOT. ASSOCIATED(Var1D)) EXIT
2324
nVar = k
2325
nCol = nCol + 2 * Var1D % Dofs
2326
2327
IF(InfoActive(25)) THEN
2328
PRINT *,'Mesh1D Range for '//TRIM(Var1D % Name)//': ',&
2329
MINVAL(Var1D % Values), MAXVAL(Var1D % Values)
2330
END IF
2331
END DO
2332
2333
m = Isomesh % NumberOfBulkElements
2334
MyPe = ParEnv % MyPe + 1
2335
PEs = ParEnv % PEs
2336
2337
! We may find use for bounding boxes later on.
2338
#if 0
2339
PolylineData(MyPe) % IsoLineBB(1) = MINVAL(IsoMesh % Nodes % x)
2340
PolylineData(MyPe) % IsoLineBB(2) = MAXVAL(IsoMesh % Nodes % x)
2341
PolylineData(MyPe) % IsoLineBB(3) = MINVAL(IsoMesh % Nodes % y)
2342
PolylineData(MyPe) % IsoLineBB(4) = MAXVAL(IsoMesh % Nodes % y)
2343
2344
PolylineData(MyPe) % MeshBB(1) = MINVAL(Mesh % Nodes % x)
2345
PolylineData(MyPe) % MeshBB(2) = MAXVAL(Mesh % Nodes % x)
2346
PolylineData(MyPe) % MeshBB(3) = MINVAL(Mesh % Nodes % y)
2347
PolylineData(MyPe) % MeshBB(4) = MAXVAL(Mesh % Nodes % y)
2348
#endif
2349
2350
DO Phase=0,1
2351
m = 0
2352
DO i=1,IsoMesh % NumberOfBulkElements
2353
i0 = IsoMesh % Elements(i) % NodeIndexes(1)
2354
i1 = IsoMesh % Elements(i) % NodeIndexes(2)
2355
2356
x0 = x(i0); y0 = y(i0)
2357
x1 = x(i1); y1 = y(i1)
2358
2359
ss = (x0-x1)**2 + (y0-y1)**2
2360
2361
! This is too short for anything useful...
2362
! Particularly difficult it is to decide on left/right if the segment is a stub.
2363
IF(ss < EPSILON(ss) ) CYCLE
2364
2365
m = m+1
2366
IF(Phase==0) CYCLE
2367
2368
TotLineLen = TotLineLen + SQRT(ss)
2369
2370
2371
! Coordinates for the polyline.
2372
PolylineData(MyPe) % Vals(m,1) = x0
2373
PolylineData(MyPe) % Vals(m,2) = x1
2374
PolylineData(MyPe) % Vals(m,3) = y0
2375
PolylineData(MyPe) % Vals(m,4) = y1
2376
2377
! Levelset values for the polyline.
2378
PolylineData(MyPe) % Vals(m,5) = PhiVar1D % Values(i0)
2379
PolylineData(MyPe) % Vals(m,6) = PhiVar1D % Values(i1)
2380
j = 7
2381
2382
DO k = 1,nVar
2383
IF(k==iAvoid) CYCLE
2384
2385
str = ListGetString( Solver % Values,'isoline variable '//I2S(k), Found )
2386
Var1D => VariableGet( IsoMesh % Variables, str, ThisOnly = .TRUE. )
2387
2388
dofs = Var1D % Dofs
2389
DO k2=1,dofs
2390
PolylineData(MyPe) % Vals(m,j) = Var1D % Values(dofs*(Var1D % Perm(i0)-1)+k2)
2391
PolylineData(MyPe) % Vals(m,j+1) = Var1D % Values(dofs*(Var1D % Perm(i1)-1)+k2)
2392
j = j+2
2393
END DO
2394
END DO
2395
END DO
2396
2397
2398
IF(Phase==0) THEN
2399
CALL Info('LevelsetUpdate','Allocating PolylineData of size '//I2S(m)//' x '//I2S(nCol),Level=8)
2400
PolylineData(MyPe) % nLines = m
2401
PolylineData(MyPe) % nNodes = Mesh % NumberOfNodes
2402
ALLOCATE(PolylineData(MyPe) % Vals(m,nCol))
2403
PolylineData(MyPe) % Vals = 0.0_dp
2404
END IF
2405
2406
END DO
2407
2408
2409
IF(PEs > 1 ) THEN
2410
BLOCK
2411
INTEGER, ALLOCATABLE :: nPar(:)
2412
INTEGER :: comm, ierr, status(MPI_STATUS_SIZE)
2413
2414
ALLOCATE(nPar(PEs))
2415
comm = Solver % Matrix % Comm
2416
2417
nPar = 0
2418
nPar(MyPe) = PolylineData(MyPe) % nLines
2419
CALL MPI_ALLREDUCE(MPI_IN_PLACE, nPar, PEs, MPI_INTEGER, MPI_MAX, comm, ierr)
2420
DO i=1,PEs
2421
PolylineData(i) % nLines = nPar(i)
2422
END DO
2423
2424
nPar = 0
2425
nPar(MyPe) = Mesh % NumberOfNodes
2426
CALL MPI_ALLREDUCE(MPI_IN_PLACE, nPar, PEs, MPI_INTEGER, MPI_MAX, comm, ierr)
2427
DO i=1,PEs
2428
PolylineData(i) % nNodes = nPar(i)
2429
END DO
2430
CALL MPI_BARRIER( comm, ierr )
2431
2432
IF( PolylineData(MyPe) % nNodes > 1) THEN
2433
DO i=1,PEs
2434
IF(i==MyPe) CYCLE
2435
m = PolylineData(i) % nLines
2436
IF(m>0) ALLOCATE(PolylineData(i) % Vals(m,nCol))
2437
END DO
2438
END IF
2439
2440
DO i=1,PEs
2441
IF(i==MyPe) CYCLE
2442
IF(PolylineData(MyPe) % nLines == 0 .OR. PolylineData(i) % nNodes == 0 ) CYCLE
2443
2444
! Sent data from partition MyPe to i
2445
k = PolylineData(MyPe) % nLines * nCol
2446
CALL MPI_BSEND( PolylineData(MyPe) % Vals, k, MPI_DOUBLE_PRECISION,i-1, &
2447
1001, comm, ierr )
2448
END DO
2449
2450
DO i=1,PEs
2451
IF(i==MyPe) CYCLE
2452
IF(PolylineData(i) % nLines == 0 .OR. PolylineData(MyPe) % nNodes == 0 ) CYCLE
2453
2454
! Recieve data from partition i to MyPe
2455
k = PolylineData(i) % nLines * nCol
2456
CALL MPI_RECV( PolylineData(i) % Vals, k, MPI_DOUBLE_PRECISION,i-1, &
2457
1001, comm, status, ierr )
2458
END DO
2459
2460
CALL MPI_BARRIER( comm, ierr )
2461
2462
k = SUM( PolylineData(1:PEs) % nLines )
2463
CALL Info('LevelSetUpdate','Number of line segments in parallel system: '//I2S(k),Level=7)
2464
2465
CALL MPI_ALLREDUCE(MPI_IN_PLACE, TotLineLen, PEs, MPI_DOUBLE_PRECISION, MPI_SUM, comm, ierr)
2466
END BLOCK
2467
END IF
2468
2469
WRITE(Message,'(A,ES12.3)') 'Cutfem isoline length:',TotLineLen
2470
CALL Info('LevelSetUpdate',Message,Level=6)
2471
2472
CALL ListAddConstReal(CurrentModel % Simulation,'res: cutfem isoline length',TotLineLen )
2473
2474
2475
IF(InfoActive(25)) THEN
2476
CALL Info('LevelSetUpdate','Polyline interval for Isoline variables')
2477
2478
j = SIZE(PolylineData(MyPe) % Vals(:,1))
2479
CALL VectorValuesRange(PolylineData(MyPe) % Vals(:,1),j,'x')
2480
CALL VectorValuesRange(PolylineData(MyPe) % Vals(:,3),j,'y')
2481
CALL VectorValuesRange(PolylineData(MyPe) % Vals(:,5),j,'phi')
2482
i = 7
2483
DO k = 1,nVar
2484
str = ListGetString( Solver % Values,'isoline variable '//I2S(k), Found )
2485
CALL VectorValuesRange(PolylineData(MyPe) % Vals(:,i),j,TRIM(str))
2486
i = i+2
2487
END DO
2488
END IF
2489
2490
END SUBROUTINE PopulatePolyline
2491
!------------------------------------------------------------------------------
2492
2493
2494
!------------------------------------------------------------------------------
2495
!> Computes the signed distance to zero levelset.
2496
!------------------------------------------------------------------------------
2497
FUNCTION SignedDistance(node) RESULT(phip)
2498
!------------------------------------------------------------------------------
2499
INTEGER :: node
2500
REAL(KIND=dp) :: phip
2501
!------------------------------------------------------------------------------
2502
REAL(KIND=dp) :: xp,yp
2503
REAL(KIND=dp) :: x0,y0,x1,y1,xm,ym,a,b,c,d,s,dir1,&
2504
dist2,mindist2,dist,mindist,smin,ss,phim
2505
INTEGER :: i,i0,i1,j,k,n,sgn,m,imin,kmin,dofs,k2
2506
TYPE(Variable_t), POINTER :: Var1D, Var2D
2507
INTEGER :: nCol, nLines
2508
REAL(KIND=dp), POINTER :: pValues(:)
2509
!------------------------------------------------------------------------------
2510
mindist2 = HUGE(mindist2)
2511
mindist = HUGE(mindist)
2512
sgn = 1
2513
2514
xp = Mesh % Nodes % x(node)
2515
yp = Mesh % Nodes % y(node)
2516
2517
m = 0
2518
nCol = 7
2519
2520
DO k = 1, ParEnv % PEs
2521
nLines = PolylineData(k) % nLInes
2522
IF(nLines == 0) CYCLE
2523
2524
DO i=1,nLines
2525
x0 = PolylineData(k) % Vals(i,1)
2526
x1 = PolylineData(k) % Vals(i,2)
2527
y0 = PolylineData(k) % Vals(i,3)
2528
y1 = PolylineData(k) % Vals(i,4)
2529
2530
a = xp - x0
2531
b = x0 - x1
2532
d = y0 - y1
2533
c = yp - y0
2534
ss = b**2 + d**2
2535
2536
s = MIN( MAX( -(a*b + c*d) / ss, 0.0d0), 1.0d0 )
2537
xm = (1-s) * x0 + s * x1
2538
ym = (1-s) * y0 + s * y1
2539
dist2 = (xp - xm)**2 + (yp - ym)**2
2540
2541
IF(nonzero) THEN
2542
! We need true distances since the offset cannot be added otherwise.
2543
dist2 = SQRT(dist2)
2544
2545
! The line segment including the zero levelset might not be exactly zero...
2546
! By definition we don't have permutation here!
2547
phim = (1-s) * PolylineData(k) % Vals(i,5) + s * PolylineData(k) % Vals(i,6)
2548
2549
! In order to test when need to be close enough.
2550
IF(dist2 > mindist2 + ABS(phim) ) CYCLE
2551
2552
! Dir is an indicator one which side of the line segment the point lies.
2553
! We have ordered the edges soe that "dir1" should be consistent.
2554
dir1 = (x1 - x0) * (yp - y0) - (y1 - y0) * (xp - x0)
2555
2556
! If the control point and found point lie on the same side they are inside.
2557
IF(dir1 < 0.0_dp ) THEN
2558
sgn = -1
2559
ELSE
2560
sgn = 1
2561
END IF
2562
2563
dist = sgn * dist2 + phim
2564
! Ok, close but no honey.
2565
IF( ABS(dist) > ABS(mindist) ) CYCLE
2566
2567
mindist = dist
2568
ELSE
2569
! Here we can compare the squares saving one expensive operation.
2570
IF(dist2 > mindist2 ) CYCLE
2571
2572
! Dir is an indicator one which side of the line segment the point lies.
2573
! We have ordered the edges soe that "dir1" should be consistent.
2574
dir1 = (x1 - x0) * (yp - y0) - (y1 - y0) * (xp - x0)
2575
2576
! If the control point and found point lie on the same side they are inside.
2577
IF(dir1 < 0.0_dp ) THEN
2578
sgn = -1
2579
ELSE
2580
sgn = 1
2581
END IF
2582
END IF
2583
2584
! Save these values for interpolation.
2585
m = m+1
2586
mindist2 = dist2
2587
smin = s
2588
imin = i
2589
kmin = k
2590
END DO
2591
END DO
2592
2593
IF(nonzero) THEN
2594
phip = mindist
2595
ELSE
2596
phip = sgn * SQRT(mindist2)
2597
END IF
2598
2599
! We can carry the fields with the zero levelset. This is like pure advection.
2600
! We should make this less laborious my fecthing the pointers first...
2601
IF( nVar > 0 .AND. CutPerm(node) == 0 ) THEN
2602
i0 = IsoMesh % Elements(imin) % NodeIndexes(1)
2603
i1 = IsoMesh % Elements(imin) % NodeIndexes(2)
2604
2605
DO i = 1,nVar
2606
IF(i==iAvoid) CYCLE
2607
str = ListGetString( Solver % Values,'isoline variable '//I2S(i), Found )
2608
2609
IF(i==iSolver) THEN
2610
j = OrigMeshPerm(node)
2611
dofs = Solver % Variable % Dofs
2612
pValues => OrigMeshValues
2613
ELSE
2614
Var2D => VariableGet( Mesh % Variables, str, ThisOnly = .TRUE. )
2615
j = Var2D % Perm(node)
2616
dofs = Var2D % dofs
2617
pValues => Var2D % Values
2618
END IF
2619
2620
IF(j==0) THEN
2621
nCol = nCol+2*dofs
2622
PRINT *,'cycling:',TRIM(str)
2623
STOP
2624
CYCLE
2625
END IF
2626
2627
! Interpolate from the closest distance.
2628
! This is done similarly as the interpolation of coordinates.
2629
DO k2 = 1,dofs
2630
pValues(dofs*(j-1)+k2) = &
2631
(1-smin) * PolylineData(kmin) % Vals(imin,nCol) + &
2632
smin * PolylineData(kmin) % Vals(imin,nCol+1)
2633
nCol = nCol+2
2634
END DO
2635
END DO
2636
END IF
2637
2638
!PRINT *,'phip:',phip, m
2639
2640
END FUNCTION SignedDistance
2641
!------------------------------------------------------------------------------
2642
2643
END SUBROUTINE LevelSetUpdate
2644
2645
END MODULE CutFemUtils
2646
2647
!> \} ElmerLib
2648
2649
2650