From 5470cb8927dbc58c9a36b322ab1eea888ed26ae3 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 29 Mar 2020 11:21:47 +0900 Subject: [PATCH 01/73] Update Zoltan_v3.83 --- contrib/Zoltan_v3.83 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/Zoltan_v3.83 b/contrib/Zoltan_v3.83 index 0d6fc95372..00deb75f30 160000 --- a/contrib/Zoltan_v3.83 +++ b/contrib/Zoltan_v3.83 @@ -1 +1 @@ -Subproject commit 0d6fc95372719b560bb2e5b250134a16837582ac +Subproject commit 00deb75f305d5b52631ee361f472ebb87241e14c From c85cbe0abc523a62b2d232927965f1e5cc9f42d6 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 29 Mar 2020 15:12:39 +0900 Subject: [PATCH 02/73] Revert "Update Zoltan_v3.83" This reverts commit 5470cb8927dbc58c9a36b322ab1eea888ed26ae3. --- contrib/Zoltan_v3.83 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/Zoltan_v3.83 b/contrib/Zoltan_v3.83 index 00deb75f30..0d6fc95372 160000 --- a/contrib/Zoltan_v3.83 +++ b/contrib/Zoltan_v3.83 @@ -1 +1 @@ -Subproject commit 00deb75f305d5b52631ee361f472ebb87241e14c +Subproject commit 0d6fc95372719b560bb2e5b250134a16837582ac From e4ecccfdd3bdb808ff1743e7d782fa4a03405859 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 28 Sep 2020 13:01:11 +0300 Subject: [PATCH 03/73] A fix for 0**0 --- fem/src/ElementDescription.F90 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fem/src/ElementDescription.F90 b/fem/src/ElementDescription.F90 index 3e703b0232..0e333275b9 100644 --- a/fem/src/ElementDescription.F90 +++ b/fem/src/ElementDescription.F90 @@ -786,7 +786,11 @@ FUNCTION InterpolateInElement1D( element,x,u ) RESULT(y) s = 0.0d0 DO i=1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) + IF (p(i)==0) THEN + s = s + Coeff(i) + ELSE + s = s + Coeff(i) * u**p(i) + END if END DO y = y + s * x(n) END IF @@ -822,7 +826,11 @@ SUBROUTINE NodalBasisFunctions1D( y,element,u ) s = 0.0d0 DO i=1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) + IF (p(i)==0) THEN + s = s + Coeff(i) + ELSE + s = s + Coeff(i) * u**p(i) + END if END DO y(n) = s END DO From ce7889babdf2d825f62009fedf0c6e25d6a1d113 Mon Sep 17 00:00:00 2001 From: Tom Gustafsson Date: Wed, 30 Sep 2020 22:46:08 +0300 Subject: [PATCH 04/73] Fix typo and make more consistent --- README.adoc | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/README.adoc b/README.adoc index 3a756b9226..bcaafe42ba 100644 --- a/README.adoc +++ b/README.adoc @@ -28,31 +28,31 @@ Elmer is a finite element software for numerical solution of partial differentia Elmer consists of several parts. The most important ones are ElmerSolver the finite element solver, ElmerGUI the graphical user interface, and ElmerGrid the mesh creation and manipulation tool. Also a visualization tool, ElmerPost, is included in the package but it is no longer developed. -=== Download Binaries: +=== Download binaries -You may download binaries and virtual machines from http://www.elmerfem.org/blog/binaries/[here] +You may download binaries and virtual machines from http://www.elmerfem.org/blog/binaries/[here]. -=== Docker: +=== Docker - * nwrichmond/elmerice: https://hub.docker.com/r/nwrichmond/elmerice/[Docker Hub], More info https://raw.githubusercontent.com/ElmerCSC/elmerfem/release/ReleaseNotes/release_8.4.txt[here] + * nwrichmond/elmerice: https://hub.docker.com/r/nwrichmond/elmerice/[Docker Hub], https://raw.githubusercontent.com/ElmerCSC/elmerfem/release/ReleaseNotes/release_8.4.txt[more info] * unifem/Elmer-desktop: https://github.com/unifem/Elmer-desktop[GitHub] - * CoSci-LLC/docker-elmerice https://hub.docker.com/repository/docker/coscillc/elmerice[Docker Hub], https://github.com/CoSci-LLC/docker-elmerice[GitHub] + * CoSci-LLC/docker-elmerice: https://hub.docker.com/repository/docker/coscillc/elmerice[Docker Hub], https://github.com/CoSci-LLC/docker-elmerice[GitHub] === Documentation -You may find the PDFs for the documentation http://www.elmerfem.org/blog/documentation/[here] +You may find the PDFs for the documentation http://www.elmerfem.org/blog/documentation/[here]. -=== Compiling: +=== Compiling -==== macOS: +==== macOS - * download this repository either az a zip file via GitHub or using `git clone https://github.com/ElmerCSC/elmerfem.git` - * go to the downloaded directory `mkdir build` and `cd build` - * Install HomeBrew - * Install GNU GCC `brew install gcc` + * Download this repository either az a zip file via GitHub or using `git clone https://github.com/ElmerCSC/elmerfem.git` + * Go to the downloaded directory `mkdir build` and `cd build` + * Install Homebrew + * Install GCC `brew install gcc` * Install CMake `brew install cmake` - * Without: + * Without MPI: ** `cmake .. -D WITH_OpenMP:BOOLEAN=TRUE` * With MPI: ** Install OpenMPI `brew install open-mpi` @@ -69,8 +69,9 @@ You may find the PDFs for the documentation http://www.elmerfem.org/blog/documen ==== Ubuntu - * install the dependencies `sudo apt install git cmake build-essential fortran libopenmpi-dev libblas-dev liblapack-dev` - * Without: + * Download the source code and create `build` directory as above + * Install the dependencies `sudo apt install git cmake build-essential gfortran libopenmpi-dev libblas-dev liblapack-dev` + * Without MPI: ** `cmake .. -DWITH_OpenMP:BOOLEAN=TRUE` * With MPI: ** `cmake .. -DWITH_OpenMP:BOOLEAN=TRUE -DWITH_MPI:BOOLEAN=TRUE` @@ -79,25 +80,27 @@ You may find the PDFs for the documentation http://www.elmerfem.org/blog/documen ** `cmake .. -DWITH_OpenMP:BOOLEAN=TRUE -DWITH_MPI:BOOLEAN=TRUE -DWITH_ELMERGUI:BOOLEAN=TRUE` * `make` * `sudo make install` - * the executable are in `/usr/local/bin` folder, you may add this to your PATH if you will. + * The executables are in `/usr/local/bin` folder, you may add this to your PATH if you will -=== Licencing: image:https://img.shields.io/badge/License-GPLv2-blue.svg["License: GPL v2", link=https://www.gnu.org/licenses/gpl-2.0] image:https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg["License: LGPL v2.1", link=https://www.gnu.org/licenses/lgpl-2.1] +=== Licensing + +image:https://img.shields.io/badge/License-GPLv2-blue.svg["License: GPL v2", link=https://www.gnu.org/licenses/gpl-2.0] image:https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg["License: LGPL v2.1", link=https://www.gnu.org/licenses/lgpl-2.1] [.text-justify] Elmer software is licensed under GPL except for the ElmerSolver library which is licensed under LGPL license. Elmer is mainly developed at CSC - IT Center for Science, Finland. However, there have been numerous contributions from other organizations and developers as well, and the project is open for new contributions. More information about Elmer's licensing http://www.elmerfem.org/blog/license/[here]. -=== Package managers: +=== Package managers [.text-center] image::https://repology.org/badge/vertical-allrepos/elmerfem.svg["Packaging status", link=https://repology.org/project/elmerfem/versions] -==== Chocolatey: +==== Chocolatey [.text-center] image:https://img.shields.io/chocolatey/dt/elmer-mpi["Chocolatey", link=https://chocolatey.org/packages/elmer-mpi] -=== Social: +=== Social [.text-justify] Here on https://discordapp.com/invite/NeZEBZn[this Discord channel] you may ask for help or dicuss different Elmer related matters: @@ -116,14 +119,13 @@ Ask your questions on Reddit: image:https://img.shields.io/reddit/subreddit-subscribers/ElmerFEM["Subreddit subscribers", link=https://www.reddit.com/r/ElmerFEM/] -=== Computational Glaciology "Elmer/Ice": +=== Computational Glaciology "Elmer/Ice" * http://elmerice.elmerfem.org[Elmer/Ice community web site] * https://github.com/ElmerCSC/elmerfem/tree/elmerice/elmerice/[Elmer/Ice README] -=== Other links: - +=== Other links * http://www.elmerfem.org/[Elmer Blog] * https://www.csc.fi/web/elmer[official CSC homepage] From f6fa9fa8ad8f87f53a64d34ee03f1e887bea63f2 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 5 Oct 2020 13:20:04 +0300 Subject: [PATCH 05/73] Remove misleading commentaries --- fem/src/ElementUtils.F90 | 2 +- fem/src/ListMatrix.F90 | 82 +++++----------------------------------- 2 files changed, 11 insertions(+), 73 deletions(-) diff --git a/fem/src/ElementUtils.F90 b/fem/src/ElementUtils.F90 index 29599f1b2c..50c5614b12 100644 --- a/fem/src/ElementUtils.F90 +++ b/fem/src/ElementUtils.F90 @@ -2993,7 +2993,7 @@ FUNCTION NormalOfDegenerateElement(Model, Element ) RESULT ( Normal ) en % z( n ), STAT=istat ) IF( istat /= 0 ) THEN - CALL Fatal('ElementCharacteristicLengths','Allocation error for ElementNodes') + CALL Fatal('NormalOfDegenerateElement','Allocation error for ElementNodes') END IF en % x(1:n) = Model % Nodes % x(Element % NodeIndexes) diff --git a/fem/src/ListMatrix.F90 b/fem/src/ListMatrix.F90 index c820505a89..3f5656354c 100644 --- a/fem/src/ListMatrix.F90 +++ b/fem/src/ListMatrix.F90 @@ -772,7 +772,7 @@ SUBROUTINE List_ExchangeRowStructure( List,n1,n2 ) TYPE(ListMatrixEntry_t), POINTER :: CList1, CList2, Lptr IF ( .NOT. ASSOCIATED(List) ) THEN - CALL Warn('List_MoveRow','No List matrix present!') + CALL Warn('List_ExchangeRowStructure','No List matrix present!') RETURN END IF @@ -806,49 +806,17 @@ END SUBROUTINE List_ExchangeRowStructure - !------------------------------------------------------------------------------ - SUBROUTINE List_GlueLocalMatrix( A,N,Dofs,Indexes,LocalMatrix ) +!> Add the entries of a local matrix to a list-format matrix. !------------------------------------------------------------------------------ -!****************************************************************************** -! -! DESCRIPTION: -! Add a set of values (.i.e. element stiffness matrix) to a CRS format -! matrix. For this matrix the entries are ordered so that 1st for one -! dof you got all nodes, and then for second etc. -! -! ARGUMENTS: -! -! TYPE(Matrix_t) :: Lmat -! INOUT: Structure holding matrix, values are affected in the process -! -! INTEGER :: Nrow, Ncol -! INPUT: Number of nodes in element, or other dofs -! -! INTEGER :: row0, col0 -! INPUT: Offset of the matrix resulting from other blocks -! -! INTEGER :: row0, col0 -! INPUT: Offset of the matrix resulting from other blocks -! -! INTEGER :: RowInds, ColInds -! INPUT: Permutation of the rows and column dofs -! -! REAL(KIND=dp) :: LocalMatrix(:,:) -! INPUT: A (Nrow x RowDofs) x ( Ncol x ColDofs) matrix holding the values to be -! added to the CRS format matrix -! -!****************************************************************************** + SUBROUTINE List_GlueLocalMatrix( A,N,Dofs,Indexes,LocalMatrix ) !------------------------------------------------------------------------------ - - REAL(KIND=dp) :: LocalMatrix(:,:) - INTEGER :: N,DOFs, Indexes(:) TYPE(ListMatrix_t), POINTER :: A(:) - + INTEGER :: N,DOFs, Indexes(:) + REAL(KIND=dp) :: LocalMatrix(:,:) !------------------------------------------------------------------------------ ! Local variables !------------------------------------------------------------------------------ - REAL(KIND=dp) :: Value INTEGER :: i,j,k,l,c,Row,Col @@ -870,49 +838,19 @@ SUBROUTINE List_GlueLocalMatrix( A,N,Dofs,Indexes,LocalMatrix ) END SUBROUTINE List_GlueLocalMatrix !------------------------------------------------------------------------------ +!------------------------------------------------------------------------------ +!> Add the entries of a local matrix to a list-format matrix by allowing +!> offsets !------------------------------------------------------------------------------ SUBROUTINE List_GlueLocalSubMatrix( List,row0,col0,Nrow,Ncol, & RowInds,ColInds,RowDofs,ColDofs,LocalMatrix ) !------------------------------------------------------------------------------ -!****************************************************************************** -! -! DESCRIPTION: -! Add a set of values (.i.e. element stiffness matrix) to a CRS format -! matrix. For this matrix the entries are ordered so that 1st for one -! dof you got all nodes, and then for second etc. -! -! ARGUMENTS: -! -! TYPE(Matrix_t) :: Lmat -! INOUT: Structure holding matrix, values are affected in the process -! -! INTEGER :: Nrow, Ncol -! INPUT: Number of nodes in element, or other dofs -! -! INTEGER :: row0, col0 -! INPUT: Offset of the matrix resulting from other blocks -! -! INTEGER :: row0, col0 -! INPUT: Offset of the matrix resulting from other blocks -! -! INTEGER :: RowInds, ColInds -! INPUT: Permutation of the rows and column dofs -! -! REAL(KIND=dp) :: LocalMatrix(:,:) -! INPUT: A (Nrow x RowDofs) x ( Ncol x ColDofs) matrix holding the values to be -! added to the CRS format matrix -! -!****************************************************************************** -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: LocalMatrix(:,:) - TYPE(ListMatrix_t), POINTER :: List(:) + TYPE(ListMatrix_t), POINTER :: List(:) INTEGER :: Nrow,Ncol,RowDofs,ColDofs,Col0,Row0,RowInds(:),ColInds(:) - + REAL(KIND=dp) :: LocalMatrix(:,:) !------------------------------------------------------------------------------ ! Local variables !------------------------------------------------------------------------------ - REAL(KIND=dp) :: Value INTEGER :: i,j,k,l,c,Row,Col From 35c46e791ee5c27036797acbbe5b73414bae4178 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 6 Oct 2020 15:15:43 +0300 Subject: [PATCH 06/73] Shell-solid coupling with drilling rotations in a special case The procedure for shell-solid coupling is modified to allow the use of drilling rotation formulation in special cases where the shell director at the coupling boundary has only a z-component. The procedure needs a future update to allow an arbitrary orientation. --- fem/src/BlockSolve.F90 | 13 ++- fem/src/CRSMatrix.F90 | 2 +- fem/src/ListMatrix.F90 | 4 +- fem/src/SolverUtils.F90 | 105 ++++++++++++++++------- fem/tests/Shell_with_Solid_Beam/case.sif | 10 ++- 5 files changed, 96 insertions(+), 38 deletions(-) diff --git a/fem/src/BlockSolve.F90 b/fem/src/BlockSolve.F90 index 9871d6cc89..35d23d4ff5 100644 --- a/fem/src/BlockSolve.F90 +++ b/fem/src/BlockSolve.F90 @@ -1787,11 +1787,11 @@ SUBROUTINE StructureCouplingBlocks( Solver ) INTEGER :: i,j,k,ind1,ind2,Novar INTEGER, POINTER :: ConstituentSolvers(:) LOGICAL :: Found - TYPE(ValueList_t), POINTER :: Params + TYPE(ValueList_t), POINTER :: Params, ShellParams TYPE(Matrix_t), POINTER :: A_fs, A_sf, A_s, A_f TYPE(Variable_t), POINTER :: FVar, SVar LOGICAL :: IsPlate, IsShell, IsBeam, IsSolid, GotBlockSolvers - + LOGICAL :: DrillingDOFs Params => Solver % Values ConstituentSolvers => ListGetIntegerArray(Params, 'Block Solvers', GotBlockSolvers) @@ -1843,9 +1843,16 @@ SUBROUTINE StructureCouplingBlocks( Solver ) IF(.NOT. ASSOCIATED( FVar ) ) THEN CALL Fatal('StructureCouplingBlocks','Slave structure variable not present!') END IF + + IF (IsShell) THEN + ShellParams => CurrentModel % Solvers(ind2) % Values + DrillingDOFs = GetLogical(ShellParams, 'Drilling DOFs', Found) + ELSE + DrillingDOFs = .FALSE. + END IF CALL StructureCouplingAssembly( Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & - IsSolid, IsPlate, IsShell, IsBeam ) + IsSolid, IsPlate, IsShell, IsBeam, DrillingDOFs) !IF (IsShell) THEN ! CALL StructureCouplingAssembly_defutils( Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & ! IsSolid, IsPlate, IsShell, IsBeam) diff --git a/fem/src/CRSMatrix.F90 b/fem/src/CRSMatrix.F90 index 5a7094d2be..5a0ff6adcb 100644 --- a/fem/src/CRSMatrix.F90 +++ b/fem/src/CRSMatrix.F90 @@ -920,7 +920,7 @@ END SUBROUTINE InsertionSort !------------------------------------------------------------------------------ !> Add a set of values (.i.e. element stiffness matrix) to a CRS format !> matrix. For this matrix the entries are ordered so that first for one -!> dof you got all nodes, and then for second etc. There may be on offset +!> dof you got all nodes, and then for second etc. There may be an offset !> to the entries making the subroutine suitable for coupled monolithic !> matrix assembly. !------------------------------------------------------------------------------ diff --git a/fem/src/ListMatrix.F90 b/fem/src/ListMatrix.F90 index 3f5656354c..82ee7b1083 100644 --- a/fem/src/ListMatrix.F90 +++ b/fem/src/ListMatrix.F90 @@ -230,7 +230,7 @@ SUBROUTINE List_ToCRSMatrix(A) A % ListMatrix => NULL() A % FORMAT = MATRIX_CRS - CALL Info('List_ToCRSMatrix','Matrix format changed from List to CRS', Level=8) + CALL Info('List_ToCRSMatrix','Matrix format changed from List to CRS', Level=7) !------------------------------------------------------------------------------- END SUBROUTINE List_ToCRSMatrix @@ -308,7 +308,7 @@ SUBROUTINE List_ToListMatrix(A,Truncate) IF( ASSOCIATED( A % Cols ) ) DEALLOCATE( A % Cols ) IF( ASSOCIATED( A % Diag ) ) DEALLOCATE( A % Diag ) IF( ASSOCIATED( A % Values ) ) DEALLOCATE( A % Values ) - CALL Info('ListToCRSMatrix','Matrix format changed from CRS to List', Level=7) + CALL Info('List_ToListMatrix','Matrix format changed from CRS to List', Level=7) !------------------------------------------------------------------------------- END SUBROUTINE List_ToListMatrix !------------------------------------------------------------------------------- diff --git a/fem/src/SolverUtils.F90 b/fem/src/SolverUtils.F90 index 9c23ed44b4..fb12726157 100644 --- a/fem/src/SolverUtils.F90 +++ b/fem/src/SolverUtils.F90 @@ -17549,7 +17549,7 @@ END FUNCTION GetElementalDirectorInt !> tied up with the value of the first entry in the "Block Solvers" array. !------------------------------------------------------------------------------ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & - IsSolid, IsPlate, IsShell, IsBeam ) + IsSolid, IsPlate, IsShell, IsBeam, DrillingDOFs) !------------------------------------------------------------------------------ TYPE(Solver_t) :: Solver !< The leading solver defining block structure TYPE(Variable_t), POINTER :: FVar !< "Slave" structure variable @@ -17559,6 +17559,7 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & TYPE(Matrix_t), POINTER :: A_fs !< (2,1)-block for interaction TYPE(Matrix_t), POINTER :: A_sf !< (1,2)-block for interaction LOGICAL :: IsSolid, IsPlate, IsShell, IsBeam !< The type of the slave variable + LOGICAL :: DrillingDOFs !< Use drilling rotation formulation for shells !------------------------------------------------------------------------------ TYPE(Mesh_t), POINTER :: Mesh LOGICAL, POINTER :: ConstrainedF(:), ConstrainedS(:) @@ -17637,6 +17638,7 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & INTEGER, ALLOCATABLE :: NodeHits(:), InterfacePerm(:), InterfaceElems(:,:) INTEGER :: InterfaceN, hits INTEGER :: p,lf,ls,ii,jj,n,m,t + INTEGER :: MaxDOF REAL(KIND=dp), POINTER :: Director(:) REAL(KIND=dp), POINTER :: Basis(:), dBasisdx(:,:) REAL(KIND=dp), ALLOCATABLE :: A_f0(:) @@ -17658,8 +17660,20 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & NodeHits = 0 InterfacePerm = 0 - ! First, zero the rows related to directional derivative dofs, + ! First, in the basic case zero the rows related to directional derivative dofs, ! i.e. the components 4,5,6. "s" refers to solid and "f" to shell. + ! The implementation for drilling rotation formulation still assumes a special + ! orientation (d = E_Z) of the model. A possible generalization would depend on + ! the use of a local (normal-tangential) coordinate system with the sixth DOF + ! being the component along the director, so that this DOF would be determined + ! solely from the shell model without a coupling with the solid DOFs. + ! + IF (DrillingDOFs) THEN + MaxDOF = 5 + ELSE + MaxDOF = 6 + END IF + InterfaceN = 0 DO i=1,Mesh % NumberOfNodes jf = FPerm(i) @@ -17670,7 +17684,7 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & InterfaceN = InterfaceN + 1 InterfacePerm(i) = InterfaceN - DO lf = 4, 6 + DO lf = 4, MaxDOF kf = fdofs*(jf-1)+lf IF( ConstrainedF(kf) ) CYCLE @@ -17808,44 +17822,77 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & !PRINT *,'Director:',ShellElement % ElementIndex,jj,Director - DO lf = 4, 6 + DO lf = 4, MaxDOF kf = fdofs*(jf-1)+lf IF( ConstrainedF(kf) ) CYCLE - DO ls = 1, dim + IF (DrillingDOFs) THEN ! - ! Directional derivative dofs of the shell equations: - ! We try to enforce the condition d_{i+3}=-<(grad u)n,e_i> - ! where i=1,2,3; i+3=lf, n is director, e_i is unit vector, and - ! u is the displacement field of the solid. - DO p=1,n + ! In the case of drilling rotation formulation, the tangential components + ! trace of the global rotations ROT is related to the directional derivative + ! of the displacement field u by -Du[d] x d = d x ROT x d. This is still + ! a toylike implementation assuming a special orientation. + ! + + IF (ABS(1.0_dp - ABS(Director(3))) > 1.0d-5) CALL Fatal(Caller, & + 'Coupling with drilling rotation formulation needs a z-oriented director') + + DO p = 1,n js = SPerm(Indexes(p)) - ks = sdofs*(js-1)+lf-3 - val = Director(ls) * dBasisdx(p,ls) + SELECT CASE(lf) + CASE(4) + ks = sdofs*(js-1)+2 + val = Director(3) * dBasisdx(p,3) + CASE(5) + ks = sdofs*(js-1)+1 + val = -Director(3) * dBasisdx(p,3) + CASE DEFAULT + CALL Fatal(Caller, 'Non-tangential component?') + END SELECT CALL AddToMatrixElement(A_fs,kf,ks,weight*val) - - ! Here the idea is to distribute the implicit moments of the shell solver - ! to forces for the solid solver. So even though the stiffness matrix related to the - ! directional derivatives is nullified, the forces are not forgotten. - ! This part may be thought of as being based on two (Råback's) conjectures: - ! in the first place the Lagrange variable formulation should bring us to a symmetric - ! coefficient matrix and the values of Lagrange variables can be estimated as nodal - ! reactions obtained by performing a matrix-vector product. - ! - ! Note that no attempt is currently made to transfer external moment - ! loads of the shell model to loads of the coupled model. Likewise - ! rotational inertia terms of the shell model are not transformed - ! to inertia terms of the coupled model. Neglecting the rotational - ! inertia might be acceptable in many cases. - ! - ! Note that the minus sign of the entries is correct here: + DO k=A_f % Rows(kf),A_f % Rows(kf+1)-1 CALL AddToMatrixElement(A_sf,ks,A_f % Cols(k),-weight*val*A_f0(k)) END DO END DO - END DO + ELSE + ! + ! Directional derivative dofs D_{i+3} of the shell equations: + ! We try to enforce the condition D_{i+3}=-<(grad u)d,e_i> + ! where i=1,2,3; i+3=lf, d is director, e_i is unit vector, and + ! u is the displacement field of the solid. + ! + DO p = 1, n + js = SPerm(Indexes(p)) + ks = sdofs*(js-1)+lf-3 + DO ls = 1, dim + val = Director(ls) * dBasisdx(p,ls) + + CALL AddToMatrixElement(A_fs,kf,ks,weight*val) + + ! Here the idea is to distribute the implicit moments of the shell solver + ! to forces for the solid solver. So even though the stiffness matrix related to the + ! directional derivatives is nullified, the forces are not forgotten. + ! This part may be thought of as being based on two (Råback's) conjectures: + ! in the first place the Lagrange variable formulation should bring us to a symmetric + ! coefficient matrix and the values of Lagrange variables can be estimated as nodal + ! reactions obtained by performing a matrix-vector product. + ! + ! Note that no attempt is currently made to transfer external moment + ! loads of the shell model to loads of the coupled model. Likewise + ! rotational inertia terms of the shell model are not transformed + ! to inertia terms of the coupled model. Neglecting the rotational + ! inertia might be acceptable in many cases. + ! + ! Note that the minus sign of the entries is correct here: + DO k=A_f % Rows(kf),A_f % Rows(kf+1)-1 + CALL AddToMatrixElement(A_sf,ks,A_f % Cols(k),-weight*val*A_f0(k)) + END DO + END DO + END DO + END IF ! This should sum up to unity! CALL AddToMatrixElement(A_f,kf,kf,weight) diff --git a/fem/tests/Shell_with_Solid_Beam/case.sif b/fem/tests/Shell_with_Solid_Beam/case.sif index 4c98ba50b2..7fc6b0e643 100644 --- a/fem/tests/Shell_with_Solid_Beam/case.sif +++ b/fem/tests/Shell_with_Solid_Beam/case.sif @@ -10,9 +10,10 @@ ! the beam length. The solution of the simple beam theory is seen to be ! fairly closed to that of the more complicated coupled model. ! -! The original version P.R. 3.9.2019, -! modified to allow a comparison with the simple beam theory -! by M.M. June, 2020. +! The original version P.R. 3.9.2019; +! - modified to allow a comparison with the simple beam theory +! by M.M. June, 2020. +! - tested to work with the drilling rotation formulation (Oct 6, 2020) !------------------------------------------------------------ Header @@ -134,6 +135,9 @@ Solver 2 Equation = "Shell equations" Procedure = "ShellSolver" "ShellSolver" + Large Deflection = False + Drilling DOFs = False + ! Linear System Solver = "Direct" ! Linear System Direct Method = MUMPS From af2a73cba4649b1b51d31d78605482b099d6049b Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 7 Oct 2020 11:19:48 +0300 Subject: [PATCH 07/73] Shell-solid coupling with drilling rotations generalized The procedure for shell-solid coupling with the drilling rotation formulation is generalized so that it is now assumed that the shell director at the coupling boundary is aligned with one of the global coordinate axes. --- fem/src/SolverUtils.F90 | 126 +++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 39 deletions(-) diff --git a/fem/src/SolverUtils.F90 b/fem/src/SolverUtils.F90 index fb12726157..6c8b51252d 100644 --- a/fem/src/SolverUtils.F90 +++ b/fem/src/SolverUtils.F90 @@ -17616,13 +17616,13 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & IF( ASSOCIATED( A_s % MassValues ) ) THEN DoMass = .TRUE. ELSE - CALL Warn(Caller,'Both solid and shell should have MassValues!') + CALL Warn(Caller,'Both models should have MassValues!') END IF END IF DoDamp = ASSOCIATED( A_f % DampValues ) IF( DoDamp ) THEN - CALL Warn(Caller,'Damping matrix values at shell interface will be dropped!') + CALL Warn(Caller,'Damping matrix values at a coupling interface will be dropped!') END IF ! This is still under development and not used for anything @@ -17638,10 +17638,10 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & INTEGER, ALLOCATABLE :: NodeHits(:), InterfacePerm(:), InterfaceElems(:,:) INTEGER :: InterfaceN, hits INTEGER :: p,lf,ls,ii,jj,n,m,t - INTEGER :: MaxDOF + INTEGER :: NormalDir REAL(KIND=dp), POINTER :: Director(:) REAL(KIND=dp), POINTER :: Basis(:), dBasisdx(:,:) - REAL(KIND=dp), ALLOCATABLE :: A_f0(:) + REAL(KIND=dp), ALLOCATABLE :: A_f0(:), rhs0(:), Mass0(:) REAL(KIND=dp) :: u,v,w,weight,detJ,val REAL(KIND=dp) :: x, y, z @@ -17655,6 +17655,14 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & ! Memorize the original values ALLOCATE( A_f0( SIZE( A_f % Values ) ) ) A_f0 = A_f % Values + IF (DrillingDOFs) THEN + ALLOCATE(rhs0(SIZE(A_f % rhs))) + rhs0 = A_f % rhs + IF (DoMass) THEN + ALLOCATE(Mass0(SIZE(A_f % MassValues))) + Mass0 = A_f % MassValues + END IF + END IF ALLOCATE( NodeHits( Mesh % NumberOfNodes ), InterfacePerm( Mesh % NumberOfNodes ) ) NodeHits = 0 @@ -17662,18 +17670,7 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & ! First, in the basic case zero the rows related to directional derivative dofs, ! i.e. the components 4,5,6. "s" refers to solid and "f" to shell. - ! The implementation for drilling rotation formulation still assumes a special - ! orientation (d = E_Z) of the model. A possible generalization would depend on - ! the use of a local (normal-tangential) coordinate system with the sixth DOF - ! being the component along the director, so that this DOF would be determined - ! solely from the shell model without a coupling with the solid DOFs. ! - IF (DrillingDOFs) THEN - MaxDOF = 5 - ELSE - MaxDOF = 6 - END IF - InterfaceN = 0 DO i=1,Mesh % NumberOfNodes jf = FPerm(i) @@ -17684,7 +17681,7 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & InterfaceN = InterfaceN + 1 InterfacePerm(i) = InterfaceN - DO lf = 4, MaxDOF + DO lf = 4, 6 kf = fdofs*(jf-1)+lf IF( ConstrainedF(kf) ) CYCLE @@ -17822,7 +17819,8 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & !PRINT *,'Director:',ShellElement % ElementIndex,jj,Director - DO lf = 4, MaxDOF + + DO lf = 4, 6 kf = fdofs*(jf-1)+lf IF( ConstrainedF(kf) ) CYCLE @@ -17831,32 +17829,77 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & ! ! In the case of drilling rotation formulation, the tangential components ! trace of the global rotations ROT is related to the directional derivative - ! of the displacement field u by -Du[d] x d = d x ROT x d. This is still - ! a toylike implementation assuming a special orientation. + ! of the displacement field u by -Du[d] x d = d x ROT x d. This implementation + ! is limited to cases where the director is aligned with one of the global + ! coordinate axes. ! + NormalDir = 0 + IF (ABS(1.0_dp - ABS(Director(1))) < 1.0d-5) THEN + NormalDir = 1 + ELSE IF (ABS(1.0_dp - ABS(Director(2))) < 1.0d-5) THEN + NormalDir = 2 + ELSE IF (ABS(1.0_dp - ABS(Director(3))) < 1.0d-5) THEN + NormalDir = 3 + END IF + IF (NormalDir == 0) CALL Fatal(Caller, & + 'Coupling with drilling rotation formulation needs an axis-aligned director') + + IF ((lf-3) /= NormalDir) THEN + + DO p = 1,n + js = SPerm(Indexes(p)) + + IF (NormalDir == 1) THEN + SELECT CASE(lf) + CASE(5) + ks = sdofs*(js-1)+3 + val = dBasisdx(p,1) + CASE(6) + ks = sdofs*(js-1)+2 + val = -dBasisdx(p,1) + END SELECT + ELSE IF (NormalDir == 2) THEN + SELECT CASE(lf) + CASE(4) + ks = sdofs*(js-1)+3 + val = -dBasisdx(p,2) + CASE(6) + ks = sdofs*(js-1)+1 + val = dBasisdx(p,2) + END SELECT + ELSE IF (NormalDir == 3) THEN + SELECT CASE(lf) + CASE(4) + ks = sdofs*(js-1)+2 + val = dBasisdx(p,3) + CASE(5) + ks = sdofs*(js-1)+1 + val = -dBasisdx(p,3) + END SELECT + END IF - IF (ABS(1.0_dp - ABS(Director(3))) > 1.0d-5) CALL Fatal(Caller, & - 'Coupling with drilling rotation formulation needs a z-oriented director') - - DO p = 1,n - js = SPerm(Indexes(p)) - SELECT CASE(lf) - CASE(4) - ks = sdofs*(js-1)+2 - val = Director(3) * dBasisdx(p,3) - CASE(5) - ks = sdofs*(js-1)+1 - val = -Director(3) * dBasisdx(p,3) - CASE DEFAULT - CALL Fatal(Caller, 'Non-tangential component?') - END SELECT - - CALL AddToMatrixElement(A_fs,kf,ks,weight*val) + CALL AddToMatrixElement(A_fs,kf,ks,weight*val) + + DO k=A_f % Rows(kf),A_f % Rows(kf+1)-1 + CALL AddToMatrixElement(A_sf,ks,A_f % Cols(k),-weight*val*A_f0(k)) + END DO + END DO + ELSE + ! + ! Return one row of deleted values to the shell matrix + ! DO k=A_f % Rows(kf),A_f % Rows(kf+1)-1 - CALL AddToMatrixElement(A_sf,ks,A_f % Cols(k),-weight*val*A_f0(k)) + A_f % Values(k) = A_f0(k) + IF (DoMass) A_f % MassValues(k) = Mass0(k) END DO - END DO + + A_f % rhs(kf) = rhs0(kf) + + ! TO DO: Return also damp values if used + + END IF + ELSE ! ! Directional derivative dofs D_{i+3} of the shell equations: @@ -17900,7 +17943,12 @@ SUBROUTINE StructureCouplingAssembly(Solver, FVar, SVar, A_f, A_s, A_fs, A_sf, & END DO END DO DEALLOCATE( Basis, dBasisdx, Nodes % x, Nodes % y, Nodes % z ) - DEALLOCATE(A_f0, NodeHits, InterfacePerm) + DEALLOCATE(A_f0, NodeHits, InterfacePerm, InterfaceElems) + IF (DrillingDOFs) THEN + DEALLOCATE(rhs0) + IF (DoMass) DEALLOCATE(Mass0) + END IF + END BLOCK END IF From 19f43a957f66d6981bba1219ed0a5d77d6058d4a Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 7 Oct 2020 16:55:34 +0300 Subject: [PATCH 08/73] Mismatch warning also with no threads --- CMakeLists.txt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 676f137724..aa0e023521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -207,14 +207,8 @@ IF(WITH_OpenMP) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") # endif() -if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -std=legacy") -endif() - FIND_PACKAGE(OpenMP REQUIRED) - - # Test compiler support for OpenMP 4.0 features used INCLUDE(testOpenMP40) IF(CMAKE_Fortran_COMPILER_SUPPORTS_OPENMP40) @@ -229,6 +223,15 @@ endif() ENDIF() ENDIF() +# Get rid of the annoying rank mismatch warning +IF("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + IF(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.9) + SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -fallow-argument-mismatch") +# SET(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} -std=legacy") + ENDIF() +ENDIF() + + IF(WITH_MPI) # Advanced properties MARK_AS_ADVANCED( From 3cf407d83ed7046240d2c332eeb8e6a328231afd Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 7 Oct 2020 16:57:16 +0300 Subject: [PATCH 09/73] Increase minimum version of cmake to 3.0.2 --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa0e023521..ef8a7f8f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,9 @@ IF(NOT CMAKE_BUILD_TYPE) ENDIF() PROJECT(Elmer Fortran C CXX) -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) + +# CMAKE_VERSION seems to require this in minimum +CMAKE_MINIMUM_REQUIRED(VERSION 3.0.2) IF(APPLE) SET(CMAKE_MACOSX_RPATH 1) From 3ce106dff019bfb0458a172a8284b208f4a5681c Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 7 Oct 2020 17:17:31 +0300 Subject: [PATCH 10/73] flush the final message --- fem/src/Solver.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/fem/src/Solver.F90 b/fem/src/Solver.F90 index 1f2ea6f2ae..fb6fc0ed88 100644 --- a/fem/src/Solver.F90 +++ b/fem/src/Solver.F90 @@ -62,6 +62,7 @@ PROGRAM Solver CPUTime()-CT, RealTime()-RT DateStr = FormatDate() WRITE( *,'(A,A)' ) 'ELMER SOLVER FINISHED AT: ', TRIM(DateStr) + CALL FLUSH(6) END IF END IF END PROGRAM Solver From cf4949d8c16f7106a38a79d967410069845ab2a5 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 7 Oct 2020 17:23:13 +0300 Subject: [PATCH 11/73] Add STOP to end --- fem/src/Solver.F90 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fem/src/Solver.F90 b/fem/src/Solver.F90 index fb6fc0ed88..4b0464ecdd 100644 --- a/fem/src/Solver.F90 +++ b/fem/src/Solver.F90 @@ -65,6 +65,9 @@ PROGRAM Solver CALL FLUSH(6) END IF END IF + + STOP + END PROGRAM Solver ! ****************************************************************************** From 72eec109285ca37d45b5c039efed3b25a3c0bf58 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 7 Oct 2020 19:13:22 +0300 Subject: [PATCH 12/73] Fix to vector helmholtz menu --- .../Application/edf-extra/vectorhelmholtz.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml b/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml index 99c5d02bd4..144e0c4a75 100644 --- a/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml +++ b/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml @@ -36,24 +36,24 @@ Material properties - Inverse Relative Permeability + Relative Permittivity String - Give the inverse relative permeability compared to vacuum (real part). + Give the relative permittivity of medium (real part). - Inverse Relative Permeability im + Relative Permittivity im String - Give the inverse relative permeability compared to vacuum (imag part). + Give the relative permittivity of medium (imag part). - Relative Permittivity + Relative Reluctivity String - Give the relative permittivity of medium (real part). + Give the relative reluctivity of the medium (real part). - Relative Permittivity im + Relative Reluctivity im String - Give the relative permittivity of medium (imag part). + Give the relative reluctivity of the medium (imag part). From 78c44b8729636f57c5aea11b36865292d074a173 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 8 Oct 2020 14:09:34 +0300 Subject: [PATCH 13/73] Module SolidMechanicsUtils introduced to avoid copies of the same code The solvers BeamSolver3D and ShellSolver now use the same subroutines to get the local stiffness matrix for structural beams. There are also other subroutines which could be placed into the module SolidMechanicsUtils to avoid having copies within the different solid mechanics solvers. --- fem/src/CMakeLists.txt | 2 +- fem/src/SOLVER.KEYWORDS | 5 + fem/src/SolidMechanicsUtils.F90 | 484 +++++++++++++++++++++++++++++++ fem/src/modules/BeamSolver3D.F90 | 336 +-------------------- fem/src/modules/ShellSolver.F90 | 442 +--------------------------- 5 files changed, 495 insertions(+), 774 deletions(-) create mode 100644 fem/src/SolidMechanicsUtils.F90 diff --git a/fem/src/CMakeLists.txt b/fem/src/CMakeLists.txt index 5c99a09e25..dfbb76cb6b 100644 --- a/fem/src/CMakeLists.txt +++ b/fem/src/CMakeLists.txt @@ -42,7 +42,7 @@ SET(solverlib_SOURCES AddrFunc.F90 NavierStokes.F90 NavierStokesGeneral.F90 cholmod.c InterpolateMeshToMesh.F90 InterpVarToVar.F90 LinearForms.F90 H1Basis.F90 CircuitUtils.F90 BackwardError.F90 ElmerSolver.F90 MagnetoDynamicsUtils.F90 ComponentUtils.F90 - ZirkaHysteresis.F90) + ZirkaHysteresis.F90 SolidMechanicsUtils.F90) SET_PROPERTY(SOURCE MaxwellAxiS.F90 PROPERTY COMPILE_DEFINITIONS FULL_INDUCTION) diff --git a/fem/src/SOLVER.KEYWORDS b/fem/src/SOLVER.KEYWORDS index 71eb4fc54a..8f306323b6 100644 --- a/fem/src/SOLVER.KEYWORDS +++ b/fem/src/SOLVER.KEYWORDS @@ -579,7 +579,9 @@ Material:Real: 'Convection Velocity 1' Material:Real: 'Convection Velocity 2' Material:Real: 'Convection Velocity 3' Material:Real: 'Critical Shear Rate' +Material:Real: 'Cross Section Area' Material:Real: 'Damping' +Material:Real: 'Director' Material:Real: 'Rayleigh Damping Alpha' Material:Real: 'Rayleigh Damping Beta' Material:Real: 'Density' @@ -671,6 +673,8 @@ Material:Real: 'Reluctivity' Material:Real: 'Residual Water Content' Material:Real: 'Saturated Hydraulic Conductivity' Material:Real: 'Saturated Water Content' +Material:Real: 'Second Moment of Area 2' +Material:Real: 'Second Moment of Area 3' Material:Real: 'Smagorinsky Constant' Material:Real: 'Sound Damping' Material:Real: 'Sound Reaction Damping' @@ -689,6 +693,7 @@ Material:Real: 'Tangent Velocity 2' Material:Real: 'Tangent Velocity 3' Material:Real: 'Tension' Material:Real: 'Thickness' +Material:Real: 'Torsional Constant' Material:Real: 'Transmissivity' Material:Real: 'Viscosity Difference' Material:Real: 'Viscosity Exponent' diff --git a/fem/src/SolidMechanicsUtils.F90 b/fem/src/SolidMechanicsUtils.F90 new file mode 100644 index 0000000000..9003bf6027 --- /dev/null +++ b/fem/src/SolidMechanicsUtils.F90 @@ -0,0 +1,484 @@ +!/*****************************************************************************/ +! * +! * Elmer, A Finite Element Software for Multiphysical Problems +! * +! * Copyright 1st April 1995 - , CSC - IT Center for Science Ltd., Finland +! * +! * This library is free software; you can redistribute it and/or +! * modify it under the terms of the GNU Lesser General Public +! * License as published by the Free Software Foundation; either +! * version 2.1 of the License, or (at your option) any later version. +! * +! * This library is distributed in the hope that it will be useful, +! * but WITHOUT ANY WARRANTY; without even the implied warranty of +! * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +! * Lesser General Public License for more details. +! * +! * You should have received a copy of the GNU Lesser General Public +! * License along with this library (in file ../LGPL-2.1); if not, write +! * to the Free Software Foundation, Inc., 51 Franklin Street, +! * Fifth Floor, Boston, MA 02110-1301 USA +! * +! *****************************************************************************/ +! +!/****************************************************************************** +! * +! * Some common subroutines for the solvers of solid mechanics +! * +! * Authors: Mika Malinen +! * Email: mika.malinen@csc.fi +! * Web: http://www.csc.fi/elmer +! * Address: CSC - IT Center for Science Ltd. +! * Keilaranta 14 +! * 02101 Espoo, Finland +! * +! * Original Date: Oct 8, 2020 +! * +! *****************************************************************************/ + +MODULE SolidMechanicsUtils + + USE DefUtils + IMPLICIT NONE + +CONTAINS + +!------------------------------------------------------------------------------ +!> Integrate and assemble the local stiffness matrix corresponding to the +!> one-dimensional Timoshenko beam equations. The local DOFs always +!> correspond to the displacement components along the tangent direction and the +!> principal axes of the cross section. The transformation to global DOFs is done +!> within this subroutine. The stiffness matrix K corresponding to the global +!> DOFs is thus obtained as K = R^T k R and the RHS vector F is obtained as +!> F = R^T f. +!------------------------------------------------------------------------------ + SUBROUTINE BeamStiffnessMatrix(Element, n, nd, nb, TransientSimulation, & + MassAssembly, HarmonicAssembly, LargeDeflection, LocalSol, RHSForce, & + CombineWithShell) +!------------------------------------------------------------------------------ + IMPLICIT NONE + TYPE(Element_t), POINTER, INTENT(IN) :: Element + INTEGER, INTENT(IN) :: n, nd, nb + LOGICAL, INTENT(IN) :: TransientSimulation + LOGICAL, INTENT(IN) :: MassAssembly ! To activate mass matrix integration + LOGICAL, OPTIONAL, INTENT(IN) :: HarmonicAssembly ! To activate the global mass matrix updates + LOGICAL, OPTIONAL, INTENT(IN) :: LargeDeflection ! To activate nonlinear terms + REAL(KIND=dp), OPTIONAL, INTENT(IN) :: LocalSol(:,:) ! The previous solution iterate + REAL(KIND=dp), OPTIONAL, INTENT(OUT) :: RHSForce(:) ! Local RHS vector corresponding to external loads + LOGICAL, OPTIONAL, INTENT(IN) :: CombineWithShell ! Set .TRUE. if the caller is the shell solver +!------------------------------------------------------------------------------ + TYPE(ValueList_t), POINTER :: BodyForce, Material + TYPE(Nodes_t) :: Nodes, LocalNodes + TYPE(GaussIntegrationPoints_t) :: IP + + LOGICAL :: Found, Stat + LOGICAL :: NonlinAssembly + + INTEGER :: DOFs + INTEGER :: i, t, p, q + INTEGER :: i0, p0, q0 + + REAL(KIND=dp), POINTER :: ArrayPtr(:,:) => NULL() + REAL(KIND=dp), POINTER :: StiffBlock(:,:), MassBlock(:,:) + REAL(KIND=dp), DIMENSION(3), PARAMETER :: ZBasis = (/ 0.0d0, 0.0d0, 0.1d1 /) + + REAL(KIND=dp), TARGET :: Mass(6*nd,6*nd), Stiff(6*nd,6*nd), Damp(6*nd,6*nd) + REAL(KIND=dp) :: Force(6*nd) + REAL(KIND=dp) :: RBlock(3,3), R(6*nd,6*nd) + REAL(KIND=dp) :: Basis(nd), dBasis(nd,3), DetJ, Weight + REAL(KIND=dp) :: Youngs_Modulus(n), Shear_Modulus(n), Area(n), Density(n) + REAL(KIND=dp) :: Torsional_Constant(n) + REAL(KIND=dp) :: Area_Moment_2(n), Area_Moment_3(n) + REAL(KIND=dp) :: Mass_Inertia_Moment(n) + REAL(KIND=dp) :: Load(3,n), f(3) + REAL(KIND=dp) :: PrevSolVec(6*nd) + REAL(KIND=dp) :: E, A, G, rho + REAL(KIND=dp) :: EA, GA, MOI, Mass_per_Length + REAL(KIND=dp) :: E_diag(3) + + REAL(KIND=dp) :: p1(3), p2(3), e1(3), e2(3), e3(3) + REAL(KIND=dp) :: L, Norm + + SAVE Nodes, LocalNodes +!------------------------------------------------------------------------------ + IF (n > 2) CALL Fatal('BeamStiffnessMatrix', & + 'Only 2-node background meshes supported currently') + + DOFs = 6 +! dim = CoordinateSystemDimension() + + CALL GetElementNodes(Nodes) + + Mass = 0.0_dp + Stiff = 0.0_dp + Damp = 0.0_dp + Force = 0.0_dp + + IF (PRESENT(RHSForce)) RHSForce = 0.0d0 + IF (PRESENT(LargeDeflection)) THEN + NonlinAssembly = LargeDeflection + ELSE + NonlinAssembly = .FALSE. + END IF + IF (NonlinAssembly) THEN + IF (.NOT. PRESENT(LocalSol)) CALL Fatal('BeamStiffnessMatrix', & + 'Previous solution iterate needed') + DO i=1,DOFs + PrevSolVec(i:DOFs*(nd-nb):DOFs) = LocalSol(i,1:(nd-nb)) + END DO + END IF + + + BodyForce => GetBodyForce() + IF ( ASSOCIATED(BodyForce) ) THEN + ! + ! Force components refer to the basis of the global frame: + ! + Load(1,1:n) = GetReal(BodyForce, 'Body Force 1', Found) + Load(2,1:n) = GetReal(BodyForce, 'Body Force 2', Found) + Load(3,1:n) = GetReal(BodyForce, 'Body Force 3', Found) + ELSE + Load = 0.0_dp + END IF + + Material => GetMaterial() + Youngs_Modulus(1:n) = GetReal(Material, 'Youngs Modulus', Found) + Shear_Modulus(1:n) = GetReal(Material, 'Shear Modulus', Found) + Area(1:n) = GetReal(Material, 'Cross Section Area', Found) + Torsional_Constant(1:n) = GetReal(Material, 'Torsional Constant', Found) + Area_Moment_2(1:n) = GetReal(Material, 'Second Moment of Area 2', Found) + Area_Moment_3(1:n) = GetReal(Material, 'Second Moment of Area 3', Found) + + IF (MassAssembly) THEN + Density(1:n) = GetReal(Material, 'Density', Found) + END IF + + ! + ! Compute the tangent vector e1 to the beam axis: + ! + p1(1) = Nodes % x(1) + p1(2) = Nodes % y(1) + p1(3) = Nodes % z(1) + p2(1) = Nodes % x(2) + p2(2) = Nodes % y(2) + p2(3) = Nodes % z(2) + e1 = p2 - p1 + L = SQRT(SUM(e1(:)**2)) + e1 = 1.0_dp/L * e1 + ! + ! Cross section parameters are given with respect to a local frame. + ! Determine its orientation: + ! + ArrayPtr => ListGetConstRealArray(Material, 'Director', Found) + IF (Found) THEN + e3 = 0.0d0 + DO i=1,SIZE(ArrayPtr,1) + e3(i) = ArrayPtr(i,1) + END DO + Norm = SQRT(SUM(e3(:)**2)) + e3 = 1.0_dp/Norm * e3 + IF (ABS(DOT_PRODUCT(e1,e3)) > 100.0_dp * AEPS) CALL Fatal('BeamStiffnessMatrix', & + 'Director should be orthogonal to the beam axis') + e2 = CrossProduct(e3, e1) + ELSE + ArrayPtr => ListGetConstRealArray(Material, 'Principal Direction 2', Found) + IF (Found) THEN + e2 = 0.0d0 + DO i=1,SIZE(ArrayPtr,1) + e2(i) = ArrayPtr(i,1) + END DO + Norm = SQRT(SUM(e2(:)**2)) + e2 = 1.0_dp/Norm * e2 + ELSE + e2 = -ZBasis + END IF + IF (ABS(DOT_PRODUCT(e1,e2)) > 100.0_dp * AEPS) CALL Fatal('BeamStiffnessMatrix', & + 'Principal Direction 2 should be orthogonal to the beam axis') + e3 = CrossProduct(e1, e2) + END IF + + + ! + ! Allocate an additional variable so as to write nodes data with respect to + ! the local frame. + ! + IF (.NOT. ASSOCIATED(LocalNodes % x)) THEN + ALLOCATE(LocalNodes % x(n), LocalNodes % y(n), LocalNodes % z(n) ) + LocalNodes % NumberOfNodes = n + LocalNodes % y(:) = 0.0_dp + LocalNodes % z(:) = 0.0_dp + END IF + LocalNodes % x(1) = 0.0d0 + LocalNodes % x(2) = L + + !----------------------- + ! Numerical integration: + !----------------------- + IF (.NOT. IsPElement(Element) .AND. nd > n) THEN + IP = GaussPoints(Element, 3) + ELSE + IP = GaussPoints(Element) + END IF + + DO t=1,IP % n + !-------------------------------------------------------------- + ! Basis function values & derivatives at the integration point: + !-------------------------------------------------------------- + stat = ElementInfo(Element, LocalNodes, IP % U(t), IP % V(t), & + IP % W(t), detJ, Basis, dBasis) + + ! Create a bubble if the element is the standard 2-node element: + IF (.NOT. IsPElement(Element) .AND. nd > n) THEN + Basis(n+1) = Basis(1) * Basis(2) + dBasis(3,:) = dBasis(1,:) * Basis(2) + Basis(1) * dBasis(2,:) + END IF + + !------------------------------------------ + ! The model data at the integration point: + !------------------------------------------ + f(1) = SUM(Basis(1:n) * Load(1,1:n)) + f(2) = SUM(Basis(1:n) * Load(2,1:n)) + f(3) = SUM(Basis(1:n) * Load(3,1:n)) + + ! TO DO: Add option to give the applied moment load + + E = SUM(Basis(1:n) * Youngs_Modulus(1:n)) + G = SUM(Basis(1:n) * Shear_Modulus(1:n)) + A = SUM(Basis(1:n) * Area(1:n)) + + E_diag(1) = G * SUM(Basis(1:n) * Torsional_Constant(1:n)) + E_diag(2) = E * SUM(Basis(1:n) * Area_Moment_2(1:n)) + E_diag(3) = E * SUM(Basis(1:n) * Area_Moment_3(1:n)) + + IF (MassAssembly) THEN + rho = SUM(Basis(1:n) * Density(1:n)) + MOI = rho/E * sqrt(E_diag(2)**2 + E_diag(3)**2) + Mass_per_Length = rho * A + END IF + + GA = G*A + EA = E*A + + ! TO DO: Add option to give shear correction factors + + Weight = IP % s(t) * DetJ + + DO p=1,nd + p0 = (p-1)*DOFs + DO q=1,nd + q0 = (q-1)*DOFs + StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) + MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) + ! + ! (Du',v'): + ! + StiffBlock(1,1) = StiffBlock(1,1) + & + EA * dBasis(q,1) * dBasis(p,1) * Weight + StiffBlock(2,2) = StiffBlock(2,2) + & + GA * dBasis(q,1) * dBasis(p,1) * Weight + StiffBlock(3,3) = StiffBlock(3,3) + & + GA * dBasis(q,1) * dBasis(p,1) * Weight + + IF (MassAssembly) THEN + MassBlock(1,1) = MassBlock(1,1) + & + Mass_per_Length * Basis(q) * Basis(p) * Weight + MassBlock(2,2) = MassBlock(2,2) + & + Mass_per_Length * Basis(q) * Basis(p) * Weight + MassBlock(3,3) = MassBlock(3,3) + & + Mass_per_Length * Basis(q) * Basis(p) * Weight + END IF + + IF (q > n) CYCLE + ! + ! -(D theta x t,v'): + ! + StiffBlock(2,6) = StiffBlock(2,6) - & + GA * Basis(q) * dBasis(p,1) * Weight + StiffBlock(3,5) = StiffBlock(3,5) + & + GA * Basis(q) * dBasis(p,1) * Weight + END DO + + Force(p0+1) = Force(p0+1) + Weight * DOT_PRODUCT(f,e1)* Basis(p) + Force(p0+2) = Force(p0+2) + Weight * DOT_PRODUCT(f,e2)* Basis(p) + Force(p0+3) = Force(p0+3) + Weight * DOT_PRODUCT(f,e3)* Basis(p) + + IF (p > n) CYCLE + + DO q=1,nd + q0 = (q-1)*DOFs + StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) + MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) + ! + ! -(D u',psi x t): + ! + StiffBlock(5,3) = StiffBlock(5,3) + & + GA * Basis(p) * dBasis(q,1) * Weight + StiffBlock(6,2) = StiffBlock(6,2) - & + GA * Basis(p) * dBasis(q,1) * Weight + + IF (q > n) CYCLE + + ! + ! (E theta',psi') + (D theta x t,psi x t): + ! + StiffBlock(4,4) = StiffBlock(4,4) + & + E_diag(1) * dBasis(q,1) * dBasis(p,1) * Weight + StiffBlock(5,5) = StiffBlock(5,5) + & + E_diag(2) * dBasis(q,1) * dBasis(p,1) * Weight + & + GA * Basis(p) * Basis(q) * Weight + StiffBlock(6,6) = StiffBlock(6,6) + & + E_diag(3) * dBasis(q,1) * dBasis(p,1) * Weight + & + GA * Basis(p) * Basis(q) * Weight + + IF (MassAssembly) THEN + MassBlock(4,4) = MassBlock(4,4) + MOI * Basis(q) * Basis(p) * Weight + MassBlock(5,5) = MassBlock(5,5) + rho/E * E_diag(2) * & + Basis(q) * Basis(p) * Weight + MassBlock(6,6) = MassBlock(6,6) + rho/E * E_diag(3) * & + Basis(q) * Basis(p) * Weight + END IF + + END DO + END DO + END DO + + CALL BeamCondensate(nd-nb, nb, DOFs, 3, Stiff, Force) + + IF (PRESENT(CombineWithShell)) THEN + IF (CombineWithShell) THEN + ! + ! Switch to rotation variables which conform with the rotated moments - M x d: + ! + R = 0.0d0 + DO i=1,nd-nb + i0 = (i-1)*DOFs + R(i0+1,i0+1) = 1.0d0 + R(i0+2,i0+2) = 1.0d0 + R(i0+3,i0+3) = 1.0d0 + R(i0+4,i0+5) = 1.0d0 + R(i0+5,i0+4) = -1.0d0 + R(i0+6,i0+6) = 1.0d0 + END DO + DOFs = (nd-nb)*DOFs + Stiff(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & + MATMUL(Stiff(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) + Force(1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)),Force(1:DOFs)) + + IF (MassAssembly) & + Mass(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & + MATMUL(Mass(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) + + ! + ! The moment around the director is not compatible with the shell model. + ! Remove its contribution: + ! + DO p=1,nd-nb + Stiff(6*p,:) = 0.0d0 + Stiff(:,6*p) = 0.0d0 + Stiff(6*p,6*p) = 0.0d0 + Force(6*p) = 0.0d0 + Mass(6*p,:) = 0.0d0 + Mass(:,6*p) = 0.0d0 + END DO + END IF + END IF + + ! + ! Build the transformation matrix in order to switch to the global DOFs + ! + DOFs = 6 + R = 0.0d0 + RBlock(1,1:3) = e1(1:3) + RBlock(2,1:3) = e2(1:3) + RBlock(3,1:3) = e3(1:3) + DO i=1,nd-nb + i0 = (i-1)*DOFs + R(i0+1:i0+3,i0+1:i0+3) = RBlock(1:3,1:3) + R(i0+4:i0+6,i0+4:i0+6) = RBlock(1:3,1:3) + END DO + + !------------------------------------------------------- + ! Transform to the global DOFs: + !------------------------------------------------------- + DOFs = (nd-nb)*DOFs + Stiff(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & + MATMUL(Stiff(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) + Force(1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)),Force(1:DOFs)) + + IF (PRESENT(RHSForce)) RHSForce(1:DOFs) = Force(1:DOFs) + IF (NonlinAssembly) Force(1:DOFs) = Force(1:DOFs) - & + MATMUL(Stiff(1:DOFs,1:DOFs), PrevSolVec(1:DOFs)) + + IF (MassAssembly) THEN + Mass(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & + MATMUL(Mass(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) + IF (TransientSimulation) THEN + CALL Default2ndOrderTime(Mass, Damp, Stiff, Force) + ELSE IF (PRESENT(HarmonicAssembly)) THEN + IF (HarmonicAssembly) CALL DefaultUpdateMass(Mass) + END IF + END IF + + CALL DefaultUpdateEquations(Stiff, Force) +!------------------------------------------------------------------------------ + END SUBROUTINE BeamStiffnessMatrix +!------------------------------------------------------------------------------ + + +!------------------------------------------------------------------------------ + SUBROUTINE BeamCondensate(n, nb, dofs, dim, K, F, F1 ) +!------------------------------------------------------------------------------ + USE LinearAlgebra + IMPLICIT NONE + INTEGER, INTENT(IN) :: n ! Nodes after condensation + INTEGER, INTENT(IN) :: nb ! The number of bubble basis functions + INTEGER, INTENT(IN) :: dofs ! DOFs per node + INTEGER, INTENT(IN) :: dim ! The first dim fields have bubbles + REAL(KIND=dp), INTENT(INOUT) :: K(:,:) ! The stiffness matrix + REAL(KIND=dp), INTENT(INOUT) :: F(:) ! The RHS vector + REAL(KIND=dp), OPTIONAL, INTENT(INOUT) :: F1(:) ! Some other RHS vector +!------------------------------------------------------------------------------ + REAL(KIND=dp) :: Kbl(nb*dim,n*dofs), Kbb(nb*dim,nb*dim), Fb(nb*dim) + REAL(KIND=dp) :: Klb(n*dofs,nb*dim) + + INTEGER :: i, m, p, Cdofs(dofs*n), Bdofs(dim*nb) +!------------------------------------------------------------------------------ + + Cdofs(1:n*dofs) = (/ (i, i=1,n*dofs) /) + + m = 0 + DO p = 1,nb + DO i = 1,dim + m = m + 1 + Bdofs(m) = dofs*(n+p-1) + i + END DO + END DO + + Kbb = K(Bdofs,Bdofs) + Kbl = K(Bdofs,Cdofs) + Klb = K(Cdofs,Bdofs) + Fb = F(Bdofs) + + CALL InvertMatrix( Kbb,nb*dim ) + + F(1:dofs*n) = F(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) + K(1:dofs*n,1:dofs*n) = & + K(1:dofs*n,1:dofs*n) - MATMUL( Klb, MATMUL( Kbb,Kbl ) ) + + IF (PRESENT(F1)) THEN + Fb = F1(Bdofs) + F1(1:dofs*n) = F1(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) + END IF +!------------------------------------------------------------------------------ + END SUBROUTINE BeamCondensate +!------------------------------------------------------------------------------ + + + ! This module could contain common implementations for + ! - SUBROUTINE ElasticityMatrix + ! - SUBROUTINE StrainEnergyDensity + ! - SUBROUTINE ShearCorrectionFactor + ! etc. + +END MODULE SolidMechanicsUtils + diff --git a/fem/src/modules/BeamSolver3D.F90 b/fem/src/modules/BeamSolver3D.F90 index 5f01bdcd73..9dff25dc22 100644 --- a/fem/src/modules/BeamSolver3D.F90 +++ b/fem/src/modules/BeamSolver3D.F90 @@ -85,6 +85,7 @@ END SUBROUTINE TimoshenkoSolver_Init0 SUBROUTINE TimoshenkoSolver(Model, Solver, dt, TransientSimulation) !------------------------------------------------------------------------------ USE DefUtils + USE SolidMechanicsUtils IMPLICIT NONE !------------------------------------------------------------------------------ @@ -126,7 +127,8 @@ SUBROUTINE TimoshenkoSolver(Model, Solver, dt, TransientSimulation) nd = GetElementNOFDOFs() nb = GetElementNOFBDOFs() - CALL LocalMatrix(Element, n, nd+nb, nb, TransientSimulation) + CALL BeamStiffnessMatrix(Element, n, nd+nb, nb, TransientSimulation, & + MassAssembly=TransientSimulation) END DO CALL DefaultFinishBulkAssembly() @@ -144,338 +146,6 @@ SUBROUTINE TimoshenkoSolver(Model, Solver, dt, TransientSimulation) END DO CALL DefaultFinish() - -CONTAINS - -!------------------------------------------------------------------------------ -! Integrate and assemble the local stiffness matrix. The local DOFs always -! correspond to the displacement components along the tangent direction and the -! principal axes of the cross section. The transformation to global DOFs is done -! within this subroutine. The stiffness matrix K corresponding to the global -! DOFs is thus obtained as K = R^T k R and the RHS vector F is obtained as -! F = R^T f. -!------------------------------------------------------------------------------ - SUBROUTINE LocalMatrix(Element, n, nd, nb, TransientSimulation) -!------------------------------------------------------------------------------ - IMPLICIT NONE - TYPE(Element_t), POINTER, INTENT(IN) :: Element - INTEGER, INTENT(IN) :: n, nd, nb - LOGICAL, INTENT(IN) :: TransientSimulation -!------------------------------------------------------------------------------ - TYPE(ValueList_t), POINTER :: BodyForce, Material - TYPE(Nodes_t) :: Nodes, LocalNodes - TYPE(GaussIntegrationPoints_t) :: IP - - LOGICAL :: Found, Stat - - INTEGER :: DOFs - INTEGER :: i, t, p, q - INTEGER :: i0, p0, q0 - - REAL(KIND=dp), POINTER :: ArrayPtr(:,:) => NULL() - REAL(KIND=dp), POINTER :: StiffBlock(:,:), MassBlock(:,:) - REAL(KIND=dp), DIMENSION(3), PARAMETER :: ZBasis = (/ 0.0d0, 0.0d0, 0.1d1 /) - - REAL(KIND=dp), TARGET :: Mass(6*nd,6*nd), Stiff(6*nd,6*nd), Damp(6*nd,6*nd) - REAL(KIND=dp) :: Force(6*nd) - REAL(KIND=dp) :: RBlock(3,3), R(6*nd,6*nd) - REAL(KIND=dp) :: Basis(nd), dBasis(nd,3), DetJ, Weight - REAL(KIND=dp) :: Youngs_Modulus(n), Shear_Modulus(n), Area(n), Density(n) - REAL(KIND=dp) :: Torsional_Constant(n) - REAL(KIND=dp) :: Area_Moment_2(n), Area_Moment_3(n) - REAL(KIND=dp) :: Mass_Inertia_Moment(n) - REAL(KIND=dp) :: Load(3,n), f(3) - REAL(KIND=dp) :: E, A, G, rho - REAL(KIND=dp) :: EA, GA, MOI, Mass_per_Length - REAL(KIND=dp) :: E_diag(3) - - REAL(KIND=dp) :: p1(3), p2(3), e1(3), e2(3), e3(3) - REAL(KIND=dp) :: L, Norm - - SAVE Nodes, LocalNodes -!------------------------------------------------------------------------------ - IF (n > 2) CALL Fatal('BeamSolver3D', & - 'Only 2-node background meshes supported currently') - - DOFs = 6 -! dim = CoordinateSystemDimension() - - CALL GetElementNodes(Nodes) - - Mass = 0.0_dp - Stiff = 0.0_dp - Damp = 0.0_dp - Force = 0.0_dp - - BodyForce => GetBodyForce() - IF ( ASSOCIATED(BodyForce) ) THEN - ! - ! Force components refer to the basis of the global frame: - ! - Load(1,1:n) = GetReal(BodyForce, 'Body Force 1', Found) - Load(2,1:n) = GetReal(BodyForce, 'Body Force 2', Found) - Load(3,1:n) = GetReal(BodyForce, 'Body Force 3', Found) - ELSE - Load = 0.0_dp - END IF - - Material => GetMaterial() - Youngs_Modulus(1:n) = GetReal(Material, 'Youngs Modulus', Found) - Shear_Modulus(1:n) = GetReal(Material, 'Shear Modulus', Found) - Area(1:n) = GetReal(Material, 'Cross Section Area', Found) - Torsional_Constant(1:n) = GetReal(Material, 'Torsional Constant', Found) - Area_Moment_2(1:n) = GetReal(Material, 'Second Moment of Area 2', Found) - Area_Moment_3(1:n) = GetReal(Material, 'Second Moment of Area 3', Found) - - IF (TransientSimulation) THEN - Density(1:n) = GetReal(Material, 'Density', Found) - END IF - - ! - ! Compute the tangent vector e1 to the beam axis: - ! - p1(1) = Nodes % x(1) - p1(2) = Nodes % y(1) - p1(3) = Nodes % z(1) - p2(1) = Nodes % x(2) - p2(2) = Nodes % y(2) - p2(3) = Nodes % z(2) - e1 = p2 - p1 - L = SQRT(SUM(e1(:)**2)) - e1 = 1.0_dp/L * e1 - ! - ! Cross section parameters are given with respect to a local frame. - ! Determine its orientation: - ! - ArrayPtr => ListGetConstRealArray(Material, 'Principal Direction 2', Found) - IF (Found) THEN - e2 = 0.0d0 - DO i=1,SIZE(ArrayPtr,1) - e2(i) = ArrayPtr(i,1) - END DO - Norm = SQRT(SUM(e2(:)**2)) - e2 = 1.0_dp/Norm * e2 - ELSE - e2 = -ZBasis - END IF - IF (ABS(DOT_PRODUCT(e1,e2)) > 100.0_dp * AEPS) CALL Fatal('BeamSolver3D', & - 'Principal Direction 2 should be orthogonal to the beam axis') - e3 = CrossProduct(e1, e2) - - ! - ! Build the transformation matrix in order to switch to the global DOFs - ! - R = 0.0d0 - RBlock(1,1:3) = e1(1:3) - RBlock(2,1:3) = e2(1:3) - RBlock(3,1:3) = e3(1:3) - DO i=1,nd-nb - i0 = (i-1)*DOFs - R(i0+1:i0+3,i0+1:i0+3) = RBlock(1:3,1:3) - R(i0+4:i0+6,i0+4:i0+6) = RBlock(1:3,1:3) - END DO - - ! - ! Allocate an additional variable so as to write nodes data with respect to - ! the local frame. - ! - IF (.NOT. ASSOCIATED(LocalNodes % x)) THEN - ALLOCATE(LocalNodes % x(n), LocalNodes % y(n), LocalNodes % z(n) ) - LocalNodes % NumberOfNodes = n - LocalNodes % y(:) = 0.0_dp - LocalNodes % z(:) = 0.0_dp - END IF - LocalNodes % x(1) = 0.0d0 - LocalNodes % x(2) = L - - !----------------------- - ! Numerical integration: - !----------------------- - IP = GaussPoints( Element ) - DO t=1,IP % n - !-------------------------------------------------------------- - ! Basis function values & derivatives at the integration point: - !-------------------------------------------------------------- - stat = ElementInfo(Element, LocalNodes, IP % U(t), IP % V(t), & - IP % W(t), detJ, Basis, dBasis) - - !------------------------------------------ - ! The model data at the integration point: - !------------------------------------------ - f(1) = SUM(Basis(1:n) * Load(1,1:n)) - f(2) = SUM(Basis(1:n) * Load(2,1:n)) - f(3) = SUM(Basis(1:n) * Load(3,1:n)) - - ! TO DO: Add option to give the applied moment load - - E = SUM(Basis(1:n) * Youngs_Modulus(1:n)) - G = SUM(Basis(1:n) * Shear_Modulus(1:n)) - A = SUM(Basis(1:n) * Area(1:n)) - - E_diag(1) = G * SUM(Basis(1:n) * Torsional_Constant(1:n)) - E_diag(2) = E * SUM(Basis(1:n) * Area_Moment_2(1:n)) - E_diag(3) = E * SUM(Basis(1:n) * Area_Moment_3(1:n)) - - IF (TransientSimulation) THEN - rho = SUM(Basis(1:n) * Density(1:n)) - MOI = rho/E * sqrt(E_diag(2)**2 + E_diag(3)**2) - Mass_per_Length = rho * A - END IF - - GA = G*A - EA = E*A - - ! TO DO: Add option to give shear correction factors - - Weight = IP % s(t) * DetJ - - DO p=1,nd - p0 = (p-1)*DOFs - DO q=1,nd - q0 = (q-1)*DOFs - StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) - MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) - ! - ! (Du',v'): - ! - StiffBlock(1,1) = StiffBlock(1,1) + & - EA * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(2,2) = StiffBlock(2,2) + & - GA * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(3,3) = StiffBlock(3,3) + & - GA * dBasis(q,1) * dBasis(p,1) * Weight - - IF (TransientSimulation) THEN - MassBlock(1,1) = MassBlock(1,1) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - MassBlock(2,2) = MassBlock(2,2) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - MassBlock(3,3) = MassBlock(3,3) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - END IF - - IF (q > n) CYCLE - ! - ! -(D theta x t,v'): - ! - StiffBlock(2,6) = StiffBlock(2,6) - & - GA * Basis(q) * dBasis(p,1) * Weight - StiffBlock(3,5) = StiffBlock(3,5) + & - GA * Basis(q) * dBasis(p,1) * Weight - END DO - - Force(p0+1) = Force(p0+1) + Weight * DOT_PRODUCT(f,e1)* Basis(p) - Force(p0+2) = Force(p0+2) + Weight * DOT_PRODUCT(f,e2)* Basis(p) - Force(p0+3) = Force(p0+3) + Weight * DOT_PRODUCT(f,e3)* Basis(p) - - IF (p > n) CYCLE - - DO q=1,nd - q0 = (q-1)*DOFs - StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) - MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) - ! - ! -(D u',psi x t): - ! - StiffBlock(5,3) = StiffBlock(5,3) + & - GA * Basis(p) * dBasis(q,1) * Weight - StiffBlock(6,2) = StiffBlock(6,2) - & - GA * Basis(p) * dBasis(q,1) * Weight - - IF (q > n) CYCLE - - ! - ! (E theta',psi') + (D theta x t,psi x t): - ! - StiffBlock(4,4) = StiffBlock(4,4) + & - E_diag(1) * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(5,5) = StiffBlock(5,5) + & - E_diag(2) * dBasis(q,1) * dBasis(p,1) * Weight + & - GA * Basis(p) * Basis(q) * Weight - StiffBlock(6,6) = StiffBlock(6,6) + & - E_diag(3) * dBasis(q,1) * dBasis(p,1) * Weight + & - GA * Basis(p) * Basis(q) * Weight - - IF (TransientSimulation) THEN - MassBlock(4,4) = MassBlock(4,4) + MOI * Basis(q) * Basis(p) * Weight - MassBlock(5,5) = MassBlock(5,5) + rho/E * E_diag(2) * & - Basis(q) * Basis(p) * Weight - MassBlock(6,6) = MassBlock(6,6) + rho/E * E_diag(3) * & - Basis(q) * Basis(p) * Weight - END IF - - END DO - END DO - END DO - - CALL BeamCondensate(nd-nb, nb, DOFs, 3, Stiff, Force) - - !------------------------------------------------------- - ! Transform to the global DOFs: - !------------------------------------------------------- - DOFs = (nd-nb)*DOFs - Stiff(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Stiff(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - Force(1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)),Force(1:DOFs)) - - IF (TransientSimulation) THEN - Mass(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Mass(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - CALL Default2ndOrderTime(Mass, Damp, Stiff, Force) - END IF - - CALL DefaultUpdateEquations(Stiff, Force) -!------------------------------------------------------------------------------ - END SUBROUTINE LocalMatrix -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -SUBROUTINE BeamCondensate(n, nb, dofs, dim, K, F, F1 ) -!------------------------------------------------------------------------------ - USE LinearAlgebra - IMPLICIT NONE - INTEGER, INTENT(IN) :: n ! Nodes after condensation - INTEGER, INTENT(IN) :: nb ! The number of bubble basis functions - INTEGER, INTENT(IN) :: dofs ! DOFs per node - INTEGER, INTENT(IN) :: dim ! The first dim fields have bubbles - REAL(KIND=dp), INTENT(INOUT) :: K(:,:) ! The stiffness matrix - REAL(KIND=dp), INTENT(INOUT) :: F(:) ! The RHS vector - REAL(KIND=dp), OPTIONAL, INTENT(INOUT) :: F1(:) ! Some other RHS vector -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Kbl(nb*dim,n*dofs), Kbb(nb*dim,nb*dim), Fb(nb*dim) - REAL(KIND=dp) :: Klb(n*dofs,nb*dim) - - INTEGER :: i, m, p, Cdofs(dofs*n), Bdofs(dim*nb) -!------------------------------------------------------------------------------ - - Cdofs(1:n*dofs) = (/ (i, i=1,n*dofs) /) - - m = 0 - DO p = 1,nb - DO i = 1,dim - m = m + 1 - Bdofs(m) = dofs*(n+p-1) + i - END DO - END DO - - Kbb = K(Bdofs,Bdofs) - Kbl = K(Bdofs,Cdofs) - Klb = K(Cdofs,Bdofs) - Fb = F(Bdofs) - - CALL InvertMatrix( Kbb,nb*dim ) - - F(1:dofs*n) = F(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) - K(1:dofs*n,1:dofs*n) = & - K(1:dofs*n,1:dofs*n) - MATMUL( Klb, MATMUL( Kbb,Kbl ) ) - - IF (PRESENT(F1)) THEN - Fb = F1(Bdofs) - F1(1:dofs*n) = F1(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) - END IF -!------------------------------------------------------------------------------ - END SUBROUTINE BeamCondensate -!------------------------------------------------------------------------------ !------------------------------------------------------------------------------ END SUBROUTINE TimoshenkoSolver diff --git a/fem/src/modules/ShellSolver.F90 b/fem/src/modules/ShellSolver.F90 index c60db9ab4d..54bcf3ff9b 100755 --- a/fem/src/modules/ShellSolver.F90 +++ b/fem/src/modules/ShellSolver.F90 @@ -137,6 +137,7 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) !------------------------------------------------------------------------------ USE DefUtils USE ElementDescription + USE SolidMechanicsUtils IMPLICIT NONE !------------------------------------------------------------------------------ @@ -582,7 +583,7 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) END IF CALL BeamStiffnessMatrix(BGElement, n, nd+nb, nb, TransientSimulation, MassAssembly, & - HarmonicAssembly, LargeDeflection, LocalSol, LocalRHSForce) + HarmonicAssembly, LargeDeflection, LocalSol, LocalRHSForce, .TRUE.) IF (LargeDeflection .AND. NonlinIter == 1) THEN ! --------------------------------------------------------------------------- @@ -6708,445 +6709,6 @@ FUNCTION EdgeMidNode(Element, e) RESULT(X) END FUNCTION EdgeMidNode !----------------------------------------------------------------------- -!------------------------------------------------------------------------------ -! Integrate and assemble the local beam stiffness matrix. The local DOFs always -! correspond to the displacement components along the tangent direction and the -! principal axes of the cross section. The transformation to global DOFs is done -! within this subroutine. The stiffness matrix K corresponding to the global -! DOFs is thus obtained as K = R^T k R and the RHS vector F is obtained as -! F = R^T f. -! -! This routine is basically a copy of the routine contained in BeamSolver3D.F90. -! The differences are within successive delimiters " !*** ". -! TO DO: Avoid having two versions of the same routine by moving this to a single -! place. -!------------------------------------------------------------------------------ - SUBROUTINE BeamStiffnessMatrix(Element, n, nd, nb, TransientSimulation, & - MassAssembly, HarmonicAssembly, LargeDeflection, LocalSol, RHSForce) -!------------------------------------------------------------------------------ - IMPLICIT NONE - TYPE(Element_t), POINTER, INTENT(IN) :: Element - INTEGER, INTENT(IN) :: n, nd, nb - LOGICAL, INTENT(IN) :: TransientSimulation - LOGICAL, OPTIONAL, INTENT(IN) :: MassAssembly ! To activate mass matrix integration - LOGICAL, OPTIONAL, INTENT(IN) :: HarmonicAssembly ! To activate the global mass matrix updates - LOGICAL, OPTIONAL, INTENT(IN) :: LargeDeflection ! To activate nonlinear terms - REAL(KIND=dp), OPTIONAL, INTENT(IN) :: LocalSol(:,:) ! The previous solution iterate - REAL(KIND=dp), INTENT(OUT) :: RHSForce(:) ! Local RHS vector corresponding to external loads -!------------------------------------------------------------------------------ - TYPE(ValueList_t), POINTER :: BodyForce, Material - TYPE(Nodes_t) :: Nodes, LocalNodes - TYPE(GaussIntegrationPoints_t) :: IP - - LOGICAL :: Found, Stat - LOGICAL :: NonlinAssembly - - INTEGER :: DOFs - INTEGER :: i, t, p, q - INTEGER :: i0, p0, q0 - - REAL(KIND=dp), POINTER :: ArrayPtr(:,:) => NULL() - REAL(KIND=dp), POINTER :: StiffBlock(:,:), MassBlock(:,:) - REAL(KIND=dp), DIMENSION(3), PARAMETER :: ZBasis = (/ 0.0d0, 0.0d0, 0.1d1 /) - - REAL(KIND=dp), TARGET :: Mass(6*nd,6*nd), Stiff(6*nd,6*nd), Damp(6*nd,6*nd) - REAL(KIND=dp) :: Force(6*nd) - REAL(KIND=dp) :: RBlock(3,3), R(6*nd,6*nd) - REAL(KIND=dp) :: Basis(nd), dBasis(nd,3), DetJ, Weight - REAL(KIND=dp) :: Youngs_Modulus(n), Shear_Modulus(n), Area(n), Density(n) - REAL(KIND=dp) :: Torsional_Constant(n) - REAL(KIND=dp) :: Area_Moment_2(n), Area_Moment_3(n) - REAL(KIND=dp) :: Mass_Inertia_Moment(n) - REAL(KIND=dp) :: Load(3,n), f(3) - REAL(KIND=dp) :: PrevSolVec(6*nd) - REAL(KIND=dp) :: E, A, G, rho - REAL(KIND=dp) :: EA, GA, MOI, Mass_per_Length - REAL(KIND=dp) :: E_diag(3) - - REAL(KIND=dp) :: p1(3), p2(3), e1(3), e2(3), e3(3) - REAL(KIND=dp) :: L, Norm - - SAVE Nodes, LocalNodes -!------------------------------------------------------------------------------ - IF (n > 2) CALL Fatal('BeamSolver3D', & - 'Only 2-node background meshes supported currently') - - DOFs = 6 -! dim = CoordinateSystemDimension() - - CALL GetElementNodes(Nodes) - - Mass = 0.0_dp - Stiff = 0.0_dp - Damp = 0.0_dp - Force = 0.0_dp -!*** - RHSForce = 0.0d0 - IF (PRESENT(LargeDeflection)) THEN - NonlinAssembly = LargeDeflection - ELSE - NonlinAssembly = .FALSE. - END IF - IF (NonlinAssembly) THEN - IF (.NOT. PRESENT(LocalSol)) CALL Fatal('BeamStiffnessMatrix', & - 'Previous solution iterate needed') - DO i=1,DOFs - PrevSolVec(i:DOFs*(nd-nb):DOFs) = LocalSol(i,1:(nd-nb)) - END DO - END IF -!*** - - BodyForce => GetBodyForce() - IF ( ASSOCIATED(BodyForce) ) THEN - ! - ! Force components refer to the basis of the global frame: - ! - Load(1,1:n) = GetReal(BodyForce, 'Body Force 1', Found) - Load(2,1:n) = GetReal(BodyForce, 'Body Force 2', Found) - Load(3,1:n) = GetReal(BodyForce, 'Body Force 3', Found) - ELSE - Load = 0.0_dp - END IF - - Material => GetMaterial() - Youngs_Modulus(1:n) = GetReal(Material, 'Youngs Modulus', Found) - Shear_Modulus(1:n) = GetReal(Material, 'Shear Modulus', Found) - Area(1:n) = GetReal(Material, 'Cross Section Area', Found) - Torsional_Constant(1:n) = GetReal(Material, 'Torsional Constant', Found) - Area_Moment_2(1:n) = GetReal(Material, 'Second Moment of Area 2', Found) - Area_Moment_3(1:n) = GetReal(Material, 'Second Moment of Area 3', Found) - -!*** IF (TransientSimulation) THEN - IF (MassAssembly) THEN - Density(1:n) = GetReal(Material, 'Density', Found) - END IF - - ! - ! Compute the tangent vector e1 to the beam axis: - ! - p1(1) = Nodes % x(1) - p1(2) = Nodes % y(1) - p1(3) = Nodes % z(1) - p2(1) = Nodes % x(2) - p2(2) = Nodes % y(2) - p2(3) = Nodes % z(2) - e1 = p2 - p1 - L = SQRT(SUM(e1(:)**2)) - e1 = 1.0_dp/L * e1 - ! - ! Cross section parameters are given with respect to a local frame. - ! Determine its orientation: - ! -!*** - ArrayPtr => ListGetConstRealArray(Material, 'Director', Found) - IF (Found) THEN - e3 = 0.0d0 - DO i=1,SIZE(ArrayPtr,1) - e3(i) = ArrayPtr(i,1) - END DO - Norm = SQRT(SUM(e3(:)**2)) - e3 = 1.0_dp/Norm * e3 - IF (ABS(DOT_PRODUCT(e1,e3)) > 100.0_dp * AEPS) CALL Fatal('BeamSolver3D', & - 'Director should be orthogonal to the beam axis') - e2 = CrossProduct(e3, e1) -!*** - ELSE - ArrayPtr => ListGetConstRealArray(Material, 'Principal Direction 2', Found) - IF (Found) THEN - e2 = 0.0d0 - DO i=1,SIZE(ArrayPtr,1) - e2(i) = ArrayPtr(i,1) - END DO - Norm = SQRT(SUM(e2(:)**2)) - e2 = 1.0_dp/Norm * e2 - ELSE - e2 = -ZBasis - END IF - IF (ABS(DOT_PRODUCT(e1,e2)) > 100.0_dp * AEPS) CALL Fatal('BeamSolver3D', & - 'Principal Direction 2 should be orthogonal to the beam axis') - e3 = CrossProduct(e1, e2) - END IF - - - ! - ! Allocate an additional variable so as to write nodes data with respect to - ! the local frame. - ! - IF (.NOT. ASSOCIATED(LocalNodes % x)) THEN - ALLOCATE(LocalNodes % x(n), LocalNodes % y(n), LocalNodes % z(n) ) - LocalNodes % NumberOfNodes = n - LocalNodes % y(:) = 0.0_dp - LocalNodes % z(:) = 0.0_dp - END IF - LocalNodes % x(1) = 0.0d0 - LocalNodes % x(2) = L - - !----------------------- - ! Numerical integration: - !----------------------- -!*** - IF (.NOT. IsPElement(Element) .AND. nd > n) THEN - IP = GaussPoints(Element, 3) - ELSE - IP = GaussPoints(Element) - END IF -!*** - DO t=1,IP % n - !-------------------------------------------------------------- - ! Basis function values & derivatives at the integration point: - !-------------------------------------------------------------- - stat = ElementInfo(Element, LocalNodes, IP % U(t), IP % V(t), & - IP % W(t), detJ, Basis, dBasis) - -!*** - ! Create a bubble if the element is the standard 2-node element: - IF (.NOT. IsPElement(Element) .AND. nd > n) THEN - Basis(n+1) = Basis(1) * Basis(2) - dBasis(3,:) = dBasis(1,:) * Basis(2) + Basis(1) * dBasis(2,:) - END IF -!*** - !------------------------------------------ - ! The model data at the integration point: - !------------------------------------------ - f(1) = SUM(Basis(1:n) * Load(1,1:n)) - f(2) = SUM(Basis(1:n) * Load(2,1:n)) - f(3) = SUM(Basis(1:n) * Load(3,1:n)) - - ! TO DO: Add option to give the applied moment load - - E = SUM(Basis(1:n) * Youngs_Modulus(1:n)) - G = SUM(Basis(1:n) * Shear_Modulus(1:n)) - A = SUM(Basis(1:n) * Area(1:n)) - - E_diag(1) = G * SUM(Basis(1:n) * Torsional_Constant(1:n)) - E_diag(2) = E * SUM(Basis(1:n) * Area_Moment_2(1:n)) - E_diag(3) = E * SUM(Basis(1:n) * Area_Moment_3(1:n)) - -!*** IF (TransientSimulation) THEN - IF (MassAssembly) THEN - rho = SUM(Basis(1:n) * Density(1:n)) - MOI = rho/E * sqrt(E_diag(2)**2 + E_diag(3)**2) - Mass_per_Length = rho * A - END IF - - GA = G*A - EA = E*A - - ! TO DO: Add option to give shear correction factors - - Weight = IP % s(t) * DetJ - - DO p=1,nd - p0 = (p-1)*DOFs - DO q=1,nd - q0 = (q-1)*DOFs - StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) - MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) - ! - ! (Du',v'): - ! - StiffBlock(1,1) = StiffBlock(1,1) + & - EA * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(2,2) = StiffBlock(2,2) + & - GA * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(3,3) = StiffBlock(3,3) + & - GA * dBasis(q,1) * dBasis(p,1) * Weight - -!*** IF (TransientSimulation) THEN - IF (MassAssembly) THEN - MassBlock(1,1) = MassBlock(1,1) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - MassBlock(2,2) = MassBlock(2,2) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - MassBlock(3,3) = MassBlock(3,3) + & - Mass_per_Length * Basis(q) * Basis(p) * Weight - END IF - - IF (q > n) CYCLE - ! - ! -(D theta x t,v'): - ! - StiffBlock(2,6) = StiffBlock(2,6) - & - GA * Basis(q) * dBasis(p,1) * Weight - StiffBlock(3,5) = StiffBlock(3,5) + & - GA * Basis(q) * dBasis(p,1) * Weight - END DO - - Force(p0+1) = Force(p0+1) + Weight * DOT_PRODUCT(f,e1)* Basis(p) - Force(p0+2) = Force(p0+2) + Weight * DOT_PRODUCT(f,e2)* Basis(p) - Force(p0+3) = Force(p0+3) + Weight * DOT_PRODUCT(f,e3)* Basis(p) - - IF (p > n) CYCLE - - DO q=1,nd - q0 = (q-1)*DOFs - StiffBlock => Stiff(p0+1:p0+DOFs,q0+1:q0+DOFs) - MassBlock => Mass(p0+1:p0+DOFs,q0+1:q0+DOFs) - ! - ! -(D u',psi x t): - ! - StiffBlock(5,3) = StiffBlock(5,3) + & - GA * Basis(p) * dBasis(q,1) * Weight - StiffBlock(6,2) = StiffBlock(6,2) - & - GA * Basis(p) * dBasis(q,1) * Weight - - IF (q > n) CYCLE - - ! - ! (E theta',psi') + (D theta x t,psi x t): - ! - StiffBlock(4,4) = StiffBlock(4,4) + & - E_diag(1) * dBasis(q,1) * dBasis(p,1) * Weight - StiffBlock(5,5) = StiffBlock(5,5) + & - E_diag(2) * dBasis(q,1) * dBasis(p,1) * Weight + & - GA * Basis(p) * Basis(q) * Weight - StiffBlock(6,6) = StiffBlock(6,6) + & - E_diag(3) * dBasis(q,1) * dBasis(p,1) * Weight + & - GA * Basis(p) * Basis(q) * Weight - -!*** IF (TransientSimulation) THEN - IF (MassAssembly) THEN - MassBlock(4,4) = MassBlock(4,4) + MOI * Basis(q) * Basis(p) * Weight - MassBlock(5,5) = MassBlock(5,5) + rho/E * E_diag(2) * & - Basis(q) * Basis(p) * Weight - MassBlock(6,6) = MassBlock(6,6) + rho/E * E_diag(3) * & - Basis(q) * Basis(p) * Weight - END IF - - END DO - END DO - END DO - - CALL BeamCondensate(nd-nb, nb, DOFs, 3, Stiff, Force) - -!*** - ! - ! Switch to rotation variables which conform with the rotated moments - M x d: - ! - R = 0.0d0 - DO i=1,nd-nb - i0 = (i-1)*DOFs - R(i0+1,i0+1) = 1.0d0 - R(i0+2,i0+2) = 1.0d0 - R(i0+3,i0+3) = 1.0d0 - R(i0+4,i0+5) = 1.0d0 - R(i0+5,i0+4) = -1.0d0 - R(i0+6,i0+6) = 1.0d0 - END DO - DOFs = (nd-nb)*DOFs - Stiff(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Stiff(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - Force(1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)),Force(1:DOFs)) - - IF (MassAssembly) & - Mass(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Mass(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - - ! - ! The moment around the director is not compatible with the shell model. - ! Remove its contribution: - ! - DO p=1,nd-nb - Stiff(6*p,:) = 0.0d0 - Stiff(:,6*p) = 0.0d0 - Stiff(6*p,6*p) = 0.0d0 - Force(6*p) = 0.0d0 - Mass(6*p,:) = 0.0d0 - Mass(:,6*p) = 0.0d0 - END DO -!*** - - ! - ! Build the transformation matrix in order to switch to the global DOFs - ! - DOFs = 6 - R = 0.0d0 - RBlock(1,1:3) = e1(1:3) - RBlock(2,1:3) = e2(1:3) - RBlock(3,1:3) = e3(1:3) - DO i=1,nd-nb - i0 = (i-1)*DOFs - R(i0+1:i0+3,i0+1:i0+3) = RBlock(1:3,1:3) - R(i0+4:i0+6,i0+4:i0+6) = RBlock(1:3,1:3) - END DO - - !------------------------------------------------------- - ! Transform to the global DOFs: - !------------------------------------------------------- - DOFs = (nd-nb)*DOFs - Stiff(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Stiff(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - Force(1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)),Force(1:DOFs)) - -!*** - RHSForce(1:DOFs) = Force(1:DOFs) - IF (NonlinAssembly) Force(1:DOFs) = Force(1:DOFs) - & - MATMUL(Stiff(1:DOFs,1:DOFs), PrevSolVec(1:DOFs)) -!*** - - IF (MassAssembly) THEN - Mass(1:DOFs,1:DOFs) = MATMUL(TRANSPOSE(R(1:DOFs,1:DOFs)), & - MATMUL(Mass(1:DOFs,1:DOFs),R(1:DOFs,1:DOFs))) - IF (TransientSimulation) THEN - CALL Default2ndOrderTime(Mass, Damp, Stiff, Force) - ELSE IF (HarmonicAssembly) THEN - CALL DefaultUpdateMass(Mass) - END IF - END IF - - CALL DefaultUpdateEquations(Stiff, Force) -!------------------------------------------------------------------------------ - END SUBROUTINE BeamStiffnessMatrix -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ - SUBROUTINE BeamCondensate(n, nb, dofs, dim, K, F, F1 ) -!------------------------------------------------------------------------------ - USE LinearAlgebra - IMPLICIT NONE - INTEGER, INTENT(IN) :: n ! Nodes after condensation - INTEGER, INTENT(IN) :: nb ! The number of bubble basis functions - INTEGER, INTENT(IN) :: dofs ! DOFs per node - INTEGER, INTENT(IN) :: dim ! The first dim fields have bubbles - REAL(KIND=dp), INTENT(INOUT) :: K(:,:) ! The stiffness matrix - REAL(KIND=dp), INTENT(INOUT) :: F(:) ! The RHS vector - REAL(KIND=dp), OPTIONAL, INTENT(INOUT) :: F1(:) ! Some other RHS vector -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Kbl(nb*dim,n*dofs), Kbb(nb*dim,nb*dim), Fb(nb*dim) - REAL(KIND=dp) :: Klb(n*dofs,nb*dim) - - INTEGER :: i, m, p, Cdofs(dofs*n), Bdofs(dim*nb) -!------------------------------------------------------------------------------ - - Cdofs(1:n*dofs) = (/ (i, i=1,n*dofs) /) - - m = 0 - DO p = 1,nb - DO i = 1,dim - m = m + 1 - Bdofs(m) = dofs*(n+p-1) + i - END DO - END DO - - Kbb = K(Bdofs,Bdofs) - Kbl = K(Bdofs,Cdofs) - Klb = K(Cdofs,Bdofs) - Fb = F(Bdofs) - - CALL InvertMatrix( Kbb,nb*dim ) - - F(1:dofs*n) = F(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) - K(1:dofs*n,1:dofs*n) = & - K(1:dofs*n,1:dofs*n) - MATMUL( Klb, MATMUL( Kbb,Kbl ) ) - - IF (PRESENT(F1)) THEN - Fb = F1(Bdofs) - F1(1:dofs*n) = F1(1:dofs*n) - MATMUL( Klb, MATMUL( Kbb, Fb ) ) - END IF -!------------------------------------------------------------------------------ - END SUBROUTINE BeamCondensate -!------------------------------------------------------------------------------ - !------------------------------------------------------------------------------ END SUBROUTINE ShellSolver !------------------------------------------------------------------------------ From 87d51cbe8747f0d71824b7ab55dfbf4a5b23144b Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 8 Oct 2020 17:38:53 +0300 Subject: [PATCH 14/73] Move some common subroutines into the module SolidMechanicsUtils --- fem/src/SolidMechanicsUtils.F90 | 176 +++++++++++++++++++++++++- fem/src/modules/FacetShellSolve.F90 | 187 ++-------------------------- fem/src/modules/ShellSolver.F90 | 76 +---------- fem/src/modules/Smitc.F90 | 186 ++------------------------- 4 files changed, 191 insertions(+), 434 deletions(-) diff --git a/fem/src/SolidMechanicsUtils.F90 b/fem/src/SolidMechanicsUtils.F90 index 9003bf6027..6b6cda9aa9 100644 --- a/fem/src/SolidMechanicsUtils.F90 +++ b/fem/src/SolidMechanicsUtils.F90 @@ -473,12 +473,176 @@ SUBROUTINE BeamCondensate(n, nb, dofs, dim, K, F, F1 ) END SUBROUTINE BeamCondensate !------------------------------------------------------------------------------ - - ! This module could contain common implementations for - ! - SUBROUTINE ElasticityMatrix - ! - SUBROUTINE StrainEnergyDensity - ! - SUBROUTINE ShearCorrectionFactor - ! etc. +!------------------------------------------------------------------------------ +!> Perform the operation +!> +!> A = A + C' * B * C * s +!> +!> with +!> +!> Size( A ) = n x n +!> Size( B ) = m x m +!> Size( C ) = m x n +!------------------------------------------------------------------------------ + SUBROUTINE StrainEnergyDensity(A, B, C, m, n, s) +!------------------------------------------------------------------------------ + IMPLICIT NONE + REAL(KIND=dp), INTENT(INOUT) :: A(:,:) + REAL(KIND=dp), INTENT(IN) :: B(:,:), C(:,:) + INTEGER, INTENT(IN) :: m, n + REAL(KIND=dp), INTENT(IN) :: s +!------------------------------------------------------------------------------ + A(1:n,1:n) = A(1:n,1:n) + s * MATMUL(TRANSPOSE(C(1:m,1:n)),MATMUL(B(1:m,1:m),C(1:m,1:n))) +!------------------------------------------------------------------------------ + END SUBROUTINE StrainEnergyDensity +!------------------------------------------------------------------------------ + + +!------------------------------------------------------------------------------ + SUBROUTINE Jacobi3(Jmat, invJ, detJ, x, y) +!------------------------------------------------------------------------------ + IMPLICIT NONE + REAL(KIND=dp), INTENT(OUT) :: Jmat(:,:), invJ(:,:), detJ + REAL(KIND=dp), INTENT(IN) :: x(:), y(:) +!------------------------------------------------------------------------------ + Jmat(1,1) = x(2)-x(1) + Jmat(2,1) = x(3)-x(1) + Jmat(1,2) = y(2)-y(1) + Jmat(2,2) = y(3)-y(1) + + detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) + + invJ(1,1) = Jmat(2,2)/detJ + invJ(2,2) = Jmat(1,1)/detJ + invJ(1,2) = -Jmat(1,2)/detJ + invJ(2,1) = -Jmat(2,1)/detJ +!------------------------------------------------------------------------------ + END SUBROUTINE Jacobi3 +!------------------------------------------------------------------------------ + +!------------------------------------------------------------------------------ + SUBROUTINE Jacobi4(Jmat, invJ, detJ, xi, eta, x, y) +!------------------------------------------------------------------------------ + IMPLICIT NONE + REAL(KIND=dp), INTENT(OUT) :: Jmat(:,:), invJ(:,:), detJ + REAL(KIND=dp), INTENT(IN) :: xi, eta, x(:), y(:) +!------------------------------------------------------------------------------ + REAL(KIND=dp) :: dNdxi(4), dNdeta(4) + INTEGER :: i +!------------------------------------------------------------------------------ + dNdxi(1) = -(1-eta)/4.0d0 + dNdxi(2) = (1-eta)/4.0d0 + dNdxi(3) = (1+eta)/4.0d0 + dNdxi(4) = -(1+eta)/4.0d0 + dNdeta(1) = -(1-xi)/4.0d0 + dNdeta(2) = -(1+xi)/4.0d0 + dNdeta(3) = (1+xi)/4.0d0 + dNdeta(4) = (1-xi)/4.0d0 + + Jmat = 0.0d0 + DO i=1,4 + Jmat(1,1) = Jmat(1,1) + dNdxi(i)*x(i) + Jmat(1,2) = Jmat(1,2) + dNdxi(i)*y(i) + Jmat(2,1) = Jmat(2,1) + dNdeta(i)*x(i) + Jmat(2,2) = Jmat(2,2) + dNdeta(i)*y(i) + END DO + + detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) + + invJ(1,1) = Jmat(2,2)/detJ + invJ(2,2) = Jmat(1,1)/detJ + invJ(1,2) = -Jmat(1,2)/detJ + invJ(2,1) = -Jmat(2,1)/detJ +!------------------------------------------------------------------------------ + END SUBROUTINE Jacobi4 +!------------------------------------------------------------------------------ + +!------------------------------------------------------------------------------ + SUBROUTINE ShearCorrectionFactor(Kappa, Thickness, x, y, n, StabParam) +!------------------------------------------------------------------------------ + REAL(KIND=dp), INTENT(OUT) :: Kappa + REAL(KIND=dp), INTENT(IN) :: Thickness, x(:), y(:) + INTEGER, INTENT(IN) :: n + REAL(KIND=dp), OPTIONAL, INTENT(IN) :: StabParam +!------------------------------------------------------------------------------ + REAL(KIND=dp) :: x21,x32,x43,x13,x14,y21,y32,y43,y13,y14, & + l21,l32,l43,l13,l14,alpha,h + REAL(KIND=dp) :: StabPar +!------------------------------------------------------------------------------ + IF (PRESENT(StabParam)) THEN + StabPar = StabParam + ELSE + StabPar = 1.0d0 + END IF + + Kappa = 1.0d0 + SELECT CASE(n) + CASE(3) + alpha = 0.20d0 * StabPar + x21 = x(2)-x(1) + x32 = x(3)-x(2) + x13 = x(1)-x(1) + y21 = y(2)-y(1) + y32 = y(3)-y(2) + y13 = y(1)-y(1) + l21 = SQRT(x21**2 + y21**2) + l32 = SQRT(x32**2 + y32**2) + l13 = SQRT(x13**2 + y13**2) + h = MAX(l21,l32,l13) + Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) + CASE(4) + alpha = 0.10d0 * StabPar + x21 = x(2)-x(1) + x32 = x(3)-x(2) + x43 = x(4)-x(3) + x14 = x(1)-x(4) + y21 = y(2)-y(1) + y32 = y(3)-y(2) + y43 = y(4)-y(3) + y14 = y(1)-y(4) + l21 = SQRT(x21**2 + y21**2) + l32 = SQRT(x32**2 + y32**2) + l43 = SQRT(x43**2 + y43**2) + l14 = SQRT(x14**2 + y14**2) + h = MAX(l21,l32,l43,l14) + Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) + CASE DEFAULT + CALL Fatal('ShearCorrectionFactor','Illegal number of nodes for Smitc elements: '//TRIM(I2S(n))) + END SELECT +!------------------------------------------------------------------------------ + END SUBROUTINE ShearCorrectionFactor +!------------------------------------------------------------------------------ + +!------------------------------------------------------------------------------ + SUBROUTINE IsotropicElasticity(Ematrix, Gmatrix, Poisson, Young, Thickness,& + Basis, n) +!------------------------------------------------------------------------------ + REAL(KIND=dp) :: Ematrix(:,:), Gmatrix(:,:), Basis(:) + REAL(KIND=dp) :: Poisson(:), Young(:), Thickness(:) + REAL(KIND=dp) :: Euvw, Puvw, Guvw, Tuvw + INTEGER :: n +!------------------------------------------------------------------------------ + Euvw = SUM( Young(1:n)*Basis(1:n) ) + Puvw = SUM( Poisson(1:n)*Basis(1:n) ) + Tuvw = SUM( Thickness(1:n)*Basis(1:n) ) + Guvw = Euvw/(2.0d0*(1.0d0 + Puvw)) + + Ematrix = 0.0d0 + Ematrix(1,1) = 1.0d0 + Ematrix(1,2) = Puvw + Ematrix(2,1) = Puvw + Ematrix(2,2) = 1.0d0 + Ematrix(3,3) = (1.0d0-Puvw)/2.0d0 + + Ematrix = Ematrix* Euvw * (Tuvw**3) / (12.0d0*(1.0d0-Puvw**2)) + + Gmatrix = 0.0d0 + Gmatrix(1,1) = Guvw*Tuvw + Gmatrix(2,2) = Guvw*Tuvw +!------------------------------------------------------------------------------ + END SUBROUTINE IsotropicElasticity +!------------------------------------------------------------------------------ + END MODULE SolidMechanicsUtils diff --git a/fem/src/modules/FacetShellSolve.F90 b/fem/src/modules/FacetShellSolve.F90 index 8fb0b41c2a..1804f5798d 100644 --- a/fem/src/modules/FacetShellSolve.F90 +++ b/fem/src/modules/FacetShellSolve.F90 @@ -905,6 +905,9 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & ElementNumber, NodalPoisson, NodalYoung, NodalDampingCoef, & LocalDeflection, LargeDeflection, StabilityAnalysis, Nvector ) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: StrainEnergyDensity, ShearCorrectionFactor, & + IsotropicElasticity + REAL(KIND=dp) :: STIFF(:,:), DAMP(:,:), MASS(:,:), & Amatrix(3,3), Bmatrix(3,3), Dmatrix(3,3), Astarmatrix(2,2) REAL(KIND=dp) :: FORCE(:) @@ -1184,7 +1187,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & END IF END DO - CALL AddEnergy(STIFF, Dmatrix, Kappa, 3, 6*n, s) + CALL StrainEnergyDensity(STIFF, Dmatrix, Kappa, 3, 6*n, s) ! In-plane stiffness: ! ------------------- @@ -1206,7 +1209,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & END IF END DO - CALL AddEnergy(STIFF, Amatrix, EPS, 3, 6*n, s) + CALL StrainEnergyDensity(STIFF, Amatrix, EPS, 3, 6*n, s) ! Coupling through the B-matrix: ! ------------------------------ @@ -1249,7 +1252,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & Gammaa(1:2,6*p-3) = dBasisdx(p,1:2) END DO - CALL AddEnergy(STIFF, Astarmatrix, Gammaa, 2, 6*n, SCF*s) + CALL StrainEnergyDensity(STIFF, Astarmatrix, Gammaa, 2, 6*n, SCF*s) ! Drilling DOFs (in-plane rotations): ! ----------------------------------- @@ -1260,7 +1263,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & Omega(1,6*p-0) = Basis(p) ! rotation END DO - CALL AddEnergy(STIFF, Gdrilling, Omega, 1, 6*n, s) + CALL StrainEnergyDensity(STIFF, Gdrilling, Omega, 1, 6*n, s) ! Newton lin. terms: ! ----------------- @@ -1528,7 +1531,9 @@ SUBROUTINE LocalStress( Element, n, Nodes, StabParam1, StabParam2, & Mten, MtenMaterial, NodalYoung, NodalPoisson, NodalThickness, & LargeDeflection ) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: ShearCorrectionFactor, IsotropicElasticity IMPLICIT NONE + REAL(KIND=dp) :: StabParam1, StabParam2, LocalDeflection(:), & Weight3(:), Weight4(:), Eps(3,3), Kap(3,3), NTen(3,3), MTen(3,3), & NtenMaterial(2,2), MtenMaterial(2,2), & @@ -1982,38 +1987,6 @@ FUNCTION LocalBasis( Nodes, n ) RESULT( BasisVectors ) END FUNCTION LocalBasis !------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ - SUBROUTINE IsotropicElasticity(Ematrix, & - Gmatrix,Poisson,Young,Thickness,Basis,n) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Ematrix(:,:), Gmatrix(:,:), Basis(:) - REAL(KIND=dp) :: Poisson(:), Young(:), Thickness(:) - REAL(KIND=dp) :: Euvw, Puvw, Guvw, Tuvw - INTEGER :: n -!------------------------------------------------------------------------------ - Euvw = SUM( Young(1:n) * Basis(1:n) ) - Puvw = SUM( Poisson(1:n) * Basis(1:n) ) - Tuvw = SUM( Thickness(1:n)* Basis(1:n) ) - Guvw = Euvw/(2.0d0*(1.0d0 + Puvw)) - - Ematrix = 0.0d0 - Ematrix(1,1) = 1.0d0 - Ematrix(1,2) = Puvw - Ematrix(2,1) = Puvw - Ematrix(2,2) = 1.0d0 - Ematrix(3,3) = (1.0d0-Puvw)/2.0d0 - Ematrix = Ematrix * Euvw * (Tuvw**3) / (12.0d0 * (1.0d0 - Puvw*Puvw)) - - Gmatrix = 0.0d0 - Gmatrix(1,1) = Guvw*Tuvw - Gmatrix(2,2) = Guvw*Tuvw -!------------------------------------------------------------------------------ - END SUBROUTINE IsotropicElasticity -!------------------------------------------------------------------------------ - -!============================================================================== - !------------------------------------------------------------------------------ SUBROUTINE IsotropicInPlaneElasticity( Ematrix, & Poisson, Young, Thickness, Basis, n ) @@ -2039,89 +2012,6 @@ SUBROUTINE IsotropicInPlaneElasticity( Ematrix, & END SUBROUTINE IsotropicInPlaneElasticity !------------------------------------------------------------------------------ -!============================================================================== - -!------------------------------------------------------------------------------ - SUBROUTINE ShearCorrectionFactor(Kappa,Thickness,x,y,n,StabParam) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Kappa,Thickness,x(:),y(:),StabParam - INTEGER :: n -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: x21,x32,x43,x13,x14,y21,y32,y43,y13,y14, & - l21,l32,l43,l13,l14,alpha,h -!------------------------------------------------------------------------------ - Kappa = 1.0d0 - SELECT CASE(n) - CASE(3) - alpha = 0.20d0 * StabParam - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x13 = x(1)-x(1) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y13 = y(1)-y(1) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l13 = SQRT(x13**2 + y13**2) - h = MAX(l21,l32,l13) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - CASE(4) - alpha = 0.10d0 * StabParam - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x43 = x(4)-x(3) - x14 = x(1)-x(4) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y43 = y(4)-y(3) - y14 = y(1)-y(4) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l43 = SQRT(x43**2 + y43**2) - l14 = SQRT(x14**2 + y14**2) - h = MAX(l21,l32,l43,l14) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - CASE DEFAULT - CALL Fatal('ShellSolver','Illegal number of nodes for Smitc elements') - END SELECT -!------------------------------------------------------------------------------ - END SUBROUTINE ShearCorrectionFactor -!------------------------------------------------------------------------------ - -!============================================================================== - -!------------------------------------------------------------------------------ - SUBROUTINE AddEnergy(A,B,C,m,n,s) -!------------------------------------------------------------------------------ -! Performs the operation -! -! A = A + C' * B * C * s -! -! with -! -! Size( A ) = n x n -! Size( B ) = m x m -! Size( C ) = m x n -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: A(:,:),B(:,:),C(:,:),s - INTEGER :: m,n -!------------------------------------------------------------------------------ - INTEGER :: i,j,k,l -!------------------------------------------------------------------------------ - DO i=1,n - DO j=1,n - DO k=1,m - DO l=1,m - A(i,j) = A(i,j) + C(k,i)*B(k,l)*C(l,j) * s - END DO - END DO - END DO - END DO -!------------------------------------------------------------------------------ - END SUBROUTINE AddEnergy -!------------------------------------------------------------------------------ - -!============================================================================== !------------------------------------------------------------------------------ SUBROUTINE AddInnerProducts(A,B,C,D,m,n,s) @@ -2160,6 +2050,7 @@ END SUBROUTINE AddInnerProducts !------------------------------------------------------------------------------ SUBROUTINE CovariantInterpolation(ShearStrain,Basis,X,Y,U,V,n) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: Jacobi3, Jacobi4 REAL(KIND=dp) :: ShearStrain(:,:),Basis(:),X(:),Y(:),U,V INTEGER :: n !------------------------------------------------------------------------------ @@ -2313,64 +2204,6 @@ SUBROUTINE CovariantInterpolation(ShearStrain,Basis,X,Y,U,V,n) END SUBROUTINE CovariantInterpolation !------------------------------------------------------------------------------ -!============================================================================== - -!------------------------------------------------------------------------------ - SUBROUTINE Jacobi3(Jmat,invJ,detJ,x,y) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Jmat(:,:),invJ(:,:),detJ,x(:),y(:) -!------------------------------------------------------------------------------ - Jmat(1,1) = x(2)-x(1) - Jmat(2,1) = x(3)-x(1) - Jmat(1,2) = y(2)-y(1) - Jmat(2,2) = y(3)-y(1) - - detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) - - invJ(1,1) = Jmat(2,2)/detJ - invJ(2,2) = Jmat(1,1)/detJ - invJ(1,2) = -Jmat(1,2)/detJ - invJ(2,1) = -Jmat(2,1)/detJ -!------------------------------------------------------------------------------ - END SUBROUTINE Jacobi3 -!------------------------------------------------------------------------------ - -!============================================================================== - -!------------------------------------------------------------------------------ - SUBROUTINE Jacobi4(Jmat,invJ,detJ,xi,eta,x,y) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Jmat(:,:),invJ(:,:),detJ,xi,eta,x(:),y(:) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: dNdxi(4), dNdeta(4) - INTEGER :: i - - dNdxi(1) = -(1-eta)/4.0d0 - dNdxi(2) = (1-eta)/4.0d0 - dNdxi(3) = (1+eta)/4.0d0 - dNdxi(4) = -(1+eta)/4.0d0 - dNdeta(1) = -(1-xi)/4.0d0 - dNdeta(2) = -(1+xi)/4.0d0 - dNdeta(3) = (1+xi)/4.0d0 - dNdeta(4) = (1-xi)/4.0d0 - - Jmat = 0.0d0 - DO i=1,4 - Jmat(1,1) = Jmat(1,1) + dNdxi(i)*x(i) - Jmat(1,2) = Jmat(1,2) + dNdxi(i)*y(i) - Jmat(2,1) = Jmat(2,1) + dNdeta(i)*x(i) - Jmat(2,2) = Jmat(2,2) + dNdeta(i)*y(i) - END DO - - detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) - - invJ(1,1) = Jmat(2,2)/detJ - invJ(2,2) = Jmat(1,1)/detJ - invJ(1,2) = -Jmat(1,2)/detJ - invJ(2,1) = -Jmat(2,1)/detJ -!------------------------------------------------------------------------------ - END SUBROUTINE Jacobi4 -!------------------------------------------------------------------------------ !============================================================================== diff --git a/fem/src/modules/ShellSolver.F90 b/fem/src/modules/ShellSolver.F90 index 54bcf3ff9b..ec7dd5e24e 100755 --- a/fem/src/modules/ShellSolver.F90 +++ b/fem/src/modules/ShellSolver.F90 @@ -3515,6 +3515,7 @@ SUBROUTINE ShellLocalMatrix(BGElement, n, nd, m, LocalSol, LargeDeflection, & DrillingDOFs, DrillingPar, MassAssembly, HarmonicAssembly, RHSForce, Area, & Error, BenchmarkProblem) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: StrainEnergyDensity, ShearCorrectionFactor IMPLICIT NONE TYPE(Element_t), POINTER, INTENT(IN) :: BGElement ! An element of background mesh INTEGER, INTENT(IN) :: n ! The number of background element nodes @@ -5381,57 +5382,6 @@ END SUBROUTINE WriteElementNodesVariables !------------------------------------------------------------------------------ -!------------------------------------------------------------------------------ - SUBROUTINE ShearCorrectionFactor(Kappa,Thickness,x,y,n) -!------------------------------------------------------------------------------ - IMPLICIT NONE - REAL(KIND=dp) :: Kappa,Thickness,x(:),y(:) - INTEGER :: n -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: x21,x32,x43,x13,x14,y21,y32,y43,y13,y14, & - l21,l32,l43,l13,l14,alpha,h -!------------------------------------------------------------------------------ - Kappa = 1.0d0 - SELECT CASE(n) - CASE(3) - alpha = 0.20d0 - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x13 = x(1)-x(1) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y13 = y(1)-y(1) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l13 = SQRT(x13**2 + y13**2) - h = MAX(l21,l32,l13) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - CASE(4) - alpha = 0.10d0 - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x43 = x(4)-x(3) - x14 = x(1)-x(4) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y43 = y(4)-y(3) - y14 = y(1)-y(4) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l43 = SQRT(x43**2 + y43**2) - l14 = SQRT(x14**2 + y14**2) - h = MAX(l21,l32,l43,l14) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - - CASE DEFAULT - CALL Fatal('ShearCorrectionFactor',& - 'Illegal number of nodes for Smitc elements: '//TRIM(I2S(n))) - END SELECT -!------------------------------------------------------------------------------ - END SUBROUTINE ShearCorrectionFactor -!------------------------------------------------------------------------------ - - !------------------------------------------------------------------------------ ! The matrix representation of the elasticity tensor with respect an orthogonal ! basis. The case A1 = A2 = 1 corresponds to an orthonormal basis. @@ -5491,29 +5441,7 @@ SUBROUTINE ElasticityMatrix(CMat, GMat, A1, A2, E, nu, DrillingDOFs, StabPar) END SUBROUTINE ElasticityMatrix !------------------------------------------------------------------------------ -!------------------------------------------------------------------------------ -! Perform the operation -! -! A = A + C' * B * C * s -! -! with -! -! Size( A ) = n x n -! Size( B ) = m x m -! Size( C ) = m x n -!------------------------------------------------------------------------------ - SUBROUTINE StrainEnergyDensity(A, B, C, m, n, s) -!------------------------------------------------------------------------------ - IMPLICIT NONE - REAL(KIND=dp), INTENT(INOUT) :: A(:,:) - REAL(KIND=dp), INTENT(IN) :: B(:,:), C(:,:) - INTEGER, INTENT(IN) :: m, n - REAL(KIND=dp), INTENT(IN) :: s -!------------------------------------------------------------------------------ - A(1:n,1:n) = A(1:n,1:n) + s * MATMUL(TRANSPOSE(C(1:m,1:n)),MATMUL(B(1:m,1:m),C(1:m,1:n))) -!------------------------------------------------------------------------------ - END SUBROUTINE StrainEnergyDensity -!------------------------------------------------------------------------------ + !------------------------------------------------------------------------------ diff --git a/fem/src/modules/Smitc.F90 b/fem/src/modules/Smitc.F90 index 31603f138f..f7be747ca4 100755 --- a/fem/src/modules/Smitc.F90 +++ b/fem/src/modules/Smitc.F90 @@ -289,8 +289,11 @@ SUBROUTINE SmitcSolver( Model,Solver,dt,TransientSimulation ) !------------------------------------------------------------------------------ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & - Force, Load, Element, n, DOFs, Nodes, DampingCoef, SpringCoef ) + Force, Load, Element, n, DOFs, Nodes, DampingCoef, SpringCoef ) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: StrainEnergyDensity, ShearCorrectionFactor, & + IsotropicElasticity + REAL(KIND=dp) :: STIFF(:,:), DAMP(:,:), & MASS(:,:), Force(:), Load(:), DampingCoef(:), SpringCoef(:) INTEGER :: n, DOFs @@ -369,7 +372,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & Curvature(3,3*p ) = dBasisdx(p,1) END DO - CALL AddInnerProducts(STIFF,Ematrix,Curvature,3,3*n,s) + CALL StrainEnergyDensity(STIFF,Ematrix,Curvature,3,3*n,s) ! In-plane stiffness: ! ------------------- @@ -388,7 +391,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & ShearStrain(2,3*p-2) = dBasisdx(p,2) END DO - CALL AddInnerProducts(STIFF, & + CALL StrainEnergyDensity(STIFF, & Gmatrix,ShearStrain,2,3*n,Kappa*s) ! ! Tensile stiffness: @@ -399,7 +402,7 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & ShearStrain(2,3*p-2) = dBasisdx(p,2) END DO - CALL AddInnerProducts(STIFF,Tmatrix,ShearStrain,2,3*n,s) + CALL StrainEnergyDensity(STIFF,Tmatrix,ShearStrain,2,3*n,s) ! Spring Coeffficient: ! ------------------- @@ -445,38 +448,8 @@ SUBROUTINE LocalMatrix( STIFF, DAMP, MASS, & END DO !------------------------------------------------------------------------------ END SUBROUTINE LocalMatrix - - -!============================================================================== - - - SUBROUTINE IsotropicElasticity(Ematrix, & - Gmatrix,Poisson,Young,Thickness,Basis,n) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Ematrix(:,:), Gmatrix(:,:), Basis(:) - REAL(KIND=dp) :: Poisson(:), Young(:), Thickness(:) - REAL(KIND=dp) :: Euvw, Puvw, Guvw, Tuvw - INTEGER :: n !------------------------------------------------------------------------------ - Euvw = SUM( Young(1:n)*Basis(1:n) ) - Puvw = SUM( Poisson(1:n)*Basis(1:n) ) - Tuvw = SUM( Thickness(1:n)*Basis(1:n) ) - Guvw = Euvw/(2.0d0*(1.0d0 + Puvw)) - - Ematrix = 0.0d0 - Ematrix(1,1) = 1.0d0 - Ematrix(1,2) = Puvw - Ematrix(2,1) = Puvw - Ematrix(2,2) = 1.0d0 - Ematrix(3,3) = (1.0d0-Puvw)/2.0d0 - - Ematrix = Ematrix* Euvw * (Tuvw**3) / (12.0d0*(1.0d0-Puvw**2)) - Gmatrix = 0.0d0 - Gmatrix(1,1) = Guvw*Tuvw - Gmatrix(2,2) = Guvw*Tuvw -!------------------------------------------------------------------------------ - END SUBROUTINE IsotropicElasticity !------------------------------------------------------------------------------ @@ -486,7 +459,6 @@ END SUBROUTINE IsotropicElasticity !> in silicon condenser microphones', Sensors and Actuators A 54 (1996) 499-504. ! The model in verified in the special assignment of Jani Paavilainen !------------------------------------------------------------------------------ - SUBROUTINE PerforatedElasticity(Ematrix, & Gmatrix,Poisson,Young,Thickness,HoleFraction, & HoleSize, Basis,n) @@ -529,94 +501,13 @@ SUBROUTINE PerforatedElasticity(Ematrix, & Gmatrix(2,2) = Gmatrix(1,1) !------------------------------------------------------------------------------ END SUBROUTINE PerforatedElasticity - -!============================================================================== - - - SUBROUTINE ShearCorrectionFactor(Kappa,Thickness,x,y,n) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Kappa,Thickness,x(:),y(:) - INTEGER :: n -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: x21,x32,x43,x13,x14,y21,y32,y43,y13,y14, & - l21,l32,l43,l13,l14,alpha,h -!------------------------------------------------------------------------------ - Kappa = 1.0d0 - SELECT CASE(n) - CASE(3) - alpha = 0.20d0 - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x13 = x(1)-x(1) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y13 = y(1)-y(1) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l13 = SQRT(x13**2 + y13**2) - h = MAX(l21,l32,l13) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - CASE(4) - alpha = 0.10d0 - x21 = x(2)-x(1) - x32 = x(3)-x(2) - x43 = x(4)-x(3) - x14 = x(1)-x(4) - y21 = y(2)-y(1) - y32 = y(3)-y(2) - y43 = y(4)-y(3) - y14 = y(1)-y(4) - l21 = SQRT(x21**2 + y21**2) - l32 = SQRT(x32**2 + y32**2) - l43 = SQRT(x43**2 + y43**2) - l14 = SQRT(x14**2 + y14**2) - h = MAX(l21,l32,l43,l14) - Kappa = (Thickness**2)/(Thickness**2 + alpha*(h**2)) - CASE DEFAULT - CALL Fatal('SmitcSolver','Illegal number of nodes for Smitc elements: '//TRIM(I2S(n))) - END SELECT -!------------------------------------------------------------------------------ - END SUBROUTINE ShearCorrectionFactor - - -!============================================================================== - - - SUBROUTINE AddInnerProducts(A,B,C,m,n,s) -!------------------------------------------------------------------------------ -! Performs the operation -! -! A = A + C' * B * C * s -! -! with -! -! Size( A ) = n x n -! Size( B ) = m x m -! Size( C ) = m x n -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: A(:,:),B(:,:),C(:,:),s - INTEGER :: m,n -!------------------------------------------------------------------------------ - INTEGER :: i,j,k,l -!------------------------------------------------------------------------------ - DO i=1,n - DO j=1,n - DO k=1,m - DO l=1,m - A(i,j) = A(i,j) + C(k,i)*B(k,l)*C(l,j) * s - END DO - END DO - END DO - END DO !------------------------------------------------------------------------------ - END SUBROUTINE AddInnerProducts - - -!============================================================================== +!------------------------------------------------------------------------------ SUBROUTINE CovariantInterpolation(ShearStrain,Basis,X,Y,U,V,n) !------------------------------------------------------------------------------ + USE SolidMechanicsUtils, ONLY: Jacobi3, Jacobi4 REAL(KIND=dp) :: ShearStrain(:,:),Basis(:),X(:),Y(:),U,V INTEGER :: n !------------------------------------------------------------------------------ @@ -772,68 +663,9 @@ SUBROUTINE CovariantInterpolation(ShearStrain,Basis,X,Y,U,V,n) END SELECT !------------------------------------------------------------------------------ END SUBROUTINE CovariantInterpolation - - -!============================================================================== - - - SUBROUTINE Jacobi3(Jmat,invJ,detJ,x,y) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Jmat(:,:),invJ(:,:),detJ,x(:),y(:) -!------------------------------------------------------------------------------ - Jmat(1,1) = x(2)-x(1) - Jmat(2,1) = x(3)-x(1) - Jmat(1,2) = y(2)-y(1) - Jmat(2,2) = y(3)-y(1) - - detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) - - invJ(1,1) = Jmat(2,2)/detJ - invJ(2,2) = Jmat(1,1)/detJ - invJ(1,2) = -Jmat(1,2)/detJ - invJ(2,1) = -Jmat(2,1)/detJ !------------------------------------------------------------------------------ - END SUBROUTINE Jacobi3 - -!============================================================================== - - SUBROUTINE Jacobi4(Jmat,invJ,detJ,xi,eta,x,y) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: Jmat(:,:),invJ(:,:),detJ,xi,eta,x(:),y(:) !------------------------------------------------------------------------------ - REAL(KIND=dp) :: dNdxi(4), dNdeta(4) - INTEGER :: i - - dNdxi(1) = -(1-eta)/4.0d0 - dNdxi(2) = (1-eta)/4.0d0 - dNdxi(3) = (1+eta)/4.0d0 - dNdxi(4) = -(1+eta)/4.0d0 - dNdeta(1) = -(1-xi)/4.0d0 - dNdeta(2) = -(1+xi)/4.0d0 - dNdeta(3) = (1+xi)/4.0d0 - dNdeta(4) = (1-xi)/4.0d0 - - Jmat = 0.0d0 - DO i=1,4 - Jmat(1,1) = Jmat(1,1) + dNdxi(i)*x(i) - Jmat(1,2) = Jmat(1,2) + dNdxi(i)*y(i) - Jmat(2,1) = Jmat(2,1) + dNdeta(i)*x(i) - Jmat(2,2) = Jmat(2,2) + dNdeta(i)*y(i) - END DO - - detJ = Jmat(1,1)*Jmat(2,2)-Jmat(1,2)*Jmat(2,1) - - invJ(1,1) = Jmat(2,2)/detJ - invJ(2,2) = Jmat(1,1)/detJ - invJ(1,2) = -Jmat(1,2)/detJ - invJ(2,1) = -Jmat(2,1)/detJ -!------------------------------------------------------------------------------ - END SUBROUTINE Jacobi4 - -!============================================================================== - - END SUBROUTINE SmitcSolver !------------------------------------------------------------------------------ From 7479d9307f5540819cfa700f5ce9e05d7066654e Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 8 Oct 2020 18:23:57 +0300 Subject: [PATCH 15/73] Remove an unnecessary subroutine --- fem/src/SolidMechanicsUtils.F90 | 2 +- fem/src/modules/FacetShellSolve.F90 | 16 ++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/fem/src/SolidMechanicsUtils.F90 b/fem/src/SolidMechanicsUtils.F90 index 6b6cda9aa9..a47f69d1aa 100644 --- a/fem/src/SolidMechanicsUtils.F90 +++ b/fem/src/SolidMechanicsUtils.F90 @@ -25,7 +25,7 @@ ! * ! * Some common subroutines for the solvers of solid mechanics ! * -! * Authors: Mika Malinen +! * Authors: Mika Malinen, Mikko Lyly ! * Email: mika.malinen@csc.fi ! * Web: http://www.csc.fi/elmer ! * Address: CSC - IT Center for Science Ltd. diff --git a/fem/src/modules/FacetShellSolve.F90 b/fem/src/modules/FacetShellSolve.F90 index 1804f5798d..411ae9772d 100644 --- a/fem/src/modules/FacetShellSolve.F90 +++ b/fem/src/modules/FacetShellSolve.F90 @@ -1958,6 +1958,7 @@ END SUBROUTINE SwitchToLocal !------------------------------------------------------------------------------ FUNCTION LocalBasis( Nodes, n ) RESULT( BasisVectors ) !------------------------------------------------------------------------------ + USE ElementDescription, ONLY: CrossProduct TYPE(Nodes_t) :: Nodes REAL(KIND=dp) :: BasisVectors(3,3) INTEGER :: n @@ -1982,7 +1983,7 @@ FUNCTION LocalBasis( Nodes, n ) RESULT( BasisVectors ) BasisVectors(1:3,1) = Tangent1 BasisVectors(1:3,2) = Tangent2 - SUM( Tangent1 * Tangent2 ) * Tangent1 BasisVectors(1:3,2) = BasisVectors(1:3,2) / SQRT( SUM( BasisVectors(1:3,2)**2 ) ) - BasisVectors(1:3,3) = CrossProductL( BasisVectors(1:3,1), BasisVectors(1:3,2) ) + BasisVectors(1:3,3) = CrossProduct( BasisVectors(1:3,1), BasisVectors(1:3,2) ) !------------------------------------------------------------------------------ END FUNCTION LocalBasis !------------------------------------------------------------------------------ @@ -2205,19 +2206,6 @@ END SUBROUTINE CovariantInterpolation !------------------------------------------------------------------------------ -!============================================================================== - -!------------------------------------------------------------------------------ - FUNCTION CrossProductL( v1, v2 ) RESULT( v3 ) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: v1(3), v2(3), v3(3) - v3(1) = v1(2)*v2(3) - v1(3)*v2(2) - v3(2) = -v1(1)*v2(3) + v1(3)*v2(1) - v3(3) = v1(1)*v2(2) - v1(2)*v2(1) -!------------------------------------------------------------------------------ - END FUNCTION CrossProductL -!------------------------------------------------------------------------------ - !------------------------------------------------------------------------------ END SUBROUTINE ShellSolver !------------------------------------------------------------------------------ From e6c7acda6bee7deed2fd3e704e08cb61d02b3351 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Thu, 8 Oct 2020 21:54:38 +0300 Subject: [PATCH 16/73] Implement dirty finish --- fem/src/ElmerSolver.F90 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fem/src/ElmerSolver.F90 b/fem/src/ElmerSolver.F90 index 7add6ec42c..fe4700ab3a 100644 --- a/fem/src/ElmerSolver.F90 +++ b/fem/src/ElmerSolver.F90 @@ -551,6 +551,12 @@ END SUBROUTINE TrilinosCleanup !------------------------------------------------------------------------------ IF ( Initialize /= 1 ) CALL Info( 'ElmerSolver', '*** Elmer Solver: ALL DONE ***',Level=3 ) + ! This may be used to study problems at the finish + IF( ListGetLogical( CurrentModel % Simulation,'Dirty Finish', GotIt ) ) THEN + CALL Info('ElmerSolver','Skipping freeing of the Model structure',Level=4) + RETURN + END IF + IF ( Initialize <= 0 ) CALL FreeModel(CurrentModel) #ifdef HAVE_TRILINOS From 8c396e0a246f037de86a873d41966b51998dee1f Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 9 Oct 2020 00:35:33 +0300 Subject: [PATCH 17/73] Better defaults for SaveScalars and vector fields --- fem/src/modules/SaveData/SaveScalars.F90 | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/fem/src/modules/SaveData/SaveScalars.F90 b/fem/src/modules/SaveData/SaveScalars.F90 index 48506d7803..e155029718 100644 --- a/fem/src/modules/SaveData/SaveScalars.F90 +++ b/fem/src/modules/SaveData/SaveScalars.F90 @@ -2023,7 +2023,7 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) REAL(KIND=dp) :: SqrtMetric,Metric(3,3),Symb(3,3,3),dSymb(3,3,3,3) REAL(KIND=dp) :: Basis(Model % MaxElementNodes), dBasisdx(Model % MaxElementNodes,3) REAL(KIND=dp) :: EnergyTensor(3,3,Model % MaxElementNodes),& - EnergyCoeff(Model % MaxElementNodes) + EnergyCoeff(Model % MaxElementNodes), ElemVals(Model % MaxElementNodes) REAL(KIND=dp) :: SqrtElementMetric,U,V,W,S,A,L,C(3,3),x,y,z REAL(KIND=dp) :: func, coeff, integral1, integral2, Grad(3), CoeffGrad(3) REAL(KIND=DP), POINTER :: Pwrk(:,:,:) @@ -2085,7 +2085,17 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) IF( .NOT. ListGetLogical( MaskList, MaskName, GotIt ) ) CYCLE END IF - + IF( NoDOFs == 1 ) THEN + ElemVals(1:n) = Var % Values(Var % Perm(PermIndexes) ) + ELSE + ElemVals(1:n) = 0.0_dp + DO i=1,NoDOFs + ElemVals(1:n) = ElemVals(1:n) + Var % Values(NoDofs*(Var % Perm(PermIndexes)-1)+i )**2 + END DO + ElemVals(1:) = SQRT(ElemVals(1:n)) + END IF + + k = ListGetInteger( Model % Bodies( Element % BodyId ) % Values, & 'Material', GotIt, minv=1, maxv=Model % NumberOfMaterials ) @@ -2163,26 +2173,26 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) integral1 = integral1 + coeff * S CASE ('int','int mean') - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) integral1 = integral1 + S * coeff * func CASE ('int square','int square mean','int rms') - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) integral1 = integral1 + S * coeff * func**2 CASE ('int abs','int abs mean') - func = ABS( SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) ) + func = ABS( SUM( ElemVals(1:n) * Basis(1:n) ) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) integral1 = integral1 + S * coeff * func CASE ('int variance') - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) integral1 = integral1 + S * coeff * func @@ -2191,7 +2201,7 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) CASE ('diffusive energy') CoeffGrad = 0.0d0 DO j = 1, DIM - Grad(j) = SUM( dBasisdx(1:n,j) * Var % Values(Var % Perm(PermIndexes)) ) + Grad(j) = SUM( dBasisdx(1:n,j) * ElemVals(1:n) ) DO k = 1, DIM CoeffGrad(j) = CoeffGrad(j) + SUM( EnergyTensor(j,k,1:n) * Basis(1:n) ) * & SUM( dBasisdx(1:n,k) * Var % Values(Var % Perm(PermIndexes)) ) @@ -2201,12 +2211,12 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) integral1 = integral1 + s * SUM( Grad(1:DIM) * CoeffGrad(1:DIM) ) CASE ('convective energy') - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) IF(NoDofs == 1) THEN - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) integral1 = integral1 + s * coeff * func**2 ELSE func = 0.0d0 @@ -2218,7 +2228,7 @@ FUNCTION BulkIntegrals(Var, OperName, GotCoeff, CoeffName) RESULT (operx) CASE ('potential energy') - func = SUM( Var % Values(Var % Perm(PermIndexes)) * Basis(1:n) ) + func = SUM( ElemVals(1:n) * Basis(1:n) ) IF( PosOper ) func = MAX( 0.0_dp, func ) IF( NegOper ) func = MIN( 0.0_dp, func ) From fe2984539c12b6eca78dbb86bb608deab604d8d3 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 9 Oct 2020 10:33:45 +0300 Subject: [PATCH 18/73] Known keywords updated --- fem/src/SOLVER.KEYWORDS | 31 ++++++++++++++----- .../DisContBoundaryNodalProj/discont.sif | 3 -- .../interpolationtest.sif | 1 - .../mgdyn_steady_piolaversion/ZCurrent.sif | 1 - 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/fem/src/SOLVER.KEYWORDS b/fem/src/SOLVER.KEYWORDS index 8f306323b6..4ad9426ba5 100644 --- a/fem/src/SOLVER.KEYWORDS +++ b/fem/src/SOLVER.KEYWORDS @@ -369,10 +369,13 @@ BodyForce:Real: 'Body Force 2' BodyForce:Real: 'Body Force 3' BodyForce:Real: 'Charge Density' BodyForce:Real: 'Concentration Diffusion Source' +BodyForce:Real: 'Current Density' BodyForce:Real: 'Current Density 1' BodyForce:Real: 'Current Density 2' BodyForce:Real: 'Current Density 3' -BodyForce:Real: 'Current Density' +BodyForce:Real: 'Current Density Im 1' +BodyForce:Real: 'Current Density Im 2' +BodyForce:Real: 'Current Density Im 3' BodyForce:Real: 'Electric Potential' BodyForce:Real: 'Current Phase Angle' BodyForce:Real: 'Current Source' @@ -403,6 +406,9 @@ BodyForce:Real: 'Magnetic BodyForce 3' BodyForce:Real: 'Magnetization 1' BodyForce:Real: 'Magnetization 2' BodyForce:Real: 'Magnetization 3' +BodyForce:Real: 'Magnetization Im 1' +BodyForce:Real: 'Magnetization Im 2' +BodyForce:Real: 'Magnetization Im 3' BodyForce:Real: 'Mesh Origin' BodyForce:Real: 'Mesh Relax' BodyForce:Real: 'Mesh Rotate 1' @@ -431,6 +437,7 @@ BodyForce:Real: 'Smart Heater Control Point' BodyForce:Real: 'Smart Heater Temperature' BodyForce:Real: 'Sound Source' BodyForce:Real: 'Source' +BodyForce:Real: 'Source Field' BodyForce:Real: 'Strain' BodyForce:Real: 'Stress Bodyforce 1' BodyForce:Real: 'Stress Bodyforce 2' @@ -638,6 +645,7 @@ Material:Real: 'Material Constants' Material:Real: 'Material Coordinates Unit Vector 1' Material:Real: 'Material Coordinates Unit Vector 2' Material:Real: 'Material Coordinates Unit Vector 3' +Material:Real: 'Material Parameter' Material:Real: 'Max FreeSurface' Material:Real: 'Mean free path' Material:Real: 'Melting Point' @@ -659,6 +667,7 @@ Material:Real: 'Porous Resistivity' Material:Real: 'Positive Ion Density' Material:Real: 'Potential Difference' Material:Real: 'Pressure Coefficient' +Material:Real: 'Principal direction 2' Material:Real: 'Re Body Force 1' Material:Real: 'Re Body Force 2' Material:Real: 'Re Body Force 3' @@ -841,6 +850,7 @@ Solver:Integer: 'Exec Intervals' Solver:Integer: 'Extend Elastic Layers' Solver:Integer: 'Extract Interval' Solver:Integer: 'Fileindex Offset' +Solver:Integer: 'Fluid Solver Index' Solver:Integer: 'Free Surface After Iterations' Solver:Integer: 'Gebhardt Factors Fixed After Iterations' Solver:Integer: 'IDRS Parameter' @@ -880,6 +890,8 @@ Solver:Integer: 'Max Outer Iterations' Solver:Integer: 'Mesh Rotation Axis Order' Solver:Integer: 'Multigrid Levels' Solver:Integer: 'NOFnuclei' +Solver:Integer: 'Nonlinear Pre Solvers' +Solver:Integer: 'Nonlinear Post Solvers' Solver:Integer: 'Nonlinear System Linesearch Iterations' Solver:Integer: 'Nonlinear System Max Iterations' Solver:Integer: 'Nonlinear System Max Stepsize Tests' @@ -893,8 +905,11 @@ Solver:Integer: 'Number Of Particles' Solver:Integer: 'Number of Eigenmodes Included' Solver:Integer: 'Parasails MaxLevel' Solver:Integer: 'Parasails Symmetry' +Solver:Integer: 'Plate Solver Index' Solver:Integer: 'Population Size' +Solver:Integer: 'Post Solvers' Solver:Integer: 'Potential Solver ID' +Solver:Integer: 'Pre Solvers' Solver:Integer: 'Reinitialize Interval' Solver:Integer: 'Relative Integration Order' Solver:Integer: 'Reload Range Maximum' @@ -904,14 +919,13 @@ Solver:Integer: 'Reload Starting Position' Solver:Integer: 'Runge-Kutta Order' Solver:Integer: 'Save Points' Solver:Integer: 'Scan Points' +Solver:Integer: 'Shell Solver Index' Solver:Integer: 'Show Norm Index' Solver:Integer: 'Slave Solvers' -Solver:Integer: 'Post Solvers' -Solver:Integer: 'Pre Solvers' -Solver:Integer: 'Nonlinear Post Solvers' -Solver:Integer: 'Nonlinear Pre Solvers' +Solver:Integer: 'Solid Solver Index' Solver:Integer: 'Start GRPulay after iterations' Solver:Integer: 'Stream Function First Node' +Solver:Integer: 'Structure Solver Index' Solver:Integer: 'Time Derivative Order' Solver:Integer: 'Time Order' Solver:Integer: 'Variable DOFs' @@ -944,6 +958,7 @@ Solver:Logical: 'Block A-V System' Solver:Logical: 'Block Diagonal A' Solver:Logical: 'Block Gauss-Seidel' Solver:Logical: 'Block Matrix ReUse' +Solver:Logical: 'Block Monolithic' Solver:Logical: 'Block Preconditioner' Solver:Logical: 'Block Preconditioning' Solver:Logical: 'Block Solver' @@ -1099,6 +1114,7 @@ Solver:Logical: 'Particle Locate Robust' Solver:Logical: 'Perform Mapping' Solver:Logical: 'Perturbations' Solver:Logical: 'Plane Stress' +Solver:Logical: 'Plate Solver' Solver:Logical: 'Quadratic Approximation' Solver:Logical: 'Radiation Solver' Solver:Logical: 'Rarefaction' @@ -1114,6 +1130,7 @@ Solver:Logical: 'Save Bulk Only' Solver:Logical: 'Save Elemental Fields' Solver:Logical: 'Save Nodal Fields' Solver:Logical: 'Save Halo Elements Only' +Solver:Logical: 'Shell Solver' Solver:Logical: 'Skip Halo Elements' Solver:Logical: 'Save Bulk System' Solver:Logical: 'Save Displacements' @@ -1144,6 +1161,7 @@ Solver:Logical: 'Stokes Stream Function' Solver:Logical: 'Stream Function Scaling' Solver:Logical: 'Stream Function Shifting' Solver:Logical: 'Stress Computation' +Solver:Logical: 'Structure-Structure Coupling' Solver:Logical: 'Sum Forces' Solver:Logical: 'Symmetrisize' Solver:Logical: 'Target Variable Complex' @@ -1430,8 +1448,6 @@ solver:string: 'divergence variable' solver:string: 'velocity initialization method' solver:string: 'velocity variable name' solver:string: 'weight variable' -material:real: 'inverse relative permeability' -material:real: 'inverse relative permeability im' solver:logical: 'calculate energy norm' solver:string: 'field variable' solver:string: 'eletric field variable' @@ -1453,7 +1469,6 @@ solver:logical: 'calculate energy functional' material:real: 'relative reluctivity' material:real: 'relative reluctivity im' material:real: 'reluctivity im' -material:real: 'inverse relative permeability im' bc:real: 'e re {f}' bc:real: 'e re {e}' bc:real: 'e im {f}' diff --git a/fem/tests/DisContBoundaryNodalProj/discont.sif b/fem/tests/DisContBoundaryNodalProj/discont.sif index dc2d0e2b63..40db05e949 100644 --- a/fem/tests/DisContBoundaryNodalProj/discont.sif +++ b/fem/tests/DisContBoundaryNodalProj/discont.sif @@ -29,9 +29,6 @@ Simulation ! Normally we don't want to apply this Discontinuous Bulk Greedy = Logical False -! Here the discontinuity loader is currently active -! This will become the default in future. - New Load Mesh = Logical True End Constants diff --git a/fem/tests/EdgeElementInterpolation/interpolationtest.sif b/fem/tests/EdgeElementInterpolation/interpolationtest.sif index 85f37d8aca..439c73798a 100644 --- a/fem/tests/EdgeElementInterpolation/interpolationtest.sif +++ b/fem/tests/EdgeElementInterpolation/interpolationtest.sif @@ -26,7 +26,6 @@ Simulation Output Intervals(1) = 1 Steady State Max Iterations = 1 ! Post File = "interpolationtest.ep" - New Load Mesh = Logical True End Body 1 diff --git a/fem/tests/mgdyn_steady_piolaversion/ZCurrent.sif b/fem/tests/mgdyn_steady_piolaversion/ZCurrent.sif index 5fc7dd8939..b959fa937a 100644 --- a/fem/tests/mgdyn_steady_piolaversion/ZCurrent.sif +++ b/fem/tests/mgdyn_steady_piolaversion/ZCurrent.sif @@ -10,7 +10,6 @@ Simulation Simulation Type = Steady Steady State Max Iterations = 1 ! Post File = zcurrent.ep - New Load Mesh = Logical True End Body 1 From c27b447403957d5d022fad35dc782afb58c02e6d Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 9 Oct 2020 10:40:59 +0300 Subject: [PATCH 19/73] Known keywords updated again --- fem/src/SOLVER.KEYWORDS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fem/src/SOLVER.KEYWORDS b/fem/src/SOLVER.KEYWORDS index 4ad9426ba5..a4e664106a 100644 --- a/fem/src/SOLVER.KEYWORDS +++ b/fem/src/SOLVER.KEYWORDS @@ -1050,6 +1050,7 @@ Solver:Logical: 'Harmonic Analysis' Solver:Logical: 'Harmonic Simulation' Solver:Logical: 'Hole Correction' Solver:Logical: 'Impose Body Force Current' +Solver:Logical: 'Incompressible' Solver:Logical: 'Initialize' Solver:Logical: 'Initialize State Variables' Solver:Logical: 'Internal Move Boundary' @@ -1073,6 +1074,7 @@ Solver:Logical: 'Linear System Componentwise Backward Error' Solver:Logical: 'Linear System Normwise Backward Error' Solver:Logical: 'Lumped Mass Matrix' Solver:Logical: 'Matrix Topology Fixed' +Solver:Logical: 'Maxwell Material' Solver:Logical: 'MG Algebraic' Solver:Logical: 'MG Coarse Nodes Save' Solver:Logical: 'MG Compatible Relax Merit Only' @@ -1173,6 +1175,7 @@ Solver:Logical: 'Update Transient System' Solver:Logical: 'Update View Factors' Solver:Logical: 'Use Absolute Norm For Convergence' Solver:Logical: 'Use Accumulation' +Solver:Logical: 'Use Density' Solver:Logical: 'Use Global Mass Matrix' Solver:Logical: 'Use Truncation' Solver:Logical: 'Use Velocity Laplacian' From 6120d4d0787a0332055c41b2552f04a838c23bf7 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 9 Oct 2020 18:13:01 +0300 Subject: [PATCH 20/73] Initialize pointer --- .../ResultOutputSolve/VtuOutputSolver.F90 | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/fem/src/modules/ResultOutputSolve/VtuOutputSolver.F90 b/fem/src/modules/ResultOutputSolve/VtuOutputSolver.F90 index 2b6299cb23..7724b9aaeb 100644 --- a/fem/src/modules/ResultOutputSolve/VtuOutputSolver.F90 +++ b/fem/src/modules/ResultOutputSolve/VtuOutputSolver.F90 @@ -851,7 +851,7 @@ SUBROUTINE WriteVtuFile( VtuFile, Model, RemoveDisp ) Params => GetSolverParams() Buffered = .TRUE. FlipActive = .FALSE. - + ! we could have huge amount of gauss points ALLOCATE( ElemInd(512)) !Model % Mesh % MaxElementDOFS)) @@ -1067,10 +1067,6 @@ SUBROUTINE WriteVtuFile( VtuFile, Model, RemoveDisp ) ! Some vectors are defined by a set of components (either 2 or 3) !--------------------------------------------------------------------- IF( ComponentVector ) THEN - !IF( NoModes + NoModes2 > 0 ) THEN - ! CALL Warn('WriteVtuXMLFile','Modes cannot currently be given componentwise!') - ! CYCLE - !END IF IF( VarType == Variable_on_gauss_points ) THEN CALL Warn('WriteVtuXMLFile','Gauss point variables cannot currently be given componentwise!') CYCLE @@ -1146,9 +1142,11 @@ SUBROUTINE WriteVtuFile( VtuFile, Model, RemoveDisp ) IF( ( DG .OR. DN ) .AND. VarType == Variable_on_nodes_on_elements ) THEN CALL Info('WriteVTUFile','Setting field type to discontinuous',Level=12) InvFieldPerm => InvDgPerm - ELSE + ELSE IF( ALLOCATED( InvNodePerm ) ) THEN CALL Info('WriteVTUFile','Setting field type to nodal',Level=14) InvFieldPerm => InvNodePerm + ELSE + InvFieldPerm => NULL() END IF IF(.NOT. EigenAnalysis ) THEN @@ -1212,6 +1210,12 @@ SUBROUTINE WriteVtuFile( VtuFile, Model, RemoveDisp ) !--------------------------------------------------------------------- IF( WriteData ) THEN + IF( .NOT. NoPermutation .AND. NumberOfDofNodes > 0 ) THEN + IF(.NOT. ASSOCIATED( InvFieldPerm ) ) THEN + CALL Fatal(Caller,'InvFieldPerm not associated!') + END IF + END IF + IF( BinaryOutput ) WRITE( VtuUnit ) k DO ii = 1, NumberOfDofNodes @@ -2178,7 +2182,7 @@ SUBROUTINE WritePvtuFile( PvtuFile, Model ) WRITE( FullName,'(A,I0)') TRIM( FieldName )//' EigenMode',IndField ! Note: this should be added for "HarmonicMode" and "ConstraintMode" too - ! now the .vptu file for these vectors is not correct! + ! now the .pvtu file for these vectors is not correct! END IF IF( AsciiOutput ) THEN From dfe0a3a901bcdcffbe0c59e43ae040ce38d317c1 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 11 Oct 2020 16:57:00 +0900 Subject: [PATCH 21/73] Slight modification to compile ElmerPost - added error_matc(char* format, ...) to avoid undefined reference error - added X11 to target_link_libraries --- post/matc/src/elmer/matc.h | 24 ++++++++++++++++++++++++ post/src/CMakeLists.txt | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/post/matc/src/elmer/matc.h b/post/matc/src/elmer/matc.h index 8e12438cae..00abddbd78 100644 --- a/post/matc/src/elmer/matc.h +++ b/post/matc/src/elmer/matc.h @@ -425,6 +425,29 @@ void error( char *format, ... ) longjmp( *jmpbuf, 2 ); } +void error_matc( char *format, ... ) +{// Just a copy of error( char *format, ...) to avoid undefined reference error + va_list args; + + va_start( args, format ); +#ifdef STRING_OUTPUT + if ( math_out_count+512 > math_out_allocated ) + { + math_out_allocated += 512; + math_out_str = (char *)realloc( math_out_str, math_out_allocated ); + } + math_out_count += sprintf( &math_out_str[math_out_count], "MATC ERROR: " ); + math_out_count += vsprintf( &math_out_str[math_out_count], format, args ); +#else + fprintf( math_err, "MATC ERROR: " ); + vfprintf( math_err, format, args ); +#endif + va_end( args ); + + (void)mem_free_all(); + longjmp( *jmpbuf, 2 ); +} + void PrintOut( char *format, ... ) { @@ -445,6 +468,7 @@ void PrintOut( char *format, ... ) } #else extern void error( char *format, ... ); +extern void error_matc( char *format, ... ); extern void PrintOut( char *format, ... ); #endif diff --git a/post/src/CMakeLists.txt b/post/src/CMakeLists.txt index 3d75883bcd..6bdd2c53b3 100644 --- a/post/src/CMakeLists.txt +++ b/post/src/CMakeLists.txt @@ -66,6 +66,7 @@ target_link_libraries(ElmerPost camera elements module objects visuals graphics glaux ${OPENGL_LIBRARIES} ${TCL_LIBRARY} ${TK_LIBRARY} matc m) IF(NOT(WIN32)) + target_link_libraries(ElmerPost X11) SET_TARGET_PROPERTIES(ElmerPost PROPERTIES INSTALL_RPATH "${ELMERSOLVER_RPATH_STRING}") ENDIF() @@ -84,7 +85,7 @@ IF(NOT(WIN32)) add_executable(QueryGLXExt ${QueryGLXExt_SRCS}) - target_link_libraries(QueryGLXExt ${OPENGL_LIBRARIES}) + target_link_libraries(QueryGLXExt ${OPENGL_LIBRARIES} X11) install(TARGETS QueryGLXExt DESTINATION bin) ENDIF(NOT(WIN32)) From 1e087d4d14be67a0b49839128a6d829a52aca4e2 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 11 Oct 2020 17:05:51 +0900 Subject: [PATCH 22/73] Slight modification to compile ElmerGUIlogger - added Qt5Widgets package - added QT5_USE_MODULES statement --- ElmerGUIlogger/CMakeLists.txt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ElmerGUIlogger/CMakeLists.txt b/ElmerGUIlogger/CMakeLists.txt index f31389e455..702cb5b5c2 100644 --- a/ElmerGUIlogger/CMakeLists.txt +++ b/ElmerGUIlogger/CMakeLists.txt @@ -2,16 +2,22 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") MESSAGE(STATUS "------------------------------------------------") +IF(ElmerGUIloggerSTANDALONE) + PROJECT(ElmerGUIlogger CXX C) + IF(WIN32) + INCLUDE(cmake/windows_bundle.cmake) + ENDIF(WIN32) +ENDIF(ElmerGUIloggerSTANDALONE) IF(WITH_QT5) MESSAGE(STATUS "------------------------------------------------") - SET(QT5_PKG_LIST Qt5OpenGL Qt5Xml Qt5Script Qt5Gui Qt5Core Qt5PrintSupport) + SET(QT5_PKG_LIST Qt5OpenGL Qt5Xml Qt5Script Qt5Gui Qt5Core Qt5Widgets Qt5PrintSupport) FOREACH(_pkg ${QT5_PKG_LIST}) FIND_PACKAGE(${_pkg} PATHS ${QT5_PATH}) ENDFOREACH() ADD_DEFINITIONS(-DWITH_QT5) MESSAGE(STATUS " [ElmerGUIlogger] Qt5: " ${Qt5_FOUND}) - MESSAGE(STATUS " [ElmerGUIlogger] Qt5 Libraries: ${Qt5OpenGL_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Script_LIBRARIES} ${Qt5Gui_LIBRARIES} ${Qt5Core_LIBRARIES}") + MESSAGE(STATUS " [ElmerGUIlogger] Qt5 Libraries: ${Qt5OpenGL_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Script_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Gui_LIBRARIES} ${Qt5Core_LIBRARIES} ${Qt5PrintSupport_LIBRARIES}") MESSAGE(STATUS "------------------------------------------------") ELSE() MESSAGE(STATUS "------------------------------------------------") @@ -21,15 +27,6 @@ ELSE() MESSAGE(STATUS "------------------------------------------------") ENDIF() - -IF(ElmerGUIloggerSTANDALONE) - PROJECT(ElmerGUIlogger CXX C) - IF(WIN32) - INCLUDE(cmake/windows_bundle.cmake) - ENDIF(WIN32) -ENDIF(ElmerGUIloggerSTANDALONE) - - SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTORCC ON) @@ -63,6 +60,11 @@ ENDIF() ADD_EXECUTABLE(ElmerGUIlogger WIN32 ${SOURCES} ${UI_RESOURCES}) + +IF(WITH_QT5) + QT5_USE_MODULES(ElmerGUIlogger OpenGL Xml Script Gui Core Svg Widgets PrintSupport) +ENDIF() + TARGET_LINK_LIBRARIES(ElmerGUIlogger ${QT_LIBRARIES}) INSTALL(TARGETS ElmerGUIlogger RUNTIME DESTINATION "bin" COMPONENT "elmergui") From 7ccc70913c2ca591420b5c71f1c1126503aea229 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 11 Oct 2020 18:32:17 +0900 Subject: [PATCH 23/73] Slight modification to compile ElmerGUItester --- ElmerGUItester/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ElmerGUItester/CMakeLists.txt b/ElmerGUItester/CMakeLists.txt index ad7c1e5c3f..12d925206b 100644 --- a/ElmerGUItester/CMakeLists.txt +++ b/ElmerGUItester/CMakeLists.txt @@ -4,7 +4,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) IF(WITH_QT5) MESSAGE(STATUS "------------------------------------------------") - SET(QT5_PKG_LIST Qt5Gui Qt5Core) + SET(QT5_PKG_LIST Qt5Gui Qt5Core Qt5Widgets) FOREACH(_pkg ${QT5_PKG_LIST}) FIND_PACKAGE(${_pkg} PATHS ${QT5_PATH}) ENDFOREACH() @@ -48,5 +48,10 @@ ENDIF() INCLUDE_DIRECTORIES(${APPLICATION_INCLUDE_DIRS}) ADD_EXECUTABLE(ElmerGUItester WIN32 ${SOURCES} ${UI_HEADERS} ${UI_RESOURCES}) + +IF(WITH_QT5) + QT5_USE_MODULES(ElmerGUItester Gui Core Widgets) +ENDIF() + TARGET_LINK_LIBRARIES(ElmerGUItester ${QT_LIBRARIES}) INSTALL(TARGETS ElmerGUItester RUNTIME DESTINATION "bin" COMPONENT "elmergui") From 88e7c802ede94865b7a8d851894e9cd2d7e35e24 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Sun, 11 Oct 2020 21:02:40 +0900 Subject: [PATCH 24/73] Issue#78 ElmerGUItester mainform.ui issie #78 ( mainform.ui in wrong folder ) --- ElmerGUItester/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ElmerGUItester/CMakeLists.txt b/ElmerGUItester/CMakeLists.txt index 12d925206b..5599fcc973 100644 --- a/ElmerGUItester/CMakeLists.txt +++ b/ElmerGUItester/CMakeLists.txt @@ -16,7 +16,7 @@ ENDIF() SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTORCC ON) -SET(CMAKE_AUTOUIC ON) +SET(CMAKE_AUTOUIC OFF) SET(TARGETS ElmerGUItester) From 0bf69a6e417a4d27fcda37e78618f167ba5fb85d Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 12 Oct 2020 16:28:33 +0300 Subject: [PATCH 25/73] Allow a complex-valued reluctivity tensor within WhitneyAVHarmonicSolver The post-processing solver is not yet updated to work with a complex reluctivity tensor. --- fem/src/modules/MagnetoDynamics/Utils.F90 | 35 ++++------- .../WhitneyAVHarmonicSolver.F90 | 60 ++++++++++++------- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/Utils.F90 b/fem/src/modules/MagnetoDynamics/Utils.F90 index 07fdcda490..4a1106dd4b 100644 --- a/fem/src/modules/MagnetoDynamics/Utils.F90 +++ b/fem/src/modules/MagnetoDynamics/Utils.F90 @@ -35,9 +35,7 @@ ! *****************************************************************************/ !------------------------------------------------------------------------------ -!> Solve Maxwell equations in vector potential formulation (or the A-V -!> formulation) and (relatively)low frequency approximation using lowest -!> order Withney 1-forms (edge elements). +!> Utilities for the A-V solvers of electromagnetism !> \ingroup Solvers !------------------------------------------------------------------------------- MODULE MagnetoDynamicsUtils @@ -249,8 +247,8 @@ SUBROUTINE GetReluctivityR(Material,Acoef,n) !------------------------------------------------------------------------------ IMPLICIT NONE TYPE(ValueList_t), POINTER :: Material - INTEGER :: n REAL(KIND=dp) :: Acoef(:) + INTEGER :: n !------------------------------------------------------------------------------ LOGICAL :: Found, FirstTime = .TRUE., Warned = .FALSE. REAL(KIND=dp) :: Avacuum @@ -291,8 +289,8 @@ SUBROUTINE GetReluctivityC(Material,Acoef,n) !------------------------------------------------------------------------------ IMPLICIT NONE TYPE(ValueList_t), POINTER :: Material - INTEGER :: n COMPLEX(KIND=dp) :: Acoef(:) + INTEGER :: n !------------------------------------------------------------------------------ LOGICAL :: L, Found, FirstTime = .TRUE., Warned = .FALSE. REAL(KIND=dp) :: Avacuum @@ -335,15 +333,10 @@ SUBROUTINE GetReluctivityTensorR(Material, Acoef, n, Found) !------------------------------------------------------------------------------- IMPLICIT NONE TYPE(ValueList_t), POINTER, INTENT(IN) :: Material - REAL(KIND=dp), POINTER :: Acoef(:,:,:) + REAL(KIND=dp), POINTER, INTENT(OUT) :: Acoef(:,:,:) INTEGER, INTENT(IN) :: n LOGICAL , INTENT(OUT) :: Found !------------------------------------------------------------------------------- - LOGICAL :: FirstTime = .FALSE. - INTEGER :: k - REAL(KIND=dp) :: Avacuum - - SAVE Avacuum CALL GetRealArray( Material, Acoef, 'Reluctivity', Found ) ! @@ -357,38 +350,30 @@ END SUBROUTINE GetReluctivityTensorR !------------------------------------------------------------------------------- !> Get complex tensorial reluctivity -!> Untested !------------------------------------------------------------------------------ SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found, Cwrk) !------------------------------------------------------------------------------- IMPLICIT NONE TYPE(ValueList_t), POINTER, INTENT(IN) :: Material - COMPLEX(KIND=dp), POINTER :: Acoef(:,:,:) - REAL(KIND=dp), POINTER, OPTIONAL :: Cwrk(:,:,:) + COMPLEX(KIND=dp), POINTER, INTENT(OUT) :: Acoef(:,:,:) INTEGER, INTENT(IN) :: n LOGICAL , INTENT(OUT) :: Found + REAL(KIND=dp), POINTER, OPTIONAL, INTENT(OUT) :: Cwrk(:,:,:) !------------------------------------------------------------------------------- - LOGICAL :: FirstTime = .FALSE. LOGICAL :: Found_im - INTEGER :: k1,k2,k3 - REAL(KIND=dp) :: Avacuum REAL(KIND=dp), POINTER :: work(:,:,:) - SAVE Avacuum - IF(.NOT. PRESENT(Cwrk)) THEN ALLOCATE(work(size(Acoef,1), size(Acoef,2), size(Acoef,3))) ELSE work => Cwrk END IF + CALL GetRealArray( Material, work, 'Reluctivity', Found ) + Acoef(:,:,1:n) = CMPLX(work(:,:,1:n), 0.0d0, kind=dp) - CALL GetRealArray( Material, work, 'Relative Reluctivity', Found ) - Acoef(:,:,:) = work(:,:,:) - - CALL GetRealArray( Material, work, 'Relative Reluctivity im', Found_im ) - - Acoef = CMPLX(REAL(Acoef), work) + CALL GetRealArray( Material, work, 'Reluctivity im', Found_im ) + IF (Found_im) Acoef(:,:,1:n) = CMPLX(REAL(Acoef(:,:,1:n)), work(:,:,1:n), kind=dp) Found = Found .OR. Found_im diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 index 58da33338a..4908bce9d6 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 @@ -144,10 +144,11 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) COMPLEX(KIND=dp), ALLOCATABLE :: STIFF(:,:), MASS(:,:), FORCE(:), JFixFORCE(:),JFixVec(:,:) COMPLEX(KIND=dp), ALLOCATABLE :: LOAD(:,:), Acoef(:), Tcoef(:,:,:) COMPLEX(KIND=dp), ALLOCATABLE :: LamCond(:) + COMPLEX(KIND=dp), POINTER :: Acoef_t(:,:,:) REAL(KIND=dp), ALLOCATABLE :: RotM(:,:,:), GapLength(:), MuParameter(:), SkinCond(:) - REAL (KIND=DP), POINTER :: Cwrk(:,:,:), Cwrk_im(:,:,:), LamThick(:) + REAL(KIND=dp), POINTER :: Cwrk(:,:,:), Cwrk_im(:,:,:), LamThick(:) REAL(KIND=dp), POINTER :: sValues(:), fixpot(:) TYPE(Variable_t), POINTER :: jfixvar, jfixvarIm, HbCurveVar @@ -155,7 +156,7 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) CHARACTER(LEN=MAX_NAME_LEN):: LaminateStackModel, CoilType, HbCurveVarName LOGICAL :: Stat, EigenAnalysis, TG, Jfix, JfixSolve, LaminateStack, CoilBody, EdgeBasis,LFact,LFactFound - LOGICAL :: PiolaVersion, SecondOrder, GotHbCurveVar + LOGICAL :: PiolaVersion, SecondOrder, GotHbCurveVar, HasTensorReluctivity REAL(KIND=dp) :: NewtonTol INTEGER :: NewtonIter LOGICAL :: ExtNewton @@ -170,7 +171,7 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) TYPE(ValueList_t), POINTER :: CompParams SAVE STIFF, LOAD, MASS, FORCE, Tcoef, JFixVec, JFixFORCE, & - Acoef, Cwrk, Cwrk_im, LamCond, & + Acoef, Acoef_t, Cwrk, Cwrk_im, LamCond, & LamThick, AllocationsDone, RotM, & GapLength, MuParameter, SkinCond !------------------------------------------------------------------------------ @@ -208,7 +209,7 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) N = Mesh % MaxElementDOFs ! just big enough ALLOCATE( FORCE(N), LOAD(7,N), STIFF(N,N), & MASS(N,N), JFixVec(3,N),JFixFORCE(n), Tcoef(3,3,N), RotM(3,3,N), & - GapLength(N), MuParameter(N), SkinCond(N), Acoef(N), LamCond(N), & + GapLength(N), MuParameter(N), SkinCond(N), Acoef(N), Acoef_t(3,3,N), LamCond(N), & LamThick(N), STAT=istat ) IF ( istat /= 0 ) THEN CALL Fatal( 'WhitneyAVHarmonicSolver', 'Memory allocation error.' ) @@ -369,10 +370,20 @@ FUNCTION DoSolve(IterNo) RESULT(Converged) END IF END IF Acoef = 0.0_dp + Acoef_t = CMPLX(0.0d0,0.0d0, kind=dp) Tcoef = 0.0_dp Material => GetMaterial( Element ) IF ( ASSOCIATED(Material) ) THEN - CALL GetReluctivity(Material,Acoef,n) + HasTensorReluctivity = .FALSE. + CALL GetReluctivity(Material,Acoef_t,n,HasTensorReluctivity) + IF (HasTensorReluctivity) THEN + IF (size(Acoef_t,1)==1 .AND. size(Acoef_t,2)==1) THEN + Acoef(1:n) = Acoef_t(1,1,1:n) + HasTensorReluctivity = .FALSE. + END IF + ELSE + CALL GetReluctivity(Material,Acoef,n) + END IF !------------------------------------------------------------------------------ ! Read conductivity values (might be a tensor) @@ -1191,7 +1202,6 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & CALL GetEdgeBasis(Element, WBasis, RotWBasis, Basis, dBasisdx) END IF - mu = SUM( Basis(1:n) * Acoef(1:n) ) ! Compute convection type term coming from rotation ! ------------------------------------------------- @@ -1234,6 +1244,8 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & IF ( Newton ) THEN muder=(DerivateCurve(Bval,Hval,Babs,CubicCoeff=Cval)-mu)/babs END IF + ELSE + mu = SUM( Basis(1:n) * Acoef(1:n) ) END IF IF (LaminateStack) THEN @@ -1251,20 +1263,28 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & END SELECT END IF - Nu = CMPLX(0._dp, 0._dp) - Nu(1,1) = mu - Nu(2,2) = mu - Nu(3,3) = mu - - IF (CoilBody .AND. StrandedHomogenization) THEN - nu_val = SUM( Basis(1:n) * nu_11(1:n) ) - nuim_val = SUM( Basis(1:n) * nuim_11(1:n) ) - Nu(1,1) = CMPLX(nu_val, nuim_val, KIND=dp) - nu_val = SUM( Basis(1:n) * nu_22(1:n) ) - nuim_val = SUM( Basis(1:n) * nuim_22(1:n) ) - Nu(2,2) = CMPLX(nu_val, nuim_val, KIND=dp) - Nu = MATMUL(MATMUL(RotMLoc, Nu),TRANSPOSE(RotMLoc)) - END IF + IF (HasTensorReluctivity) THEN + DO i = 1,3 + DO j = 1,3 + Nu(i,j) = SUM(Basis(1:n)*Acoef_t(i,j,1:n)) + END DO + END DO + ELSE + Nu = CMPLX(0._dp, 0._dp) + Nu(1,1) = mu + Nu(2,2) = mu + Nu(3,3) = mu + + IF (CoilBody .AND. StrandedHomogenization) THEN + nu_val = SUM( Basis(1:n) * nu_11(1:n) ) + nuim_val = SUM( Basis(1:n) * nuim_11(1:n) ) + Nu(1,1) = CMPLX(nu_val, nuim_val, KIND=dp) + nu_val = SUM( Basis(1:n) * nu_22(1:n) ) + nuim_val = SUM( Basis(1:n) * nuim_22(1:n) ) + Nu(2,2) = CMPLX(nu_val, nuim_val, KIND=dp) + Nu = MATMUL(MATMUL(RotMLoc, Nu),TRANSPOSE(RotMLoc)) + END IF + END IF M = MATMUL( LOAD(4:6,1:n), Basis(1:n) ) L = MATMUL( LOAD(1:3,1:n), Basis(1:n) ) From 0c7ee90c55d082ae5d79bd2eacfea3f5eef0bdc2 Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Tue, 13 Oct 2020 19:55:45 +0900 Subject: [PATCH 26/73] ElmerPost tcl scrpt adjustment This is to avoid number format error. --- post/src/tcl/colorscale.tcl | 6 +++--- post/src/tcl/isosurface.tcl | 6 +++--- post/src/tcl/mesh.tcl | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/post/src/tcl/colorscale.tcl b/post/src/tcl/colorscale.tcl index 98750d4d4c..54ae3899ae 100755 --- a/post/src/tcl/colorscale.tcl +++ b/post/src/tcl/colorscale.tcl @@ -67,14 +67,14 @@ set ColorScaleColorMax 1.0 proc colscale_update {} { + .colscale.set.min delete 0 end + .colscale.set.max delete 0 end + global ColorScaleColor ColorScaleColorMin ColorScaleColorMax UpdateVariable ColorScaleColor - .colscale.set.min delete 0 end .colscale.set.min insert end [format %-10.5g $ColorScaleColorMin] - - .colscale.set.max delete 0 end .colscale.set.max insert end [format %-10.5g $ColorScaleColorMax] } diff --git a/post/src/tcl/isosurface.tcl b/post/src/tcl/isosurface.tcl index 00c380aacd..1653de1bea 100755 --- a/post/src/tcl/isosurface.tcl +++ b/post/src/tcl/isosurface.tcl @@ -76,14 +76,14 @@ set IsosurfaceColorSetMinMax 0 proc isosurface_update {} { + .isosurface.cset.min delete 0 end + .isosurface.cset.max delete 0 end + global IsosurfaceColor IsosurfaceColorMin IsosurfaceColorMax UpdateVariable IsosurfaceColor - .isosurface.cset.min delete 0 end .isosurface.cset.min insert end [format %-10.5g $IsosurfaceColorMin] - - .isosurface.cset.max delete 0 end .isosurface.cset.max insert end [format %-10.5g $IsosurfaceColorMax] } diff --git a/post/src/tcl/mesh.tcl b/post/src/tcl/mesh.tcl index 67d2573e8f..faf7ddc566 100755 --- a/post/src/tcl/mesh.tcl +++ b/post/src/tcl/mesh.tcl @@ -67,14 +67,14 @@ set NumberOfScalarVariables 1 proc mesh_update {} { + .mesh.set.min delete 0 end + .mesh.set.max delete 0 end + global MeshColor MeshColorMin MeshColorMax UpdateVariable MeshColor - .mesh.set.min delete 0 end .mesh.set.min insert end [format %-10.5g $MeshColorMin] - - .mesh.set.max delete 0 end .mesh.set.max insert end [format %-10.5g $MeshColorMax] } From 166a02bc1d1172bbe4f5d51cae8a94fa65e2cf00 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 13 Oct 2020 14:15:08 +0300 Subject: [PATCH 27/73] Fix for getting a complex-valued reluctivity --- fem/src/modules/MagnetoDynamics/Utils.F90 | 47 ++++++++++++++----- .../WhitneyAVHarmonicSolver.F90 | 8 ++-- .../mgdyn_harmonic/MGDynamicsHarmonic.sif | 10 +++- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/Utils.F90 b/fem/src/modules/MagnetoDynamics/Utils.F90 index 4a1106dd4b..36e100b036 100644 --- a/fem/src/modules/MagnetoDynamics/Utils.F90 +++ b/fem/src/modules/MagnetoDynamics/Utils.F90 @@ -361,25 +361,48 @@ SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found, Cwrk) REAL(KIND=dp), POINTER, OPTIONAL, INTENT(OUT) :: Cwrk(:,:,:) !------------------------------------------------------------------------------- LOGICAL :: Found_im - REAL(KIND=dp), POINTER :: work(:,:,:) + REAL(KIND=dp), POINTER :: work(:,:,:) => NULL() + INTEGER :: n1, n2, n3 - IF(.NOT. PRESENT(Cwrk)) THEN - ALLOCATE(work(size(Acoef,1), size(Acoef,2), size(Acoef,3))) - ELSE - work => Cwrk - END IF + IF (ASSOCIATED(Acoef)) DEALLOCATE(Acoef) + +! IF(.NOT. PRESENT(Cwrk)) THEN +! ALLOCATE(work(size(Acoef,1), size(Acoef,2), size(Acoef,3))) +! ELSE +! work => Cwrk +! END IF +! IF (PRESENT(Cwrk)) work => Cwrk CALL GetRealArray( Material, work, 'Reluctivity', Found ) - Acoef(:,:,1:n) = CMPLX(work(:,:,1:n), 0.0d0, kind=dp) - CALL GetRealArray( Material, work, 'Reluctivity im', Found_im ) - IF (Found_im) Acoef(:,:,1:n) = CMPLX(REAL(Acoef(:,:,1:n)), work(:,:,1:n), kind=dp) + IF (Found) THEN + n1 = SIZE(work,1) + n2 = SIZE(work,2) + n3 = SIZE(work,3) + ALLOCATE(Acoef(n1, n2, n3)) + Acoef(:,:,:) = CMPLX(work(:,:,:), 0.0d0, kind=dp) + END IF + CALL GetRealArray( Material, work, 'Reluctivity im', Found_im ) + IF (Found_im) THEN + n1 = SIZE(work,1) + n2 = SIZE(work,2) + n3 = SIZE(work,3) + IF (.NOT. ASSOCIATED(Acoef)) THEN + ALLOCATE(Acoef(n1, n2, n3)) + Acoef(:,:,:) = CMPLX(0.0d0, work(:,:,:), kind=dp) + ELSE + IF (SIZE(Acoef,1) /= n1 .OR. SIZE(Acoef,2) /= n2 .OR. SIZE(Acoef,3) /= n3) & + CALL Fatal('GetReluctivityTensorC', 'Reluctivity and Reluctivity im of different size') + Acoef(1:n1,1:n2,1:n3) = CMPLX(REAL(Acoef(1:n1,1:n2,1:n3)), work(1:n1,1:n2,1:n3), kind=dp) + END IF + END IF Found = Found .OR. Found_im + IF (ASSOCIATED(work)) DEALLOCATE(work) - IF(.NOT. PRESENT(Cwrk)) THEN - DEALLOCATE(work) - END IF +! IF(.NOT. PRESENT(Cwrk)) THEN +! DEALLOCATE(work) +! END IF !------------------------------------------------------------------------------- END SUBROUTINE GetReluctivityTensorC !------------------------------------------------------------------------------- diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 index 4908bce9d6..b385ee9cbf 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 @@ -144,7 +144,7 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) COMPLEX(KIND=dp), ALLOCATABLE :: STIFF(:,:), MASS(:,:), FORCE(:), JFixFORCE(:),JFixVec(:,:) COMPLEX(KIND=dp), ALLOCATABLE :: LOAD(:,:), Acoef(:), Tcoef(:,:,:) COMPLEX(KIND=dp), ALLOCATABLE :: LamCond(:) - COMPLEX(KIND=dp), POINTER :: Acoef_t(:,:,:) + COMPLEX(KIND=dp), POINTER :: Acoef_t(:,:,:) => NULL() REAL(KIND=dp), ALLOCATABLE :: RotM(:,:,:), GapLength(:), MuParameter(:), SkinCond(:) @@ -209,7 +209,7 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) N = Mesh % MaxElementDOFs ! just big enough ALLOCATE( FORCE(N), LOAD(7,N), STIFF(N,N), & MASS(N,N), JFixVec(3,N),JFixFORCE(n), Tcoef(3,3,N), RotM(3,3,N), & - GapLength(N), MuParameter(N), SkinCond(N), Acoef(N), Acoef_t(3,3,N), LamCond(N), & + GapLength(N), MuParameter(N), SkinCond(N), Acoef(N), LamCond(N), & LamThick(N), STAT=istat ) IF ( istat /= 0 ) THEN CALL Fatal( 'WhitneyAVHarmonicSolver', 'Memory allocation error.' ) @@ -370,7 +370,6 @@ FUNCTION DoSolve(IterNo) RESULT(Converged) END IF END IF Acoef = 0.0_dp - Acoef_t = CMPLX(0.0d0,0.0d0, kind=dp) Tcoef = 0.0_dp Material => GetMaterial( Element ) IF ( ASSOCIATED(Material) ) THEN @@ -380,11 +379,12 @@ FUNCTION DoSolve(IterNo) RESULT(Converged) IF (size(Acoef_t,1)==1 .AND. size(Acoef_t,2)==1) THEN Acoef(1:n) = Acoef_t(1,1,1:n) HasTensorReluctivity = .FALSE. + ELSE IF (size(Acoef_t,1)/=3 .AND. size(Acoef_t,2)/=3) THEN + CALL Fatal('WhitneyAVHarmonicSolver', 'Reluctivity tensor should be of size 3x3') END IF ELSE CALL GetReluctivity(Material,Acoef,n) END IF - !------------------------------------------------------------------------------ ! Read conductivity values (might be a tensor) !------------------------------------------------------------------------------ diff --git a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif index 45907c6f42..cef3dff145 100755 --- a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif +++ b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif @@ -26,7 +26,15 @@ End Material 1 - Reluctivity = Real 1 + Reluctivity = Real 1 + Reluctivity Im = Real 0 +! Reluctivity(3,3) = 1.0 0.0 0.0 \ +! 0.0 1.0 0.0 \ +! 0.0 0.0 1.0 +! Reluctivity Im(3,3) = 0.0 0.0 0.0 \ +! 0.0 0.0 0.0 \ +! 0.0 0.0 0.0 + ! Permittivity = Real 1 Electric Conductivity = Real 1 End From 468693a9806e0e1ff3bd49bd6a90ee754f1a0d8c Mon Sep 17 00:00:00 2001 From: t7saeki <53612710+t7saeki@users.noreply.github.com> Date: Tue, 13 Oct 2020 21:27:46 +0900 Subject: [PATCH 28/73] Adjustment to compile ElmerGUIlogger in Ubuntu Removal of redundant module use. --- ElmerGUIlogger/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ElmerGUIlogger/CMakeLists.txt b/ElmerGUIlogger/CMakeLists.txt index 702cb5b5c2..55ee453ca5 100644 --- a/ElmerGUIlogger/CMakeLists.txt +++ b/ElmerGUIlogger/CMakeLists.txt @@ -62,7 +62,7 @@ ENDIF() ADD_EXECUTABLE(ElmerGUIlogger WIN32 ${SOURCES} ${UI_RESOURCES}) IF(WITH_QT5) - QT5_USE_MODULES(ElmerGUIlogger OpenGL Xml Script Gui Core Svg Widgets PrintSupport) + QT5_USE_MODULES(ElmerGUIlogger OpenGL Xml Script Gui Core Widgets PrintSupport) ENDIF() TARGET_LINK_LIBRARIES(ElmerGUIlogger ${QT_LIBRARIES}) From e7601f3998fe823c3454c6c2b655fd69d71b0823 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 13 Oct 2020 19:02:59 +0300 Subject: [PATCH 29/73] Add a size check for reluctivity tensor --- fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 index e887db2739..9b4f845788 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 @@ -231,7 +231,7 @@ SUBROUTINE WhitneyAVSolver( Model,Solver,dt,Transient ) TYPE(Mesh_t), POINTER :: Mesh REAL(KIND=dp), POINTER :: VecPot(:) - REAL(KIND=dp), POINTER :: Cwrk(:,:,:), Acoef_t(:,:,:) + REAL(KIND=dp), POINTER :: Cwrk(:,:,:), Acoef_t(:,:,:) => NULL() REAL(KIND=dp), ALLOCATABLE :: LOAD(:,:), Acoef(:), Tcoef(:,:,:), & GapLength(:), AirGapMu(:), LamThick(:), & LamCond(:), Wbase(:), RotM(:,:,:), & @@ -372,14 +372,14 @@ SUBROUTINE WhitneyAVSolver( Model,Solver,dt,Transient ) IF(ALLOCATED(FORCE)) THEN DEALLOCATE(FORCE, JFixFORCE, JFixVec, LOAD, STIFF, MASS, TCoef, GapLength, AirGapMu, & - Acoef, LamThick, LamCond, WBase, RotM, DConstr, Acoef_t,ThinLineCrossect, ThinLineCond ) + Acoef, LamThick, LamCond, WBase, RotM, DConstr, ThinLineCrossect, ThinLineCond ) END IF ALLOCATE( FORCE(N), JFixFORCE(n), JFixVec(3,n), LOAD(7,N), STIFF(N,N), & MASS(N,N), Tcoef(3,3,N), GapLength(N), & AirGapMu(N), Acoef(N), LamThick(N), & LamCond(N), Wbase(N), RotM(3,3,N), & - DConstr(N,N), Acoef_t(3,3,N), & + DConstr(N,N), & ThinLineCrossect(N), ThinLineCond(N), STAT=istat ) IF ( istat /= 0 ) THEN CALL Fatal( 'WhitneyAVSolver', 'Memory allocation error.' ) @@ -621,7 +621,6 @@ LOGICAL FUNCTION DoSolve(IterNo) RESULT(Converged) END IF Acoef = 0.0d0 - Acoef_t = 0.0d0 Tcoef = 0.0d0 Material => GetMaterial( Element ) IF ( ASSOCIATED(Material) ) THEN @@ -636,6 +635,10 @@ LOGICAL FUNCTION DoSolve(IterNo) RESULT(Converged) ELSE CALL GetReluctivity(Material,Acoef,n) END IF + IF (HasTensorReluctivity) THEN + IF (size(Acoef_t,1)/=3 .AND. size(Acoef_t,2)/=3) CALL Fatal('WhitneyAVSolver', & + 'Reluctivity tensor should be of size 3x3') + END IF !------------------------------------------------------------------------------ ! Read conductivity values (might be a tensor) !------------------------------------------------------------------------------ From 6a6d25a1bc830e35c9be806f8e40abf05cb06457 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 14 Oct 2020 11:47:47 +0300 Subject: [PATCH 30/73] Small changes - add SAVE attributes - alter an existing test to read the reluctivity as a tensor - clean a bit --- fem/src/modules/MagnetoDynamics/Utils.F90 | 73 ++++++++++--------- .../mgdyn_harmonic/MGDynamicsHarmonic.sif | 21 ++++-- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/Utils.F90 b/fem/src/modules/MagnetoDynamics/Utils.F90 index 36e100b036..7f517ecfa6 100644 --- a/fem/src/modules/MagnetoDynamics/Utils.F90 +++ b/fem/src/modules/MagnetoDynamics/Utils.F90 @@ -250,15 +250,16 @@ SUBROUTINE GetReluctivityR(Material,Acoef,n) REAL(KIND=dp) :: Acoef(:) INTEGER :: n !------------------------------------------------------------------------------ - LOGICAL :: Found, FirstTime = .TRUE., Warned = .FALSE. + LOGICAL :: Found, FirstTime = .TRUE. REAL(KIND=dp) :: Avacuum - SAVE Avacuum + SAVE FirstTime, Avacuum +!------------------------------------------------------------------------------ IF ( FirstTime ) THEN Avacuum = GetConstReal( CurrentModel % Constants, & 'Permeability of Vacuum', Found ) - IF(.NOT. Found ) Avacuum = PI * 4.0d-7 + IF (.NOT. Found ) Avacuum = PI * 4.0d-7 FirstTime = .FALSE. END IF @@ -273,10 +274,9 @@ SUBROUTINE GetReluctivityR(Material,Acoef,n) ELSE Acoef(1:n) = GetReal( Material, 'Reluctivity', Found ) END IF - IF( .NOT. Found .AND. .NOT. Warned .AND. & + IF( .NOT. Found .AND. & .NOT. ListCheckPresent(Material, 'H-B Curve') ) THEN CALL Fatal('GetReluctivityR','Give > Relative Permeability < or > Reluctivity < for material!') - Warned = .TRUE. END IF !------------------------------------------------------------------------------ @@ -292,10 +292,11 @@ SUBROUTINE GetReluctivityC(Material,Acoef,n) COMPLEX(KIND=dp) :: Acoef(:) INTEGER :: n !------------------------------------------------------------------------------ - LOGICAL :: L, Found, FirstTime = .TRUE., Warned = .FALSE. + LOGICAL :: L, Found, FirstTime = .TRUE. REAL(KIND=dp) :: Avacuum - SAVE Avacuum + SAVE Avacuum, FirstTime +!------------------------------------------------------------------------------ IF ( FirstTime ) THEN Avacuum = GetConstReal( CurrentModel % Constants, & @@ -318,16 +319,15 @@ SUBROUTINE GetReluctivityC(Material,Acoef,n) GetReal( Material, 'Reluctivity im', L ), KIND=dp ) Found = Found .OR. L END IF - IF( .NOT. Found .AND. .NOT. Warned .AND. & + IF( .NOT. Found .AND. & .NOT. ListCheckPresent(Material, 'H-B Curve') ) THEN CALL Fatal('GetReluctivityC','Give > Relative Permeability < or > Reluctivity < for material!') - Warned = .TRUE. END IF !------------------------------------------------------------------------------ END SUBROUTINE GetReluctivityC !------------------------------------------------------------------------------ -!> Get real tensorial reluctivity +!> Get a real-valued reluctivity tensor !------------------------------------------------------------------------------ SUBROUTINE GetReluctivityTensorR(Material, Acoef, n, Found) !------------------------------------------------------------------------------- @@ -335,43 +335,48 @@ SUBROUTINE GetReluctivityTensorR(Material, Acoef, n, Found) TYPE(ValueList_t), POINTER, INTENT(IN) :: Material REAL(KIND=dp), POINTER, INTENT(OUT) :: Acoef(:,:,:) INTEGER, INTENT(IN) :: n - LOGICAL , INTENT(OUT) :: Found -!------------------------------------------------------------------------------- + LOGICAL, INTENT(OUT) :: Found +!------------------------------------------------------------------------------ + REAL(KIND=dp), SAVE :: nu_vacuum + LOGICAL, SAVE :: FirstTime +!------------------------------------------------------------------------------ + + IF ( FirstTime ) THEN + nu_vacuum = GetConstReal( CurrentModel % Constants, & + 'Permeability of Vacuum', Found ) + IF (.NOT. Found ) THEN + nu_vacuum = 1.0d0/(PI * 4.0d-7) + ELSE + nu_vacuum = 1.0d0/nu_vacuum + END IF + FirstTime = .FALSE. + END IF CALL GetRealArray( Material, Acoef, 'Reluctivity', Found ) - ! - ! Earlier versions used 'Relative Reluctivity' although 'Relative' appears - ! to lack a physical meaning. For backward compatibility seek for - ! the old keyword command if needed: - ! - IF (.NOT. Found) CALL GetRealArray( Material, Acoef, 'Relative Reluctivity', Found ) + + IF (.NOT. Found) THEN + CALL GetRealArray( Material, Acoef, 'Relative Reluctivity', Found ) + IF (Found) Acoef = nu_vacuum * Acoef + END IF !------------------------------------------------------------------------------- END SUBROUTINE GetReluctivityTensorR !------------------------------------------------------------------------------- -!> Get complex tensorial reluctivity +!> Get a complex-valued reluctivity tensor !------------------------------------------------------------------------------ - SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found, Cwrk) + SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found) !------------------------------------------------------------------------------- IMPLICIT NONE TYPE(ValueList_t), POINTER, INTENT(IN) :: Material COMPLEX(KIND=dp), POINTER, INTENT(OUT) :: Acoef(:,:,:) - INTEGER, INTENT(IN) :: n - LOGICAL , INTENT(OUT) :: Found - REAL(KIND=dp), POINTER, OPTIONAL, INTENT(OUT) :: Cwrk(:,:,:) + INTEGER, INTENT(IN) :: n ! An inactive variable + LOGICAL, INTENT(OUT) :: Found !------------------------------------------------------------------------------- LOGICAL :: Found_im REAL(KIND=dp), POINTER :: work(:,:,:) => NULL() INTEGER :: n1, n2, n3 IF (ASSOCIATED(Acoef)) DEALLOCATE(Acoef) - -! IF(.NOT. PRESENT(Cwrk)) THEN -! ALLOCATE(work(size(Acoef,1), size(Acoef,2), size(Acoef,3))) -! ELSE -! work => Cwrk -! END IF -! IF (PRESENT(Cwrk)) work => Cwrk CALL GetRealArray( Material, work, 'Reluctivity', Found ) @@ -398,11 +403,8 @@ SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found, Cwrk) END IF END IF Found = Found .OR. Found_im - IF (ASSOCIATED(work)) DEALLOCATE(work) -! IF(.NOT. PRESENT(Cwrk)) THEN -! DEALLOCATE(work) -! END IF + IF (ASSOCIATED(work)) DEALLOCATE(work) !------------------------------------------------------------------------------- END SUBROUTINE GetReluctivityTensorC !------------------------------------------------------------------------------- @@ -417,13 +419,14 @@ SUBROUTINE GetPermittivity(Material,Acoef,n) !------------------------------------------------------------------------------ LOGICAL :: Found, FirstTime = .TRUE., Warned = .FALSE. REAL(KIND=dp) :: Pvacuum = 0._dp + SAVE FirstTime, Warned, Pvacuum +!------------------------------------------------------------------------------ IF ( FirstTime ) THEN Pvacuum = GetConstReal( CurrentModel % Constants, & 'Permittivity of Vacuum', Found ) FirstTime = .FALSE. END IF - Acoef(1:n) = GetReal( Material, 'Relative Permittivity', Found ) IF ( Found ) THEN diff --git a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif index cef3dff145..b17c7d6dc8 100755 --- a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif +++ b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif @@ -26,14 +26,19 @@ End Material 1 - Reluctivity = Real 1 - Reluctivity Im = Real 0 -! Reluctivity(3,3) = 1.0 0.0 0.0 \ -! 0.0 1.0 0.0 \ -! 0.0 0.0 1.0 -! Reluctivity Im(3,3) = 0.0 0.0 0.0 \ -! 0.0 0.0 0.0 \ -! 0.0 0.0 0.0 +! Reluctivity = Real 1 +! Reluctivity Im = Real 0 + + ! + ! The reluctivity could be described by using a scalar parameter but + ! here we give it as a higher-order tensor to test the functionality: + ! + Reluctivity(3,3) = 1.0 0.0 0.0 \ + 0.0 1.0 0.0 \ + 0.0 0.0 1.0 + Reluctivity Im(3,3) = 0.0 0.0 0.0 \ + 0.0 0.0 0.0 \ + 0.0 0.0 0.0 ! Permittivity = Real 1 Electric Conductivity = Real 1 From 692d7f46ea359a1f971e7655e0b975459fce6fd6 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 15 Oct 2020 11:49:02 +0300 Subject: [PATCH 31/73] Try to harmonize the use of keywords for the AV solvers The AV solvers for 2D and 3D cases have supposed different ways to specify the magnetic anisotropy. Now the AV solvers in 3D should also allow the user to specify the reluctivity by using a 3-tuple when the coordinate lines are the principal axes of the reluctivity tensor, i.e. just the diagonal entries of the reluctivity tensor may be given instead of all nine entries. --- .../modules/MagnetoDynamics/CalcFields.F90 | 17 +++++++--- fem/src/modules/MagnetoDynamics/Utils.F90 | 8 +++-- .../WhitneyAVHarmonicSolver.F90 | 19 +++++++---- .../MagnetoDynamics/WhitneyAVSolver.F90 | 33 +++++++++++-------- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 3f12e105b3..1e128620fc 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1321,12 +1321,19 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) w_dens = IntegrateCurve(HBBval,HBHval,HBCval,0._dp,Babs) ELSE R_ip = SUM( Basis(1:n)*R(1:n) ) - IF(HasTensorReluctivity) THEN - DO k = 1,3 - DO l = 1,3 - R_t_ip(k,l) = sum(Basis(1:n)*R_t(k,l,1:n)) + IF (HasTensorReluctivity) THEN + IF (SIZE(R_t,2) == 1) THEN + R_t_ip = 0.0d0 + DO k = 1,3 + R_t_ip(k,k) = SUM(Basis(1:n)*R_t(k,1,1:n)) END DO - END DO + ELSE + DO k = 1,3 + DO l = 1,3 + R_t_ip(k,l) = sum(Basis(1:n)*R_t(k,l,1:n)) + END DO + END DO + END IF w_dens = 0.5*SUM(B(1,:)*MATMUL(R_t_ip,B(1,:))) END IF w_dens = 0.5*R_ip*SUM(B(1,:)**2) diff --git a/fem/src/modules/MagnetoDynamics/Utils.F90 b/fem/src/modules/MagnetoDynamics/Utils.F90 index 7f517ecfa6..c6bb2f83c5 100644 --- a/fem/src/modules/MagnetoDynamics/Utils.F90 +++ b/fem/src/modules/MagnetoDynamics/Utils.F90 @@ -327,7 +327,9 @@ SUBROUTINE GetReluctivityC(Material,Acoef,n) END SUBROUTINE GetReluctivityC !------------------------------------------------------------------------------ -!> Get a real-valued reluctivity tensor +!> Get a real-valued reluctivity tensor. This subroutine seeks values which +!> are strictly given as reluctivity (giving the permeability is not an option +!> here). !------------------------------------------------------------------------------ SUBROUTINE GetReluctivityTensorR(Material, Acoef, n, Found) !------------------------------------------------------------------------------- @@ -362,7 +364,9 @@ SUBROUTINE GetReluctivityTensorR(Material, Acoef, n, Found) END SUBROUTINE GetReluctivityTensorR !------------------------------------------------------------------------------- -!> Get a complex-valued reluctivity tensor +!> Get a complex-valued reluctivity tensor. This subroutine seeks values which +!> are strictly given as reluctivity (giving the permeability is not an option +!> here). !------------------------------------------------------------------------------ SUBROUTINE GetReluctivityTensorC(Material, Acoef, n, Found) !------------------------------------------------------------------------------- diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 index b385ee9cbf..22d2a08f43 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 @@ -379,7 +379,7 @@ FUNCTION DoSolve(IterNo) RESULT(Converged) IF (size(Acoef_t,1)==1 .AND. size(Acoef_t,2)==1) THEN Acoef(1:n) = Acoef_t(1,1,1:n) HasTensorReluctivity = .FALSE. - ELSE IF (size(Acoef_t,1)/=3 .AND. size(Acoef_t,2)/=3) THEN + ELSE IF (size(Acoef_t,1)/=3) THEN CALL Fatal('WhitneyAVHarmonicSolver', 'Reluctivity tensor should be of size 3x3') END IF ELSE @@ -1264,13 +1264,20 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & END IF IF (HasTensorReluctivity) THEN - DO i = 1,3 - DO j = 1,3 - Nu(i,j) = SUM(Basis(1:n)*Acoef_t(i,j,1:n)) + IF (SIZE(Acoef_t,2) == 1) THEN + Nu = CMPLX(0._dp, 0._dp, kind=dp) + DO i = 1,3 + Nu(i,i) = SUM(Basis(1:n)*Acoef_t(i,1,1:n)) END DO - END DO + ELSE + DO i = 1,3 + DO j = 1,3 + Nu(i,j) = SUM(Basis(1:n)*Acoef_t(i,j,1:n)) + END DO + END DO + END IF ELSE - Nu = CMPLX(0._dp, 0._dp) + Nu = CMPLX(0._dp, 0._dp, kind=dp) Nu(1,1) = mu Nu(2,2) = mu Nu(3,3) = mu diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 index 9b4f845788..399ffd845f 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVSolver.F90 @@ -636,7 +636,7 @@ LOGICAL FUNCTION DoSolve(IterNo) RESULT(Converged) CALL GetReluctivity(Material,Acoef,n) END IF IF (HasTensorReluctivity) THEN - IF (size(Acoef_t,1)/=3 .AND. size(Acoef_t,2)/=3) CALL Fatal('WhitneyAVSolver', & + IF (size(Acoef_t,1)/=3) CALL Fatal('WhitneyAVSolver', & 'Reluctivity tensor should be of size 3x3') END IF !------------------------------------------------------------------------------ @@ -1503,7 +1503,7 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & LOGICAL :: PiolaVersion, SecondOrder !------------------------------------------------------------------------------ REAL(KIND=dp) :: Aloc(nd), JAC(nd,nd), mu, muder, B_ip(3), Babs - REAL(KIND=dp) :: WBasis(nd,3), RotWBasis(nd,3), A, Acoefder(n), C(3,3), & + REAL(KIND=dp) :: WBasis(nd,3), RotWBasis(nd,3), Acoefder(n), C(3,3), & RotMLoc(3,3), RotM(3,3,n), velo(3), omega(3), omega_velo(3,n), & lorentz_velo(3,n), VeloCrossW(3), RotWJ(3), CVelo(3), & A_t(3,3) @@ -1607,15 +1607,19 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & CALL GetEdgeBasis(Element, WBasis, RotWBasis, Basis, dBasisdx) END IF - A = SUM( Basis(1:n) * Acoef(1:n) ) - mu = A - - IF(HasTensorReluctivity) THEN - DO i = 1,3 - DO j = 1,3 - A_t(i,j) = sum(Basis(1:n)*Acoef_t(i,j,1:n)) + IF (HasTensorReluctivity) THEN + IF (SIZE(Acoef_t,2) == 1) THEN + A_t = 0.0d0 + DO i = 1,3 + A_t(i,i) = SUM(Basis(1:n)*Acoef_t(i,1,1:n)) END DO - END DO + ELSE + DO i = 1,3 + DO j = 1,3 + A_t(i,j) = SUM(Basis(1:n)*Acoef_t(i,j,1:n)) + END DO + END DO + END IF END IF ! Compute convection type term coming from a rigid motion: @@ -1686,6 +1690,7 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & muder=(DerivateCurve(Bval,Hval,Babs,CubicCoeff=Cval)-mu)/babs END IF ELSE + mu = SUM( Basis(1:n) * Acoef(1:n) ) muder = 0._dp END IF @@ -1813,12 +1818,12 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & SUM(M*RotWBasis(i,:)))*detJ*IP%s(t) DO j = 1,nd-np q = j+np - STIFF(p,q) = STIFF(p,q) + mu * SUM(RotWBasis(i,:)*RotWBasis(j,:))*detJ*IP%s(t) - ! Aniostropic part - IF(HasTensorReluctivity) THEN + IF (HasTensorReluctivity) THEN STIFF(p,q) = STIFF(p,q) & - + SUM(RotWBasis(i,:) * MATMUL(A_t, RotWBasis(j,:)))*detJ*IP%s(t) + + SUM(RotWBasis(i,:) * MATMUL(A_t, RotWBasis(j,:)))*detJ*IP%s(t) + ELSE + STIFF(p,q) = STIFF(p,q) + mu * SUM(RotWBasis(i,:)*RotWBasis(j,:))*detJ*IP%s(t) END IF IF ( Newton ) THEN JAC(p,q) = JAC(p,q) + muder * SUM(B_ip(:)*RotWBasis(j,:)) * & From b5b55c206073117e40f36470fe4fe420707e7b79 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 15 Oct 2020 12:22:08 +0300 Subject: [PATCH 32/73] Revert to a previous form of a test MagnetoDynamicsCalcFields cannot yet handle the given magnetic anisotropy correctly in all places, so this feature is not yet fully functional when postprocessing. --- .../mgdyn_harmonic/MGDynamicsHarmonic.sif | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif index b17c7d6dc8..eadf2b70bd 100755 --- a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif +++ b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif @@ -26,19 +26,25 @@ End Material 1 -! Reluctivity = Real 1 -! Reluctivity Im = Real 0 + Reluctivity = Real 1 + Reluctivity Im = Real 0 ! - ! The reluctivity could be described by using a scalar parameter but - ! here we give it as a higher-order tensor to test the functionality: + ! Here the reluctivity can be described by using a scalar parameter but + ! the following commands could define it as a higher-order tensor to test + ! the functionality. Note that the postprocessing subroutine + ! MagnetoDynamicsCalcFields cannot yet handle the given magnetic anisotropy + ! correctly in all places. ! - Reluctivity(3,3) = 1.0 0.0 0.0 \ - 0.0 1.0 0.0 \ - 0.0 0.0 1.0 - Reluctivity Im(3,3) = 0.0 0.0 0.0 \ - 0.0 0.0 0.0 \ - 0.0 0.0 0.0 +! Reluctivity(3,3) = 1.0 0.0 0.0 \ +! 0.0 1.0 0.0 \ +! 0.0 0.0 1.0 +! Reluctivity Im(3,3) = 0.0 0.0 0.0 \ +! 0.0 0.0 0.0 \ +! 0.0 0.0 0.0 +! +! Reluctivity(3) = 1.0 1.0 1.0 +! Reluctivity Im(3) = 0.0 0.0 0.0 ! Permittivity = Real 1 Electric Conductivity = Real 1 From bbc59fdac143007015f4978f0c17219570becb79 Mon Sep 17 00:00:00 2001 From: Samuel Date: Fri, 16 Oct 2020 13:55:37 +0200 Subject: [PATCH 33/73] Add files via upload --- ...dIceHydrologyCalvingPlumesDocumentation.md | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 elmerice/Solvers/Documentation/CoupledIceHydrologyCalvingPlumesDocumentation.md diff --git a/elmerice/Solvers/Documentation/CoupledIceHydrologyCalvingPlumesDocumentation.md b/elmerice/Solvers/Documentation/CoupledIceHydrologyCalvingPlumesDocumentation.md new file mode 100644 index 0000000000..7fef9e63b8 --- /dev/null +++ b/elmerice/Solvers/Documentation/CoupledIceHydrologyCalvingPlumesDocumentation.md @@ -0,0 +1,195 @@ +# Hydro-Calving-Plumes Elmer/Ice Documentation +## Modified Files: +* GlaDSCoupledSolver.F90 +* GlaDSchannelSolver.F90 +* MeshUtils.F90 +* CalvingRemesh.F90 +* GroundedSolver.F90 +* InterpVarToVar.F90 +* ModelDescription.F90 +* ElmerSolver.F90 +## New Files: +* PlumeSolver.F90 (and associated ODEPack library files: opkda1.F, opkda2.F, opkdmain.F) +* CalvingHydroInterp.F90 +* HydroRestart.F90 +* USF_SourceCalcCalving.F90 +* BasalMelt3D.F90 +* GMValid.F90 + +## Background +This suite of code changes and additions to the Elmer/Ice framework are designed to allow the coupling and concurrent use of the 3D calving solvers, GlaDS hydrology module and a new plume model within a single Elmer/Ice simulation. They are aimed at situations where you may want to model a large, fast-flowing tidewater glacier and were developed using the test case of Store Glacier in Greenland. If your glacier is small, slow or land-terminating, or you’re modelling an entire ice sheet, you probably don’t need to worry about this, or at least not all of it! In intent, all these changes should be backwards-compatible and shouldn’t break anything that doesn’t use them, but may be using some of the components. If you do find that your old simulations don’t work anymore, it’s probably that I’ve forgotten to put an if statement somewhere to only use the new code in the right circumstances, in which case I apologise. Below I list the changes made to existing files, including new SIF keywords, and the purpose and mode-of-use of new ones, as well as some more general considerations for making everything work. + +## THE MOST IMPORTANT CHANGE +All of this code will only function properly if you put `Calving = Logical True` in the Simulation section of the sif. Without that, none of it will activate properly (or indeed, at all); if you use it, it will all try to run in any of the modified or new files that are used. As things stand, this isn’t therefore terribly modular – you either turn it all on or none of it. If you just want calving, or just GlaDS, they work fine as standalones, but the plume solver requires the use of GlaDS to function (though it can work without the 3D calving solvers being used – this is explained more below). + +## Timestepping +One thing you need to consider with this set-up is the timesteps different parts of the model need to run on. Obviously, liquid water moves and evolves much faster than solid water (i.e. ice), so, in practice, the hydrological solvers want to be running at a much smaller timestep than the ice solvers. However, you can’t run everything at the smallest timestep, unless you’ve got infinite computer time…. So, I found a timestep of 0.1 days for the hydrology worked well, with a timestep of 1 day for the ice (giving a runtime of about 30 hours for one year of simulation). This can be achieved by adding ‘Exec Intervals = 10’ to all the ice solvers (assuming your base timestep is 0.1 days, this means the ice solvers will only run every 10 timesteps, i.e. every day). If you do this, though, you **MUST** also add ‘Timestep Scale = Real 10.0’ to all those solvers, because Elmer is not intelligent enough to recognise, when it comes to time-dependent solvers, that they’re not running at the smallest timestep (essentially, Elmer just looks at the timestep size, which will be the smallest timestep, and assumes everything is running at that, rather than actually checking the time). Otherwise, your ice will evolve 10 times too slowly. Yes, I found this out the hard way. +You should also avoid, if possible, having more than two different timestep sizes for the sake of model stability. So, have your base timestep for the hydrology and some multiple of that for the ice, but try to not start having intermediate ones, because it’s more likely the model will fall over if you do that. + +## Restarting runs +If you need to restart from fully coupled model runs, set up the restart machinery as normal, but then include the HydroRestart solver (see below) as a solver that is executed ‘Before Simulation’. This will allow all the ice and hydrology variables to be restarted properly. If calving has been going on, you’ll also need to make sure you use the right ice mesh – this will be the mesh found in the directory you’ve listed under ‘Remesh Move Mesh Dir’ in the Remesh solver. You’ll find one folder created in that directory for every time the model remeshed, so make sure you pick the right one (usually, this will be the one from the latest timestep, but if you’re restarting from a crashed run, you want to pick the one that lines up with the last result output timestep). + +##Meshes +The main issue with coupling calving and hydrology within Elmer/Ice is that of meshing. The calving solvers rely on modifying the existing ice mesh, creating a new one, and interpolating all the variables between the two. With GlaDS, this doesn’t work, because the channel variables are defined on the edge elements of the mesh itself so i) modifying the mesh causes problems and ii) the channel variables are not obviously interpolatable. To get round this, this setup makes use of two meshes: the standard 3D, internally extruded ice mesh, on which all the usual ice and calving stuff happens, and a second 2D plane hydrology mesh that the GlaDS solvers work on. +As such, you need to create a secondary mesh that the model can use. Ideally, this should be the same footprint as your ice mesh, except you probably want it to extend a bit past the frontal boundary of your ice mesh. The calving model could see the glacier advance or retreat, so the hydrology mesh needs to have a footprint that covers the entire potential area that the calving front could reach. Resolution-wise, things tend to work best if the two meshes are as similar as possible – large differences in resolution will lead to lots of interpolation artefacts (discussed more fully under CalvingHydroInterp.F90 below) that will most likely mess up your simulation eventually. But, you do want the hydrology mesh to be at a finer resolution than the ice, so try a few things and see what works best. +Once you have got your 2D footprint hydrology mesh, you need to get it into Elmer format in the usual way (whatever that might be for you). When you do this, you’ll want to use the -bcoffset option to increment the boundary condition numbers on the new mesh so that they follow on from the boundaries on the ice mesh (so, if you have 4 boundaries on your ice mesh, plus a surface and basal boundary once it’s extruded, you’ll want to offset the hydrology mesh boundaries by 6 so that they start at 7). Once you’ve done that (and before you partition it), you need to edit the mesh.elements file and ensure the **second** column is exclusively populated with the number 2, rather than 1, which is what it’ll be by default. This tells Elmer that this is a different body to the main ice mesh. I find the awk command the easiest way to perform this replacement (awk ‘$2=”2”’). Once you’ve done that, you can partition the mesh as usual. You also need to copy the new mesh directory to create a renamed version that will be used by solvers that need the hydrology mesh, but that you don’t want to create results files from (this is something that may get fixed, but, as it stands, the results output solver will try to output vtu files from every individual solver mesh – if they’re all using the same mesh, all the vtu files will have the same name and will overwrite each other). +When you’ve done all this, there are a couple of things you need to do in the SIF: +* In the Simulation section of the SIF, put `Need Edges 2D = Logical True` and `Need Edges 3D = Logical False` – this will ensure edges are generated on all the 2D hydrology meshes, but not on the 3D ice mesh (edges are important for channel variables) +* In the solver section for GlaDSCoupledSolver, put `Mesh = “.” “"` (assuming your mesh directory is some subdirectory of the working directory the SIF lives in) +* In the solver section for the other two GlaDS solvers (the thickness and channel output ones), and for any solvers where you’re reading in a variable that you want to be applied to the hydrology mesh (say, an expanded basal DEM covering the larger footprint area), put the same line, but replace the path with the equivalent for your renamed secondary hydrology mesh directory +* You need to use the Target Bodies feature to differentiate the ice and hydrology meshes. Assuming Body 1 is your main ice body, put the line `Target Bodies(1) = 1` in the Body 1 section of the SIF +* Then, you need to create a new Body 2 for the hydrology mesh (in theory, this can be any number – you could make it Body 7, if you already have Bodies 2-6, say, I think). This needs the line `Target Bodies(1) = 2` in it, though you could replace the ‘2’ with whatever number you substituted into the mesh.elements file earlier. Though I reckon Elmer would be angry if you, say, used Target Bodies(1) = 3 if there isn’t a Target Bodies(1) = 2 somewhere else. This body will have the same material, but a different equation, body force and initial condition to the ice, just as if you were defining a basal boundary body for the hydrology on a standard 3D mesh +* You can then list all the other bodies you’re going to need to define on the boundaries of the ice mesh in the usual way without needing to put in any more Target Bodies statements +* In the Boundary Condition section, you can list BCs for the hydrology mesh as usual, making sure you use the correct BC number (so, in the example above, you’d probably have BCs 7-10 defined on the hydrology mesh, and 1-6 on the ice mesh). But, make sure that all the hydrology-mesh-defined BCs include `Body ID = 2` (I’m not sure if, assuming you’ve numbered the BCs correctly or pointed them at the correct Target Boundary if you’re not setting them by numbering, you actually need this, but it doesn’t hurt) +* For the initial condition and the equation, just list them as usual – the equation will be any solvers you want to execute on the hydrology mesh, typically the three GlaDS solvers, any reader solvers that are reading variables onto the hydrology mesh, the hydrology weights calculator, and the hydrology restart solver (if relevant – all of these are properly explained below) +* In the relevant Body Force section, a few conditional boundary conditions need to be added (turns out Elmer is perfectly happy with boundary conditions in the body force section – they get applied to all nodes on the relevant body, which can be really useful). These work with some of the changes in GlaDSCoupledSolver.F90 (see below) to stop GlaDS trying to do hydrology on ungrounded parts of the mesh – strictly speaking, I should probably either put it all in the code, or put it all in as body force boundary conditions, but this is the currently working set-up: + * `Hydraulic Potential = Real 0.0` + * `Hydraulic Potential Condition = Variable GMCheck, NormalStress` + * `Real MATC “if(tx(0)>0.0){1}else{(tx(1)*(-1))+1E-32}”` + * The same form of boundary condition should be set for effective pressure, sheet thickness, sheet discharge and sheet storage, as well as the no channel BC (`= Logical True`) + * Water pressure should instead be set to: + * `Water Pressure = Variable Zb` (or whatever you’ve called your basal DEM variable) + * `Real MATC “abs(tx*RhoWS*g)”` + * Because water pressure just depends on depth if the ice is ungrounded + * A simpler form of the first BC above is: + * `Hydraulic Potential = Real 0.0` + * `Hydraulic Potential Condition = Variable GroundedMask` + * `Real MATC “(tx*-1)-0.5”` + * This is fine, provided you don’t have any discrete ungrounded areas inland of the calving front and unconnected to it; if you do, you need the more complicated BC to stop these ungrounded patches turning into unphysical sinks of water + +## Modified Files: +### GlaDSCoupledSolver.F90 +Modifications here are about making sure everything points at Solver % Mesh, rather than Model % Mesh, and to do with correctly importing and saving the other hydrology variables defined on the other hydrology solvers. There are also several blocks that deal with determining whether to ignore elements based on their groundedness (if it’s ungrounded, there’s no hydrology, though the model will treat isolated ungrounded patches inland as grounded here, because otherwise they become unphysical sinks of water – this discrimination is done based on the GMCheck variable from GMValid.F90), and a few lines that stop channel area from blowing up to stupid proportions, which is a problem on fine-resolution meshes. These are activated by specifying `Max Channel Area = Real…` and `Max Sheet Thickness = Real…` in the solver section of the SIF, should you wish to use them. +In practical terms in the SIF, this doesn’t require anything else new, beyond the listing of the hydrology mesh in the Solver section. Otherwise, all that’s required is a line saying `Need Edges = True` in the solver section, which is picked up by MeshUtils.F90 (see below) and allows the channel variables to be added to the mesh from their (identical) solver mesh. + +### GlaDSchannelSolver.F90 +Similarly, changes here are to point everything at Solver % Mesh and towards the appropriate location of the channel variables on the primary hydrology mesh, rather than the secondary hydrology mesh this solver will initially be using. None of this requires anything new in the SIF. + +### MeshUtils.F90 +All the changes in here are pretty much to do with making sure internally-extruded meshes keep edges after extrusion, to make GlaDS work on them, and then making solver-specific meshes able to have edges without the primary solver variable being defined on them. This should all just work – in the current situation, the only line required is the Need Edges line in the GlaDSCoupledSolver section to make that solver meshes have edges, so it can have the channel variables added to it, even though hydraulic potential isn’t an edge variable. If you’re just using GlaDS on the base of a 3D internally extruded mesh, the relevant keyword is instead `Preserve Edges = Logical True` in the Simulation section of the sif. + +### CalvingRemesh.F90 +Very minor change to stop the solver trying to add the hydrology variables to the post-calving ice mesh. This is activated by a new solver option for the CalvingRemesh solver: +* `Solvers To Ignore(n) = Integer n1 n2 n3…` + * n1, n2, n3 and so on should be the number of the solvers assigned to any of the hydrology meshes; n should be the total number of solvers concerned +The remesh solver needs to be run every timestep for complicated reasons, but will only actually (possibly) remesh if it’s a timestep the rest of the calving solvers have been run on. This is done by an addition to the file that compares the current time to the time a calving event occurred (which is recorded by an addition to Calving3D.F90). If the time is the same (i.e. the other calving solvers were also run this timestep), remeshing proceeds as normal; if the time is different (i.e. we’re on an intermediary hydrology timestep between ice timesteps), then the CalvingOccurs Boolean is set to false, and no remeshing occurs. The extra computational time required by running the solver in this way every timestep is negligible. + +### GroundedSolver.F90 +Now should list all grounded nodes on the frontal ice boundary as grounding-line nodes, which is needed for the PlumeSolver, provided a line in the solver section saying `Front Variable = String “”` is added, specifying the name of a variable defined on the calving front (the toe calving variable is useful here – see below). Also added an option to force the entire domain to be grounded, which can be activated by putting a line saying `All Grounded = Logical True` in the solver section. This is useful because if you’re running a hydrology-only simulation where you want to use two meshes (say, for mesh resolution reasons, or because you want to restart a full calving-hydro-plume simulation from it), you’ll need to use the `Calving = True` switch, which will make GlaDSCoupledSolver look for the groundedness of elements to determine whether it needs to bother with them, so you’ll need to include the GroundedSolver in the sif, but you don’t actually want it to do anything beyond marking everything as grounded. + +### InterpVarToVar.F90 +A couple of modifications to allow it to deal properly with interpolating between 2D and 3D meshes, rather than just between 3D meshes, and for situations where mesh connectivity is not perfect (i.e. it now allocates perms that will actually work). + +### ModelDescription.F90 +One modification to stop it trying to re-allocate a variable in the case of multiple restarts happening in the same simulation (i.e. if you’ve got more than one mesh). Restarting in the usual manner will load the first mesh, which, unless you’ve set things up strangely, should be the ice mesh (which will also be the mesh defined as `Model % Mesh`). The hydrology mesh can be restarted using the HydroRestart solver described below. + +### ElmerSolver.F90 +A couple of minor modifications to stop the model trying to restart all the meshes. If you do nothing, the model will only restart the first mesh, which will usually be the ice mesh, if the `Calving=Logical True` switch is set in the sif. You can also specify in the Simulation section a new keyword, `Meshes To Restart(n) = n1, n2, n3…` if you want to modify this default behaviour. + +## New Files: +### PlumeSolver.F90 +This is the new solver I’ve written that allows meltwater plumes and their resulting melt rate to be modelled at the calving front. The actual plume model itself is a 1D ODE model, which is an adaptation of Tom Cowton’s MITgcm plume model, which is itself an adaptation of Donald Slater’s MATLAB plume model. It uses the ODEPack library (consisting, here, of odpka1.F, odpka2.F and odpkmain.F), which should be compiled alongside it. ODEPack is written in FORTRAN 77 – I made the minimum changes necessary to get it to compile with the elmerf90 compiler, but if the compiler gets updated at some point, it may find more things inside ODEPack that it doesn’t like. Also note that ODEPack is very particular about the format of its inputs and code that calls it (this is all detailed in odpkmain.F), hence why you’ll notice that outdated things like implicit variables and common blocks appear in the bowels of the PlumeSolver.F90 file. ODEPack is not currently included as part of the Elmer repository, and if you download your own version, it won’t compile. We’re working on setting it up to be included (or to use an alternative library), but, for now, you’re best contacting me (samuel.cook@univ-grenoble-alpes.fr) to get the library files. +PlumeSolver.F90 has three main subroutines: +* Plume: this is the wrapper routine that handles the interaction with the rest of Elmer, chiefly getting the necessary inputs from GlaDS and solver options, and then turning the outputs into a melt rate across the calving front +* PlumeSolver: this is the actual plume model that takes the input discharges and works out a resulting plume profile +* SheetPlume: this defines the system of equations actually solved by ODEPack +The Plume subroutine is a substantially modified and expanded version of a solver written by Joe Todd that takes a provided set of plume profiles and melt rates at fixed locations and applies them to the relevant bits of the calving front. All this functionality still exists and works, if anyone wants to use it (and some of it is hijacked by the new code anyway), but I’m not going to detail it here, because it’s not what I’ve focused on. +The overall strategy of the solver is to dynamically model a continuous line plume along the whole calving front. Each grounding-line node on the hydrology mesh is assigned to the nearest basal frontal node on the ice mesh, with the discharge of the plume at that point being the sum of the sheet and channel discharge across all of the hydrology nodes assigned to that ice node. The plumes are all then modelled to get a set of melt-rate profiles across the calving front, and the melt rate at each node on the calving front is then interpolated from this. This allows melt rates across the calving front to vary as the subglacial drainage evolves over time, and avoids the user having to specify the location or size of any of the plumes. +As things stand, the solver assumes the calving front is a flat, vertical plane. The plume model itself can handle non-vertical profiles, but I haven’t yet got round to working out how to extract this information from Elmer/how far the nature of the internally-extruded meshes in Elmer I’m using even allows non-vertical profiles to exist. +As regards solver options and inputs: +* `Plume Melt Mode = String “…”` + * Options are `constant`, `seasonal` or `off` + * If `constant`, you need to specify `Salinity Temp Depth Input File` (see below) + * If `seasonal`, you need to specify `Summer Salinity Temp Depth Input File` and `Winter Salinity Temp Depth Input File` (see below) + * Additionally, you need to specify `Plume Melt Summer Start = Real…` and `Plume Melt Summer Stop = Real…` in model time to say when your summer conditions start and stop +* `(Summer/Winter) Salinity Temp Depth Input File = File “Name”` + * This is the file that contains the data on the ambient water conditions. This should be a .csv or other text file with three columns – depth (ordered from 0 downwards, so a depth of 50 m should be expressed as -50 in the file), salinity (PSU) and temperature (°C) + * Because of the strategy used by the solver to make plumes work properly in parallel, the sequence of depths in the ambient data file is that used to model the plume (it means the solver knows that the size of all the output profiles will be the same, which makes MPI much simpler). Therefore, the ambient data file **must** extend to the maximum depth of the calving front, so that any plume can be emplaced at the correct depth. This may mean you have to just add a few lines on the bottom of the file, copying your deepest data point downwards in increments of a few metres. Essentially, that’s what the toe calving routine (see below) does anyway, so it’s a reasonable assumption to make +* `Force Toe Calving = Logical True/False` + * This is something Joe implemented, the upshot of which is to extend melt rates downwards, if your ambient data doesn’t extend as deep as the calving front. This stops unphysical toes forming at the calving front and messing up the mesh + * If using this, specify `Exported Variable 1 = “Name”` for the toe calving variable + * It’s useful to export the toe calving variable anyway, even if you don’t want to activate the routine, as it (or a similar variable) is needed by GroundedSolver.F90 to help it define the grounding line +* `Mesh Resolution = Real n` + * This is the nominal mesh resolution at the calving front, which the solver needs to work out the width of each plume segment and how far inland it should look for grounding-line nodes. This should work, even if you have a grounding line a long way inland, because the solver applies a couple of tests to work out if it should ignore a given grounding-line node on the hydrology mesh (e.g. it’s possible to have closed loops of ungrounded areas inland that don’t connect to the front and which the solver should ignore), but you may need to fiddle with this number slightly if you find it’s not doing what it should +Known issues: +* None yet + +### CalvingHydroInterp.F90 +This represents the other major block of new code written as part of this suite. It handles the interpolation of necessary variables between the ice and hydrology meshes, but also moves read-in variables (using GridDataReader or similar) from their solver-specific secondary hydrology meshes to the primary hydrology mesh associated with GlaDSCoupledSolver. It also corrects interpolation artefacts that will otherwise nix your simulation sooner or later, and ensures conservation of the temperature residual (one of the interpolated variables) to stop the glacier accidentally destroying or creating some energy…. +The file contains two main subroutines, imaginatively titled “IceToHydroInterp” and “HydroToIceInterp”. Make sure you get them the right way round. IceToHydroInterp interpolates the ice normal stress, velocity, grounded mask and temperature residual over to the hydrology mesh and then spends a lot of time clearing up artefacts and conserving the temperature residual. HydroToIceInterp is much simpler, as the hydrology mesh is usually finer than the ice mesh, so the interpolation routine doesn’t create anywhere near as many artefacts in problematic locations. Therefore, it pretty much just interpolates the water pressure, effective pressure and sheet discharge onto the ice mesh. +There are also two small subroutines: “HydroWeightsSolver” and “IceWeightsSolver”. These calculate the boundary weights used in the main routines (the reason this happens in a separate solver is complicated – suffice to say it does exist). These need to be called as solvers before the relevant interpolation routines (IceToHydro or HydroToIce) are called, otherwise they’ll crash. IceWeightsSolver needs to run every time the ice mesh is updated (probably every n timesteps); HydroWeightsSolver every time the hydrology mesh is updated (probably never, so it can just run once at the start of the simulation). +Solver options and inputs (for IceToHydroInterp; others have nothing fancy): +* `Load Reader Variables = Logical True/False` + * Set this option to true if you’ve got variables read onto secondary hydrology meshes (say, a basal DEM or a runoff raster) that need to be transferred to the main hydrology mesh +* `Number of Variables To Read = Integer n` + * If you are loading read-in variables, say how many there are. Note: the solver is currently only set up to deal with a maximum of 10 variables; if you have more than that, you’ll need to modify the source code +* `Reader Solver 1 = Integer n` and `Reader V1 = String “name”` + * If loading variables, say which solver number the reader solver is and what the name of the variable is + * You should provide as many `Reader Solver` and `Reader V` entries as the number you’ve defined under `Number Of Variables To Read`, numbered sequentially +* `Reference Node(3) = Integer x y z` and `Threshold Distance = Real n` + * These are used as part of the artefact correction for the grounded mask – if specified, all nodes on the hydrology mesh greater than the Threshold Distance from the Reference Node will be automatically set to grounded. This can be useful if dealing with artefacts a long way inland +* `Side = Logical True/False` + * This isn’t a solver option, but something that should be set in the boundary condition section of the SIF. The interpolation routine tends to create a lot of artefacts on the lateral boundaries of the domain – if you set `Side = Logical True` in the boundary condition sections for the sidewalls of the hydrology mesh, this will force them to be grounded and remove the frequent ungrounded artefacts. +Known issues: +* None, though the artefact correction could probably be improved + +### HydroRestart.F90 +Largely a direct copy of the Restart() subroutine within the Elmer source code, with a few modifications to disentangle it from all the other restart machinery and to make it pick up the right variables from the right place and send them to the right place. +Solver options and inputs: +* `hp: Restart Variable 1 = String “Name”` + * All the variables that should be restarted on the primary hydrology mesh (i.e. all those that are normally calculated by or associated with GlaDSCoupledSolver) should be listed like this – just number the entries consecutively +* `channel: Restart Variable 1 = String “Name”` + * Same as above, but for the channel variables (i.e. channel area and channel flux) +* `sheet: Restart Variable 1 = String “Name”` + * Same as above, but for the sheet variables (i.e. sheet thickness – it’s the only one associated with the separate sheet thickness solver) +* The changes to GlaDSCoupledSolver.F90 will then ensure all the variables end up associated with the primary hydrology mesh, but it’s best to restart them on their own solver meshes +Known issues: +* None + +### USF_SourceCalcCalving.F90 +This is a USF that calculates the Hydraulic Potential Volume Source term required in the Body Force section of the SIF for GlaDS. If provided with a surface runoff variable (I usually load it in from a raster) and the temperature residual variable, it will calculate the resulting internal melt and add on the surface melt for each node on the hydrology mesh (or, at the base of your 3D ice mesh, if you’re using GlaDS without all the other bells and whistles), so you can easily vary the source term spatially across your domain. +USF options and inputs: +* These all go in the same Body Force section as where you define the source term +* `Internal Melt = Logical True/False` + * Switch for whether you want to work out internal melt or not +* `Surface Melt = Logical True/False` + * Same for surface melt +* `Internal Melt Variable Name = String “Name”` + * If you are using internal melt, give the name of the variable that you want it worked out from (note: the USF is set up on the assumption that this will be the temperature residual from the TemperateIceSolver. If you want to use something else, you’ll need to change the code in the USF) +* `Surface Melt Variable Name = String “Name”` + * If using surface melt, the name of the variable that contains it +* Finally, when defining the source term, you call this USF just like any other, and it does not matter what variable you use in the call – the USF will ignore it. + +### BasalMelt3D.F90 +This is a very simple solver written by Joe Todd that applies a specified basal melt rate to any ungrounded parts of the glacier base. +Solver options and inputs: +* `Basal Melt Stats File = String …` + * The path to write a file containing some basal melt stats to. +* `GroundedMask Variable = String …` + * The name of the variable used by the grounded solver +* `Basal Melt Mode = String …` + * Can be ‘seasonal’ or ‘off’. The latter is fairly self-explanatory; the former requires the following additional options: +* `Basal Melt Summer Rate = Real …` + * The rate to apply basal melt at in summer. +* `Basal Melt Winter Rate = Real …` + * The rate to apply basal melt at in winter. +* `Basal Melt Summer Start = Real …` + * The time in the simulation to begin using summer melt rates (expressed as a number between 0 and 1) +* `Basal Melt Summer Stop = Real …` + * The time in the simulation to begin using summer melt rates (expressed as a number between 0 and 1) + +### GMValid.F90 +This is a very simple solver that’s more-or-less just a stripped-down copy of BasalMelt3D.F90 and exists to set up a mask variable for which ungrounded areas are connected to the fjord and which aren’t. +Solver options and inputs: +* None – just the usual lines to define the equation, etc. + +## Problems +If you find something that doesn’t work or you can’t easily fix or isn’t listed here, email Samuel Cook (samuel.cook@univ-grenoble-alpes.fr) + +## Known Bugs +* There is an unresolved issue somewhere in the calving code that means, sometimes, the coupled model will just crash or hang at the end of the SwitchMesh routine for a reason I haven’t yet pinned down – it’s something to do with particularly complicated or large calving events (I think it's down to calving producing strange meshes). If this happens, rerunning the simulation will usually fix things (because the calving code has an element of randomness, the same event won’t recur in exactly the same way). However, be prepared to give it a few goes (5-6) if it keeps happening. If you’re running long simulations, the model **WILL** crash on this at some point, so the best plan is to just restart from the last full result output timestep the run completed and carry on from there, rather than trying to get a complete run in one go. If the model just keeps crashing at the same point, consider running the previous simulation you’ve restarted from – I found that helps. + +## Other Modifications +There have also been other changes to the source code that are not of particular relevance to the end user, but are included here for completeness: +* New MeshTag field defined as a new integer field on the Mesh_t variable type in **Types.F90**. This integer is incremented by **CalvingRemesh.F90** every time remeshing occurs and is then used by all solvers and USFs to decide whether memory allocation needs to be redone, rather than relying on the Mesh % Changed Boolean, which is unreliable +* Line adding CalvingTime as an entry to the Model % Simulation list in **Calving3D.F90**, which is then used by **CalvingRemesh.F90** as described above From 2bf8de8390014f089c4dbae9162778b1a540077f Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 16 Oct 2020 14:59:34 +0300 Subject: [PATCH 34/73] Fix reluctivity evaluation when tensor-valued reluctivity parameters exist MagnetoDynamicsCalcFields didn't update all reluctivity parameters when some part had a tensor-valued reluctivity. --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 2 ++ fem/tests/mgdyn_anisotropic_rel/cyl-case.sif | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 1e128620fc..1fe5113427 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -985,6 +985,8 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) l = MIN(SIZE(R), SIZE(R_t,3)) R(1:l) = R_t(1,1,1:l) HasTensorReluctivity = .FALSE. + ELSE + R = 0.0d0 END IF ELSE CALL GetReluctivity(Material,R,n) diff --git a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif index 5db16f7413..005fdbd26b 100644 --- a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif +++ b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif @@ -171,5 +171,5 @@ Equation 1 active solvers(4) = 1 2 3 4 End -solver 3::Reference norm = 11.516506516399119 +solver 3::Reference norm = 9.54680124E+00 solver 3::Reference norm tolerance = 1e-5 From 61776235ca8857a68ff2ee38897ec9a6dac780a7 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 16 Oct 2020 16:09:23 +0300 Subject: [PATCH 35/73] MagnetoDynamicsCalcFields has ignored the imaginary part of reluctivity WhitneyAVHarmonicSolver and MagnetoDynamics2DHarmonic treat the reluctivity as a complex-valued parameter but the postprocessor MagnetoDynamicsCalcFields has handled the same parameter as real-valued. This is only a partial fix. MagnetoDynamicsCalcFields now reads a complex-valued reluctivity but omits the imaginary part in the calculations, so the postprocessed fields may still be as wrong as before if the reluctivity has an imaginary part. --- .../modules/MagnetoDynamics/CalcFields.F90 | 93 ++++++++++++------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 1fe5113427..03dbc3cc08 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -492,24 +492,29 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) ! a background element of type 827): !------------------------------------------------------------------------------ REAL(KIND=dp) :: WBasis(54,3), RotWBasis(54,3), Basis(27), dBasisdx(27,3) - REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), R(27), C(27) + REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), C(27) REAL(KIND=dp) :: Wbase(27), alpha(27), NF_ip(27,3) REAL(KIND=dp) :: PR(27), omega_velo(3,27), lorentz_velo(3,27) - COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) + COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) + COMPLEX(KIND=dp) :: R_Z(27) !------------------------------------------------------------------------------ REAL(KIND=dp) :: s,u,v,w, Norm REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), B2 - REAL(KIND=dp) :: detJ, C_ip, R_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens, R_t_ip(3,3) + REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens REAL(KIND=dp) :: Freq, FreqPower, FieldPower, LossCoeff, ValAtIP REAL(KIND=dp) :: Freq2, FreqPower2, FieldPower2, LossCoeff2 REAL(KIND=dp) :: ComponentLoss(2,2), rot_velo(3), angular_velo(3) REAL(KIND=dp) :: Coeff, Coeff2, TotalLoss(3), LumpedForce(3), localAlpha, localV(2), nofturns, coilthickness REAL(KIND=dp) :: Flux(2), AverageFluxDensity(2), Area, N_j, wvec(3), PosCoord(3), TorqueDeprecated(3) + REAL(KIND=dp) :: R_ip, mu_r COMPLEX(KIND=dp) :: MG_ip(3), BodyForceCurrDens_ip(3) COMPLEX(KIND=dp) :: CST(3,3) COMPLEX(KIND=dp) :: CMat_ip(3,3) COMPLEX(KIND=dp) :: imag_value, Zs + COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) + COMPLEX(KIND=dp), POINTER :: Reluct_Z(:,:,:) + COMPLEX(KIND=dp) :: R_ip_Z, Nu(3,3) INTEGER, PARAMETER :: ind1(6) = [1,2,3,1,2,1] INTEGER, PARAMETER :: ind2(6) = [1,2,3,2,3,3] @@ -527,11 +532,14 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CHARACTER(LEN=MAX_NAME_LEN) :: Pname, CoilType, ElectricPotName, LossFile, CurrPathPotName TYPE(ValueList_t), POINTER :: Material, BC, BodyForce, BodyParams, SolverParams + LOGICAL :: Found, FoundMagnetization, stat, Cubic, LossEstimation, & CalcFluxLogical, CoilBody, PreComputedElectricPot, ImposeCircuitCurrent, & ItoJCoeffFound, ImposeBodyForceCurrent, HasVelocity, HasAngularVelocity, & HasLorenzVelocity, HaveAirGap, UseElementalNF, HasTensorReluctivity, & ImposeBodyForcePotential, JouleHeatingFromCurrent, HasZirka + LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder + LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. TYPE(GaussIntegrationPoints_t) :: IP TYPE(Nodes_t), SAVE :: Nodes @@ -551,15 +559,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) REAL(KIND=dp), ALLOCATABLE :: ThinLineCrossect(:),ThinLineCond(:) REAL(KIND=DP), POINTER :: Cwrk(:,:,:)=>NULL(), Cwrk_im(:,:,:)=>NULL() - COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) - REAL(KIND=dp), POINTER :: R_t(:,:,:) - LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder REAL(KIND=dp) :: ItoJCoeff, CircuitCurrent, CircEqVoltageFactor TYPE(ValueList_t), POINTER :: CompParams REAL(KIND=dp) :: DetF, F(3,3), G(3,3), GT(3,3) REAL(KIND=dp), ALLOCATABLE :: EBasis(:,:), CurlEBasis(:,:) - LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. + REAL(KIND=dp) :: xcoord, grads_coeff, val TYPE(ValueListEntry_t), POINTER :: HBLst REAL(KIND=dp) :: HarmPowerCoeff @@ -751,7 +756,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF ( ASSOCIATED(EL_ML2) ) ElementalFields=.TRUE. n = Mesh % MaxElementDOFs - ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n), R_t(3,3,n)) + ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n)) SOL = 0._dp; PSOL=0._dp @@ -790,7 +795,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF - C = 0._dp; R=0._dp; PR=0._dp + C = 0._dp; PR=0._dp Magnetization = 0._dp Power = 0._dp; Energy = 0._dp @@ -954,7 +959,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) !--------------------------------------------------------------------------------------------- - + R_Z = CMPLX(0.0_dp, 0.0_dp, kind=dp) HasTensorReluctivity = .FALSE. CALL GetConstRealArray( Material, HB, 'H-B curve', Found ) IF ( ASSOCIATED(HB) ) THEN @@ -979,17 +984,22 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF END IF ELSE - CALL GetReluctivity(Material,R_t,n,HasTensorReluctivity) + ! + ! Seek reluctivity as complex-valued: A given reluctivity can be a tensor + ! + CALL GetReluctivity(Material,Reluct_Z,n,HasTensorReluctivity) IF (HasTensorReluctivity) THEN - IF (SIZE(R_t,1)==1 .AND. SIZE(R_t,2)==1) THEN - l = MIN(SIZE(R), SIZE(R_t,3)) - R(1:l) = R_t(1,1,1:l) + IF (SIZE(Reluct_Z,1)==1 .AND. SIZE(Reluct_Z,2)==1) THEN + l = MIN(SIZE(R_Z), SIZE(Reluct_Z,3)) + R_Z(1:l) = Reluct_Z(1,1,1:l) HasTensorReluctivity = .FALSE. ELSE - R = 0.0d0 + R_Z(1:l) = CMPLX(0.0_dp, 0.0_dp, kind=dp) END IF ELSE - CALL GetReluctivity(Material,R,n) + ! Seek via a given permeability: In this case the reluctivity will be + ! a complex scalar: + CALL GetReluctivity(Material,R_Z,n) END IF END IF @@ -1316,30 +1326,44 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT END IF - + Nu = CMPLX(0.0d0, 0.0d0, kind=dp) IF ( ASSOCIATED(HB) ) THEN + ! Why just the real part of B is used? Babs=SQRT(SUM(B(1,:)**2)) R_ip = InterpolateCurve(HBBval,HBHval,Babs,HBCval)/Babs w_dens = IntegrateCurve(HBBval,HBHval,HBCval,0._dp,Babs) + DO k=1,3 + Nu(k,k) = CMPLX(R_ip, 0.0d0, kind=dp) + END DO ELSE - R_ip = SUM( Basis(1:n)*R(1:n) ) IF (HasTensorReluctivity) THEN - IF (SIZE(R_t,2) == 1) THEN - R_t_ip = 0.0d0 - DO k = 1,3 - R_t_ip(k,k) = SUM(Basis(1:n)*R_t(k,1,1:n)) + IF (SIZE(Reluct_Z,2) == 1) THEN + DO k = 1, MIN(3, SIZE(Reluct_Z,1)) + Nu(k,k) = SUM(Basis(1:n)*Reluct_Z(k,1,1:n)) END DO ELSE - DO k = 1,3 - DO l = 1,3 - R_t_ip(k,l) = sum(Basis(1:n)*R_t(k,l,1:n)) + DO k = 1, MIN(3, SIZE(Reluct_Z,1)) + DO l = 1, MIN(3, SIZE(Reluct_Z,2)) + Nu(k,l) = sum(Basis(1:n)*Reluct_Z(k,l,1:n)) END DO END DO END IF - w_dens = 0.5*SUM(B(1,:)*MATMUL(R_t_ip,B(1,:))) + ! + ! Why just the real part of B? + ! + w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) + R_ip = 0.0d0 + ELSE + R_ip_Z = SUM(Basis(1:n)*R_Z(1:n)) + DO k=1,3 + Nu(k,k) = R_ip_Z + END DO + ! Ensure that works as before (the complex part has been ignored): + R_ip = REAL(R_ip_Z) + w_dens = 0.5*R_ip*SUM(B(1,:)**2) END IF - w_dens = 0.5*R_ip*SUM(B(1,:)**2) END IF + PR_ip = SUM( Basis(1:n)*PR(1:n) ) IF ( ASSOCIATED(MFS).OR.ASSOCIATED(EL_MFS) ) THEN @@ -1904,7 +1928,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF IF(ALLOCATED(Gforce)) DEALLOCATE(Gforce) - DEALLOCATE( MASS,FORCE,Tcoef,RotM, R_t ) + DEALLOCATE( MASS,FORCE,Tcoef,RotM ) IF (LossEstimation) THEN CALL ListAddConstReal( Model % Simulation,'res: harmonic loss linear',TotalLoss(1) ) @@ -2110,10 +2134,10 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT IF (.NOT. ActiveBoundaryElement(Element)) CYCLE - C = GetConstReal(BC, 'Layer Electric Conductivity', Found) - IF (ANY(ABS(C(1:n)) > AEPS)) THEN - R = GetConstReal(BC, 'Layer Relative Permeability', Found) - IF (.NOT. Found) R = 1.0_dp + C_ip = GetConstReal(BC, 'Layer Electric Conductivity', Found) + IF (ABS(C_ip) > AEPS) THEN + mu_r = GetConstReal(BC, 'Layer Relative Permeability', Found) + IF (.NOT. Found) mu_r = 1.0_dp ELSE CYCLE END IF @@ -2140,10 +2164,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CALL GetEdgeBasis(Element, WBasis, RotWBasis, Basis, dBasisdx) END IF - C_ip = SUM(Basis(1:n) * C(1:n)) - R_ip = SUM(Basis(1:n) * R(1:n)) - R_ip = 4.0d0 * PI * 1d-7 * R_ip - val = SQRT(2.0_dp/(C_ip * Omega * R_ip)) ! The layer thickness + val = SQRT(2.0_dp/(C_ip * Omega * 4.0d0 * PI * 1d-7 * mu_r)) ! The layer thickness Zs = CMPLX(1.0_dp, 1.0_dp, KIND=dp) / (C_ip*val) E(1,:) = Omega * MATMUL(SOL(2,np+1:nd), WBasis(1:nd-np,:)) - MATMUL(SOL(1,1:np), dBasisdx(1:np,:)) From 7b0976dc3be712da3655d189604d67131ca111bf Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 16 Oct 2020 18:44:08 +0300 Subject: [PATCH 36/73] Revert to bbc59fd --- .../modules/MagnetoDynamics/CalcFields.F90 | 93 +++++++------------ fem/tests/mgdyn_anisotropic_rel/cyl-case.sif | 2 +- 2 files changed, 36 insertions(+), 59 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 03dbc3cc08..1e128620fc 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -492,29 +492,24 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) ! a background element of type 827): !------------------------------------------------------------------------------ REAL(KIND=dp) :: WBasis(54,3), RotWBasis(54,3), Basis(27), dBasisdx(27,3) - REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), C(27) + REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), R(27), C(27) REAL(KIND=dp) :: Wbase(27), alpha(27), NF_ip(27,3) REAL(KIND=dp) :: PR(27), omega_velo(3,27), lorentz_velo(3,27) - COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) - COMPLEX(KIND=dp) :: R_Z(27) + COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) !------------------------------------------------------------------------------ REAL(KIND=dp) :: s,u,v,w, Norm REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), B2 - REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens + REAL(KIND=dp) :: detJ, C_ip, R_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens, R_t_ip(3,3) REAL(KIND=dp) :: Freq, FreqPower, FieldPower, LossCoeff, ValAtIP REAL(KIND=dp) :: Freq2, FreqPower2, FieldPower2, LossCoeff2 REAL(KIND=dp) :: ComponentLoss(2,2), rot_velo(3), angular_velo(3) REAL(KIND=dp) :: Coeff, Coeff2, TotalLoss(3), LumpedForce(3), localAlpha, localV(2), nofturns, coilthickness REAL(KIND=dp) :: Flux(2), AverageFluxDensity(2), Area, N_j, wvec(3), PosCoord(3), TorqueDeprecated(3) - REAL(KIND=dp) :: R_ip, mu_r COMPLEX(KIND=dp) :: MG_ip(3), BodyForceCurrDens_ip(3) COMPLEX(KIND=dp) :: CST(3,3) COMPLEX(KIND=dp) :: CMat_ip(3,3) COMPLEX(KIND=dp) :: imag_value, Zs - COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) - COMPLEX(KIND=dp), POINTER :: Reluct_Z(:,:,:) - COMPLEX(KIND=dp) :: R_ip_Z, Nu(3,3) INTEGER, PARAMETER :: ind1(6) = [1,2,3,1,2,1] INTEGER, PARAMETER :: ind2(6) = [1,2,3,2,3,3] @@ -532,14 +527,11 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CHARACTER(LEN=MAX_NAME_LEN) :: Pname, CoilType, ElectricPotName, LossFile, CurrPathPotName TYPE(ValueList_t), POINTER :: Material, BC, BodyForce, BodyParams, SolverParams - LOGICAL :: Found, FoundMagnetization, stat, Cubic, LossEstimation, & CalcFluxLogical, CoilBody, PreComputedElectricPot, ImposeCircuitCurrent, & ItoJCoeffFound, ImposeBodyForceCurrent, HasVelocity, HasAngularVelocity, & HasLorenzVelocity, HaveAirGap, UseElementalNF, HasTensorReluctivity, & ImposeBodyForcePotential, JouleHeatingFromCurrent, HasZirka - LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder - LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. TYPE(GaussIntegrationPoints_t) :: IP TYPE(Nodes_t), SAVE :: Nodes @@ -559,12 +551,15 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) REAL(KIND=dp), ALLOCATABLE :: ThinLineCrossect(:),ThinLineCond(:) REAL(KIND=DP), POINTER :: Cwrk(:,:,:)=>NULL(), Cwrk_im(:,:,:)=>NULL() + COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) + REAL(KIND=dp), POINTER :: R_t(:,:,:) + LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder REAL(KIND=dp) :: ItoJCoeff, CircuitCurrent, CircEqVoltageFactor TYPE(ValueList_t), POINTER :: CompParams REAL(KIND=dp) :: DetF, F(3,3), G(3,3), GT(3,3) REAL(KIND=dp), ALLOCATABLE :: EBasis(:,:), CurlEBasis(:,:) - + LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. REAL(KIND=dp) :: xcoord, grads_coeff, val TYPE(ValueListEntry_t), POINTER :: HBLst REAL(KIND=dp) :: HarmPowerCoeff @@ -756,7 +751,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF ( ASSOCIATED(EL_ML2) ) ElementalFields=.TRUE. n = Mesh % MaxElementDOFs - ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n)) + ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n), R_t(3,3,n)) SOL = 0._dp; PSOL=0._dp @@ -795,7 +790,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF - C = 0._dp; PR=0._dp + C = 0._dp; R=0._dp; PR=0._dp Magnetization = 0._dp Power = 0._dp; Energy = 0._dp @@ -959,7 +954,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) !--------------------------------------------------------------------------------------------- - R_Z = CMPLX(0.0_dp, 0.0_dp, kind=dp) + HasTensorReluctivity = .FALSE. CALL GetConstRealArray( Material, HB, 'H-B curve', Found ) IF ( ASSOCIATED(HB) ) THEN @@ -984,22 +979,15 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF END IF ELSE - ! - ! Seek reluctivity as complex-valued: A given reluctivity can be a tensor - ! - CALL GetReluctivity(Material,Reluct_Z,n,HasTensorReluctivity) + CALL GetReluctivity(Material,R_t,n,HasTensorReluctivity) IF (HasTensorReluctivity) THEN - IF (SIZE(Reluct_Z,1)==1 .AND. SIZE(Reluct_Z,2)==1) THEN - l = MIN(SIZE(R_Z), SIZE(Reluct_Z,3)) - R_Z(1:l) = Reluct_Z(1,1,1:l) + IF (SIZE(R_t,1)==1 .AND. SIZE(R_t,2)==1) THEN + l = MIN(SIZE(R), SIZE(R_t,3)) + R(1:l) = R_t(1,1,1:l) HasTensorReluctivity = .FALSE. - ELSE - R_Z(1:l) = CMPLX(0.0_dp, 0.0_dp, kind=dp) END IF ELSE - ! Seek via a given permeability: In this case the reluctivity will be - ! a complex scalar: - CALL GetReluctivity(Material,R_Z,n) + CALL GetReluctivity(Material,R,n) END IF END IF @@ -1326,44 +1314,30 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT END IF - Nu = CMPLX(0.0d0, 0.0d0, kind=dp) + IF ( ASSOCIATED(HB) ) THEN - ! Why just the real part of B is used? Babs=SQRT(SUM(B(1,:)**2)) R_ip = InterpolateCurve(HBBval,HBHval,Babs,HBCval)/Babs w_dens = IntegrateCurve(HBBval,HBHval,HBCval,0._dp,Babs) - DO k=1,3 - Nu(k,k) = CMPLX(R_ip, 0.0d0, kind=dp) - END DO ELSE + R_ip = SUM( Basis(1:n)*R(1:n) ) IF (HasTensorReluctivity) THEN - IF (SIZE(Reluct_Z,2) == 1) THEN - DO k = 1, MIN(3, SIZE(Reluct_Z,1)) - Nu(k,k) = SUM(Basis(1:n)*Reluct_Z(k,1,1:n)) + IF (SIZE(R_t,2) == 1) THEN + R_t_ip = 0.0d0 + DO k = 1,3 + R_t_ip(k,k) = SUM(Basis(1:n)*R_t(k,1,1:n)) END DO ELSE - DO k = 1, MIN(3, SIZE(Reluct_Z,1)) - DO l = 1, MIN(3, SIZE(Reluct_Z,2)) - Nu(k,l) = sum(Basis(1:n)*Reluct_Z(k,l,1:n)) + DO k = 1,3 + DO l = 1,3 + R_t_ip(k,l) = sum(Basis(1:n)*R_t(k,l,1:n)) END DO END DO END IF - ! - ! Why just the real part of B? - ! - w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) - R_ip = 0.0d0 - ELSE - R_ip_Z = SUM(Basis(1:n)*R_Z(1:n)) - DO k=1,3 - Nu(k,k) = R_ip_Z - END DO - ! Ensure that works as before (the complex part has been ignored): - R_ip = REAL(R_ip_Z) - w_dens = 0.5*R_ip*SUM(B(1,:)**2) + w_dens = 0.5*SUM(B(1,:)*MATMUL(R_t_ip,B(1,:))) END IF + w_dens = 0.5*R_ip*SUM(B(1,:)**2) END IF - PR_ip = SUM( Basis(1:n)*PR(1:n) ) IF ( ASSOCIATED(MFS).OR.ASSOCIATED(EL_MFS) ) THEN @@ -1928,7 +1902,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF IF(ALLOCATED(Gforce)) DEALLOCATE(Gforce) - DEALLOCATE( MASS,FORCE,Tcoef,RotM ) + DEALLOCATE( MASS,FORCE,Tcoef,RotM, R_t ) IF (LossEstimation) THEN CALL ListAddConstReal( Model % Simulation,'res: harmonic loss linear',TotalLoss(1) ) @@ -2134,10 +2108,10 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT IF (.NOT. ActiveBoundaryElement(Element)) CYCLE - C_ip = GetConstReal(BC, 'Layer Electric Conductivity', Found) - IF (ABS(C_ip) > AEPS) THEN - mu_r = GetConstReal(BC, 'Layer Relative Permeability', Found) - IF (.NOT. Found) mu_r = 1.0_dp + C = GetConstReal(BC, 'Layer Electric Conductivity', Found) + IF (ANY(ABS(C(1:n)) > AEPS)) THEN + R = GetConstReal(BC, 'Layer Relative Permeability', Found) + IF (.NOT. Found) R = 1.0_dp ELSE CYCLE END IF @@ -2164,7 +2138,10 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CALL GetEdgeBasis(Element, WBasis, RotWBasis, Basis, dBasisdx) END IF - val = SQRT(2.0_dp/(C_ip * Omega * 4.0d0 * PI * 1d-7 * mu_r)) ! The layer thickness + C_ip = SUM(Basis(1:n) * C(1:n)) + R_ip = SUM(Basis(1:n) * R(1:n)) + R_ip = 4.0d0 * PI * 1d-7 * R_ip + val = SQRT(2.0_dp/(C_ip * Omega * R_ip)) ! The layer thickness Zs = CMPLX(1.0_dp, 1.0_dp, KIND=dp) / (C_ip*val) E(1,:) = Omega * MATMUL(SOL(2,np+1:nd), WBasis(1:nd-np,:)) - MATMUL(SOL(1,1:np), dBasisdx(1:np,:)) diff --git a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif index 005fdbd26b..5db16f7413 100644 --- a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif +++ b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif @@ -171,5 +171,5 @@ Equation 1 active solvers(4) = 1 2 3 4 End -solver 3::Reference norm = 9.54680124E+00 +solver 3::Reference norm = 11.516506516399119 solver 3::Reference norm tolerance = 1e-5 From 04f45ad29da3bd6a79b9e1fbaaae41f45a2f29ce Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Sat, 17 Oct 2020 08:37:46 +0300 Subject: [PATCH 37/73] (Recommit) Fix reluctivity evaluation when tensor-valued reluctivities exist MagnetoDynamicsCalcFields didn't update all reluctivity parameters when some part had a tensor-valued reluctivity. --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 4 +++- fem/tests/mgdyn_anisotropic_rel/cyl-case.sif | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 1e128620fc..4d444d3f7d 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -985,7 +985,9 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) l = MIN(SIZE(R), SIZE(R_t,3)) R(1:l) = R_t(1,1,1:l) HasTensorReluctivity = .FALSE. - END IF + ELSE + R = 0.0d0 + END IF ELSE CALL GetReluctivity(Material,R,n) END IF diff --git a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif index 5db16f7413..005fdbd26b 100644 --- a/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif +++ b/fem/tests/mgdyn_anisotropic_rel/cyl-case.sif @@ -171,5 +171,5 @@ Equation 1 active solvers(4) = 1 2 3 4 End -solver 3::Reference norm = 11.516506516399119 +solver 3::Reference norm = 9.54680124E+00 solver 3::Reference norm tolerance = 1e-5 From 77a441e7e0f9c63c84b24bb28b72185f62702fde Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Sat, 17 Oct 2020 10:47:04 +0300 Subject: [PATCH 38/73] (Recommit) MagnetoDynamicsCalcFields has ignored the imaginary part of reluctivity WhitneyAVHarmonicSolver and MagnetoDynamics2DHarmonic treat the reluctivity as a complex-valued parameter but the postprocessor MagnetoDynamicsCalcFields has handled the same parameter as real-valued. This is only a partial fix. MagnetoDynamicsCalcFields now reads a complex-valued reluctivity but omits the imaginary part in the calculations, so the postprocessed fields may still be as wrong as before if the reluctivity has an imaginary part. This is a recommit with a small correction. --- .../modules/MagnetoDynamics/CalcFields.F90 | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 4d444d3f7d..3fa4f543b4 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -492,24 +492,29 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) ! a background element of type 827): !------------------------------------------------------------------------------ REAL(KIND=dp) :: WBasis(54,3), RotWBasis(54,3), Basis(27), dBasisdx(27,3) - REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), R(27), C(27) + REAL(KIND=dp) :: SOL(2,81), PSOL(81), ElPotSol(1,27), C(27) REAL(KIND=dp) :: Wbase(27), alpha(27), NF_ip(27,3) REAL(KIND=dp) :: PR(27), omega_velo(3,27), lorentz_velo(3,27) - COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) + COMPLEX(KIND=dp) :: Magnetization(3,27), BodyForceCurrDens(3,27) + COMPLEX(KIND=dp) :: R_Z(27) !------------------------------------------------------------------------------ REAL(KIND=dp) :: s,u,v,w, Norm REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), B2 - REAL(KIND=dp) :: detJ, C_ip, R_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens, R_t_ip(3,3) + REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens REAL(KIND=dp) :: Freq, FreqPower, FieldPower, LossCoeff, ValAtIP REAL(KIND=dp) :: Freq2, FreqPower2, FieldPower2, LossCoeff2 REAL(KIND=dp) :: ComponentLoss(2,2), rot_velo(3), angular_velo(3) REAL(KIND=dp) :: Coeff, Coeff2, TotalLoss(3), LumpedForce(3), localAlpha, localV(2), nofturns, coilthickness REAL(KIND=dp) :: Flux(2), AverageFluxDensity(2), Area, N_j, wvec(3), PosCoord(3), TorqueDeprecated(3) + REAL(KIND=dp) :: R_ip, mu_r COMPLEX(KIND=dp) :: MG_ip(3), BodyForceCurrDens_ip(3) COMPLEX(KIND=dp) :: CST(3,3) COMPLEX(KIND=dp) :: CMat_ip(3,3) COMPLEX(KIND=dp) :: imag_value, Zs + COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) + COMPLEX(KIND=dp), POINTER :: Reluct_Z(:,:,:) + COMPLEX(KIND=dp) :: R_ip_Z, Nu(3,3) INTEGER, PARAMETER :: ind1(6) = [1,2,3,1,2,1] INTEGER, PARAMETER :: ind2(6) = [1,2,3,2,3,3] @@ -527,11 +532,14 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CHARACTER(LEN=MAX_NAME_LEN) :: Pname, CoilType, ElectricPotName, LossFile, CurrPathPotName TYPE(ValueList_t), POINTER :: Material, BC, BodyForce, BodyParams, SolverParams + LOGICAL :: Found, FoundMagnetization, stat, Cubic, LossEstimation, & CalcFluxLogical, CoilBody, PreComputedElectricPot, ImposeCircuitCurrent, & ItoJCoeffFound, ImposeBodyForceCurrent, HasVelocity, HasAngularVelocity, & HasLorenzVelocity, HaveAirGap, UseElementalNF, HasTensorReluctivity, & ImposeBodyForcePotential, JouleHeatingFromCurrent, HasZirka + LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder + LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. TYPE(GaussIntegrationPoints_t) :: IP TYPE(Nodes_t), SAVE :: Nodes @@ -551,15 +559,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) REAL(KIND=dp), ALLOCATABLE :: ThinLineCrossect(:),ThinLineCond(:) REAL(KIND=DP), POINTER :: Cwrk(:,:,:)=>NULL(), Cwrk_im(:,:,:)=>NULL() - COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) - REAL(KIND=dp), POINTER :: R_t(:,:,:) - LOGICAL :: PiolaVersion, ElementalFields, NodalFields, RealField, SecondOrder REAL(KIND=dp) :: ItoJCoeff, CircuitCurrent, CircEqVoltageFactor TYPE(ValueList_t), POINTER :: CompParams REAL(KIND=dp) :: DetF, F(3,3), G(3,3), GT(3,3) REAL(KIND=dp), ALLOCATABLE :: EBasis(:,:), CurlEBasis(:,:) - LOGICAL :: CSymmetry, HBCurve, LorentzConductivity, HasThinLines=.FALSE. + REAL(KIND=dp) :: xcoord, grads_coeff, val TYPE(ValueListEntry_t), POINTER :: HBLst REAL(KIND=dp) :: HarmPowerCoeff @@ -751,7 +756,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF ( ASSOCIATED(EL_ML2) ) ElementalFields=.TRUE. n = Mesh % MaxElementDOFs - ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n), R_t(3,3,n)) + ALLOCATE( MASS(n,n), FORCE(n,DOFs), Tcoef(3,3,n), RotM(3,3,n), Pivot(n)) SOL = 0._dp; PSOL=0._dp @@ -790,7 +795,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF - C = 0._dp; R=0._dp; PR=0._dp + C = 0._dp; PR=0._dp Magnetization = 0._dp Power = 0._dp; Energy = 0._dp @@ -954,7 +959,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) !--------------------------------------------------------------------------------------------- - + R_Z = CMPLX(0.0_dp, 0.0_dp, kind=dp) HasTensorReluctivity = .FALSE. CALL GetConstRealArray( Material, HB, 'H-B curve', Found ) IF ( ASSOCIATED(HB) ) THEN @@ -979,17 +984,22 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF END IF ELSE - CALL GetReluctivity(Material,R_t,n,HasTensorReluctivity) + ! + ! Seek reluctivity as complex-valued: A given reluctivity can be a tensor + ! + CALL GetReluctivity(Material,Reluct_Z,n,HasTensorReluctivity) IF (HasTensorReluctivity) THEN - IF (SIZE(R_t,1)==1 .AND. SIZE(R_t,2)==1) THEN - l = MIN(SIZE(R), SIZE(R_t,3)) - R(1:l) = R_t(1,1,1:l) + IF (SIZE(Reluct_Z,1)==1 .AND. SIZE(Reluct_Z,2)==1) THEN + l = MIN(SIZE(R_Z), SIZE(Reluct_Z,3)) + R_Z(1:l) = Reluct_Z(1,1,1:l) HasTensorReluctivity = .FALSE. ELSE - R = 0.0d0 - END IF + R_Z = CMPLX(0.0_dp, 0.0_dp, kind=dp) + END IF ELSE - CALL GetReluctivity(Material,R,n) + ! Seek via a given permeability: In this case the reluctivity will be + ! a complex scalar: + CALL GetReluctivity(Material,R_Z,n) END IF END IF @@ -1316,29 +1326,42 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT END IF - + Nu = CMPLX(0.0d0, 0.0d0, kind=dp) IF ( ASSOCIATED(HB) ) THEN + ! Why just the real part of B is used? Babs=SQRT(SUM(B(1,:)**2)) R_ip = InterpolateCurve(HBBval,HBHval,Babs,HBCval)/Babs w_dens = IntegrateCurve(HBBval,HBHval,HBCval,0._dp,Babs) + DO k=1,3 + Nu(k,k) = CMPLX(R_ip, 0.0d0, kind=dp) + END DO ELSE - R_ip = SUM( Basis(1:n)*R(1:n) ) IF (HasTensorReluctivity) THEN - IF (SIZE(R_t,2) == 1) THEN - R_t_ip = 0.0d0 - DO k = 1,3 - R_t_ip(k,k) = SUM(Basis(1:n)*R_t(k,1,1:n)) + IF (SIZE(Reluct_Z,2) == 1) THEN + DO k = 1, MIN(3, SIZE(Reluct_Z,1)) + Nu(k,k) = SUM(Basis(1:n)*Reluct_Z(k,1,1:n)) END DO ELSE - DO k = 1,3 - DO l = 1,3 - R_t_ip(k,l) = sum(Basis(1:n)*R_t(k,l,1:n)) + DO k = 1, MIN(3, SIZE(Reluct_Z,1)) + DO l = 1, MIN(3, SIZE(Reluct_Z,2)) + Nu(k,l) = sum(Basis(1:n)*Reluct_Z(k,l,1:n)) END DO END DO END IF - w_dens = 0.5*SUM(B(1,:)*MATMUL(R_t_ip,B(1,:))) + ! + ! Why just the real part of B? + ! + w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) + R_ip = 0.0d0 + ELSE + R_ip_Z = SUM(Basis(1:n)*R_Z(1:n)) + DO k=1,3 + Nu(k,k) = R_ip_Z + END DO + ! Ensure that works as before (the complex part has been ignored): + R_ip = REAL(R_ip_Z) + w_dens = 0.5*R_ip*SUM(B(1,:)**2) END IF - w_dens = 0.5*R_ip*SUM(B(1,:)**2) END IF PR_ip = SUM( Basis(1:n)*PR(1:n) ) @@ -1410,7 +1433,22 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3)+s*(R_ip*B(2,:)-AIMAG(MG_ip))*Basis(p) k = k+3 END IF + +! IF (RealField) THEN +! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + & +! s * (MATMUL(REAL(Nu), B(1,:)) - REAL(MG_ip)) * Basis(p) +! k = k+3 +! ELSE +! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & +! (MATMUL(REAL(Nu), B(1,:)) - MATMUL(AIMAG(Nu), B(2,:)) - REAL(MG_ip)) * Basis(p) +! k = k+3 +! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & +! (MATMUL(AIMAG(Nu), B(1,:)) + MATMUL(REAL(Nu), B(2,:)) - AIMAG(MG_ip)) * Basis(p) +! k = k+3 +! END IF + ELSE + ! Never here? FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3)-s*(REAL(MG_ip))*Basis(p) END IF END IF @@ -1904,7 +1942,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF IF(ALLOCATED(Gforce)) DEALLOCATE(Gforce) - DEALLOCATE( MASS,FORCE,Tcoef,RotM, R_t ) + DEALLOCATE( MASS,FORCE,Tcoef,RotM ) IF (LossEstimation) THEN CALL ListAddConstReal( Model % Simulation,'res: harmonic loss linear',TotalLoss(1) ) @@ -2110,10 +2148,10 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT IF (.NOT. ActiveBoundaryElement(Element)) CYCLE - C = GetConstReal(BC, 'Layer Electric Conductivity', Found) - IF (ANY(ABS(C(1:n)) > AEPS)) THEN - R = GetConstReal(BC, 'Layer Relative Permeability', Found) - IF (.NOT. Found) R = 1.0_dp + C_ip = GetConstReal(BC, 'Layer Electric Conductivity', Found) + IF (ABS(C_ip) > AEPS) THEN + mu_r = GetConstReal(BC, 'Layer Relative Permeability', Found) + IF (.NOT. Found) mu_r = 1.0_dp ELSE CYCLE END IF @@ -2140,10 +2178,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CALL GetEdgeBasis(Element, WBasis, RotWBasis, Basis, dBasisdx) END IF - C_ip = SUM(Basis(1:n) * C(1:n)) - R_ip = SUM(Basis(1:n) * R(1:n)) - R_ip = 4.0d0 * PI * 1d-7 * R_ip - val = SQRT(2.0_dp/(C_ip * Omega * R_ip)) ! The layer thickness + val = SQRT(2.0_dp/(C_ip * Omega * 4.0d0 * PI * 1d-7 * mu_r)) ! The layer thickness Zs = CMPLX(1.0_dp, 1.0_dp, KIND=dp) / (C_ip*val) E(1,:) = Omega * MATMUL(SOL(2,np+1:nd), WBasis(1:nd-np,:)) - MATMUL(SOL(1,1:np), dBasisdx(1:np,:)) From 39a73862bd1b8a705788694971bc64b9ce13c34d Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Sat, 17 Oct 2020 14:54:48 +0300 Subject: [PATCH 39/73] Initialize a pointer --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 3fa4f543b4..f76b783f7e 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -513,7 +513,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) COMPLEX(KIND=dp) :: CMat_ip(3,3) COMPLEX(KIND=dp) :: imag_value, Zs COMPLEX(KIND=dp), ALLOCATABLE :: Tcoef(:,:,:) - COMPLEX(KIND=dp), POINTER :: Reluct_Z(:,:,:) + COMPLEX(KIND=dp), POINTER, SAVE :: Reluct_Z(:,:,:) => NULL() COMPLEX(KIND=dp) :: R_ip_Z, Nu(3,3) INTEGER, PARAMETER :: ind1(6) = [1,2,3,1,2,1] From 3001f2739ff59b41a8608c16494855d9024e8e56 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 19 Oct 2020 09:12:24 +0300 Subject: [PATCH 40/73] Calculate the field H in terms of reluctivity tensor The computation of the magnetic field strength H now takes into account magnetic anisotropy defined in terms of a complex-valued reluctivity tensor. Previously MagnetoDynamicsCalcFields treated the reluctivity as a real-valued scalar in the computation of H. --- .../modules/MagnetoDynamics/CalcFields.F90 | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index f76b783f7e..bf3ae3987f 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1427,26 +1427,18 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF ( (ASSOCIATED(MFS).OR.ASSOCIATED(EL_MFS)) .and. .not. HasZirka) THEN IF(.NOT. HasZirka) then - FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3)+s*(R_ip*B(1,:)-REAL(MG_ip))*Basis(p) - k = k+3 - IF ( Vdofs>1 ) THEN - FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3)+s*(R_ip*B(2,:)-AIMAG(MG_ip))*Basis(p) + IF (RealField) THEN + FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + & + s * (MATMUL(REAL(Nu), B(1,:)) - REAL(MG_ip)) * Basis(p) + k = k+3 + ELSE + FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & + (MATMUL(REAL(Nu), B(1,:)) - MATMUL(AIMAG(Nu), B(2,:)) - REAL(MG_ip)) * Basis(p) + k = k+3 + FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & + (MATMUL(AIMAG(Nu), B(1,:)) + MATMUL(REAL(Nu), B(2,:)) - AIMAG(MG_ip)) * Basis(p) k = k+3 END IF - -! IF (RealField) THEN -! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + & -! s * (MATMUL(REAL(Nu), B(1,:)) - REAL(MG_ip)) * Basis(p) -! k = k+3 -! ELSE -! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & -! (MATMUL(REAL(Nu), B(1,:)) - MATMUL(AIMAG(Nu), B(2,:)) - REAL(MG_ip)) * Basis(p) -! k = k+3 -! FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3) + s * & -! (MATMUL(AIMAG(Nu), B(1,:)) + MATMUL(REAL(Nu), B(2,:)) - AIMAG(MG_ip)) * Basis(p) -! k = k+3 -! END IF - ELSE ! Never here? FORCE(p,k+1:k+3) = FORCE(p,k+1:k+3)-s*(REAL(MG_ip))*Basis(p) From a90e020cf49b595939926d4343cd291f6568f487 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 19 Oct 2020 11:02:28 +0300 Subject: [PATCH 41/73] Correct the magnetic energy in the case of magnetic anisotropy The magnetic energy is now computed in terms of the reluctivity tensor. Previously MagnetoDynamicsCalcFields treated the reluctivity as a scalar in the computation of the magnetic energy. --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index bf3ae3987f..d13ca97280 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1349,7 +1349,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END DO END IF ! - ! Why just the real part of B? + ! Note that this uses just the real part of B: ! w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) R_ip = 0.0d0 @@ -1409,10 +1409,15 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) Energy(1) = Energy(1) + s*0.5*PR_ip*SUM(E**2) - IF(ASSOCIATED(HB) .AND. RealField) THEN + IF (ASSOCIATED(HB) .AND. RealField) THEN Energy(2) = Energy(2) + s*w_dens ELSE - Energy(2) = Energy(2) + s*0.5*R_ip*SUM(B**2) + IF (RealField) THEN + Energy(2) = Energy(2) + s*0.5* SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + ELSE + Energy(2) = Energy(2) + s*0.5*( SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & + SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) ) + END IF END IF DO p=1,n From 60c3d4df40e58a692b0f77627c66c6799cab7aa4 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 19 Oct 2020 18:15:05 +0300 Subject: [PATCH 42/73] Alter an existing test to read the reluctivity as a tensor --- .../mgdyn_harmonic/MGDynamicsHarmonic.sif | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif index eadf2b70bd..a1331bf000 100755 --- a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif +++ b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif @@ -26,22 +26,22 @@ End Material 1 - Reluctivity = Real 1 - Reluctivity Im = Real 0 +! Reluctivity = Real 1 +! Reluctivity Im = Real 0 ! ! Here the reluctivity can be described by using a scalar parameter but - ! the following commands could define it as a higher-order tensor to test + ! the following commands define it as a higher-order tensor to test ! the functionality. Note that the postprocessing subroutine ! MagnetoDynamicsCalcFields cannot yet handle the given magnetic anisotropy - ! correctly in all places. + ! correctly in the calculation of nodal forces and the Maxwell stress tensor. ! -! Reluctivity(3,3) = 1.0 0.0 0.0 \ -! 0.0 1.0 0.0 \ -! 0.0 0.0 1.0 -! Reluctivity Im(3,3) = 0.0 0.0 0.0 \ -! 0.0 0.0 0.0 \ -! 0.0 0.0 0.0 + Reluctivity(3,3) = 1.0 0.0 0.0 \ + 0.0 1.0 0.0 \ + 0.0 0.0 1.0 + Reluctivity Im(3,3) = 0.0 0.0 0.0 \ + 0.0 0.0 0.0 \ + 0.0 0.0 0.0 ! ! Reluctivity(3) = 1.0 1.0 1.0 ! Reluctivity Im(3) = 0.0 0.0 0.0 From 8cb46a421ac529ea06c6d921e60998e6299961d2 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 20 Oct 2020 09:50:16 +0300 Subject: [PATCH 43/73] Fix the H-B evaluation in the postprocessing of harmonic case --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index d13ca97280..74d2f2357a 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1328,8 +1328,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) Nu = CMPLX(0.0d0, 0.0d0, kind=dp) IF ( ASSOCIATED(HB) ) THEN - ! Why just the real part of B is used? - Babs=SQRT(SUM(B(1,:)**2)) + IF (RealField) THEN + Babs=SQRT(SUM(B(1,:)**2)) + ELSE + Babs = SQRT(SUM(B(1,:)**2 + B(2,:)**2)) + END IF + Babs = MAX(Babs, 1.d-8) R_ip = InterpolateCurve(HBBval,HBHval,Babs,HBCval)/Babs w_dens = IntegrateCurve(HBBval,HBHval,HBCval,0._dp,Babs) DO k=1,3 @@ -1415,6 +1419,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF (RealField) THEN Energy(2) = Energy(2) + s*0.5* SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) ELSE + ! This yields twice the time average: Energy(2) = Energy(2) + s*0.5*( SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) ) END IF From c77fb2e54d52ea478a97613170e82fcdf28596ac Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 21 Oct 2020 15:40:32 +0300 Subject: [PATCH 44/73] Swap indices of trial and test functions In this way the weak formulation does not involve any assumptions about the reluctivity and electric conductivity tensors --- .../WhitneyAVHarmonicSolver.F90 | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 index 22d2a08f43..e671e9dcc5 100644 --- a/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 +++ b/fem/src/modules/MagnetoDynamics/WhitneyAVHarmonicSolver.F90 @@ -107,12 +107,12 @@ END SUBROUTINE WhitneyAVHarmonicSolver_Init !------------------------------------------------------------------------------ -!> Solve vector potential A, scale potential V +!> Solve a vector potential A and scalar potential V from ! !> j omega sigma A+rot (1/mu) rot A+sigma grad(V) = J^s+rot M^s-sigma grad(V^s) !> -div(sigma (j omega A+grad(V)))=0 ! -!> using edge elements (Nedelec/W basis of lowest degree) + nodal basis for V. +!> by using edge elements (Nedelec) + nodal basis for V. !> \ingroup Solvers !------------------------------------------------------------------------------ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) @@ -130,15 +130,16 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) ! Local variables !------------------------------------------------------------------------------ LOGICAL :: AllocationsDone = .FALSE., Found, L1 - TYPE(Element_t),POINTER :: Element, Edge - - REAL(KIND=dp) :: Norm, Omega - TYPE(ValueList_t), POINTER :: BodyForce, Material, BC, BodyParams, SolverParams + LOGICAL :: Stat, EigenAnalysis, TG, Jfix, JfixSolve, LaminateStack, CoilBody, EdgeBasis,LFact,LFactFound + LOGICAL :: PiolaVersion, SecondOrder, GotHbCurveVar, HasTensorReluctivity + LOGICAL :: ExtNewton + LOGICAL, ALLOCATABLE, SAVE :: TreeEdges(:) INTEGER :: n,nb,nd,t,istat,i,j,k,l,nNodes,Active,FluxCount=0 INTEGER :: NoIterationsMin, NoIterationsMax - - TYPE(Mesh_t), POINTER :: Mesh + INTEGER :: NewtonIter + INTEGER, POINTER :: Perm(:) + INTEGER, ALLOCATABLE :: FluxMap(:) COMPLEX(kind=dp) :: Aval COMPLEX(KIND=dp), ALLOCATABLE :: STIFF(:,:), MASS(:,:), FORCE(:), JFixFORCE(:),JFixVec(:,:) @@ -146,28 +147,20 @@ SUBROUTINE WhitneyAVHarmonicSolver( Model,Solver,dt,Transient ) COMPLEX(KIND=dp), ALLOCATABLE :: LamCond(:) COMPLEX(KIND=dp), POINTER :: Acoef_t(:,:,:) => NULL() + REAL(KIND=dp) :: Norm, Omega REAL(KIND=dp), ALLOCATABLE :: RotM(:,:,:), GapLength(:), MuParameter(:), SkinCond(:) - REAL(KIND=dp), POINTER :: Cwrk(:,:,:), Cwrk_im(:,:,:), LamThick(:) - REAL(KIND=dp), POINTER :: sValues(:), fixpot(:) - TYPE(Variable_t), POINTER :: jfixvar, jfixvarIm, HbCurveVar + REAL(KIND=dp) :: NewtonTol CHARACTER(LEN=MAX_NAME_LEN):: LaminateStackModel, CoilType, HbCurveVarName - LOGICAL :: Stat, EigenAnalysis, TG, Jfix, JfixSolve, LaminateStack, CoilBody, EdgeBasis,LFact,LFactFound - LOGICAL :: PiolaVersion, SecondOrder, GotHbCurveVar, HasTensorReluctivity - REAL(KIND=dp) :: NewtonTol - INTEGER :: NewtonIter - LOGICAL :: ExtNewton - - INTEGER, POINTER :: Perm(:) - INTEGER, ALLOCATABLE :: FluxMap(:) - LOGICAL, ALLOCATABLE, SAVE :: TreeEdges(:) - + TYPE(Mesh_t), POINTER :: Mesh + TYPE(Element_t),POINTER :: Element, Edge + TYPE(ValueList_t), POINTER :: BodyForce, Material, BC, BodyParams, SolverParams + TYPE(Variable_t), POINTER :: jfixvar, jfixvarIm, HbCurveVar TYPE(Matrix_t), POINTER :: A TYPE(ListMatrix_t), POINTER :: BasicCycles(:) - TYPE(ValueList_t), POINTER :: CompParams SAVE STIFF, LOAD, MASS, FORCE, Tcoef, JFixVec, JFixFORCE, & @@ -1064,32 +1057,31 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & LOGICAL :: PiolaVersion, SecondOrder !------------------------------------------------------------------------------ REAL(KIND=dp) :: WBasis(nd,3), RotWBasis(nd,3) - COMPLEX(KIND=dp) :: mu, C(3,3), L(3), G(3), M(3), JfixPot(n), Nu(3,3) REAL(KIND=dp) :: Basis(n),dBasisdx(n,3),DetJ, & RotMLoc(3,3), RotM(3,3,n), velo(3), omega_velo(3,n), & lorentz_velo(3,n), RotWJ(3) + REAL(KIND=dp) :: LocalLamThick, skind, babs, muder, AlocR(2,nd) + REAL(KIND=dp) :: nu_11(nd), nuim_11(nd), nu_22(nd), nuim_22(nd) + REAL(KIND=dp) :: nu_val, nuim_val + REAL(KIND=dp), POINTER :: Bval(:), Hval(:), Cval(:), & + CubicCoeff(:)=>NULL(),HB(:,:)=>NULL() + COMPLEX(KIND=dp) :: mu, C(3,3), L(3), G(3), M(3), JfixPot(n), Nu(3,3) COMPLEX(KIND=dp) :: LocalLamCond, JAC(nd,nd), B_ip(3), Aloc(nd), & CVelo(3), CVeloSum - REAL(KIND=dp) :: LocalLamThick, skind, babs, muder, AlocR(2,nd) CHARACTER(LEN=MAX_NAME_LEN):: LaminateStackModel, CoilType LOGICAL :: Stat, LaminateStack, Newton, Cubic, HBCurve, CoilBody, & HasVelocity, HasLorenzVelocity, HasAngularVelocity + LOGICAL :: StrandedHomogenization, FoundIm + INTEGER :: t, i, j, p, q, np, siz, EdgeBasisDegree - TYPE(GaussIntegrationPoints_t) :: IP - REAL(KIND=dp), POINTER :: Bval(:), Hval(:), Cval(:), & - CubicCoeff(:)=>NULL(),HB(:,:)=>NULL() + TYPE(GaussIntegrationPoints_t) :: IP TYPE(ValueListEntry_t), POINTER :: Lst - TYPE(Nodes_t), SAVE :: Nodes - TYPE(ValueList_t), POINTER :: CompParams - LOGICAL :: StrandedHomogenization, FoundIm - REAL(KIND=dp) :: nu_11(nd), nuim_11(nd), nu_22(nd), nuim_22(nd) - REAL(KIND=dp) :: nu_val, nuim_val !------------------------------------------------------------------------------ IF (SecondOrder) THEN EdgeBasisDegree = 2 @@ -1313,7 +1305,7 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & L = L - MATMUL(JfixPot, dBasisdx(1:n,:)) END IF END IF - + ! Compute element stiffness matrix and force vector: ! -------------------------------------------------- @@ -1332,7 +1324,7 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & ! matrix (anisotropy taken into account) ! ------------------------------------------- IF ( SUM(C) /= 0._dp ) THEN - STIFF(p,q) = STIFF(p,q) + SUM(MATMUL(C, dBasisdx(p,:)) * dBasisdx(q,:))*detJ*IP % s(t) + STIFF(p,q) = STIFF(p,q) + SUM(MATMUL(C, dBasisdx(q,:)) * dBasisdx(p,:))*detJ*IP % s(t) END IF END DO DO j=1,nd-np @@ -1393,7 +1385,7 @@ SUBROUTINE LocalMatrix( MASS, STIFF, FORCE, JFixFORCE, JFixVec, LOAD, & END IF STIFF(p,q) = STIFF(p,q) + & - SUM(MATMUL(Nu, RotWBasis(i,:))*RotWBasis(j,:))*detJ*IP%s(t) + SUM(MATMUL(Nu, RotWBasis(j,:))*RotWBasis(i,:))*detJ*IP%s(t) ! Compute the conductivity term ! for stiffness matrix (anisotropy taken into account) From e66a57e5d50eccacd591d78dbe43d83675322759 Mon Sep 17 00:00:00 2001 From: "alvaro.gonzalez" Date: Thu, 22 Oct 2020 09:50:52 +0300 Subject: [PATCH 45/73] Add ENV DEBIAN_FRONTEND='noninteractive' so tzdata stops asking for the timezone interactively --- docker/elmerice.dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/elmerice.dockerfile b/docker/elmerice.dockerfile index 2234654220..b7c85e066e 100644 --- a/docker/elmerice.dockerfile +++ b/docker/elmerice.dockerfile @@ -8,6 +8,8 @@ WORKDIR /home RUN printf "Acquire::http::Pipeline-Depth 0;\nAcquire::http::No-Cache true;\nAcquire::BrokenProxy true;" \ >> /etc/apt/apt.conf.d/99fixbadproxy +ENV DEBIAN_FRONTEND="noninteractive" + # Add the necessary packages to compile Elmer/Ice RUN apt update -o Acquire::CompressionTypes::Order::=gz && apt upgrade -y && apt install -y \ build-essential \ From 98d0c593b87c18d5ec256971ab6fd8d0e6312106 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Sun, 25 Oct 2020 22:25:14 +0200 Subject: [PATCH 46/73] Add obsolite warning --- fem/src/modules/MagnetoDynamics2D.F90 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fem/src/modules/MagnetoDynamics2D.F90 b/fem/src/modules/MagnetoDynamics2D.F90 index c999872bde..6f521791b5 100644 --- a/fem/src/modules/MagnetoDynamics2D.F90 +++ b/fem/src/modules/MagnetoDynamics2D.F90 @@ -2011,6 +2011,9 @@ SUBROUTINE Bsolver( Model,Solver,dt,Transient ) SAVE Visited + CALL Warn('BSolver','This module is obsolite! USE MagnetoDynamicsCalcFields instead') + + CALL Info( 'BSolver', '-------------------------------------',Level=4 ) CALL Info( 'BSolver', 'Computing the magnetic field density ',Level=4 ) CALL Info( 'BSolver', '-------------------------------------',Level=4 ) From fb3b751fa5e84e86efd72a786c0f9f045f78e449 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 27 Oct 2020 16:25:17 +0200 Subject: [PATCH 47/73] Simplify two loops --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 74d2f2357a..3642fc13bf 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1393,19 +1393,17 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) B2 = sum(B(1,:)*B(1,:) + B(2,:)*B(2,:)) DO k=1,n DO l=1,3 - DO m=1,3 - NF_ip(k,l) = NF_ip(k,l) - (R_ip*(B(1,l)*B(1,m)))*dBasisdx(k,m) - END DO - NF_ip(k,l) = NF_ip(k,l) + (R_ip*B2-w_dens)*dBasisdx(k,l) + val = SUM(dBasisdx(k,1:3)*B(1,1:3)) + NF_ip(k,l) = NF_ip(k,l) - R_ip*B(1,l)*val + & + (R_ip*B2-w_dens)*dBasisdx(k,l) END DO END DO IF (.NOT. RealField) THEN DO k=1,n DO l=1,3 - DO m=1,3 - NF_ip(k,l) = NF_ip(k,l) - (R_ip*(B(2,l)*B(2,m)))*dBasisdx(k,m) - END DO + val = SUM(dBasisdx(k,1:3)*B(2,1:3)) + NF_ip(k,l) = NF_ip(k,l) - R_ip*B(2,l)*val END DO END DO END IF From 5a9ab8664f1807f099ae5aa0beee99628a5a3ea3 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Tue, 27 Oct 2020 18:17:06 +0200 Subject: [PATCH 48/73] Correct the magnetic energy density for nodal forces in the complex case The imaginary part wasn't taken into account --- .../modules/MagnetoDynamics/CalcFields.F90 | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 3642fc13bf..27e508ec34 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -499,7 +499,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) COMPLEX(KIND=dp) :: R_Z(27) !------------------------------------------------------------------------------ REAL(KIND=dp) :: s,u,v,w, Norm - REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), B2 + REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), HdotB REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens REAL(KIND=dp) :: Freq, FreqPower, FieldPower, LossCoeff, ValAtIP REAL(KIND=dp) :: Freq2, FreqPower2, FieldPower2, LossCoeff2 @@ -1352,10 +1352,6 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END DO END DO END IF - ! - ! Note that this uses just the real part of B: - ! - w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) R_ip = 0.0d0 ELSE R_ip_Z = SUM(Basis(1:n)*R_Z(1:n)) @@ -1364,7 +1360,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END DO ! Ensure that works as before (the complex part has been ignored): R_ip = REAL(R_ip_Z) - w_dens = 0.5*R_ip*SUM(B(1,:)**2) + END IF + IF (RealField) THEN + w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) + ELSE + w_dens = 0.5*( SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & + SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) ) END IF END IF PR_ip = SUM( Basis(1:n)*PR(1:n) ) @@ -1390,12 +1391,17 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF (ASSOCIATED(NF).OR.ASSOCIATED(EL_NF)) THEN NF_ip = 0._dp - B2 = sum(B(1,:)*B(1,:) + B(2,:)*B(2,:)) + IF (RealField) THEN + HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + ELSE + HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & + SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) + END IF DO k=1,n DO l=1,3 val = SUM(dBasisdx(k,1:3)*B(1,1:3)) NF_ip(k,l) = NF_ip(k,l) - R_ip*B(1,l)*val + & - (R_ip*B2-w_dens)*dBasisdx(k,l) + (HdotB-w_dens)*dBasisdx(k,l) END DO END DO From b94081cde32b82c7d72d5f0b32e472940679cda2 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Tue, 27 Oct 2020 19:24:57 +0200 Subject: [PATCH 49/73] Add some license information --- license_texts/ElmerCorporateCLA.pdf | Bin 0 -> 115515 bytes license_texts/ElmerIndividualCLA.pdf | Bin 0 -> 113955 bytes license_texts/ElmerLicensePolicy.txt | 50 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 license_texts/ElmerCorporateCLA.pdf create mode 100644 license_texts/ElmerIndividualCLA.pdf create mode 100644 license_texts/ElmerLicensePolicy.txt diff --git a/license_texts/ElmerCorporateCLA.pdf b/license_texts/ElmerCorporateCLA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..41562272b19291ed3b12930de72bb7c2e65c639e GIT binary patch literal 115515 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�#r+lRI!Ee`S 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

?B=JaMl!b63h?Ew7 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

qGppJo4gu zo*UmO>770q)4!M8AZa}O`vMVE>1CR>0n&ff0l%vp3xDA|6oRm} z)%iG9Y3k-v>LiWIahpi_m6w>P+RN7yflJ4gE5ez}K-=CS_I1Hbs{NI_Vr|t#?a@|g z+yJP%iS%D0^3WEHkTRhMIJnNB*ezb($Swa(;hfpBC+|xRq;ef`m{=52>sG+GN%aA= zPV|^)I_>BsW4kOIf_EL9UAF4C?Va79FsTG;zGj@Jox4mkbF7IK92T7u-Js~r&Dks1 zYmFq+rWtD$lV@*M#Xn_{PX@>mMWJ7}G>R;Hp!9NNy~w%qqrjdMP_ zSC#iXYc2b-Y9aT)Yt7%W6+3JKN7+t}?;osW2~Sez3UuYBqL&?t(@Zr!(91Yfj>M-C z=gMTwRzL(Fu+t1SO1OF!XI-tu9W#{+#CP-R#>kH> z?yy$RQ|HQlw|C?!rCKAAIBK=Ijv<#TL0i50(u{LSzCI0~wH^uYc--Ue5ZrU`$lrUu z0poInbW>;L?q2m4$BX-!fszmA=j^nrN* zuYQ9t&Or^Xr{B~fv#>^dfIDM%|8(ypLnYBp3)@89hh-6 zo2C$-dm&U!PIe~+3xp&tVs)wtmoW_}p~vAC)rjW=3t_!Qg+fQD^Q^TbhI{%x{W&<5 zu+l8`N~(XZa7_y#m_3gP*aQD`D^rf8|FVa&b`JhQ5-;&fhVg^84T=bvPs2g5}G*3WWONjSaiUUHb{G7w){iSq8d%j1%+J~V)QkQL(J zvrCDK9_(Wsa?I7pw70Q)*S>(@8FJh`jOsWzo|4-p zAWI-yVhLr24pelHcx%UO%fX-_Gg}g?%kEM?$Vt~jww^H|?nNH_kIJ}9~ zj}%MemDy*>BguqqTK*ZWT5+37T6|^wrp6oP()!K0$T`7Au*DK*4{PP~0nBH@BhP_u z|B^IYw%WPi&U)P3dhuqAzV3%G`BZX2HzBcG@hI+1;n}q~x!(Gd(aE*sDcw|20x|jt zzC>%+g>qrS^*FRiN+rE~Y;_K^48(YNAIV_NO%=?eC6rQ*4BvNiCtL1A zus2Hev7jY~(kk~tW=W2P&b2g3X2|zhXiAtwarh~kg*f$c5 z%{jFZuD`U8lMQc$_jb}2)eb-_R9Zn9_7ZJSt;DW0_Ig&A@m23=4ySX!C#?2T4K$)9 z=MlK)3pHeU$tT~F8cXMpLLuhDuC30^%ogptA1fNJG%UDjEnivGX;)&d`Ol5g$ErhK zoX$EMVObipEj!J_!L2VEh+hr*vuYmOs28(mIP}JENSkFeC_c<8oByqTP1wNYFs|PH zL7O3)=Ky>-v57R(tU`UTS5h|VfBP;eAz5lHTmEQp_gK6_JI*&rcqLCF#GOTK6OJTI~VH zlDGVv$LCaZ~bDIQ&XEr%Iw3fBXir!pt3 z4(Vn{tg{ILg|vRD3r5=XddCcqd*|nFIrUdFGOdt&iufZtNwu^DJWoTB{5M+_>zS;Bm?g#V*G!_ir#*ALm=7jKNw zP#+`5_p?aT5TkB5s2+l?9QO(SWNHnWi#^Ex$#4hJU7c;OpRMPMGAR6M!Y1KahcRN+ zo(mji{|TgL29BEbB5;@Ny1O;JSS;giS{^UQW1Y^7)^`OKaiiNbr8ZfxAc*a%+aRT0 z#kC9u`bmAq=$$`ApZQ5-n%`~uTCNMfmC^Z<1CXmXY4wnR&2r&aymZq;GdgYw)jr-m zdfHAQNS{$!PNLeM+J5GHsRe7tdwexF1~#>A6r-eRjSe@+kB|8DuO%vQy0Kd2B`a9mE;Az9a&@Q=MMBsH}u_`>5ASA`$ z4#Bz8f@@}6+F=tS(QaNVJ~ym}V@Aqha>QVxvZ_^{?4NV}pC9%qA#+Fjc z+4=5fv8quPxp}J*=&`a2+!q?774eMM*W5q#yG zA_`{`)2f=e+%#a57JGqCy~N=9T;T!NE0CPlKTE-HQYIULxjZ!ET1XsG&>M%j@hvM_ zs0^ugVgTvvc4enMhUeay)0!!Bd}mgx^VOQ6fkZ_W<HEzorwmn%j*>lq`uY#2DSZmr$95>doj5XdAvWuK#R;30lYKBQ`}3|5CL@kJ_)C z;z4Nj)V(X7*r~$r<-ALsQ1I;G!D*4Y%9sK_w~O*C_}9TnD|7_{T4?zfeu2W?0<$pz zRs@+HO+!B)IT!Ja4 zQrEe3`|Qu3HC0?0k?0%gcfuVPUJx*sGj`1ym`f?a_fEDzqkC}Z3N+A++UHR~hY)YK zHpE*eQ6QNR6n8eTUNf0w(>GyPD7sCYT3P!B?ezlE2dy|&#Tz-N=ToqicaWQ}cNp0l zT10?5v(3U8#`<;vD^r6@?Qg*{TvM*RE@&o6jS6lQ*(TzZXfXW-ig$V)4~;rcp8>e{ zQa()$EWMI!NTMdAwO|nPo_x*>gKiWtYfjy6q!Dwd|T_4j`{4b-VGfU$H-@XKaTivbNV3Nn@Rk0)V(fJXkOOiq=wNjExM0d>vxGgTAj zWx8(>OYaw5PVqkiF9zKxQ_3BmPH8(bkgJ~)!k%3p_=}~QkY;8b&5j7{Z7qsJ9(Z^? ziha%(97oMJe!v7wMuBJDX)G3-4L;*qt|8MF1(c=%mQw-*y^p@oazPHUWy5Jy4!32^ zX;2QmWy@*OmW*!JX@2&AvPe6@GTrXPNN~NPA0@@A{gMvcs*#%G!q=7BsfGK{(iz7K zAimBmTJ1rPlM4Z!)W9GlSi<@CQDt+qgmuh@r)t)I&4#JENHBcbktX z2i;=IwJMY`MmWyT%1?t*1LhQy3Y!XW=!nIrO5NJpsZc+oAwe0Xnz@w&MR1D*TsR-T9*~G1hDGn;O?86`2dkg+xl!x4>W`>%3*%D!Qq`pt z1dl1J1IaVlnyraC{5lQRCU-?s7jm9`dp%<@xLxZMNS6PlorxYmz`drvfVA~f%)}L- z?(S`(1TbFrol@V%+Re(SIkV=Sb*(w(d)GBF0Q?=*c9RfGnp2SZ3Y$|v&9Z%<#SkU* zE_HEm(v1A5^!YN%DQ@yCQ9vIdxa~atGrPkbIe_Kvu-gqM_g%Bvta%t z)pGm-g{wZcduyh(Lf_tdxJySj!LHYC2AhQZDl*_^zS3buih55*vaNr|pl@H1| z{Ph=kDm*HTJoyuu;tAxSb|BWJ(g68WzGql5YUqi$aNV9-gHCLf3nF*nhP= zt>=T3qR~QjZUHx{`CC!VbXO$SY`35%S;*V$ zJ6XUN&?k~~jt)KjNjIapG5%YOYn*GE9>!fHvgB5}VU`|N3JXb~nTdcD%ltTw2}LT< z(fh=5O1F_$uLt2-XUQ{0yS)4wM(|wu17qfx_F*$-^BwLXXN1bD*5NZo_8s_`b83#A z5c`;iq^tO=!BNRw@mMy)=h%1A2j>y)8~2gzVZ@#1QTrI^;m;KF5cjZ~&VNjY(t9#P z`Ahk=dIEk_!(k%z=RIWCu-9n!t?m05_TshtFTyVUKnuAHA}$R8aS zI{dP(0tyu}CDgKJ6pF?5G7;g5GPOdLf)W@Cx!9t*Qo}(L#claV@~)gS6<7*wl}|oL zWjW!l4B3TJ5k+1V4<+WpG zq?`YwA+6A2eYfjgV2RF<7s9)5oCj^N9BmC&VO(|OxCo&x5IY(2IR$HNZO7fTRpPN8 zkmQO|Ye*-XVwA=EEwKlv4kdm+>n%#wVDl|OmjbRrW>mED`fV+1*%sX%kOx$DZ@)_e z$7iHn4F&Pa3s&RpHbKy9%qs$UPu{*P%7uGWS7;atulxrDeb_U~b#Is4piVy7AOMAz z-~A-Lgkq2urJIlH6X^nS@I6coMR1#6X}%foOzu_sLL63joyk-B(zv%w$2pQ-cq=D` zQh(nKb{#68EA)1#DzB)oNbr_lQcyyaAhnI+$wEO%0nIJi4J8>#5k(-jN96JzVVxNa&zyfmDnm!<8Y~!I-$EHRV%G(glAx{~ zyzD~wfa-!vdrPx59{iF|XH4Q9fxiQ|HQ9MfCAAr_h_POJo_CM?O$=4YCzmJnz_8|T zmm`!vEubfdm_RB^pMaqVY(z^TYCY3D-VLK2?BV=M^k%={vnPw3;Bv$1_JG15AkL>u zG)qgP^`IdM@0ri#6W5F1>7OOM9e9WH_ADzu>&<5}dw(J-tIbC!J2xAh+Y#~wodENI zOh%jg9ah!hP$@}-MSQED5XR!=?^eI}cMmVq{pcJ`Wkycc_Ej+O&k7W7_PBz()z zAp*`bwM$Z4y8Fr_ycb4{`|EbB_ zEbHlVru2?DEg>x9P(&`8amFj=93yK=w;i!{Y1hpCrxe5ge;%O`wjw^_~7KC70+iiNsRq zKn6axJ+M87zZnrHfY4@^r45Q~=V9jy%!Zgu)aj_xuhN#JuzTRyK!*iF50P37X(GoD z>{>uH|ElyQ+AC>NJI7cYIAp{kvxVB*YSRqh9!A-MY>;2UBH>j*3#LH{I-7jL`xtoO&pe^+y+#BkeeSuE{tUtDQAgQa)qk9 zGBn$GuCaB|dA<=p~-t^!GUNP)-1<_7wG}^MyLelV?P4iOSXl z^?USE=43gGFC_w<%8FaZWP^?G`?0=YIBN$_rCx!6F6|Z$RR6OJK!wY&VV!Pz?bmy1FkW%+afq+2b@jA0XyJz|e^(BZ25{qoJ z&AMANU6%Ffv?eNe2sR=LGDQMO-KO>Fc*|g2?4yRfD24D^&5o2C+my{GXwA5k4{^Hp z%j8pxQhhAppp;RgaQ5gw0InMhsDVCFS*j!t^Muet%|g{j4D$i?Ds8S846L7F@>RVn z{E}S4nEHdoP6gGCTm#=;VY^}>UGOO+_oiqQMk9mP8SNW^4vf=SWW!JYVCEC8Hs4CP zH;elaKJ3Kz1piA$t={xAf*BoNe3C2wRx7`uT~|nA?eS=JfTT@GTN6iMA`u{vPvHkjp*2RG31$uSuo! zdY5N2?bcHwC(l)rQun?;T4m9um*KFC6Yuinah+%MmFgtt^HbJh*Ly|@?I~zBu>JN1 z`V-L6{_d;f@uE0=>JV&=^SxP*+WVrVJ4<#j^#|N)^tHyO%lz3ed}U|H+?wbfG`<11 z%-U$4g7rE`x` zM?Xzg9lcDFXTR#EErPE22yr=+%wu@9n5vPC&<`$Ndtt|WE*%_ z)`3wmuKiC0Z-VF4>pmCV+j00WLSo<3gCZAsrtKZ4|K3<%2ICzcU+6lH0H$gO|)pRjDQv zUEYT^9<(35Mxd8*v{avgmCKek*srp}Y+X{3xD?FL$aaH6LV5-y(&k>BU^a;9lp&sw z;Ew>K2%k0TPl*TSytJq@PB-`;af%bwVn!cjilfz%%ng|ndPtHZ z9f7VwN66j8i_l``f4anE#nm}zn~wPhXyw=aY(uh<7k1JK>;rgcZ!`{GEb(7fHr`e` z)^2InkKqjvy2%G5!R{ski6F0W0B`*s$nwN;BQY5V9fAME(e4WVJnkO2?O+7}pN7a% zJ1;6ZCespcWaepb6MEtWDkh7EKd}+zv zxTFmA-gt^__at~I+>hZg#NPnP-BP#g((ShD`j`FZPb;kJ7E^Vrd3KTIPJIBTM#XE} z=aSQxepbyiG{0HK8~q69#QYO+nt~4iS)`!dk1Epg`1g^<6YAN#*axXA$gSi3lM+(? zq~w$1eVC^tMlxQC3hqe9m27G=jybg5fQDieQTSS{gneB|cfoMn22~vEY=#M^=wAcj zp`Z6eZ=+!LXh&Ty51iIrSKdTDKX=_ux_7;1 zU4fU4M{Q7MSmIc%2;)e3{GyNjVmGc4g|5zr@7r)=H2-1aPoFp0H*CkItJU?t3~WRk zXt&?f#@SmQYqi@^*;PHL&XL*y@X|Htz5~Ec2Mq2dC}FLRRsHy}kZK&FUh*!z{Kz5^ z+R-xRgv|l#UOo?h?cpw>t)j(P1H8=QmH=XmeK|~`HSS%If^)#Ph<)s4scXl=W|y^G zV1FXSu~BrRe>>nIbvyPK<*bWiw{wG!W*{8>gY5I8b~X7z#Bccb-hJT-tEjZxc=g4N z;4Kr9Tk@MacL+f!-;|18IZ`;_zPQ%BBZ*@7!t+Z%01KXeNpb;8vVwkxaS2PXV+l)O z7NcAd7OVfUT2pcXiFrx3N?kt@sT{2ma69>035z5kI&Zs;y&pLn#eC#kWIxUPtZqNa zygbeR_jZICz5mlh2aa^w3a;w-Vppz4`jwECAtT63{jK$> zhLDwmi=LH*iwLO-Q_eV@gOPAmIv+ySqp`; z>kHjA5c_xY(a}cYZrTR(t~$c%zGIL9XETvU+Gkcfy=Jxsn$-^5_$HQ>5L~|PL?why zZ~fQ}>`Dn#vklEOcSiN$(t-16JZo-9xaTo@p zS%}^w-G1`*NWi|dbwksvie7Re(m1u%@eb-o3?QRpxq<;cow;a}f|9@S$c%A~;?dZD z)l{3_I)$2Na(NK_F#LA#70DyCi@!^+3$cs7%R#}6x?uNC`cC?Ol8#XL7PBjcHu~Xi zR=^6;;*QY^zti{nwLMwxpUIa_E4btQMhf2{&xZd~5BJGu3-~A26~94&4@#VyM19G? z9?JvJM$l8Q<;k)wHdgfd47e@!8|)9$Z+Rlv0_%y=W`X#-{Uh!m7JW3lpxB0#1JXHfyEpR$ zWg|$YF!hV_)XX)xL-IcxUhl~~xVIo@mQv4qQ5o&i!XBA&HfqK3-@oM?%+r{9Cjc{f zr;jGrF##xAu*@)xs0^a$r^#~a22_nv zFv}wyhrsd|R{Z9s#TdY_`y*q?416(zf^F*PH%E^8Z1Z7qS};uqrQtiW2}N@0-BR*` z4TK55i)9~dDpn9E5mCtZ$%0;{KJdb#PEiDL@U;-}H*El0nQxC&!bqZmD1!p@gUG}D z@L_(^C;%Lw**SN`t{{jI{@)J;x7z^Q@#o!6vkBf@h~<~(BToCV1)ImxXQ~meLju>j*EdW056@8zV=GaEs%MioZbI}waXxoaY)bLD zTnvi{q-zI?RYecYh{%_W!_0a02!`tpT666vRijWckwE8E=Ttdy)rUiUMOPK~ z<;FzV3X4Sf@_(nQej0x3o$HlfU@ zf!iv>#4)BTr}tduIVsXQO;_9)V!?s_m06G4D8w!^?yK(XsuC+7he%ukpY4mvTtRcn z1j45S3c-NFiRy%ChYwnS2DJRG5Z1S5vU`zukrXoA~{Rk?#iQfh+;&u@@jI$$VCdHL0{vGI%d>W~i6PDC{ z?^_&Lh+)?Dzx7%BaSV#=BT{Le0@y?M`!hr~U>;ajnI;g_Q3P}XiK7zsF$xKL6?!>@ z`dwYV3Ai>*k7hS8rM|GQaLta~B_}}-jM`gM)>KTZa#W{SROC9Q8JDrbTC?<9$%1I`T?RLc~4!tp$HccThE*;-W53Qd_cb#q#cm2zX=c$uQtM{b!)Oo-OaF`X#XV*NiAbHi?rtaP=zZZQw-TX53 zuHfC;-p-clf_qh9+xjg1lKW!n?&pm)$lwa6e+Doklds8ts&%P-;ZOj5|1&^AcVk)3 z-pdAX+h8c1jtUTtcPH#8&<%HQbkB7U>Fk{!Z=ZCY&@uvlRiZ@Kw_1Nec@uXs1`{~Y zJkz?YwsWuG(2+4-KyT&UmgavI$aSCuCdd*Q`Od{;G7R!>n|m9Ot*wu@a~7}h(xYI9 z9lJ`?3vNL@!V?F}dlcp_%#el2PT@=@Os;Si_HOoW3iNBBuwh9MYGhm^^vC>KQT)DTeU1cwcLa8hum39buSznX?Rba_rC{2=#5%;ht6GNq}@NgNfp_aCYF zTG+YWxt-~oksq@^7OsM(hOZp>`F3)dx59Igri=rU1&MPbbL(c7_@qJ@+IS@mX%4DJ zb_QjQOj~17%l499%1txaoa#P;KbdiO^HL~FHo-p|GvYsr%_7btLzy^WQB+0~;Nw0c z56&aMD2K?tVZFnbk!OGtoI8#|y&N9yUa3Xg^!qUgLTzv!h^mS!*Rdt1QBVzZ< z4Z7W%u}gg)e8%0`>B#9Ooq6c|ji(tT=>8h8baqEA4F~mLr1d(Dsj)v0(Z=Ab>JEUR zfkS~%0+)AyiWurz>U!$NKfB~`)lk>W*ML98DY@%<=d8JhQ7wZ;$){f|Bq94U`qukG z`xf;uo9`pm;huI%rLp?U&m3u?U5Km}flm^{4@%RP#Y@%3VO>bxu47yrZz733u)bkW zqJFtqXI{I|k+ZR_M1SP`4h9zm8-9!W&FGi4bFlNE>okKtL5K6eKE8~Tft(?_2I`CY zm9`VN!(d=lL)OC4LeuiMhCtOEYegFNV;S;3`y>D}=I*9E2;-}J)G%4%jxh`+volr# zGqF;UqD0YHMW#4TD^ZL2eqlmkHMn%aaG6h8@{kENX;Hx}N8quDS=wUEs=$soeYz%W z(;%DgI;1aDskB4HQ&a# z6}~v}otS+rBrTF>*Ex8#pNM^3IGgaqKF&#JsOaLMS^y5*GYQ%99lJpxhEP&=XscT6 zJ+zE7H{TD({`7u8g>YEljmuzGEu02otO7YZidl$wfE7{eoq>Jqcr}r# z4$<+QAlDj~tX>rS>=9m8C(kQWR-V);70fwjS0|F|;CUie$lx2y2;wdSA>s5jj49RB zjWmIJL~uXp^xga*w{|kaXci~@2=Y!7A^XbhjY~$6ui;?)AQxjq^XYliezH!^aMnD} z5!Y0ZRB_F>=>w!8)6|)?A^Jp@)S&@?RuVNY_kIfCu^;zq3G*!2A`|Ei$RgCE*{1JB+m#hhS zudV-tF^fl3HQRv0dU~5c)I0A$hAfb*5gF@I*siE~eEag=(xR*3E@eJ(Gf5{SzOFlk zSADVCGtsVnn#kfrf3;fX!R5l+KCd^ut%C3~h^P>MDDSjL}BD1p@OcQx%4 zsKAt`9SsQu$}4mFaQIz;J?gA_Eg0Lf#_MNMh%@PH0W&YBnY6tX@~7if!v`M=yH1@n z!g}GYgS@sw+;zsnPpqg@_jUP>ba{|twJj*q^4O$qKP~{)>nQKBZ*kIGW!UzNPQ=ne z;+zdNtZ{ZxuwinsTo)k&gWR3Twrx(_&mrsH%2}v{Hj6nt7PayTw%6ZWikx?{WCB{(vdi z^|Pl#8vTA$_&mZQYDBoob*ZD~h~Gl{U`3xZ<2WW;{a7q_L_@Qi8r&&E1NTVD!pkgO zBT)H0S=FHH!piex-ot)&?LeJ+?J|#fbkIUJGcmpez5%SYN%iqd8}2M+m~-CD(w_9S zn*+Y>iTg0g?29a&hT9`b&f`~X*_r)ve`jC5S~pGkn@rnLlpAN!nO6IO-i>C zCs)xj|Fgcz_)>>tW@}{e-yuELJS2{Wq!5pN{q?HOQkJejP~y1Dr;g?K+L&*4U&xC)@^cG_5Ol;h_- zYpJ%_%>M*D8L!uOw|uW9dpd6~dPOH%E^cm4k>VbsP4X_sUeHcbF5g}>aVLczJ7$gu zTfUxk{3H+;rp!`5teF2di0>`0ZL4uVC2c=piN4^~)tWCHWiuT1;_!TJh%a~`(?lkW zaQA-zy+A_0Alpy1K1bc1gFGkPwAQ&Nwwr&X=bkF&o14~XcMefNtUaxSd{id<#C>+p zCYo#6rhd5RTm7{3-E^jNFC|(|v@#pH$i0S$&T#IHL}gBafpe(Rwz>vN25Lau4khJ+wdcaPFb~n1}XZ9@8?ym()RN=s!Fx=t|Udo z6ZI!bCQ2bnB^uT4GmXeWG?8d3Q5I1SQGjSR(OjbWL<@O*7_#piT1-?(w1Q|A z(ORPQM4O1pt7@y@>is?>aM_ z9_M7|bZ55H2b$%a(cQTa}S1QpcSDMS=n&_J9EOKQzOI5KRl2HNhg~(UTGwe;y{p01=(^&%;c9Vf?k?^qcZ@sE zo#0M%4{{H4k8qE1r@LMJoaB?-)7&%MhCAfWbLYDkxEF&8-7DOyI;FMl_3lm1S?+SH zY@^Z{zmv*d_kMRZ@kgz4!hHsGmdXVxP3~*%n;ywy19kKCpf#a)r+;xlvi{;iB>v(; z7yQMAZupA}cKpSKDE!5R9{7t3(fErCG5CuMJ@K~|dfVgeL!<}nL+xqOK)cr-kVe~Q z*}pH1v(L4^C^_u~_Lrom>`Uw~OW&~<+uxU_*!S4?N(=3W?U$s*`0ERs{y%P+EccM} z@Ozcr{C|^ANU?}AOTs9N(kybcsI>*wS#H@5&qj~9ZX46>Ri@&rX;csjx_!}FlJ;k0P zY4&mUbV;{o+TBtF{@zA6`%HTdjYH7>tklOo$Nsz&Z=Y}fcPYWX*#1MQzrE64DJ9xJ zuzw&uVsErJN=g5Zt;;Rn)xJ$QouWf-=?cCJ_#xnjfFG)55DwGlLY@QpPrxrHOwr#1 zp8;Of9RhwL>OM+12=ZatY}nztDX^cV4Q157%fEfn=Mx{Tjb+rsgxjF=7Qgp4_}hEX zhQ*LS3w|j0q2Py!)>=3p_#nTi#{igN= zqy8*fG6I^+4^^)~NBChmtX0AqkHw=}DdYlEfs>$Pz-9$(k~fs6_>IayJ)7`loo2U^ z4}LW8ls<>BfOTXA_&W5_PDNr=eb9Ve_Y!V`&HGp)pJZOXil@cU9|!%{z(>Q{RbV~z zZ)*QVaz#5pI7zGJw=ge%4v9}YNjQ>qq-I9h0sJd)52LC=GZS{Eu)cf_7D~Zy#?$f8 zzXSaw@D0E!;11|t(sD`DrT-`KH#lw;oBjduEi9LF^lHMh8byK<%R15}eG%bb807{_ zLa3&|PAo7EbxmL~u%~thPoE+FBSz&6_)6eW;OklzkCE*XumV^HECe<&M)ab04HF{> z&6jQbH&k-Kj|O(PunbrTe0$2tt1U3O#KNFe@P)ic1 z|CFNw{Of4>Ch)s~n=DBgun^b;OryT+(+d$r0Y<$2B=9Qg7F$>WECUt-*+MbA@fz!> zYk;qz%~yf-uv5=%mM`f?2(JMz>2Ct_fHQ%J1ig_g^u&1GfMz}LqJ@hP163RwkhH); z3oNw2LJOkeES{c)}Vz?r~fjEIDK5o63#q_MSQ;4jghp4&hY zWrT&)7v&H~sS*cC93*j=-EnN0@BnZ(BD0+OIA6pkEEfTbt-3rfo=430lz3WY2 z9&je`0I(6rHqW5$8F=F;tQ`gaI{3FNWbaj@olixuweA8%q=DZgP=6mIn}>1ar&qyW zvVDs^ERb6zK|*I*##qRYVq}j(vJG>jKQJCyXbAG#%eF+qmmvQPx#$}BmArz;e?U&_ zF7hF3%3X9n;r+;FOMpG$jV;hg#$5Oq{8PYED+kj0BE!BC!Du_iqi_2$dqYPq)w0DBdE1g-^O*dEoc=#4T*ei+MC=2R`_5zmf?z!&4` zAFMt;kM{Ub_ZQIr1^kQ*qhDbEFjB5&6yr6}M(d-J5B=`o_d~zH${uCN1BJXgU=I2q ze;s%q@FBeAB=Al|29IV$Io``p$MGoB%w;~>Lbmysm6J<|?}eB=21z{dwCw>NU*y}j z5YJB|Z*S);svk#89%W5E5wRD^5*j;>;b_k1@)0qM>8wmE0>8N(unr6hlksaajrva-{jR${tec|ShV&wviVfhVcUWMh~ga1AF@vwh{Jx|D%9cc4x zMrkYfi@>M#E)wr6*!~k3sU%pa5$iASqiCP-46+|b_%iHXE@~n4g5&KQc)AuXKZbYh zfwfrfsrFm;h7O--*D=2NyuPb3Y)$=`dF=`C>F940dT|}O;yPwxEZT`|uT*ji)nVxS zJyvVEB?;h@HQKw%htTU^X|x-Z$DnP8Q5Vs!jL>HB97nCmya!e$tA8R~rHv!YqcD!! zHG|_*d4u>`So_REw)Ph6JO-O7nnrkzk)p1T#Je)`fEocmyp39OfJKOb3mSWG3oIl8 ze*!xm@L}}*P55~%`WQm3&wyuuLG~E!vbYzEdDlw(JscHSgPw!69JF&X?AY|xoB^z8 zqJj?hnH9brCQ>_TDZ81NA69DY}mOTQokIICdOs=a;0Y63XY=>|U zEZ2Ai91ol0VRJlej)%?husI%H8qb#1B-T`u;DIVw zr~#wj2J_-k0}4g1Z&6TouRod!%t-JOts20Wrsl-d%w zU$!zvEyOxnjlPR!I^JcQqw_kI1z+XC1Am3SoxN%+!@M4X)*eHgoC6kF=aiEeor8$U zlNhy=@C@RUXBl~*KaZ5&lW-l*L<%(fLh~5-C3yQt5pNiacUYpT7%3IO6{s)MtPZ;%$&>4!Th-FRn6^!hkz=!bk5j^dMXi0+waaJt{X5d|;u`75BD_}Z% zOg)Dcq!77rKUR%{ygyObV@J9IYtRPl4;Mi4GIljhu(KCf3;Y=P3GAE))&O6?+Vlz5 z*`18~hu{l=V1Stjx#BCuv&k_&--{6 zG}S`Rr5YrJ+=~ma{E&qez%pPVkgVyC@R`*10b=Hm9z(Ud!rFSE7l`wz7LC5Y2)@F? z3Sb$q5Kp;ZFS4(+JbZvyjxGl7UD-nrSj zVBEh4&2-L<`a}95;y>3vB>XAlCoIeZ&IBF+KFaZirw1W92+1qF*Hz!;87luf_R@WU z-vYjXy=6SkL62kanv7lX3PjWkh}adp4^+}&`Iq2d#OZZA+V&~rpRygziT2mAM0>!( zH-UM;nLwUj4|A)uuk#t!_O^(5o)OA%Ke z06OGP?LmHv{vqEh60*)N)Y=6;8XjndmrmXpZ5*)@_+4oEE<6=kbQG|w9!)6mcf9y zD`ELLSQ`Mog3rHn!sVxV(5z&P+`|zbDdDvB6IjNbNrcGu|Ao^{A!g`B%%B~Z9ghKV zI#xHs!rQPWX)jZ)O4#`ow@t%6m4*{BjRmivCeFgoGfx(pfVG&{O~6{r)+S&rTKh}l zU&R}D!b`V!51_t*>~sjIp>6N;8Cm-oa4w^Yn-jI#wv^X9%;IA3JD~p->b{DZUdR$# zfU|+^S;#+xem!ssaJ{XWF=9HebkO-ZM^3~8;A4P8VeJ<5SE0>acy-Wv@I96Gs2+>> zVP4_3>5V-4S`W;f1>(GkcqrEobDXeTJ7=A#`NpygXPn0|ugCKm5s`th`xqnj1oO5o zdUtwy18deUJ`q#&a!yF&rx6)gS=RGxqI|U*cmR26H;~s*2kwyuKwbxV9ps-wejf5q zAkTuePw^IU@>+nEu@<cVik8HS)Rkz{9y#Z_78rE0-lh4Ear+x!H`VmgJwU`kaB(L(RTpNIPei!tUjlvspUu@=@IvP~ zSQrlc9`BcE-^jCam)4Wl1+?%p#JM<4KZ!^fkG?;uR#LyFvmM)9;Cx1Hy|x*dlV`-Y znb+s(^Ux~x=Va)J?>qJ&o9X;ieV$h_U51?~_9`QT33I8 zzTC?#(J_Oxr?uWBDM9-es-F|Sq*5ek7x`Nr+t08ke1=c!)H`m^1AJyzm%--q7P1e! z1E1z|y1oneZ+wnW|2OyD76T7Ba0Wk%TG@O~SHA{+FP>gQd;W+PZiD>K_%0)!J!4D6 z+eZOsqQA3%!>zp~ud2OK>jkVLuHUN^7kgU~c#AVb~`T>{Xy!;)Um%oEk*yA|g{gls26bYoS1fcl|-WY@Z z=RjB+$aS?e@QVaOvWTq_?twf3*as^+c}72m-i0(e6{_oC^KsZ0y&DDm0iQjo{pba3 z4uH)8=xGyr+Qh!no@UFoH2pa6ha5$)uvH-ZlLI~jei&_Y!B>nEaDG3{=XdIbi|@X) zV|cF%Gw64)a23{EyjpRqDDyDBp*=)6Lt9MpdB~_ZFVp#&pS}W&gJhJpg!nv-+cppq z*|B>maoBPCU69BBdiq)U%|rXz(jcXMc{Ls@gfef z2O>T2=M9|up2aQwbIeEBuJ-jo~TW}tH9Q_ipk&01H z!?>&i9}Np5q4NvAA5gQ||5_|YVJl+eL0-kw2JW4@8GID@0_fBM2clL2FdCy7!*%ry zJmUIB$cIAnZH)3O%qxF_*J>Bv}|H#)5feG_VA?UW@|x6rcor9k{85cc=S=|5+lx3%rV_zk%g7 zqIckX3eQ8Q1v(FkzCcosRyBY>X7%Ms^ed9;&PUw^ERoT7s<4YeLa3q^!qLyzEp`Lq}j^z6_|s9OR%tp5>g9>>#nfbgeu8NI#;d=vPa zJNFabMjyLC@*em;;NvlB6T#P@_#nTda0MV`v0WW5HEPG6O z1adJWehqvBmQ7eNVflpRCFVtDJA!$=AG7r{i~_QXtikh7z>dg-HNZdK8OojnUoCPV zG?zj18zKkdEyp0o?9&!v?i>Pt{myu5r&vceAlrP*zS5ompALBw>|6(rOsL!g8~{uZ za~3*>fWNY`(_@5KDdfY@*)C=@5E(#O1uu<)PAyR64aBX2b%DG}X9^W_Uw#`n2Pkqy zBJd}`FywQ=j|GMhfiFV-j9J3wK(6jQ$eJ2Z1+{hbJMEYzEGN=1t_` zZs?0xr=qRA8jslK%rFv?k?`I~WR+&fyTcEAEacp)LNn7swor|2icVzsb3cyX8mtSB~;Wj7k~4|2=@S zCvK?ZKWO~RoP9Wv;oBA^&DND?I=;cFVP5$V{1xpK&Z_v%i}&Z!bfrI!3*ShnNm`KK zuKkSn7|bh8+MhWZ8PzM=CW-HtNK-n)Cj6BbG}+V`qiuyBi? z+9spcB;YXoKH&y1Qj6sgSAW1BQ}?qD^U6a0FI<#ajB1`*2WzA$9cGjhL@h{;Gir^D z)CZ{xf*h%AC1?8ZygQwFN^~JDo16T)K2Hd9deNGaiQmOLkKAz9@$_+?*=+n_o z{uNm_mPp5$m(DPfKJ$lJUmpoO@u)Re<=^l1SJ&|9s~ga^a=z0>t!=;&kR-Ccz8AI5 zTP@+I(;z8P=X17!e3XU!J0i`pwt>BbwpB1M*P}hEA~WjO_^;l8R}_UM@?JdUUmy>a z&*B%e{HwN!$}+-%eCwiADz9?OIL z+rub7sl>2E*~u%H{0ClhfpNe=jAV^aItRW8*h8*^W*yIT@Mq-tyqdt~NAgC(Rr2q6 zJ>r%~pTWXkxFyOhN#b9jYnK^q(cu3Ze0Tghy({={LjEo!WJmiC8`;#R1Ji(wjOqp8 zW=6dm>`Nh8!g4B$6`&;=s(js;}F7nbG(3 z{@(vzZ@qWdS?lXvyLRoW`f68Icb(K4auZmdg$8u-FyBYFHv?lv`J-d>hyaxDS@G zkkK!34>i69c|F>*A9xX(kKwH?N|lA>7U*+BUJQ9UI9Ud{4{#>1FiL$S_3jhM?v0L- zt;@hvZ;Xs=yU^T+T-h5VXvTUYWn|F;*c#Xa-dcL&g;LTRHOSy3aLpSv*nb8*i`+a0 zWlm_mN83ukaul$$#}8_M8ZBJoabh%jPc*W}78(%Vyr4q84NBoWrLZ~ zP63-5@URpZ1$}n(x)7Mz0{aQb#T>ErqEtTA1#ZQ7Z{C5!11Jk_t&zZj@bfinV&ETq z@+v611bWOe+l$qXsLPv)Ilb6}pY4!g$w#2nCD@DudJzKlL{Bf0QEzT=wb&rL0nJp@ z<;CISzzV>ID7y=CX?Q3FJO}(0`gc%Q8_3VW5Bg=JcDoELN5jt~$Y7pheX!;L2V|O5!m(W5~9rh?|#Q{GB&WGMx8zGCG&}@W!9JM2^1m+IEiBgC<+nY^Zk5<7Z1p8^wc;mhr z`bNNLAlfXjW(J1Cr`IY(g>?joIb|U>7+M|}h*EFBJ_#~rqSYUG5PFQM0RMJ#=&?pw zShXy~1IMavwFfSTeIjCJ5aP#MuNDBY7TK6b;%#WWmGdgfdSi-l7Z|AkVo5XuVy=lX zKt!+HVK9iOu(5yHi1q;3mgsu`xfbjN)+kX9Sl`r6WS&8+k5bILMNNU#fR_!5^T5vx z+Aj3>4RQmoM~A~dK)Y=zto z@*rRXXa)oO8f0iKuY}yqkWZpL0oZ&1T!7pSkiUd{1y~>W67=YiNJd%D+cx0az)rw@ zu#AO_eu;ai@ioZn(VqRli_m-wZ*5VkEG)M`pBwUG$kV~eGRS>^Gl7LsHap6u!lp9h zvB>QKxh3=xh`t1_0ZSlv6tFY!X_Q)ndUK$KUdz#6Yc{YV%A(io26E9Oz7l0I`u2EW z0_V19T&-)#k-7)M?JF%SX!Gq70*`6*a>V_g-9QbFVfGozgX zHZ|a3DKHAT6Cfipt-Y|x2hU(ep#9=&8396r5PVce}FK+FOQqtDRtz(ACG1NKRfF?X!~ zz=P0ZOa=J2n?sM;Z6OjkRzj;ia5-!c)t0xCT!rk76h>2k{{SLHGy@{$#TX!BPwqhc zW2|kg=Qd(B01hR36hN*8dx7;qlru<2_#rZTTkmX5=GtMq!=}XFxO5Kk+Dz@79@9l-f8yNy2$Z6wXE#K|jZo<-4G1kKBon zIXqki#slZ0@2){U2n;kfCnP)prxvl0W5rTa*4R)g&MO}B&rdYcDPv=of}chxRShR3 z<&B0tWKGlhEdJTZBAkIN!g+|sl(DJioT@?U4zu<`ZepHo`7R@~XCOC4ZZ*iOARmNf zf5<_|<&nD%r7l8~$@2!y=@j#%Uko;xP$~x48oB#DL~bX;Ge)A4K{|@Ei-BX{ z4JRJ_3^a|6ja4AUd{*r#h)+2Yn$<=)~qvOJJBj~fj=lf`X_S7WZ&cSHyZM5yMDP{LSU(P~53sE<~lwxI2 zS3k&k;3p>KKHfpNZ}U44#B02W-wl&SX$LjaJ2C)DlovUwCeh7M?U1u)u90 zFG5nEH&JJ7cqXGUzrUkbNX#-T()1;e74~P1Y*&Yk3r$lm4#9Q~)Z&7fOolP`4tm`r z#hlG{K?~V)h51@Gpme&mG6$j(x#Ua{X7kxQ}Hk?Exc#%Bb^jbd!90yAnciB%aB{opTDJaUjk_J z67+F9^f`dvqdgrxOZ2y!*K1>`qX?(gh^TTE%eNTx7M%A_$h^v z%Hpk5D0LO3*1|Fc(H;ZKC5X%@BM0(O21MEljJRq4G;c=xbHsRSjxovyd<8AM?5}v) zu-BMcw)t~*lFt3Yo6E+VUM(l0Ri#kMTUU+eAvh(zN^KEWZOxvZ+EB|cAnGo{2J3+E z=FupWt;e?_@>gIqO&`~IYa=WNLf;t7k2k2-r4?8!%&Ovz$b4WBxw+8ms_5@=;Ar4| zc&>)=stn8ntnJ&FRc?Skj%H;WXVy#|4=XU6K0#F6$2+K zHfT(*r=X`5yPH&CI8)Q<|Dj!l&T-ASofpB_WrDT-tn2J0;e9mR{?7K)(R;%jWZA z=9Bqa-T?V3w2j^D8$-PF`i?l)-`b&9B_Z>&AcJj6rz~@)6@-o`-x7vS-r}SQ?hC zfNjn1-k7!}pY+-s_z?E9A$z4RqRolW=Yu}dpsbAE6$f5Je}^D<2~a}+1T3qfuFKGG zfzMLVlm|1OCJDLCU_TNyZUIY4z=5c}3M`AFtg(!N90R#jU=#FrAs2-oPhS8nc>!`^ z$S=Xq0fRva8NcKc3?jD~G$Y{cJM$Yj@bCmYl!ROoa%uB@d2RnA zur%J67s;uR!JBy}J*8yw1=F5njD>mUU2ATd+~ie;Nl%k95SR;yQt(hRc|9yqHW|H6 zo(}m8e6EAMAM#b?BBGKHL30j%%qMV<q^OB8fJy_v}JZ9eTEVl?HTa71xcvBK_p| zT%)SFNIzMmrc!2dDL^?WFJ+?Q^f;BH%2bIi&=G%u!(j%0Q z9-NAjZrrF&E}8@{592pOAJuDf0qwCQRivk> z20cqn=@k-Ggc{QT8ba-<3-zSFG!kVZC>IqZv(r6E)u_juhS^M1sTbqJd~XZYY(yX6ji5M)PR~(YqIGv zdVvPfJJgZ7Q5^L%zh&B{RIfJT1z<~Hdtf(UJa9mpR$b!6JHRo(DZn|v#lU54TJ>rt z)&e&HcL4VS4+BrOY16fbI15ZPXmMa9Fek7WuzcGtojO?6fpvk6+IH*S)oKE432Y1O z4D11n2lj8@vsD{wFmM!bB5)RP0dNU$wRYUf+63GI+zUM1rCaZ=)=A)5;AP-V;9X#f zLEGxmy-gQ81dIe`1?JS5J?(tJBET468DK?VRbb8T#!o$9V_;L@E5P=^F2J7Md$#Rn z_XQ3B4(Va^BY|Up6M@r!vw`!0i+kxYvX=mt1J?pK0e1lR_v+NGy?qpT8h9Rf6_^N2 z>eaPPj{pHfz$jo&U?E_sUcF*U1}Xxp0qX)A1DgZeYAhA#0*nLp2Mz&_0*=>MIxr15 z2e=5h1h^8oUSn)v8*n%9An-Ww4De#F-mQBDt^@A??;DgH7zC>~tOTqEtPN}cd;w_g#<^a5qW*=e(DOVy<@hg23_l$De@0uc7`9$TB?bR& zDGMo2W_s02kKwEaKh_58~plP-TS(p;|(nYuV?)0^*^#M@|AyG>0cB4>+ZCzD5}!-vgq=(=@Ns} z+Lt(*HeGUC+H|RJ)1{ZCO_y$*F8xBfbdPlDp6SvPABZw7l$n$+y*#abnRKJ{yZjyL z(!0{7cc&f2ayv9C%}g6Mf!ecaSZ!PD7wMTgo|qkJXT)BSi2`mdmEhJ?>(v^yR%-(6 z3cI51uCwdJ*iCkmIJ?bmlTcMu6|&UR>S?`int0&i8Bx>J$20NFT4TOJq)dB6SeGoW z(^>S>NKcwZ33QT@Sd?x%Y9B`ZQc4vy(Z_`|a;hLcK6W6JO8WYrEjhl2|O@u~M9(7W;b&bwFg=>O3t*>i!`X^iy z9B-bxJ%^Yq%xgE(PHLIC5tHQ1B*3hi4QEMom+q70QCA5WkH`3en_0-bkTVcEi zBgI$}OVXp3%2G93HrMw(@N2_w0DdKp=Ft@7d3YX*;DvZ03W=I}G#$IO-I`pxt=*Qw zc6+-$DZ8WHkuuml?Vc2A$Jud|(T=y{DU-^ja?wL7kIJL_luzZO%&LGYKv`5FrJwm! z5mkh;s$!}bWmCmfaf((kDh9vHU7B*J$JOKXh zdP4l<)>OsX+ zoQk8zRJ@9(;;OIeOC{6*HGpE&AT@|esv&9!l~TjhFe&F;!M` z)m*BgK2e`gRrRU*l%7@#)k3PKK2x93GwKWV1yxsHsjsMpTB4RvP4%t%mTIYGY8lm5 z%hhtKqgJU^R2Sp@EXKQ@j+OOPU&l%UHBg&$v@}#(baXXR+jMk2r*`P*YOHqYsCr)Q z)=~9>+N-1LMYUhgktXV(o+B@*!+MT1RmatFYKGs$ZmzDWYt%yBP&cThx}|Q>%j&ke zP3CvL2kZGUSVvL@mVpMcX!ZyV!at)9VNF?c8p^t`t~4C~hB}hH#^UH*Hj<5`_t_*i znZ~di>;{cxci0^o$L_IvG~O&SG=T?rfF^R6yYwN?$TQL;o`q+j$vivHPE&YZo|mTb z!n`m|<1xGeP3LjEH?8J}IovHP&H_MX6SRxb2hs1>^w8s;#T;zqj9@YmgD?hO8UxNW#I zzZD)B9>tRsS3wa}xm9kFLFH9>MWo8F@{5eBpeiUbsluwTct{mhMMac)Og$zts}ib& z$f8QBlHy?%t71h~RYsK&*;H9oRz$1vs=UaqDyoVihpMD1iAPjrRaxXzRaI5-sH&!_ ziCn6tswr}-ma3)5WB#04|SvTBB!AB{SnkSxA z^VNJ&Ni9$d#8YaKS|lo~#cHvrqP|pLimK{s^|g3feWSh+)znh8R6L`;Q{Rc|YK2-M zYN*v}wWz6nP(O%TYMojqYO4)ugQ%l6s*R$q+N?HX3L|9Z^Tb3+jYAAzoBh)D_W0U02t|OX{Y&DVnN8 zl_;92JL(tF+<%rbcRsOLYYXdewpk0-LaW=ccEqfu)>7hugR>d`u@G2z8@G#V`&P_rUmpBEvL1#g?7_nI!zbproOW$7GQ2fUlI|$BH|)m$F+Av zKV0J@-oUj_#GCpWr=e1L23h_SfFM~uU@Pegy> zI(M+f-Vwt!#z&0S*ynFBG66mB6*19g=0l&ENj@`^eP*Wk%uMwendUPx-DhN`&&VvF zk&k>vX8VkM>@zagXJnqw$R|D{^L<7>^%+^{GqT8M^BpOJ5TMwa@FEc0XKJD-{5J~JzPW>)<n!&z?KASd&&Urx zBWry|*7=OA_ZivXGqTZVWRuUxW}lHQJ|kOwM!oXHSpP6GmGsk^qPWa56^qD#3GxC$q z$QhrJpM6Ho`iz|O8QBI#c7u_Fh?LWRlaUKPBNu%}F8PdH_8Gb2Gjh#mOt#b@NM&&aPnBlmnp?)!`+`;4Ucv6AXB6C#h95c8M`agUjh@R>Oe zMy`U9L@<(MBE|eqqiHuz)0$9Wwwuosbwz#AUUU$h#jB#1=q>t)*TtJ+xELWuig(2* z@t%HmIW2w?XT;CqtT-pmiwokSxFjx%E8?oSCa#Mc;-bdiQICXZE83AII*Lvd5Isdt3W_)pM-d`k#OphOzM?NVqQB@*t{5QRrm&bQ zrqV-Vx!6uo;m5;|S8Q($%4K`jt4A?J&>oF6kW76Sy7hxEwDmXA#(hBqpZwU7VBXvtCh`)HotwRE&f?A zr+k#b%49uc*;c@kR?v#DLY8B>R@hQj1}oAs=UtZW+d$m}Q-)hlT9wGPs#sM>>AW(O zRm>Cf#Uk;!ST8n+1hG+U5}U;qu~lpn+y97hb6O_min-e3C+2(2Vxd^5`CcsaI;xpl zr|UZ{F8#y%Ty3>b=Pnm3#7ePBtQOykHR1=cR;>G@F^2bfVxIOsU(7e3$P|mT_s@l1 zoplShYwxBX#(Qy!`a`dO&<}8S+ULxa`-{H`(`&^FT1l&DHGNNO=m))WtfTd`ff8sVZKBP3 z9ob6TXglqoowSR7)T_!K+DrRrKOLZhbV#o;N9ZUWqvLdfPSPp8;`~Hs=w~`h=jc3L z(Cg17x=dH-DqW-NbVIL3wVRgvA#cQ=VO?flkoVVaD`OEwj-io*8ZFpPWj<@F>ct_recjmA1F1#!6#=G+#{53v{ALNJl zVSa=k<;VDOeuAImr}%096FwtC8I%FNTj#x*nW7cu&gmuz7Wu3Nuvd&mPTW76v)_LoK zbsKqux@X+UZvZ{put;%U5L=*;RIv-DMB?n(Qfi$vD|t#>+mkuY6th)35&WO*udgl!N43 za6OSk93P1mj~oQc}O0XN90j?Odgjf zge{3X;k^m?dY=#5bS(3_zFp@E@6p|?VVLvM$MgocLRao%wH zJ8wDzoPo|D=PhTj^R_d@dB+*%40lF2Bb|4hQOP&N{J2RY_&MfC6 zXSVaPGsl_h%yT|*<~yG{3!H_{XU<~hbLR`^OXn-+YiEh`jq|;;#`(cn>#TFuI~$w? zXN$Ad+3xIcb~?M9AD!LK9%rw!&)M%BbWS-xIX^q+oD0q+=bCfFx#c7}x1BrAFV0=( zS0~B2=iGNvoK%-w=5klKmTS8K_b2y^`?Gu2J?EZxFSr-oOYUX&ihI?)=3aMixHsKf zZlZhJz2pAk-gSR!{VJmEh17R5sh9knEuoHH};jjv42uFr9 zhBJj93P*)AhqHtq4rdK#3rB~uhjWA<3Fizy8qO8Y9nKTZ8_pNbA1)A{8lD!O9-a}N z8J-pXC_Fp-ad=L6Zg^h!lkj}J`-~G=oQw|SGr3+tao+insIB++ed1ZYzaJ1yMKd}i zT8fwH2=>rpqKD`~$Muf)7M&1d#aQ}DOcImm40iUPv2UHlzI6`!)_Lq(7sR(>DP6=4 zaoH+om1l%=Q*MXs5R0)R?MPMX=|L4&eOa7O^VQW2YK|KUr*lwP0>5RYP~w^Dx9Ij)n-VGAer}QA}I^ya5n1n z0cVrOL(XQ6N1Sc{usxuqA1J8rC9+aZeFsp)d-upWWc1Dvja!{#8n-(qH12e+n35{9 z@u;#GkLqFL5x$b)^|$wEB6Mnl3TfP^oHRKZkQ0fVjA?SRA}1SiqLITXLXTRE9=o#K z{9955UV%8T&a0E)wRkPEczs@Exs(`f9IP4pgVc1Le-Z1)#; z#r}V?AO3zfoUHf3f9`+}?k@kg?S}u@?exAlN$=ke?uu3P-G{mN@a{a!eMbX*=kcq5 z-*Hdhb-bkSIA-9UW2V02NYYXJf{xUeOso5#)T=5zDA1>AyeA-Aww>p-PhC)SB#*{iH8 zJX30;sg0xG@QT9hta!y1b>gl@b~#x znt=B_C-aG1ul9JxpCMe4iDrr@5k>Pv7Ey#g5yeCaS}973YP1GB{$A|(`|$L(-FY&J0r_#-?8toZ1!FIE{nF4>?D>wpy^^c0wV(Nu}1>$2gb2HfeC?0tYBbDU^*)r zm>F2WN(2@KmaqzeZvzReT3}OP7kfUiKd_&*3>*p^VJ`=c1x~Qm`raU!wF{)`dxOEU zjI6+x$tUGgY;~}3un7AhSS(nAtqYb6mSh`)WrAhcreL{XIkwrnf5x^1D+epHt--3n zs%%@Zdayd%9;_9t#dZYi2J5n&!N$SHY*+Bb;EU|XVAEhzwmbN8unpT2>=f+84o2w5 zes(C-I@E?84z&w)WJg1tLvie6C_WU=F6n)CD!Z)r+3(qHz0cldDGqm!3Wdzx6CKUcpDF4lJib8$7P zReoxfP94)P^C_F6tf-Ef;;gvd?#i;V+Gh<`LvJp1SRJz1BsK}3ve`tf>}Wf?ox^^_ z&S^ht=dyF#dF;G)K0Ci%z%FPPvJ2Zq?4out`!Tz?UBZsBOWLLE(rgdg%l5JT>;OB+ z4za`R2s_G-vE%FnJIPM5)9fd9hW*UWvUBV_yTC58OYE{GEKA%GzlgiySCJ&{iTfg1 zq=?kNqqB5ZERK@~3*T1AhX)D#`ekY63hJH~BlEem68aU?td`gJi52xLM3wZ* zp=$IDxl~8L6utPr+Pe<0D3Wbm)iVP_&(LIvB1t4l4><`ERa8JgG2xO0L_k0=paKeJ zF<@d*0doMeU_ii#igDMhnASC;fC&S<>LV(;?%VtByZ7z)zVA{Wb*j5(divBk^`HM# zCsO3l5HytV!x5w?qDWGdPz))`C^zi zGl#3GoT%ZX$2wCIN(=*y@ReU-j})aYCEJ;JFoM3a#&i6YaG0o{FdRMg$q zuM(0{3Wy>MARtOi2i*vWbV*1fE#06XC6Xc`B~nUANW;)AA)rV}2?$6@hrs;~&*OdH zpRm?_|GDd~b%!;>KKr}(+55B4XZAT03+P!QAt6*P?J-S`CSq&9X`-nv*M(RUM^A{C z5qw-_5%Bx56D8D9!~`PpTZo8cYuMs!4>34^8Rf1zFHlr%rcDR)j!eu?i((>qlB5MY zQ)l2_pYEKyDodS$x-9FjFWxxa3S#&adI4)9#wYbjK7lvAoI1?qlV>Ey{9Anf#Xib; znf6E@aS=`r`&TYR3hb}n@jSFtoSwWN&6&qJ9C-WTGU5Fl)?W*VMfo=aGSO*X{0E)i zX0MQ3)qce9pg1BCzRZ8W#J4s0`S_k&N1XHJ@u%fc?6Z8SY;2Q6-jjEueEd@;{cQ+8 zF{^xH?*GIrK2OQe=qW*U5zC5a6`E#evcR%Q)g0O71&wAK{wi*?;k9p`A>~jg)c*MW z>)c45j^-3L`l=|O&^N?E@!`^!%D~JDpd|ycPL3j9)cw0ghw3GG*81m>6Y{LP*Gnk5 z>zKKH&v8E>i4`G^Ex`Mz`J(-Z@k`|VcrZm2@iFyvj$gW@$t;XA7xh?cr0Kt0;x+d3 z;mnhKdX>0ZNi^(6t5qcVV-QP6WGkrIG_pN8(Nq+_BG*Rg^}{zqWXUWO3(w<@n^OI= z^^OvS4_la(&{{7EQ+HM;DVH>TMS(9ueB>u}Z>sbJ;HOhX@Me6CoXE?v3W+EWu85Kt z6puY#xZ^Z+WoSG7w^v>`LH7}f6X%55d2Y$a0apqmiL}~@xG$fhlRHl*hfha(p3L+d znMJ)gwLuzi@jQ17K6eZew;BPr8XmXW1#ZAijgWh0#YR{XPNj-ykfydtbnJbkfrSV&BeI*qUIHqE9JtL6goJh3*r|X|~-P!ZQ1F7Z!qo zD$dt6uiiNq{iZpFHB;I`^+F8Eg0YUZcnONt+mOQHW=MS!c>@JV#y}=g#E>&GZHU3- z%F1~tWA)?b_3uG!_KK0pLl;fp+XM`uSh3a@Z+ki`OeNsuL>g~>ht!9d3)!1`-}7T3iI|n^Y+ho9f;p^|2VG! zy0V}ZUaj!d^Hm|A?pM2~+)s$=t*@*Q)Mk+iziB;0mec1QWzsge(*$7&6k{J3h;}Du zHmwt+QE9Na(_Xmo=G=pdikv{R=_lwblnwsBK#UTU9Mq5Ivnwquxe*Dk#CYat*O2Qj zVCMLUySE3Vm0Sq1(fU&KeJxHT*jM@zRONxflFV5VpJWz{H|?up-J6uiBc@<@($AvI zo;r~ohWjY`z82}vcS;PdPkH#{VcNr}n-5B~N+e6vO752EmdL2_=1?uuE?-)vzfPt8 zjN;k*5ZmBIsd6b-sk#s!dZEigbV9U$-wxo3o5yMU@rR zL*iY}*p1|iUJl*2W-Fr|4OwoXFp?>HF{C{vvBc^^yK}iZX|Q`_a(k?1lIa2 z7vDrxBxp!BRZqupUZ>qejaVmMZgVUs@Q97>@CWyLBbVh_~|`^wb&%9r;0 z&cRVc-8Nzy=U=;1tz}Fq4riYmN8T9`4o=i@kr}yuc*gp(cplO2tU32@uy zf?o$N$k(FIZ{S?so$+5~s91t&o?mVzRUt4YTzJl#-s@yq1!;ZGW!$3$rfGduWV*y4 z)0$v!=mA}M&N-0C&>#uzyGssxC2P_OHcoId6NCkH7&*iR-G0hR5# zX78b26Lh*@bA$uUH75GUKkh{w>+bCFRS!s{JUV*#gk~e9+3TzCe3c87#y;tD7=xcq`=f_>GmCkqA;(-h5*g&%LstPK!kGY`wKBFV_(&O1jR zDAm)AZ{Sxwejj;cJms}GdaNT` z9X37gC$8)@?T9D$tR$0br6Zs9*~~-?ca5rK3M&Sgd57yo8R?gh67@>gr;!yovLp9n zcrAV^-*8MPuPBd*ykd2Cgd>K;B2gtP!$vxRWhMXCT*kNL>G2Djuq~Jcy-Gj^*6+U9 z$ZdD*q-?P_Rm2ZArRfxPPS8FnoIrnUfR+$TrT9sQs#WN=E|vU<{a9j%Y~DT2W&1JJ zTmp_yBKG) zeba)O?Jg~B)W*aRh7AN*Z!g7eQ6~?<3&^#DMCH8vFgnwxjOmJR~ zYCCe28cx1DWL?XesDQmatsKufspM}aAkeFXUR~#(UdT_$TZ&_A@p8B;OQSld^$Zos zr_#T1=kC>1Mgb^?dfUxLEt%;_gX{qjBr68O4&hgCV}oGcRZAv*+21sLX;*%OBPg?# zgX50VFz=X4O7zIr0xT73jM416lz~d=f;L#YY^rgMwkCow9}#A(a{^P zdL!A!ZYj)OzV-@os9+;Z7sc1I! zxjsTdNZ`j_%q#T}S4w2wFCWqBAwJ0H!=YHJ{C;n&)Sp!B3h6PgXJ=&UYI?I z_0||-<^Ld!}?Wp!ye~EZAX&jSr+l zd6^08O+D;F5eoY6L=1}8!;dyb?$L4{PmJ;%@OdS*4L|xNp}aLVmuZRYy;a!o(kWnW zDlIlw0yVvm6s6|_EmQ|yUq~Een$X+%R zV<7Jrl)2ev{iS?Iru8F*o8Iu1M>XpW)Z6~yl4jCBT3W9j^0{OUBF)Cnr7fC>#o}rk4IG+E%bG}nAIul+DPnUOMpM-+EY5`x12X>`|O(aH2nMf z(Xl=C(Bshyd_K!cw-25k-7lV+b?QiZzXUR&<(wXA$=$VXY*$-{t#dc!$7n=T1V3X;j;Gq&tDhGU0DEif3_P)L8e?L}ymUnX z`eaGHZK7$JPdUx&cZNKR0G1J{Wf!GuwO~2GiQV^(d)bC<6%<)LT>FX%lG!37_N`3f zA3K&ybtA)%dz%B>pM5hvtJ*rgnKKsCw#&S1Iij{Hs!{jR9p&#j^|3d{Ghc6Jz;tgR zna(4~y9-8Bkn>QW+dk5@Lfhgubfkk(r>xZFC)UVW@Mh;QB9~E0e?CrRL3K8qGGb-#0qIoy%aSbH*ID zyopQ3Lr!y#?JPScZQpSiyUzKHjq&+1E*{U%Tq?a*B$E*R+^qBaP{)F^9sW;>{2a!Z zUDig&bWTex6`e=Qx#A^qddTB-#InkF+1}KA`Qd2IRd7LDaRvT%I$zgN&hf9obonAE znVDMIxY$8{;)F>_`)wC`L5O~FyLU`qyu(4wXn!%MxY0dLv#PLX%}pt0$Q!zrao}=39c3#gtH;&^9~6T-|nsEe~mVx`t`XLa&#QAgkTzr$dT3 zi67)iS%zDk7TU^X6*Jkx0^OaVUsFcB6wDQAb5qjOxvd;PlbNH-)Jxa^>7oIv=oMta zgcrwv5GIV}$|V^v$Ls6HjWexVM1jpe(cuIH=0~l{hYXMMv|>TwwC5QIpOntfwDn;KCI(jcWQV3P_PFHqY;$f0<&k$xdlgNQ7XXTaentHa-6ie@A zTMTlZA`y##%@%&ew!j7?e0`K&=^LZI$_W>!apW+V`j}*=EByN%J*M49`8FheSa#p|wiO!xqAFr$- z->w!Pa}=ZThXaj@cvj=sd!CY_O` zYra>@BwRQ1y?wfI;`#H5i#0VhU+OHMcJ*L?rkJAm2JSJguc&{&{)_KJ0B^FIbC2fg z_98o+{9^1v;<30^d4X?ZXf>8L%-ZZH$Hcxx@1T~H|L;JVLFiXb&KtZL`1f9I{`}C< z&%I-4uNOS^1iz;|XgU7Yh*|LJzY zW5JDZpI=T((bMsorGD6XgzzB5Gw{5~YY85?0HCaK6O3g<_W< zQRaslSH~Rg48&HNEWq5x2nTYlGJlBpe){g6Y1q2Bv%{%SCg@q(W=&L`Rj!$oCvaE= zKEQ4b#3z}A)r`^}+G@_gb^6p4WbZ#ysEce}7Z85IuQ?M@J)V`EKD{C|*A-*i@a#wS zJ|@{)^Vnk>rs=e5}APB?dOn)<{eL=amEQZdz08To9q`a zVz#r#UM}qL{yzM`8idz2$VpmiI5Op5@JURSkAG{~)72nT>TPRH0A7R@i(;|GVJ1`x zPxkYYRPBgu6+77v-_7++xsu@(Q_ZW@kerqgOao^xyP|7$#QsIkND4=RM8Whdp^G3>2p&9(>L8%t!5XcMB-(x`n<-Y%6j+zP%)? zAp4B?AkLuohfwAbu~>kw=oIrgwWX)l4EEU@Tjp4EmjZq}Ar>n+6dUTr+yf_isJy%{ z@bSvndLBo+c~b^T?uTS2&tam2x)N|aOb)9VOvGr7-{mg6Hu9P|lD;|dXH_k@|ESa^ zzqpM_CeD=8gfgN7Y8kH2S1BNPqktVW*|WwOI#&?#BJnD` z<)bL}XO#B1O!c^G?{kEPPrk=eK#F$k4)H)7&(HA8>UqwxywI)$O&Z?nK3&hI!#!Tr zk+SZC!cB|sGDm|0-aX_B-`3x2zYEQ3O88REd)=)vH?6G1B68dFN9%|@M+#96sUD|P zZt{5AC9%|{VYW^cA!8Y-h^tNGwH}KfFU=Jq!pLd~Z|+AEry zWN(xAyIK}4nLEVOg~(X#Tz{pOttw4ys1n$vMhKw~b^}9Ai)8!0S4oMCsU8WMuPy{C zFw=ew4xDGuNm_Pyrg{Lg*_m(e6BhewhaA+*f8`;#F&N_$qj!|M__9%K1cJTyAphPq zTZo0C=Fxkxrs)#a5hyI_8t;1bw(A|mmlPQIZ@Kr$!$X(4jBSV#o|g>L{vx?#E8^Ut zQ{_Ou@ggMkzT4Z`PBK+*g{{Cgx9<1l@f@pl>89naW4nRxMQ&q-*nKfU?g`1UMxNHc zo<>(Gz03BqsnnRY+Xzx{OhlqNMGST5Ka4wv1Td9hzH!)`GXS*Q6ly=P5 z&*#pSuuY;w+cm597T@EDit$UGmsrAdXsN~J;MO;fW}b}IDs&%3emy+*di~(`9SYOk zRn2%!MvkxqjI6$6YBqs-RVQ)R=&cI`a}R|awUlZ-BH#M-FmZNotd{4ADA#&FesA^$ z?6Q7SKO|-6!>(RBZ1Yx)Z^EFU=F00$?$KAAFPPZ_-16_crLA;zyMJ-bxqHuCP)OO# z-F|;um0HHSH+MlJ^NRrO(&xL{?*zVgzSnyu=BQ7{^lY6A4CN+IX8+-&%Vnv%dT1`6 z&A)F$#;n^b>@o9cZ@eqZib9)5uF8=W#+I=w|BCb`WoXDaY`auhGyT5TV!%T3>E^ zEbaJyboYdus3j}_l8898>*}oSpsnk%r#ICzepxp(N-Uq@j#uH4u;J%o+_`pRI(%Sp z=3y;w$PMyQ`&`o8CH65t zQ7=XqDHF&^hk#+sVJcA+q~hO|X|%7(4hBVOkY9aZ9dEIgJ%w=gQkia_Da?rf#Xr$- zP2?I|a?Zr7s zt`nQOlw7koJZKqb9FZ$%m+UIAwjEWxpH~|eTKIK1HLv+Yc1*YSGNgRl-%dwzp|L5O z{o94CB~qRT^}dec$o#yW3TO^!!(MAnHi%jVFtF#B#GDh4}p(Y<63j`%TsF)DL$a- zD@!*EBR>7sXd&r#)^sLpYf(&hyf`xU?gJCwEvr!H{@5|0N9KKkEh(-S732na;;$oR z@-ur(s_R-e%P-LjQV5Q0O!;g?*1%H8*e+cNaB-%;-kS;ic6BpWQP2|8P{-oaYPV{w z{@{8d7F^?~)KIYV@^UC&{!PyTx~2A?Tk}RulXDO0%cHzyVwm%4UKtz*>6 z*8O^$gfbbCe@&;(YjD?IR#hHLMzNg4KTsZ*zVKO8@8iY%h=}T}EafkziP88Hw~WGp zuNW;*tA677;2!t6jHpiSX1wWx;DQkktFG`M)h5(ZgT)de%X~82&~FY^ZOumbr0UiO z84u3(wlJnQmm>@1=?>|o<|v0QlA79@I666)8rqzQc1D&LNzoV(7<3}(fWSH+1Q?|U z0>N}ZP$&|FKw*FZ1O=$!a1a6p14dA|4hRWB;3TvT2#!YM%yAL|146^!;B)|)eK!A{NB%ntj052rY7LAJs!KH!0dBJfrVSqV-2!I3u z14=j=cNqf4MS|meAP~SEfkc7O7+eqt6c^>h2#UZZcLD^8g5a`10P# zNWch#!ve(sA`Fbv!eBTp3Wo^>2gHADVQ>t93Wf^<1KxiIL|j@hs_h`?$A9ssHSC-)P3 zT!~JA>4E->q!{pjkrYQvFfJ1C2*#NLkGR->(!yzRpaC2>^5LNUPlBJ={3#S}4%`m_ z0{0~NLw_9cag_f*gT7Z!_DxQo1ZsAx5%!<__!Ye1O+zkj)eBN2{0Cu2BJ30$rI zCCWbx`ELcrRrB9207U_`_-~h>e=Z3M@~OE$GUd}!F*V~;weT*!2L!A;2zV0m zso2>$0Ru2dg-_Pj%nk%B%84wgBM1}$gMdjOP+(~>q%<5O4v|2D(Gak-6h<5)AucZb zf3^SuNLyGtnL6-ETN^r=N}3wmnV9n3HMKQ&dI*#ODJ*=E^iOkhOG5NP?%^3y1y1dq zKQ9&)K&Ym4nfg4f+GQzG+j`m>e4(N#R%B8=fsy>}mzg9aMXX4T*y)uTXp3Ni2}i=r zC40+C_`PyT-F>ifS0c^zTN;`nrH1dDQ!gJ-3%`8&#C%sJnpw^=?K06C>4e;Z=DPza z!JbPDl|dXOug@Rfw%M69M=-2CrXOq|I0(W#E#pOwYXG(t1q(+7$3BsI5=i?p`A}Ru}y=~$d!%V?# zE|wx3A$+Q)pH*8|S$!jIIDAf4>JMmUGwPU$eGb#Nr`4EaxAI22(0-#R$J(7$y^B(UrJ<%EG)M0EV8nI|B!>mZ$B`zyWm1Q+Dt(aKO*N0rczBzGvV7jz_2M&cFdb0|&s} z)4pfmfS-W_;Ls^wfV-!4XW&4bfdg>{4%`v#Zyq4dz=1de2jUDIxP#)~^8vl&wC)TX zKz})HcLomR890z<;6R>%12{#Vj&TMKpog8ZL!E&G*tJjFoq+>&1`gC2I8bNcK%IdD z=zpiNlgr$hZWJ0}N23u{w{i=@CA=%14h?Em_4=brhpmgFz4%N!i(@6s0ec{@LNd7fJtLB*&-ZVCn+A?84=Aav)T7vH^y;s-4Ok Mc;N^fLr(JkUpzT{&;S4c literal 0 HcmV?d00001 diff --git a/license_texts/ElmerIndividualCLA.pdf b/license_texts/ElmerIndividualCLA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5e8624560485f2021fb60703915b7b18f993462a GIT binary patch literal 113955 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 z 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%0AXL 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 VbN3ffJAM1$necW9r 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 literal 0 HcmV?d00001 diff --git a/license_texts/ElmerLicensePolicy.txt b/license_texts/ElmerLicensePolicy.txt new file mode 100644 index 0000000000..f44e224769 --- /dev/null +++ b/license_texts/ElmerLicensePolicy.txt @@ -0,0 +1,50 @@ +Elmer licensing policy +====================== + +Elmer is a finite element code published under open source. It uses both the GPL (GNU General Public License, +v. 2.1) and LGPL license (GNU Lesser General Public License, v. 2.0). + +This is a short description of the licensing policy of Elmer suite from practical point of view. It applies to +the use, modification and contribution of the code. + + +Licences used +------------- +The code under LGPL license include the ElmerSolver main library (libelmersolver, codewise /fem/src/*.F90), +as well as the libraries matc and fhutiter. ElmerSolver library also depends on iso_varying_string that is +already published under LGPL. + +The parts of Elmer project still under the more restrictive GPL license include ElmerGUI, ElmerGrid, +and most of the existing physical modules a.k.a. solvers of Elmer (codewise /fem/src/modules/*). + +Upon building or running ElmerSolver may also utilize other libraries that are compatible with LGPL. These include Umfpack +(LGPL up to version 5.1.), Hypre (LGPL) and (P)Arpack (Free BSD). Note that if you build Elmer utilizing some more +limiting optional libraries you might not be able to use modules that have been distributed in a non-free fashion. + + +Using Elmer +----------- +If you’re just using Elmer then the open source licenses do not set any limitations for your work. However, if +linking other code with Elmer you should not combine viral licenses (e.g. GPL) and proprierity code. + + +Modifying Elmer +--------------- +Everybody has to freedom to modify the code for their own needs. However, if the modified code is distributed it must +be done under the very same license than the original code was under i.e. GPL modules stay under GPL even if modified by +the user. + + +Contributing to Elmer +--------------------- +Elmer project accepts contributions. If you want to contribute non-trivial contributions to the ElmerSolver library +or to the modules the you may sign an Contributor License Agreement (CLA) that grants certain rights to the main developer +of the code. The CA is based on Apache Contributor License Agreement widely accepted by the community. + +Some parts of the code is more relaxed when it comes to contributing. To be more specific, modules under "elmerice" and +"contrib" do not result to the need to sign a CA. + +Contact +------- +If you have further questions on the licensing of Elmer in your work or want to contribute to Elmer, please contact +elmeradm(at)csc.fi for more details. From 4accb2cd21bd165b5fdd8a474362c7ae18632338 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Tue, 27 Oct 2020 19:31:14 +0200 Subject: [PATCH 50/73] Change link --- README.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index bcaafe42ba..48344f4c22 100644 --- a/README.adoc +++ b/README.adoc @@ -87,7 +87,8 @@ You may find the PDFs for the documentation http://www.elmerfem.org/blog/documen image:https://img.shields.io/badge/License-GPLv2-blue.svg["License: GPL v2", link=https://www.gnu.org/licenses/gpl-2.0] image:https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg["License: LGPL v2.1", link=https://www.gnu.org/licenses/lgpl-2.1] [.text-justify] -Elmer software is licensed under GPL except for the ElmerSolver library which is licensed under LGPL license. Elmer is mainly developed at CSC - IT Center for Science, Finland. However, there have been numerous contributions from other organizations and developers as well, and the project is open for new contributions. More information about Elmer's licensing http://www.elmerfem.org/blog/license/[here]. +Elmer software is licensed under GPL except for the ElmerSolver library which is licensed under LGPL license. Elmer is mainly developed at CSC - IT Center for Science, Finland. However, there have been numerous contributions from other organizations and developers as well, +and the project is open for new contributions. More information about Elmer's licensing license_texts/ElmerLicensePolicy.txt[here]. === Package managers From 40da0e128737130e33bfad6f0472e59d0f3233c4 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Tue, 27 Oct 2020 20:02:40 +0200 Subject: [PATCH 51/73] add missing link --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 48344f4c22..9843e9e41f 100644 --- a/README.adoc +++ b/README.adoc @@ -88,7 +88,7 @@ image:https://img.shields.io/badge/License-GPLv2-blue.svg["License: GPL v2", lin [.text-justify] Elmer software is licensed under GPL except for the ElmerSolver library which is licensed under LGPL license. Elmer is mainly developed at CSC - IT Center for Science, Finland. However, there have been numerous contributions from other organizations and developers as well, -and the project is open for new contributions. More information about Elmer's licensing license_texts/ElmerLicensePolicy.txt[here]. +and the project is open for new contributions. More information about Elmer's licensing link=license_texts/ElmerLicensePolicy.txt[here]. === Package managers From 501f8f90c3b39c67daad79fb62006dbf4eb234c6 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Tue, 27 Oct 2020 20:04:58 +0200 Subject: [PATCH 52/73] fix link --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 9843e9e41f..2eb735d333 100644 --- a/README.adoc +++ b/README.adoc @@ -88,7 +88,7 @@ image:https://img.shields.io/badge/License-GPLv2-blue.svg["License: GPL v2", lin [.text-justify] Elmer software is licensed under GPL except for the ElmerSolver library which is licensed under LGPL license. Elmer is mainly developed at CSC - IT Center for Science, Finland. However, there have been numerous contributions from other organizations and developers as well, -and the project is open for new contributions. More information about Elmer's licensing link=license_texts/ElmerLicensePolicy.txt[here]. +and the project is open for new contributions. More information about Elmer's licensing link:license_texts/ElmerLicensePolicy.txt[here]. === Package managers From 694f61508ff2c90e9de2993510e8f2f570c5e024 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 28 Oct 2020 01:27:34 +0200 Subject: [PATCH 53/73] Steps to eliminate BSolver --- .../modules/MagnetoDynamics/CalcFields.F90 | 52 ++++++++++++++----- fem/tests/InductionHeating3/crucible.sif | 27 +++++----- fem/tests/mgdyn2D_pm/ptest.sif | 18 ++++--- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 4d444d3f7d..62684f1eaa 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -51,7 +51,7 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) CHARACTER(LEN=MAX_NAME_LEN) :: sname,pname LOGICAL :: Found, ElementalFields, RealField, FoundVar INTEGER, POINTER :: Active(:) - INTEGER :: mysolver,i,j,k,l,n,m,vDOFs, soln + INTEGER :: mysolver,i,j,k,l,n,m,vDOFs, soln,dim TYPE(ValueList_t), POINTER :: SolverParams, DGSolverParams TYPE(Solver_t), POINTER :: Solvers(:), PSolver @@ -59,6 +59,8 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) ! as is done in this initialization. SolverParams => GetSolverParams() + dim = CoordinateSystemDimension() + ! The only purpose of this parsing of the variable name is to identify ! whether the field is real or complex. As the variable has not been ! created at this stage we have to do some dirty parsing. @@ -86,7 +88,11 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) END IF END DO ELSE - CALL ListAddString(SolverParams,'Potential Variable','av') + IF( dim == 3 ) THEN + CALL ListAddString(SolverParams,'Potential Variable','av') + ELSE + CALL ListAddString(SolverParams,'Potential Variable','potential') + END IF END IF ! When we created the case for GUI where "av" is not given in sif then it is impossible to @@ -95,17 +101,19 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) DO i=1,Model % NumberOfSolvers sname = GetString(Model % Solvers(i) % Values, 'Procedure', Found) - j = INDEX( sname,'WhitneyAVSolver') + j = INDEX( sname,'WhitneyAVHarmonicSolver') + IF( j == 0 ) j = INDEX( sname,'MagnetoDynamics2DHarmonic') IF( j > 0 ) THEN - CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be real valued',Level=12) - Vdofs = 1 + CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be complex valued',Level=12) + Vdofs = 2 EXIT END IF - j = INDEX( sname,'WhitneyAVHarmonicSolver') + j = INDEX( sname,'WhitneyAVSolver') + IF( j == 0 ) j = INDEX( sname,'MagnetoDynamics2D') IF( j > 0 ) THEN - CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be complex valued',Level=12) - Vdofs = 2 + CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be real valued',Level=12) + Vdofs = 1 EXIT END IF END DO @@ -565,6 +573,8 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) REAL(KIND=dp) :: HarmPowerCoeff REAL(KIND=dp) :: line_tangent(3) INTEGER :: IOUnit + REAL(KIND=dp) :: SaveNorm + INTEGER :: NormIndex INTEGER, POINTER, SAVE :: SetPerm(:) => NULL() !------------------------------------------------------------------------------------------- @@ -576,6 +586,10 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) dim = CoordinateSystemDimension() SolverParams => GetSolverParams() + ! This is a hack to be able to control the norm that is tested for + NormIndex = GetInteger(SolverParams,'Show Norm Index',Found ) + SaveNorm = 0.0_dp + IF (GetLogical(SolverParams, 'Calculate harmonic peak power', Found)) THEN HarmPowerCoeff = 1.0_dp ELSE @@ -583,7 +597,16 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF Pname = GetString(SolverParams, 'Potential Variable',Found) - IF(.NOT. Found ) Pname = 'av' + IF(.NOT. Found ) THEN + IF( dim == 3 ) THEN + Pname = 'av' + ELSE + Pname = 'potential' + END IF + END IF + +PRINT *,'pname:',found,dim,TRIM(pname) + Found = .FALSE. DO i=1,Model % NumberOfSolvers pSolver => Model % Solvers(i) @@ -2284,8 +2307,11 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) DEALLOCATE(ThinLineCrossect, ThinLineCond) END IF - - + IF( NormIndex > 0 ) THEN + WRITE(Message,*) 'Reverting norm to: ', SaveNorm + CALL Info( 'MagnetoDynamicsCalcFields', Message ) + Solver % Variable % Norm = SaveNorm + END IF CONTAINS @@ -2803,7 +2829,9 @@ SUBROUTINE GlobalSol(Var, m, b, dofs ) Solver % Variable % Values=0 Norm = DefaultSolve() var % Values(i::m) = Solver % Variable % Values - END DO + + IF( NormIndex == dofs ) SaveNorm = Norm + END DO !------------------------------------------------------------------------------ END SUBROUTINE GlobalSol !------------------------------------------------------------------------------ diff --git a/fem/tests/InductionHeating3/crucible.sif b/fem/tests/InductionHeating3/crucible.sif index 1f705ad569..c43825d579 100644 --- a/fem/tests/InductionHeating3/crucible.sif +++ b/fem/tests/InductionHeating3/crucible.sif @@ -17,7 +17,7 @@ Simulation Coordinate System = "Axi Symmetric" Simulation Type = Steady State Steady State Max Iterations = 1 -! Post File = "crucible.vtu" + Post File = "crucible.vtu" Frequency = Real 50.0e3 End @@ -88,22 +88,16 @@ Solver 1 End Solver 2 - Exec Solver = never + Equation = CalcFields - Equation = BSolver + Procedure = "MagnetoDynamics" "MagnetoDynamicsCalcFields" - Procedure = "MagnetoDynamics2D" "BSolver" + Calculate Elemental Fields = Logical True + Calculate Nodal Fields = Logical True - Discontinuous Galerkin = Logical True - Average Within Materials = Logical True - -! Frequency = Real 50.0e3 Calculate Joule Heating = Logical True Desired Heating Power = Real 3.0e3 - Target Variable = Potential - Target Variable Complex = Logical True - Linear System Solver = Iterative Linear System Symmetric=True Linear System Convergence Tolerance = 1.e-10 @@ -115,8 +109,10 @@ Solver 2 Linear System Iterative Method = BicgstabL Nonlinear System Max Iterations = 1 - Nonlinear System Convergence Tolerance = 1.0e-6 - Nonlinear System Relaxation Factor = 1 + +! Pick the right norm for consistency check + Skip Compute Steady State Change = Logical True + Show Norm Index = 1 End @@ -150,6 +146,7 @@ Boundary Condition 1 Potential Im = Real 0.0 End -Solver 1 :: Reference Norm = Real 0.88822024E-05 -RUN +Solver 1 :: Reference Norm = 0.88822024E-05 +Solver 2 :: Reference Norm = 0.16749970E-03 + diff --git a/fem/tests/mgdyn2D_pm/ptest.sif b/fem/tests/mgdyn2D_pm/ptest.sif index d97a03676f..efcaf2d653 100644 --- a/fem/tests/mgdyn2D_pm/ptest.sif +++ b/fem/tests/mgdyn2D_pm/ptest.sif @@ -3,13 +3,13 @@ Check Keywords "Warn" Header:: Mesh DB "." "square" Simulation - Max Output Level = 5 + Max Output Level = 7 Coordinate System = Cartesian Simulation Type = Steady Steady State Max Iterations = 1 -! Post File = "ptest.ep" +! Post File = "ptest.vtu" End Body 1 @@ -68,15 +68,18 @@ End ! magnetic flux density Solver 3 - Equation = ComputeFlux + Equation = MgDynCalcFields - Procedure = "MagnetoDynamics2D" "bSolver" - Variable = -nooutput temp - Target Variable="A" - Exported Variable 1 = B[B:2] +! Procedure = "MagnetoDynamics2D" "bSolver" + Procedure = "MagnetoDynamics" "MagnetoDynamicsCalcFields" + + Potential Variable="A" Linear System Solver = "Direct" Linear System Direct Method = UMFPack + + Skip Compute Steady State Change = Logical True + Show Norm Index = 2 End ! make outer boundary circular + asymptotic bc @@ -98,4 +101,3 @@ Boundary Condition 2 End Solver 3 :: Reference Norm = Real 0.11769191 -RUN From 6dccfa96f31c824f82dbfda6d55acab1b6c95675 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 28 Oct 2020 01:28:23 +0200 Subject: [PATCH 54/73] Remove debug comment --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 62684f1eaa..61f6551344 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -605,8 +605,6 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF END IF -PRINT *,'pname:',found,dim,TRIM(pname) - Found = .FALSE. DO i=1,Model % NumberOfSolvers pSolver => Model % Solvers(i) From d1a4e545efb5e5f2e215bccafd6d53c97ca67ef4 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 28 Oct 2020 08:55:05 +0200 Subject: [PATCH 55/73] With H-B the magnetic energy density must always depend on the curve In the complex case the magnetic energy density wasn't calculated by using the H-B curve --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 27e508ec34..f3949a1cce 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1364,6 +1364,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) IF (RealField) THEN w_dens = 0.5*SUM(B(1,:)*MATMUL(REAL(Nu), B(1,:))) ELSE + ! This yields twice the time average: w_dens = 0.5*( SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) ) END IF @@ -1416,18 +1417,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END IF Energy(1) = Energy(1) + s*0.5*PR_ip*SUM(E**2) - - IF (ASSOCIATED(HB) .AND. RealField) THEN - Energy(2) = Energy(2) + s*w_dens - ELSE - IF (RealField) THEN - Energy(2) = Energy(2) + s*0.5* SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) - ELSE - ! This yields twice the time average: - Energy(2) = Energy(2) + s*0.5*( SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & - SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) ) - END IF - END IF + Energy(2) = Energy(2) + s*w_dens DO p=1,n DO q=1,n From 1552543ae6aad2de58d3c222deb3d3b590d02cf5 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 28 Oct 2020 10:33:15 +0200 Subject: [PATCH 56/73] The calculation of nodal forces in the case of magnetic anisotropy The calculation of the Maxwell stress tensor doesn't yet support magnetic anisotropy. Otherwise CalcFields should now be able to handle the reluctivity as a tensor. --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 11 ++++++++--- fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index f3949a1cce..9d7edc127f 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1358,7 +1358,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) DO k=1,3 Nu(k,k) = R_ip_Z END DO - ! Ensure that works as before (the complex part has been ignored): + ! + ! The calculation of the Maxwell stress tensor doesn't yet support + ! a tensor-form reluctivity. Create the scalar reluctivity parameter + ! so that the Maxwell stress tensor may be calculated. The complex + ! part will be ignored. + ! R_ip = REAL(R_ip_Z) END IF IF (RealField) THEN @@ -1401,7 +1406,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) DO k=1,n DO l=1,3 val = SUM(dBasisdx(k,1:3)*B(1,1:3)) - NF_ip(k,l) = NF_ip(k,l) - R_ip*B(1,l)*val + & + NF_ip(k,l) = NF_ip(k,l) - SUM(REAL(Nu(l,1:3)) * B(1,1:3)) * val + & (HdotB-w_dens)*dBasisdx(k,l) END DO END DO @@ -1410,7 +1415,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) DO k=1,n DO l=1,3 val = SUM(dBasisdx(k,1:3)*B(2,1:3)) - NF_ip(k,l) = NF_ip(k,l) - R_ip*B(2,l)*val + NF_ip(k,l) = NF_ip(k,l) - SUM(REAL(Nu(l,1:3)) * B(2,1:3)) * val END DO END DO END IF diff --git a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif index a1331bf000..635066451f 100755 --- a/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif +++ b/fem/tests/mgdyn_harmonic/MGDynamicsHarmonic.sif @@ -34,7 +34,7 @@ Material 1 ! the following commands define it as a higher-order tensor to test ! the functionality. Note that the postprocessing subroutine ! MagnetoDynamicsCalcFields cannot yet handle the given magnetic anisotropy - ! correctly in the calculation of nodal forces and the Maxwell stress tensor. + ! correctly in the calculation of the Maxwell stress tensor. ! Reluctivity(3,3) = 1.0 0.0 0.0 \ 0.0 1.0 0.0 \ From 311f84edc4eb82320c3379ae90bfc2c54e266c4a Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Wed, 28 Oct 2020 17:13:29 +0200 Subject: [PATCH 57/73] Fixes for detection of the target solver and variable --- .../modules/MagnetoDynamics/CalcFields.F90 | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 61f6551344..a3093831f2 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -49,23 +49,22 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) LOGICAL :: Transient !------------------------------------------------------------------------------ CHARACTER(LEN=MAX_NAME_LEN) :: sname,pname - LOGICAL :: Found, ElementalFields, RealField, FoundVar + LOGICAL :: Found, ElementalFields, RealField, FoundVar, Hcurl INTEGER, POINTER :: Active(:) - INTEGER :: mysolver,i,j,k,l,n,m,vDOFs, soln,dim + INTEGER :: mysolver,i,j,k,l,n,m,vDOFs, soln,pIndex TYPE(ValueList_t), POINTER :: SolverParams, DGSolverParams TYPE(Solver_t), POINTER :: Solvers(:), PSolver ! This is really using DG so we don't need to make any dirty tricks to create DG fields ! as is done in this initialization. SolverParams => GetSolverParams() - - dim = CoordinateSystemDimension() ! The only purpose of this parsing of the variable name is to identify ! whether the field is real or complex. As the variable has not been ! created at this stage we have to do some dirty parsing. pname = GetString(SolverParams, 'Potential variable', Found) vdofs = 0 + pIndex = 0 FoundVar = .FALSE. IF( Found ) THEN @@ -83,36 +82,47 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) k = k+j IF(k 0 ) THEN - CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be complex valued',Level=12) + Hcurl = .TRUE. + vDofs = 2 + EXIT + END IF + + j = INDEX( sname,'MagnetoDynamics2DHarmonic') + IF( j > 0 ) THEN Vdofs = 2 EXIT END IF j = INDEX( sname,'WhitneyAVSolver') - IF( j == 0 ) j = INDEX( sname,'MagnetoDynamics2D') IF( j > 0 ) THEN - CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be real valued',Level=12) + Hcurl = .TRUE. + vDofs = 1 + EXIT + END IF + + j = INDEX( sname,'MagnetoDynamics2D') + IF( j > 0 ) THEN Vdofs = 1 EXIT END IF @@ -121,12 +131,20 @@ SUBROUTINE MagnetoDynamicsCalcFields_Init0(Model,Solver,dt,Transient) IF( Vdofs == 0 ) THEN CALL Fatal('MagnetoDynamicsCalcFields_Init0','Could not determine target variable type (real or complex)') END IF + pIndex = i END IF - IF ( Vdofs==0 ) Vdofs=1 + RealField = ( Vdofs /= 2 ) + IF( RealField ) THEN + CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be real valued',Level=12) + ELSE + CALL Info('MagnetoDynamicsCalcFields_Init0','The target solver seems to be complex valued',Level=12) + END IF - RealField = ( Vdofs == 1 ) - CALL ListAddLogical( SolverParams, 'Target Variable Real Field', RealField ) + CALL ListAddNewLogical( SolverParams, 'Target Variable Real Field', RealField ) + CALL Info('MagnetoDynamicsCalcFields_Init0','Target Variable Solver Index: '& + //TRIM(I2S(pIndex)),Level=12) + CALL ListAddNewInteger( SolverParams, 'Target Variable Solver Index', pIndex ) !------------------------------------------------------------------------------ END SUBROUTINE MagnetoDynamicsCalcFields_Init0 @@ -572,7 +590,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) TYPE(ValueListEntry_t), POINTER :: HBLst REAL(KIND=dp) :: HarmPowerCoeff REAL(KIND=dp) :: line_tangent(3) - INTEGER :: IOUnit + INTEGER :: IOUnit, pIndex REAL(KIND=dp) :: SaveNorm INTEGER :: NormIndex @@ -595,29 +613,12 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) ELSE HarmPowerCoeff = 0.5_dp END IF - - Pname = GetString(SolverParams, 'Potential Variable',Found) - IF(.NOT. Found ) THEN - IF( dim == 3 ) THEN - Pname = 'av' - ELSE - Pname = 'potential' - END IF - END IF - Found = .FALSE. - DO i=1,Model % NumberOfSolvers - pSolver => Model % Solvers(i) - IF ( Pname == getVarName(pSolver % Variable)) THEN - Found = .TRUE. - EXIT - END IF - END DO + pIndex = ListGetInteger( SolverParams,'Target Variable Solver Index',UnfoundFatal=.TRUE.) + pSolver => Model % Solvers(pIndex) + pname = getVarName(pSolver % Variable) - IF(.NOT. Found ) THEN - CALL Fatal('MagnetoDynamicsCalcFields','Solver associated to potential variable > '& - //TRIM(Pname)//' < not found!') - END IF + CALL Info('MagnetoDynamicsCalcFields','Using potential variable: '//TRIM(Pname),Level=7) ! Inherit the solution basis from the primary solver vDOFs = pSolver % Variable % DOFs From e88f20f4a4adf3f0bcebaefe5355a82db58629f7 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 29 Oct 2020 13:45:40 +0200 Subject: [PATCH 58/73] Unimportant changes --- fem/src/modules/ElectricForce.F90 | 2 +- fem/src/modules/MagnetoDynamics2D.F90 | 2 +- fem/tests/VectorHelmholtzWaveguide/waveguide.sif | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fem/src/modules/ElectricForce.F90 b/fem/src/modules/ElectricForce.F90 index e4aafe339d..325f37aa74 100644 --- a/fem/src/modules/ElectricForce.F90 +++ b/fem/src/modules/ElectricForce.F90 @@ -334,7 +334,7 @@ SUBROUTINE StatElecForce( Model,Solver,dt,TransientSimulation ) DEALLOCATE( LocalPotential, Permittivity ) DEALLOCATE( NodalWeight ) - ! These are some obsolite stuff but let's do this anyways + ! These are some obsolete stuff but let's do this anyways IF( CalculateField ) THEN FieldVar % PrimaryMesh => Mesh CALL InvalidateVariable( Model % Meshes, Mesh, 'Electric Force Density' ) diff --git a/fem/src/modules/MagnetoDynamics2D.F90 b/fem/src/modules/MagnetoDynamics2D.F90 index 6f521791b5..95fb67a084 100644 --- a/fem/src/modules/MagnetoDynamics2D.F90 +++ b/fem/src/modules/MagnetoDynamics2D.F90 @@ -2011,7 +2011,7 @@ SUBROUTINE Bsolver( Model,Solver,dt,Transient ) SAVE Visited - CALL Warn('BSolver','This module is obsolite! USE MagnetoDynamicsCalcFields instead') + CALL Warn('BSolver','This module is obsolete! USE MagnetoDynamicsCalcFields instead') CALL Info( 'BSolver', '-------------------------------------',Level=4 ) diff --git a/fem/tests/VectorHelmholtzWaveguide/waveguide.sif b/fem/tests/VectorHelmholtzWaveguide/waveguide.sif index dfc7c9cc5c..ffc14279be 100755 --- a/fem/tests/VectorHelmholtzWaveguide/waveguide.sif +++ b/fem/tests/VectorHelmholtzWaveguide/waveguide.sif @@ -47,8 +47,6 @@ Material 1 Relative Permittivity = Real 1 !Relative Permittivity im = Real 0 - !Inverse Relative Permeability = Real 1 - !Inverse Relative Permeability im = Variable coordinate 3 !Real MATC "if (tx>0.1) -2.2135; else 0;" !Relative Permittivity im = Variable coordinate 3 !Real MATC "if (tx>0.1) 2.2135/5; else 0;" From a944c1ae5323521d4ce5dcef3cd5345fe8890c6e Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 29 Oct 2020 16:13:18 +0200 Subject: [PATCH 59/73] A tentative option to use rotated DOFs within the shell solver A tentative option to apply a rotation to the so-called rotations so that the DOFs are more intuitive when thinking in terms of moments --- fem/src/modules/ShellSolver.F90 | 42 +++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/fem/src/modules/ShellSolver.F90 b/fem/src/modules/ShellSolver.F90 index ec7dd5e24e..ece98672a5 100755 --- a/fem/src/modules/ShellSolver.F90 +++ b/fem/src/modules/ShellSolver.F90 @@ -205,7 +205,7 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) LOGICAL :: MassAssembly, HarmonicAssembly LOGICAL :: Parallel LOGICAL :: SolidShellCoupling - LOGICAL :: DrillingDOFs + LOGICAL :: DrillingDOFs, RotateDOFs INTEGER, POINTER :: Indices(:) => NULL() INTEGER, POINTER :: VisitsList(:) => NULL() @@ -277,6 +277,7 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) StrainReductionMethod = AutomatedChoice Bubbles = GetLogical(SolverPars, 'Bubbles', Found) + DrillingDOFs = GetLogical(SolverPars, 'Drilling DOFs', Found) IF (DrillingDOFs) CALL Warn('ShellSolver', & 'Drilling DOFs do not support all options and alters the meaning of all rotational DOFs/BCs') @@ -287,6 +288,8 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) DrillingPar = 1.0d0 END IF + RotateDOFs = GetLogical(SolverPars, 'Rotate DOFs', Found) + !----------------------------------------------------------------------------------- ! The field variables for saving the orientation of lines of curvature basis ! vectors at the nodes: Since the global DOFs are expressed with respect to the @@ -493,8 +496,8 @@ SUBROUTINE ShellSolver(Model, Solver, dt, TransientSimulation) ! ----------------------------------------------------------------------------- CALL ShellLocalMatrix(BGElement, n, nd+nb, ShellModelPar, LocalSol, & LargeDeflection, StrainReductionMethod, MembraneStrainReductionMethod, & - ApplyBubbles, DrillingDOFs, DrillingPar, MassAssembly, HarmonicAssembly, LocalRHSForce, & - ShellModelArea, TotalErr, BenchmarkProblem=SolveBenchmarkCase) + ApplyBubbles, DrillingDOFs, DrillingPar, RotateDOFs, MassAssembly, HarmonicAssembly, & + LocalRHSForce, ShellModelArea, TotalErr, BenchmarkProblem=SolveBenchmarkCase) IF (LargeDeflection .AND. NonlinIter == 1) THEN ! --------------------------------------------------------------------------- @@ -3512,8 +3515,8 @@ END SUBROUTINE SurfaceBasisVectors !------------------------------------------------------------------------------ SUBROUTINE ShellLocalMatrix(BGElement, n, nd, m, LocalSol, LargeDeflection, & StrainReductionMethod, MembraneStrainReductionMethod, Bubbles, & - DrillingDOFs, DrillingPar, MassAssembly, HarmonicAssembly, RHSForce, Area, & - Error, BenchmarkProblem) + DrillingDOFs, DrillingPar, RotateDOFs, MassAssembly, HarmonicAssembly, & + RHSForce, Area, Error, BenchmarkProblem) !------------------------------------------------------------------------------ USE SolidMechanicsUtils, ONLY: StrainEnergyDensity, ShearCorrectionFactor IMPLICIT NONE @@ -3529,6 +3532,7 @@ SUBROUTINE ShellLocalMatrix(BGElement, n, nd, m, LocalSol, LargeDeflection, & LOGICAL, INTENT(IN) :: Bubbles ! To indicate that bubble functions are used LOGICAL, INTENT(IN) :: DrillingDOFs ! Switches to drilling DOFs (limited functionality) REAL(KIND=dp), INTENT(IN) :: DrillingPar ! A stabilization parameter for drilling DOFs + LOGICAL, INTENT(IN) :: RotateDOFs ! Use rotated DOFs (a tentative option) LOGICAL, INTENT(IN) :: MassAssembly ! To activate mass matrix integration LOGICAL, INTENT(IN) :: HarmonicAssembly ! To activate the global mass matrix updates REAL(KIND=dp), INTENT(OUT) :: RHSForce(:) ! Local RHS vector corresponding to external loads @@ -3570,7 +3574,7 @@ SUBROUTINE ShellLocalMatrix(BGElement, n, nd, m, LocalSol, LargeDeflection, & REAL(KIND=dp) :: StrainVec(6), StressVec(6) REAL(KIND=dp) :: PrevSolVec(m*nd), PrevField(7) - REAL(KIND=dp) :: QBlock(3,3), Q(m*nd,m*nd), TMat(m*nd,m*nd) + REAL(KIND=dp) :: QBlock(3,3), Q(m*nd,m*nd), TMat(m*nd,m*nd), RotMat(3,3) REAL(KIND=dp) :: CMat(4,4), GMat(2,2) REAL(KIND=dp) :: A11, A22, SqrtDetA, A1, A2, B11, B22 REAL(KIND=dp) :: C111, C112, C221, C222, C211, C212 @@ -3777,7 +3781,31 @@ SUBROUTINE ShellLocalMatrix(BGElement, n, nd, m, LocalSol, LargeDeflection, & i0 = (j-1)*m Q(i0+1:i0+3,i0+1:i0+3) = QBlock(1:3,1:3) - Q(i0+4:i0+6,i0+4:i0+6) = QBlock(1:3,1:3) + ! + ! Optionally we can switch to rotated components theta such that + ! -Du[d] = d x theta + d. The tangent plane components are + ! then more intuitive when thinking in terms of moments. + ! + IF (RotateDOFs) THEN + ! + ! Create a matrix RotMat such that d x v = RotMat * v + ! + RotMat = 0.0d0 + RotMat(2,1) = abasis3(3) + RotMat(3,1) = -abasis3(2) + RotMat(1,2) = -abasis3(3) + RotMat(3,2) = abasis3(1) + RotMat(1,3) = abasis3(2) + RotMat(2,3) = -abasis3(1) + + DO k=1,3 + Q(i0+4,i0+3+k) = DOT_PRODUCT(RotMat(:,k), abasis1(:)) + Q(i0+5,i0+3+k) = DOT_PRODUCT(RotMat(:,k), abasis2(:)) + Q(i0+6,i0+3+k) = abasis3(k) + END DO + ELSE + Q(i0+4:i0+6,i0+4:i0+6) = QBlock(1:3,1:3) + END IF IF (DrillingDOFs) THEN ! From 5a2fe7f12c7dfccb9d0ed15766070f39bf0bb1b6 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 30 Oct 2020 01:11:38 +0200 Subject: [PATCH 60/73] Implement integer inline parameters --- fem/src/ElmerSolver.F90 | 23 ++++++++++++++++++++--- fem/src/ModelDescription.F90 | 32 +++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/fem/src/ElmerSolver.F90 b/fem/src/ElmerSolver.F90 index fe4700ab3a..2a0555f3c5 100644 --- a/fem/src/ElmerSolver.F90 +++ b/fem/src/ElmerSolver.F90 @@ -118,8 +118,9 @@ SUBROUTINE ElmerSolver(initialize) TYPE(Model_t), POINTER, SAVE :: Control CHARACTER(LEN=MAX_NAME_LEN) :: MeshDir, MeshName LOGICAL :: DoControl, GotParams - INTEGER :: nr + INTEGER :: nr,ni REAL(KIND=dp), ALLOCATABLE :: rpar(:) + INTEGER, ALLOCATABLE :: ipar(:) #ifdef HAVE_TRILINOS INTERFACE @@ -169,9 +170,25 @@ END SUBROUTINE TrilinosCleanup CALL GET_COMMAND_ARGUMENT(i, OptionString) READ( OptionString,*) rpar(j) END DO - CALL Info('MAIN','Read '//TRIM(I2S(nr))//' parameters from command line!') - CALL SetParametersMATC(nr,rpar) + CALL Info('MAIN','Read '//TRIM(I2S(nr))//' real parameters from command line!') + CALL SetRealParametersMATC(nr,rpar) END IF + + IF( OptionString=='-ipar' ) THEN + ! Followed by number of paramters + the parameter values + i = i + 1 + CALL GET_COMMAND_ARGUMENT(i, OptionString) + READ( OptionString,*) ni + ALLOCATE( ipar(nr) ) + DO j=1,ni + i = i + 1 + CALL GET_COMMAND_ARGUMENT(i, OptionString) + READ( OptionString,*) ipar(j) + END DO + CALL Info('MAIN','Read '//TRIM(I2S(ni))//' integer parameters from command line!') + CALL SetIntegerParametersMATC(ni,ipar) + END IF + Silent = Silent .OR. & ( OptionString=='-s' .OR. OptionString=='--silent' ) Version = Version .OR. & diff --git a/fem/src/ModelDescription.F90 b/fem/src/ModelDescription.F90 index b1140751bc..8bda3457a9 100644 --- a/fem/src/ModelDescription.F90 +++ b/fem/src/ModelDescription.F90 @@ -5699,7 +5699,7 @@ END SUBROUTINE FreeModel !> This routine makes it possible to refer to the parameters !> in the .sif file by rpar(0), rpar(1),... !----------------------------------------------------------------------------- - SUBROUTINE SetParametersMATC(NoParam,Param) + SUBROUTINE SetRealParametersMATC(NoParam,Param) INTEGER :: NoParam REAL(KIND=dp), ALLOCATABLE :: Param(:) @@ -5719,8 +5719,34 @@ SUBROUTINE SetParametersMATC(NoParam,Param) !$OMP END PARALLEL END DO - END SUBROUTINE SetParametersMATC + END SUBROUTINE SetRealParametersMATC + !------------------------------------------------------------------------------ + !> This routine makes it possible to refer to the parameters + !> in the .sif file by rpar(0), rpar(1),... + !----------------------------------------------------------------------------- + SUBROUTINE SetIntegerParametersMATC(NoParam,Param) + + INTEGER :: NoParam + INTEGER, ALLOCATABLE :: Param(:) + + INTEGER :: i,j,tj + CHARACTER(LEN=MAX_STRING_LEN) :: cmd, tmp_str, tcmd, ttmp_str + + DO i=1,NoParam + WRITE( cmd, * ) 'ipar('//TRIM(i2s(i-1))//')=', Param(i) + j = LEN_TRIM(cmd) + !$OMP PARALLEL DEFAULT(NONE) & + !$OMP SHARED(cmd, tmp_str, j ) & + !$OMP PRIVATE(tcmd, ttmp_str, tj) + tj = j + tcmd = cmd + CALL matc( tcmd, ttmp_str, tj ) + !$OMP END PARALLEL + END DO + + END SUBROUTINE SetIntegerParametersMATC + !------------------------------------------------------------------------------ !> Adds parameters used in the simulation either predefined or from run control. @@ -5804,7 +5830,7 @@ SUBROUTINE ControlParameters(Params,piter,GotParams,FinishEarly,PostSimulation) END IF ! Set parametes to be accessible to the MATC preprocessor when reading sif file. - CALL SetParametersMATC(NoParam,Param) + CALL SetRealParametersMATC(NoParam,Param) CALL Info(Caller, '-----------------------------------------', Level=5 ) From 3b524958236242ecd009f9bbac85c52be33f9d2b Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 30 Oct 2020 01:13:24 +0200 Subject: [PATCH 61/73] Complain if Gebhardt factors needed but not present --- fem/src/Radiation.F90 | 12 +++++++++--- fem/src/modules/HeatSolveVec.F90 | 7 ++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/fem/src/Radiation.F90 b/fem/src/Radiation.F90 index 749045aff3..8317db3a81 100644 --- a/fem/src/Radiation.F90 +++ b/fem/src/Radiation.F90 @@ -65,13 +65,19 @@ FUNCTION ComputeRadiationLoad( Model, Mesh, Element, Temperature, & REAL(KIND=dp) :: Asum TYPE(Element_t),POINTER :: RadElement - INTEGER :: i,j,n, bindex + INTEGER :: i,j,n, bindex,nf REAL(KIND=dp), POINTER :: Vals(:) INTEGER, POINTER :: Cols(:) REAL(KIND=dp) :: A1,A2,Emissivity1 LOGICAL :: Found !------------------------------------------------------------------------------ + IF( .NOT. ASSOCIATED( Element % BoundaryInfo % GebhardtFactors ) ) THEN + CALL Fatal('ComputeRadiationLoad','Gebhardt factors not calculated for boundary!') + END IF + + nf = Element % BoundaryInfo % GebhardtFactors % NumberOfFactors + IF(PRESENT(Areas) .AND. PRESENT(Emiss) ) THEN bindex = Element % ElementIndex - Mesh % NumberOfBulkElements @@ -82,7 +88,7 @@ FUNCTION ComputeRadiationLoad( Model, Mesh, Element, Temperature, & T = 0._dp Asum = 0._dp - DO i=1,Element % BoundaryInfo % GebhardtFactors % NumberOfFactors + DO i=1,nf RadElement => Mesh % Elements(Cols(i)) n = RadElement % TYPE % NumberOfNodes @@ -100,7 +106,7 @@ FUNCTION ComputeRadiationLoad( Model, Mesh, Element, Temperature, & T = 0.0_dp Asum = 0.0_dp - DO i=1,Element % BoundaryInfo % GebhardtFactors % NumberOfFactors + DO i=1,nf RadElement => Mesh % Elements(Cols(i)) n = RadElement % TYPE % NumberOfNodes diff --git a/fem/src/modules/HeatSolveVec.F90 b/fem/src/modules/HeatSolveVec.F90 index 5af40302e0..b6b79d536a 100644 --- a/fem/src/modules/HeatSolveVec.F90 +++ b/fem/src/modules/HeatSolveVec.F90 @@ -1001,12 +1001,17 @@ SUBROUTINE LocalMatrixDiffuseGray( Element, n, nd, nb ) n = GetElementNOFNodes(Element) CALL GetElementNodes( Nodes, UElement=Element) n = Element % TYPE % NumberOfNodes - + + IF( .NOT. ASSOCIATED( Element % BoundaryInfo % GebhardtFactors ) ) THEN + CALL Fatal('HeatSolverVec','Gebhardt factors not calculated for boundary!') + END IF + Fact => Element % BoundaryInfo % GebhardtFactors % Factors ElementList => Element % BoundaryInfo % GebhardtFactors % Elements bindex = Element % ElementIndex - Solver % Mesh % NumberOfBulkElements nf = Element % BoundaryInfo % GebhardtFactors % NumberOfFactors + nf_imp = Element % BoundaryInfo % GebhardtFactors % NumberOfImplicitFactors IF( nf_imp == 0 ) nf_imp = nf From ce82c00a88f60c3071ea1dac6917c03dba48051a Mon Sep 17 00:00:00 2001 From: Juha Ruokolainen Date: Fri, 30 Oct 2020 12:26:48 +0200 Subject: [PATCH 62/73] Fix for parallel p-element natural internal DOFs when included in the global linear system. --- fem/src/ParallelUtils.F90 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fem/src/ParallelUtils.F90 b/fem/src/ParallelUtils.F90 index 5ffb5460a8..577bacd9c3 100644 --- a/fem/src/ParallelUtils.F90 +++ b/fem/src/ParallelUtils.F90 @@ -272,8 +272,10 @@ SUBROUTINE ParallelInitMatrix( Solver, Matrix, inPerm ) DO i=1,Mesh % NumberOfBulkElements Element=>Mesh % Elements(i) - bdofs = Solver % Def_Dofs(Element % Type % ElementCode/100, & - Element % Bodyid, 5) + j = Element % Type % ElementCode/100 + bdofs = Solver % Def_Dofs(j, Element % Bodyid, 5) + IF ( bdofs<=0 .AND. & + Solver % Def_Dofs(j,Element % Bodyid,6)>1) bdofs = Element % BDOFs DO l=1,bdofs DO j=1,DOFs @@ -631,6 +633,7 @@ SUBROUTINE ParallelInitMatrix( Solver, Matrix, inPerm ) END DO END IF END DO + DO i=1,n MtrxN => MatrixPI % NeighbourList(i) IF ( .NOT.ASSOCIATED( MtrxN % Neighbours) ) THEN From 66fb50ab073f4a401ebf447992840979650a6647 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 30 Oct 2020 15:10:52 +0200 Subject: [PATCH 63/73] Add missing definition for FALSE --- ElmerGUI/Application/vtkpost/matc.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ElmerGUI/Application/vtkpost/matc.cpp b/ElmerGUI/Application/vtkpost/matc.cpp index dbbe6ad061..fd6b367399 100755 --- a/ElmerGUI/Application/vtkpost/matc.cpp +++ b/ElmerGUI/Application/vtkpost/matc.cpp @@ -50,6 +50,12 @@ #include #include +#ifndef FALSE +#define FALSE 0 +#endif + + + using namespace std; Matc::Matc(QWidget *parent) From c24ebad8a18300557bb10246ac5be2d919d801b9 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Fri, 30 Oct 2020 15:43:05 +0200 Subject: [PATCH 64/73] Fix the sign of an electric conductivity term in the vectorial Helmholtz model --- fem/src/modules/VectorHelmholtz.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fem/src/modules/VectorHelmholtz.F90 b/fem/src/modules/VectorHelmholtz.F90 index c56f6d76b9..684f6a2c4a 100755 --- a/fem/src/modules/VectorHelmholtz.F90 +++ b/fem/src/modules/VectorHelmholtz.F90 @@ -542,7 +542,7 @@ SUBROUTINE LocalMatrix( Element, n, nd, InitHandles ) DO i = 1,nd DO j = 1,nd ! the term i\omega\sigma E.v - STIFF(i,j) = STIFF(i,j) + im * Omega * Cond * & + STIFF(i,j) = STIFF(i,j) - im * Omega * Cond * & SUM(WBasis(j,:) * WBasis(i,:)) * weight END DO END DO From e831780ccb67fe502e4282530d8f8e774f1a0d08 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 2 Nov 2020 10:58:06 +0200 Subject: [PATCH 65/73] Remove an accidentally (?) added file --- fem/src/ElementDescription.F90.NEW | 12573 --------------------------- 1 file changed, 12573 deletions(-) delete mode 100644 fem/src/ElementDescription.F90.NEW diff --git a/fem/src/ElementDescription.F90.NEW b/fem/src/ElementDescription.F90.NEW deleted file mode 100644 index c23c76f211..0000000000 --- a/fem/src/ElementDescription.F90.NEW +++ /dev/null @@ -1,12573 +0,0 @@ -!/*****************************************************************************/ -! * -! * Elmer, A Finite Element Software for Multiphysical Problems -! * -! * Copyright 1st April 1995 - , CSC - IT Center for Science Ltd., Finland -! * -! * This library is free software; you can redistribute it and/or -! * modify it under the terms of the GNU Lesser General Public -! * License as published by the Free Software Foundation; either -! * version 2.1 of the License, or (at your option) any later version. -! * -! * This library is distributed in the hope that it will be useful, -! * but WITHOUT ANY WARRANTY; without even the implied warranty of -! * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -! * Lesser General Public License for more details. -! * -! * You should have received a copy of the GNU Lesser General Public -! * License along with this library (in file ../LGPL-2.1); if not, write -! * to the Free Software Foundation, Inc., 51 Franklin Street, -! * Fifth Floor, Boston, MA 02110-1301 USA -! * -! *****************************************************************************/ -! -!/****************************************************************************** -! * -! * Authors: Juha Ruokolainen -! * Email: Juha.Ruokolainen@csc.fi -! * Web: http://www.csc.fi/elmer -! * Address: CSC - IT Center for Science Ltd. -! * Keilaranta 14 -! * 02101 Espoo, Finland -! * -! * Original Date: 01 Oct 1996 -! * -! ******************************************************************************/ - -!-------------------------------------------------------------------------------- -!> Module defining element type and operations. The most basic FEM routines -!> are here, handling the basis functions, global derivatives, etc... -!-------------------------------------------------------------------------------- -!> \ingroup ElmerLib -!> \{ - -#include "../config.h" - -MODULE ElementDescription - USE Integration - USE GeneralUtils - USE LinearAlgebra - USE CoordinateSystems - ! Use module P element basis functions - USE PElementMaps - USE PElementBase - ! Vectorized P element basis functions - USE H1Basis - USE Lists - - IMPLICIT NONE - - INTEGER, PARAMETER,PRIVATE :: MaxDeg = 4, MaxDeg3 = MaxDeg**3, & - MaxDeg2 = MaxDeg**2 - - INTEGER, PARAMETER :: MAX_ELEMENT_NODES = 256 - - ! - ! Module global variables - ! - LOGICAL, PRIVATE :: TypeListInitialized = .FALSE. - TYPE(ElementType_t), PRIVATE, POINTER :: ElementTypeList - ! Local workspace for basis function values and mapping -! REAL(KIND=dp), ALLOCATABLE, PRIVATE :: BasisWrk(:,:), dBasisdxWrk(:,:,:), & -! LtoGMapsWrk(:,:,:), DetJWrk(:), uWrk(:), vWrk(:), wWrk(:) -! !$OMP THREADPRIVATE(BasisWrk, dBasisdxWrk, LtoGMapsWrk, DetJWrk, uWrk, vWrk, wWrk) -! !DIR$ ATTRIBUTES ALIGN:64::BasisWrk, dBasisdxWrk -! !DIR$ ATTRIBUTES ALIGN:64::LtoGMapsWrk -! !DIR$ ATTRIBUTES ALIGN:64::DetJWrk -! !DIR$ ATTRIBUTES ALIGN:64::uWrk, vWrk, wWrk - -CONTAINS - - -!------------------------------------------------------------------------------ -!> Add an element description to global list of element types. -!------------------------------------------------------------------------------ - SUBROUTINE AddElementDescription( element,BasisTerms ) -!------------------------------------------------------------------------------ - INTEGER, DIMENSION(:) :: BasisTerms !< List of terms in the basis function that should be included for this element type. - ! BasisTerms(i) is an integer from 1-27 according to the list below. - TYPE(ElementType_t), TARGET :: element !< Structure holding element type description -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(ElementType_t), POINTER :: temp - - INTEGER, DIMENSION(MaxDeg3) :: s - INTEGER :: i,j,k,l,m,n,upow,vpow,wpow,i1,i2,ii(9),jj - - REAL(KIND=dp) :: u,v,w,r - REAL(KIND=dp), DIMENSION(:,:), ALLOCATABLE :: A, B -!------------------------------------------------------------------------------ - -! PRINT*,'Adding element type: ', element % ElementCode - - n = element % NumberOfNodes - element % NumberOfEdges = 0 - element % NumberOfFaces = 0 - element % BasisFunctionDegree = 0 - NULLIFY( element % BasisFunctions ) - - IF ( element % ElementCode >= 200 ) THEN - - ALLOCATE( A(n,n) ) - -!------------------------------------------------------------------------------ -! 1D bar elements -!------------------------------------------------------------------------------ - IF ( element % DIMENSION == 1 ) THEN - - DO i = 1,n - u = element % NodeU(i) - DO j = 1,n - k = BasisTerms(j) - 1 - upow = k - IF ( u==0 .AND. upow == 0 ) THEN - A(i,j) = 1 - ELSE - A(i,j) = u**upow - END IF - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,upow) - END DO - END DO - -! ALLOCATE( element % BasisFunctions(MaxDeg,MaxDeg) ) - -!------------------------------------------------------------------------------ -! 2D surface elements -!------------------------------------------------------------------------------ - ELSE IF ( element % DIMENSION == 2 ) THEN - - DO i = 1,n - u = element % NodeU(i) - v = element % NodeV(i) - DO j = 1,n - k = BasisTerms(j) - 1 - vpow = k / MaxDeg - upow = MOD(k,MaxDeg) - - IF ( upow == 0 ) THEN - A(i,j) = 1 - ELSE - A(i,j) = u**upow - END IF - - IF ( vpow /= 0 ) THEN - A(i,j) = A(i,j) * v**vpow - END IF - - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,upow) - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,vpow) - END DO - END DO - -! ALLOCATE( element % BasisFunctions(MaxDeg2,MaxDeg2) ) - -!------------------------------------------------------------------------------ -! 3D volume elements -!------------------------------------------------------------------------------ - ELSE - - DO i = 1,n - u = element % NodeU(i) - v = element % NodeV(i) - w = element % NodeW(i) - DO j = 1,n - k = BasisTerms(j) - 1 - upow = MOD( k,MaxDeg ) - wpow = k / MaxDeg2 - vpow = MOD( k / MaxDeg, MaxDeg ) - - IF ( upow == 0 ) THEN - A(i,j) = 1 - ELSE - A(i,j) = u**upow - END IF - - IF ( vpow /= 0 ) THEN - A(i,j) = A(i,j) * v**vpow - END IF - - IF ( wpow /= 0 ) THEN - A(i,j) = A(i,j) * w**wpow - END IF - - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,upow) - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,vpow) - element % BasisFunctionDegree = MAX(element % BasisFunctionDegree,wpow) - END DO - END DO - -! ALLOCATE( element % BasisFunctions(MaxDeg3,MaxDeg3) ) - END IF - -!------------------------------------------------------------------------------ -! Compute the coefficients of the basis function terms -!------------------------------------------------------------------------------ - CALL InvertMatrix( A,n ) - - IF ( Element % ElementCode == 202 ) THEN - ALLOCATE( Element % BasisFunctions(14) ) - ELSE - ALLOCATE( Element % BasisFunctions(n) ) - END IF - - upow = 0 - vpow = 0 - wpow = 0 - - DO i = 1,n - Element % BasisFunctions(i) % n = n - ALLOCATE( Element % BasisFunctions(i) % p(n) ) - ALLOCATE( Element % BasisFunctions(i) % q(n) ) - ALLOCATE( Element % BasisFunctions(i) % r(n) ) - ALLOCATE( Element % BasisFunctions(i) % Coeff(n) ) - - DO j = 1,n - k = BasisTerms(j) - 1 - - SELECT CASE( Element % DIMENSION ) - CASE(1) - upow = k - CASE(2) - vpow = k / MaxDeg - upow = MOD(k,MaxDeg) - CASE(3) - upow = MOD( k,MaxDeg ) - wpow = k / MaxDeg2 - vpow = MOD( k / MaxDeg, MaxDeg ) - END SELECT - - Element % BasisFunctions(i) % p(j) = upow - Element % BasisFunctions(i) % q(j) = vpow - Element % BasisFunctions(i) % r(j) = wpow - Element % BasisFunctions(i) % Coeff(j) = A(j,i) - END DO - END DO - - DEALLOCATE( A ) - - IF ( Element % ElementCode == 202 ) THEN - ALLOCATE( A(14,14) ) - A = 0 - CALL Compute1DPBasis( A,14 ) - - DO i=3,14 - ALLOCATE( Element % BasisFunctions(i) % p(i) ) - ALLOCATE( Element % BasisFunctions(i) % q(i) ) - ALLOCATE( Element % BasisFunctions(i) % r(i) ) - ALLOCATE( Element % BasisFunctions(i) % Coeff(i) ) - - k = 0 - DO j=1,i - IF ( A(i,j) /= 0.0d0 ) THEN - k = k + 1 - Element % BasisFunctions(i) % p(k) = j-1 - Element % BasisFunctions(i) % q(k) = 0 - Element % BasisFunctions(i) % r(k) = 0 - Element % BasisFunctions(i) % Coeff(k) = A(i,j) - END IF - END DO - Element % BasisFunctions(i) % n = k - END DO - DEALLOCATE( A ) - END IF - -!------------------------------------------------------------------------------ - - SELECT CASE( Element % ElementCode / 100 ) - CASE(3) - Element % NumberOfEdges = 3 - CASE(4) - Element % NumberOfEdges = 4 - CASE(5) - Element % NumberOfFaces = 4 - Element % NumberOfEdges = 6 - CASE(6) - Element % NumberOfFaces = 5 - Element % NumberOfEdges = 8 - CASE(7) - Element % NumberOfFaces = 5 - Element % NumberOfEdges = 9 - CASE(8) - Element % NumberOfFaces = 6 - Element % NumberOfEdges = 12 - END SELECT - - END IF ! type >= 200 - -!------------------------------------------------------------------------------ -! And finally add the element description to the global list of types -!------------------------------------------------------------------------------ - IF ( .NOT.TypeListInitialized ) THEN - ALLOCATE( ElementTypeList ) - ElementTypeList = element - TypeListInitialized = .TRUE. - NULLIFY( ElementTypeList % NextElementType ) - ELSE - ALLOCATE( temp ) - temp = element - temp % NextElementType => ElementTypeList - ElementTypeList => temp - END IF - -!------------------------------------------------------------------------------ - -CONTAINS - - -!------------------------------------------------------------------------------ -!> Subroutine to compute 1D P-basis from Legendre polynomials. -!------------------------------------------------------------------------------ - SUBROUTINE Compute1DPBasis( Basis,n ) -!------------------------------------------------------------------------------ - INTEGER :: n - REAL(KIND=dp) :: Basis(:,:) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s,P(n+1),Q(n),P0(n),P1(n+1) - INTEGER :: i,j,k,np,info - -!------------------------------------------------------------------------------ - - IF ( n <= 1 ) THEN - Basis(1,1) = 1.0d0 - RETURN - END IF -!------------------------------------------------------------------------------ -! Compute coefficients of n:th Legendre polynomial from the recurrence: -! -! (i+1)P_{i+1}(x) = (2i+1)*x*P_i(x) - i*P_{i-1}(x), P_{0} = 1; P_{1} = x; -! -! CAVEAT: Computed coefficients inaccurate for n > ~15 -!------------------------------------------------------------------------------ - P = 0 - P0 = 0 - P1 = 0 - P0(1) = 1 - P1(1) = 1 - P1(2) = 0 - - Basis(1,1) = 0.5d0 - Basis(1,2) = -0.5d0 - - Basis(2,1) = 0.5d0 - Basis(2,2) = 0.5d0 - - DO k=2,n - IF ( k > 2 ) THEN - s = SQRT( (2.0d0*(k-1)-1) / 2.0d0 ) - DO j=1,k-1 - Basis(k,k-j+1) = s * P0(j) / (k-j) - Basis(k,1) = Basis(k,1) - s * P0(j)*(-1)**(j+1) / (k-j) - END DO - END IF - - i = k - 1 - P(1:i+1) = (2*i+1) * P1(1:i+1) / (i+1) - P(3:i+2) = P(3:i+2) - i*P0(1:i) / (i+1) - P0(1:i+1) = P1(1:i+1) - P1(1:i+2) = P(1:i+2) - END DO -!-------------------------------------------------------------------------- - END SUBROUTINE Compute1DPBasis -!-------------------------------------------------------------------------- - - END SUBROUTINE AddElementDescription -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Read the element description input file and add the element types to a -!> global list. The file is assumed to be found under the name -!> $ELMER_HOME/lib/elements.def -!> This is the first routine the user of the element utilities should call -!> in his/her code. -!------------------------------------------------------------------------------ - SUBROUTINE InitializeElementDescriptions() -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - CHARACTER(LEN=:), ALLOCATABLE :: str - CHARACTER(LEN=MAX_STRING_LEN) :: tstr,elmer_home - - INTEGER :: k, n - INTEGER, DIMENSION(MaxDeg3) :: BasisTerms - - TYPE(ElementType_t) :: element - - LOGICAL :: gotit, fexist -!------------------------------------------------------------------------------ -! PRINT*,' ' -! PRINT*,'----------------------------------------------' -! PRINT*,'Reading element definition file: elements.def' -! PRINT*,'----------------------------------------------' - - - ! - ! Add connectivity element types: - ! ------------------------------- - BasisTerms = 0 - element % GaussPoints = 0 - element % GaussPoints0 = 0 - element % GaussPoints2 = 0 - element % StabilizationMK = 0 - NULLIFY( element % NodeU ) - NULLIFY( element % NodeV ) - NULLIFY( element % NodeW ) - DO k=3,64 - element % NumberOfNodes = k - element % ElementCode = 100 + k - CALL AddElementDescription( element,BasisTerms ) - END DO - - ! then the rest of them.... - !-------------------------- -#ifdef USE_ISO_C_BINDINGS - tstr = 'ELMER_LIB' -#else - tstr = 'ELMER_LIB'//CHAR(0) -#endif - CALL envir( tstr,elmer_home,k ) - - fexist = .FALSE. - IF ( k > 0 ) THEN - WRITE( tstr, '(a,a)' ) elmer_home(1:k),'/elements.def' - INQUIRE(FILE=TRIM(tstr), EXIST=fexist) - END IF - IF (.NOT. fexist) THEN -#ifdef USE_ISO_C_BINDINGS - tstr = 'ELMER_HOME' -#else - tstr = 'ELMER_HOME'//CHAR(0) -#endif - CALL envir( tstr,elmer_home,k ) - IF ( k > 0 ) THEN - WRITE( tstr, '(a,a)' ) elmer_home(1:k),& -'/share/elmersolver/lib/elements.def' - INQUIRE(FILE=TRIM(tstr), EXIST=fexist) - END IF - IF ((.NOT. fexist) .AND. k > 0) THEN - WRITE( tstr, '(a,a)' ) elmer_home(1:k),& - '/elements.def' - INQUIRE(FILE=TRIM(tstr), EXIST=fexist) - END IF - END IF - IF (.NOT. fexist) THEN - CALL GetSolverHome(elmer_home, n) - WRITE(tstr, '(a,a)') elmer_home(1:n), & - '/lib/elements.def' - INQUIRE(FILE=TRIM(tstr), EXIST=fexist) - END IF - IF (.NOT. fexist) THEN - CALL Fatal('InitializeElementDescriptions', & - 'elements.def not found') - END IF - - OPEN( 1,FILE=TRIM(tstr), STATUS='OLD' ) - - ALLOCATE(CHARACTER(MAX_STRING_LEN)::str) - DO WHILE( ReadAndTrim(1,str) ) - - IF ( SEQL(str, 'element') ) THEN - - BasisTerms = 0 - - NULLIFY( element % NodeU ) - NULLIFY( element % NodeV ) - NULLIFY( element % NodeW ) - - gotit = .FALSE. - DO WHILE( ReadAndTrim(1,str) ) - - IF ( SEQL(str, 'dimension') ) THEN - READ( str(10:), * ) element % DIMENSION - - ELSE IF ( SEQL(str, 'code') ) THEN - READ( str(5:), * ) element % ElementCode - - ELSE IF ( SEQL(str, 'nodes') ) THEN - READ( str(6:), * ) element % NumberOfNodes - - ELSE IF ( SEQL(str, 'node u') ) THEN - ALLOCATE( element % NodeU(element % NumberOfNodes) ) - READ( str(7:), * ) (element % NodeU(k),k=1,element % NumberOfNodes) - - ELSE IF ( SEQL(str, 'node v') ) THEN - ALLOCATE( element % NodeV(element % NumberOfNodes) ) - READ( str(7:), * ) (element % NodeV(k),k=1,element % NumberOfNodes) - - ELSE IF ( SEQL(str, 'node w') ) THEN - ALLOCATE( element % NodeW(element % NumberOfNodes ) ) - READ( str(7:), * ) (element % NodeW(k),k=1,element % NumberOfNodes) - - ELSE IF ( SEQL(str, 'basis') ) THEN - READ( str(6:), * ) (BasisTerms(k),k=1,element % NumberOfNodes) - - ELSE IF ( SEQL(str, 'stabilization') ) THEN - READ( str(14:), * ) element % StabilizationMK - - ELSE IF ( SEQL(str, 'gauss points') ) THEN - - Element % GaussPoints2 = 0 - READ( str(13:), *,END=10 ) element % GaussPoints,& - element % GaussPoints2, element % GaussPoints0 - -10 CONTINUE - - IF ( Element % GaussPoints2 <= 0 ) & - Element % GaussPoints2 = Element % GaussPoints - - IF ( Element % GaussPoints0 <= 0 ) & - Element % GaussPoints0 = Element % GaussPoints - - ELSE IF ( str == 'end element' ) THEN - gotit = .TRUE. - EXIT - END IF - END DO - - IF ( gotit ) THEN - Element % StabilizationMK = 0.0d0 - IF ( .NOT.ASSOCIATED( element % NodeV ) ) THEN - ALLOCATE( element % NodeV(element % NumberOfNodes) ) - element % NodeV = 0.0d0 - END IF - - IF ( .NOT.ASSOCIATED( element % NodeW ) ) THEN - ALLOCATE( element % NodeW(element % NumberOfNodes) ) - element % NodeW = 0.0d0 - END IF - - CALL AddElementDescription( element,BasisTerms ) - ELSE - IF ( ASSOCIATED( element % NodeU ) ) DEALLOCATE( element % NodeU ) - IF ( ASSOCIATED( element % NodeV ) ) DEALLOCATE( element % NodeV ) - IF ( ASSOCIATED( element % NodeW ) ) DEALLOCATE( element % NodeW ) - END IF - END IF - END DO - - CLOSE(1) -!------------------------------------------------------------------------------ - END SUBROUTINE InitializeElementDescriptions -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element type code return pointer to the corresponding element type -!> structure. -!------------------------------------------------------------------------------ - FUNCTION GetElementType( code,CompStabFlag ) RESULT(element) -!------------------------------------------------------------------------------ - INTEGER :: code - LOGICAL, OPTIONAL :: CompStabFlag - TYPE(ElementType_t), POINTER :: element -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(Nodes_t) :: Nodes - INTEGER :: sdim - TYPE(Element_t), POINTER :: Elm -!------------------------------------------------------------------------------ - element => ElementTypeList - - DO WHILE( ASSOCIATED(element) ) - IF ( code == element % ElementCode ) EXIT - element => element % NextElementType - END DO - - IF ( .NOT. ASSOCIATED( element ) ) THEN - WRITE( message, * ) & - 'Element type code ',code,' not found. Ignoring element.' - CALL Warn( 'GetElementType', message ) - RETURN - END IF - - IF ( PRESENT( CompStabFlag ) ) THEN - IF ( .NOT. CompStabFlag ) RETURN - END IF - - IF ( Element % StabilizationMK == 0.0d0 ) THEN - ALLOCATE( Elm ) - Elm % TYPE => element - Elm % BDOFs = 0 - Elm % DGDOFs = 0 - NULLIFY( Elm % PDefs ) - NULLIFY( Elm % DGIndexes ) - NULLIFY( Elm % EdgeIndexes ) - NULLIFY( Elm % FaceIndexes ) - NULLIFY( Elm % BubbleIndexes ) - Nodes % x => Element % NodeU - Nodes % y => Element % NodeV - Nodes % z => Element % NodeW - - sdim = CurrentModel % Dimension - CurrentModel % Dimension = Element % Dimension - CALL StabParam( Elm, Nodes, Element % NumberOfNodes, & - Element % StabilizationMK ) - CurrentModel % Dimension = sdim - - DEALLOCATE(Elm) - END IF - - END FUNCTION GetElementType -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Compute convection diffusion equation stab. parameter for each and every -!> element of the model by solving the largest eigenvalue of -! -!> Lu = \lambda Gu, -! -!> L = (\nablda^2 u,\nabla^ w), G = (\nabla u,\nabla w) -!------------------------------------------------------------------------------ - SUBROUTINE StabParam(Element,Nodes,n,mK,hK,UseLongEdge) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), POINTER :: Element - INTEGER :: n - TYPE(Nodes_t) :: Nodes - REAL(KIND=dp) :: mK - REAL(KIND=dp), OPTIONAL :: hK - LOGICAL, OPTIONAL :: UseLongEdge -!------------------------------------------------------------------------------ - INTEGER :: info,p,q,i,j,t,dim - REAL(KIND=dp) :: EIGR(n),EIGI(n),Beta(n),s,ddp(3),ddq(3),dNodalBasisdx(n,n,3) - REAL(KIND=dp) :: u,v,w,L(n-1,n-1),G(n-1,n-1),Work(16*n) - REAL(KIND=dp) :: Basis(n),dBasisdx(n,3),ddBasisddx(n,3,3),detJ - - LOGICAL :: stat - TYPE(GaussIntegrationPoints_t) :: IntegStuff - - IF ( Element % TYPE % BasisFunctionDegree <= 1 ) THEN - SELECT CASE( Element % TYPE % ElementCode ) - CASE( 202, 303, 404, 504, 605, 706 ) - mK = 1.0d0 / 3.0d0 - CASE( 808 ) - mK = 1.0d0 / 6.0d0 - END SELECT - IF ( PRESENT( hK ) ) hK = ElementDiameter( Element, Nodes, UseLongEdge) - RETURN - END IF - - dNodalBasisdx = 0._dp - DO p=1,n - u = Element % TYPE % NodeU(p) - v = Element % TYPE % NodeV(p) - w = Element % TYPE % NodeW(p) - stat = ElementInfo( Element, Nodes, u,v,w, detJ, Basis, dBasisdx ) - dNodalBasisdx(1:n,p,:) = dBasisdx(1:n,:) - END DO - - dim = CoordinateSystemDimension() - IntegStuff = GaussPoints( Element ) - L = 0.0d0 - G = 0.0d0 - DO t=1,IntegStuff % n - u = IntegStuff % u(t) - v = IntegStuff % v(t) - w = IntegStuff % w(t) - - stat = ElementInfo( Element,Nodes,u,v,w,detJ,Basis, & - dBasisdx ) - - s = detJ * IntegStuff % s(t) - - DO p=2,n - DO q=2,n - ddp = 0.0d0 - ddq = 0.0d0 - DO i=1,dim - G(p-1,q-1) = G(p-1,q-1) + s * dBasisdx(p,i) * dBasisdx(q,i) - ddp(i) = ddp(i) + SUM( dNodalBasisdx(p,1:n,i) * dBasisdx(1:n,i) ) - ddq(i) = ddq(i) + SUM( dNodalBasisdx(q,1:n,i) * dBasisdx(1:n,i) ) - END DO - L(p-1,q-1) = L(p-1,q-1) + s * SUM(ddp) * SUM(ddq) - END DO - END DO - END DO - - IF ( ALL(ABS(L) < AEPS) ) THEN - mK = 1.0d0 / 3.0d0 - IF ( PRESENT(hK) ) THEN - hK = ElementDiameter( Element,Nodes,UseLongEdge) - END IF - RETURN - END IF - - - CALL DSYGV( 1,'N','U',n-1,L,n-1,G,n-1,EIGR,Work,12*n,info ) - mK = EIGR(n-1) - - IF ( mK < 10*AEPS ) THEN - mK = 1.0d0 / 3.0d0 - IF ( PRESENT(hK) ) THEN - hK = ElementDiameter( Element,Nodes,UseLongEdge ) - END IF - RETURN - END IF - - IF ( PRESENT( hK ) ) THEN - hK = SQRT( 2.0d0 / (mK * Element % TYPE % StabilizationMK) ) - mK = MIN( 1.0d0 / 3.0d0, Element % TYPE % StabilizationMK ) - ELSE - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(2,4,8) - mK = 4 * mK - END SELECT - mK = MIN( 1.0d0/3.0d0, 2/mK ) - END IF - -!------------------------------------------------------------------------------ - END SUBROUTINE StabParam -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Given element structure return value of a quantity x given at element nodes -!> at local coordinate point u inside the element. Element basis functions are -!> used to compute the value. This is for 1D elements, and shouldn't probably -!> be called directly by the user but through the wrapper routine -!> InterpolateInElement. -!------------------------------------------------------------------------------ - FUNCTION InterpolateInElement1D( element,x,u ) RESULT(y) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the value - REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity whose value we want to know - REAL(KIND=dp) :: y !< value of the quantity y = x(u) -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s - INTEGER :: i,j,k,n - TYPE(ElementType_t), POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - - elt => element % TYPE - k = Elt % NumberOfNodes - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n=1,k - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) - END DO - y = y + s * x(n) - END IF - END DO - END FUNCTION InterpolateInElement1D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalBasisFunctions1D( y,element,u ) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the value - REAL(KIND=dp) :: y(:) !< value of the quantity y = x(u) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s - INTEGER :: i,n - TYPE(ElementType_t), POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - DO n=1,Elt % NumberOfNodes - p => BasisFunctions(n) % p - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) - END DO - y(n) = s - END DO - END SUBROUTINE NodalBasisFunctions1D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate of a quantity x given at element nodes at local -!> coordinate point u inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivative1D( element,x,u ) RESULT(y) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the partial derivative - REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity whose partial derivative we want to know - REAL(KIND=dp) :: y !< value of the quantity y = @x/@u -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - INTEGER :: i,j,k,n,l - TYPE(ElementType_t), POINTER :: elt - REAL(KIND=dp) :: s - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - elt => element % TYPE - k = Elt % NumberOfNodes - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n=1,k - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - IF ( p(i) >= 1 ) THEN - s = s + p(i) * Coeff(i) * u**(p(i)-1) - END IF - END DO - y = y + s * x(n) - END IF - END DO - END FUNCTION FirstDerivative1D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalFirstDerivatives1D( y,element,u ) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: u !< Point at which to evaluate the partial derivative - REAL(KIND=dp) :: y(:,:) !< value of the quantity y = @x/@u - TYPE(Element_t) :: element !< element structure -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(ElementType_t), POINTER :: elt - INTEGER :: i,n - REAL(KIND=dp) :: s - - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - DO n=1, Elt % NumberOfNodes - p => BasisFunctions(n) % p - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - IF (p(i)>=1) s = s + p(i)*Coeff(i)*u**(p(i)-1) - END DO - y(n,1) = s - END DO - END SUBROUTINE NodalFirstDerivatives1D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the second partial derivative with -!> respect to local coordinate of a quantity x given at element nodes at local -!> coordinate point u inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION SecondDerivatives1D( element,x,u ) RESULT(y) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the partial derivative - REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity whose partial derivative we want to know - REAL(KIND=dp) :: y !< value of the quantity y = @x/@u -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: usum - INTEGER :: i,j,k,n - TYPE(ElementType_t), POINTER :: elt - INTEGER, POINTER :: p(:),q(:) - REAL(KIND=dp), POINTER :: Coeff(:) - REAL(KIND=dp) :: s - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - elt => element % TYPE - k = Elt % NumberOfNodes - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n=1,k - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - IF ( p(i) >= 2 ) THEN - s = s + p(i) * (p(i)-1) * Coeff(i) * u**(p(i)-2) - END IF - END DO - y = y + s * x(n) - END IF - END DO - END FUNCTION SecondDerivatives1D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of a quantity x given at element nodes -!> at local coordinate point (u,vb) inside the element. Element basis functions -!> are used to compute the value.This is for 2D elements, and shouldn't probably -!> be called directly by the user but through the wrapper routine -!> InterpolateInElement. -!------------------------------------------------------------------------------ - FUNCTION InterpolateInElement2D( element,x,u,v ) RESULT(y) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the partial derivative - REAL(KIND=dp) :: v !< Point at which to evaluate the partial derivative - REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity whose partial derivative we want to know - REAL(KIND=dp) :: y !< value of the quantity y = x(u,v) -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s,t - - INTEGER :: i,j,k,m,n - - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) * v**q(i) - END DO - y = y + s*x(n) - END IF - END DO - - END FUNCTION InterpolateInElement2D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalBasisFunctions2D( y,element,u,v ) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y(:) !< The values of the reference element basis - TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the value - REAL(KIND=dp) :: v !< Point at which to evaluate the value -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s - INTEGER :: i,n - TYPE(ElementType_t), POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: ult(0:6), vlt(0:6) - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - ult(0) = 1 - ult(1) = u - - vlt(0) = 1 - vlt(1) = v - - DO i=2,elt % BasisFunctionDegree - ult(i) = u**i - vlt(i) = v**i - END DO - - DO n=1,Elt % NumberOfNodes - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - s = s + Coeff(i)*ult(p(i))*vlt(q(i)) - END DO - y(n) = s - END DO - END SUBROUTINE NodalBasisFunctions2D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate u of i quantity x given at element nodes at local -!> coordinate point u,v inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivativeInU2D( element,x,u,v ) RESULT(y) -!------------------------------------------------------------------------------ -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v)/@u -! -!****************************************************************************** - ! - ! Return first partial derivative in u of a quantity x at point u,v - ! - ! - ! - - TYPE(Element_t) :: element - - REAL(KIND=dp) :: u,v - REAL(KIND=dp), DIMENSION(:) :: x - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: y,s,t - - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - INTEGER :: i,j,k,m,n - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( p(i) >= 1 ) THEN - s = s + p(i) * Coeff(i) * u**(p(i)-1) * v**q(i) - END IF - END DO - y = y + s*x(n) - END IF - END DO - - END FUNCTION FirstDerivativeInU2D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate v of i quantity x given at element nodes at local -!> coordinate point u,v inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivativeInV2D( element,x,u,v ) RESULT(y) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v)/@v -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in v of a quantity x at point u,v - ! - ! - ! - TYPE(Element_t) :: element - - REAL(KIND=dp), DIMENSION(:) :: x - REAL(KIND=dp) :: u,v - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y,s,t - - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - INTEGER :: i,j,k,m,n - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( q(i) >= 1 ) THEN - s = s + q(i) * Coeff(i) * u**p(i) * v**(q(i)-1) - END IF - END DO - y = y + s*x(n) - END IF - END DO - - END FUNCTION FirstDerivativeInV2D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalFirstDerivatives2D( y,element,u,v ) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v)/@u -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in u of a quantity x at point u,v - ! - ! - ! - - TYPE(Element_t) :: element - REAL(KIND=dp) :: u,v,y(:,:) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: s,t - - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - INTEGER :: i,n - - REAL(KIND=dp) :: ult(0:6), vlt(0:6) - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - ult(0) = 1 - ult(1) = u - - vlt(0) = 1 - vlt(1) = v - - DO i=2,elt % BasisFunctionDegree - ult(i) = u**i - vlt(i) = v**i - END DO - - - DO n = 1,elt % NumberOfNodes - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - t = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF (p(i)>=1) s = s + p(i)*Coeff(i)*ult(p(i)-1)*vlt(q(i)) - IF (q(i)>=1) t = t + q(i)*Coeff(i)*ult(p(i))*vlt(q(i)-1) - END DO - y(n,1) = s - y(n,2) = t - END DO - - END SUBROUTINE NodalFirstDerivatives2D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the second partial derivatives with -!> respect to local coordinates of a quantity x given at element nodes at local -!> coordinate point u,v inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION SecondDerivatives2D( element,x,u,v ) RESULT(ddx) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivatives we want to know -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: s -! value of the quantity s = @^2x(u,v)/@v^2 -! -!------------------------------------------------------------------------------ - - TYPE(Element_t) :: element - - REAL(KIND=dp), DIMENSION(:) :: x - REAL(KIND=dp) :: u,v - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), DIMENSION (2,2) :: ddx - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - REAL(KIND=dp) :: s,t - INTEGER, POINTER :: p(:),q(:) - REAL(KIND=dp), POINTER :: Coeff(:) - - INTEGER :: i,j,k,n,m - -!------------------------------------------------------------------------------ - elt => element % TYPE - k = elt % NumberOfNodes - BasisFunctions => elt % BasisFunctions - - ddx = 0.0d0 - - DO n = 1,k - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - Coeff => BasisFunctions(n) % Coeff -!------------------------------------------------------------------------------ -! @^2x/@u^2 -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1, BasisFunctions(n) % n - IF ( p(i) >= 2 ) THEN - s = s + p(i) * (p(i)-1) * Coeff(i) * u**(p(i)-2) * v**q(i) - END IF - END DO - ddx(1,1) = ddx(1,1) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@u@v -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1, BasisFunctions(n) % n - IF ( p(i) >= 1 .AND. q(i) >= 1 ) THEN - s = s + p(i) * q(i) * Coeff(i) * u**(p(i)-1) * v**(q(i)-1) - END IF - END DO - ddx(1,2) = ddx(1,2) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@v^2 -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1, BasisFunctions(n) % n - IF ( q(i) >= 2 ) THEN - s = s + q(i) * (q(i)-1) * Coeff(i) * u**p(i) * v**(q(i)-2) - END IF - END DO - ddx(2,2) = ddx(2,2) + s*x(n) - END IF - END DO - - ddx(2,1) = ddx(1,2) - - END FUNCTION SecondDerivatives2D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of a quantity x given at element nodes -!> at local coordinate point (u,v,w) inside the element. Element basis functions -!> are used to compute the value. This is for 3D elements, and shouldn't probably -!> be called directly by the user but through the wrapper routine -!> InterpolateInElement. -!------------------------------------------------------------------------------ - FUNCTION InterpolateInElement3D( element,x,u,v,w ) RESULT(y) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose value we want to know -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the value -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = x(u,v,w) -! -!------------------------------------------------------------------------------ - ! - ! Return value of a quantity x at point u,v,w - ! - TYPE(Element_t) :: element - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp), DIMENSION(:) :: x -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y - - TYPE(ElementType_t),POINTER :: elt - - INTEGER :: i,j,k,l,n,m - - REAL(KIND=dp) :: s,t - INTEGER, POINTER :: p(:),q(:), r(:) - REAL(KIND=dp), POINTER :: Coeff(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - - elt => element % TYPE - l = elt % BasisFunctionDegree - BasisFunctions => elt % BasisFunctions - - IF ( Elt % ElementCode == 605 ) THEN - s = 0.0d0 - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( (1-u) * (1-v) - w + u*v*w * s ) / 4 - y = y + x(2) * ( (1+u) * (1-v) - w - u*v*w * s ) / 4 - y = y + x(3) * ( (1+u) * (1+v) - w + u*v*w * s ) / 4 - y = y + x(4) * ( (1-u) * (1+v) - w - u*v*w * s ) / 4 - y = y + x(5) * w - RETURN - ELSE IF ( Elt % ElementCode == 613 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * (-u-v-1) * ( (1-u) * (1-v) - w + u*v*w * s ) / 4 - y = y + x(2) * ( u-v-1) * ( (1+u) * (1-v) - w - u*v*w * s ) / 4 - y = y + x(3) * ( u+v-1) * ( (1+u) * (1+v) - w + u*v*w * s ) / 4 - y = y + x(4) * (-u+v-1) * ( (1-u) * (1+v) - w - u*v*w * s ) / 4 - y = y + x(5) * w*(2*w-1) - y = y + x(6) * (1+u-w)*(1-u-w)*(1-v-w) * s / 2 - y = y + x(7) * (1+v-w)*(1-v-w)*(1+u-w) * s / 2 - y = y + x(8) * (1+u-w)*(1-u-w)*(1+v-w) * s / 2 - y = y + x(9) * (1+v-w)*(1-v-w)*(1-u-w) * s / 2 - y = y + x(10) * w * (1-u-w) * (1-v-w) * s - y = y + x(11) * w * (1+u-w) * (1-v-w) * s - y = y + x(12) * w * (1+u-w) * (1+v-w) * s - y = y + x(13) * w * (1-u-w) * (1+v-w) * s - RETURN - END IF - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - s = s + Coeff(i) * u**p(i) * v**q(i) * w**r(i) - END DO - y = y + s*x(n) - END IF - END DO -!------------------------------------------------------------------------------ - END FUNCTION InterpolateInElement3D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalBasisFunctions3D( y,element,u,v,w ) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: u -! INPUT: Point at which to evaluate the value -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = x(u) -! -!------------------------------------------------------------------------------ - - TYPE(Element_t) :: element - REAL(KIND=dp) :: u,v,w,y(:) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: s - - INTEGER :: i,n - - TYPE(ElementType_t), POINTER :: elt - - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:),r(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: ult(0:6), vlt(0:6), wlt(0:6) - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - ult(0) = 1 - ult(1) = u - - vlt(0) = 1 - vlt(1) = v - - wlt(0) = 1 - wlt(1) = w - - DO i=2,elt % BasisFunctionDegree - ult(i) = u**i - vlt(i) = v**i - wlt(i) = w**i - END DO - - DO n=1,Elt % NumberOfNodes - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i=1,BasisFunctions(n) % n - s = s + Coeff(i)*ult(p(i))*vlt(q(i))*wlt(r(i)) - END DO - y(n) = s - END DO - END SUBROUTINE NodalBasisFunctions3D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate u of a quantity x given at element nodes at -!> local coordinate point u,v,w inside the element. Element basis functions -!> are used to compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivativeInU3D( element,x,u,v,w ) RESULT(y) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v,w)/@u -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in u of a quantity x at point u,v,w - ! - - TYPE(Element_t) :: element - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp), DIMENSION(:) :: x - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y - - TYPE(ElementType_t),POINTER :: elt - INTEGER :: i,j,k,l,n,m - - REAL(KIND=dp) :: s,t - - INTEGER, POINTER :: p(:),q(:), r(:) - REAL(KIND=dp), POINTER :: Coeff(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - elt => element % TYPE - l = elt % BasisFunctionDegree - BasisFunctions => elt % BasisFunctions - -IF ( Elt % ElementCode == 605 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( -(1-v) + v*w * s ) / 4 - y = y + x(2) * ( (1-v) - v*w * s ) / 4 - y = y + x(3) * ( (1+v) + v*w * s ) / 4 - y = y + x(4) * ( -(1+v) - v*w * s ) / 4 - RETURN -ELSE IF ( Elt % ElementCode == 613 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( -( (1-u) * (1-v) - w + u*v*w * s ) + & - (-u-v-1) * ( -(1-v) + v*w * s ) ) / 4 - - y = y + x(2) * ( ( (1+u) * (1-v) - w - u*v*w * s ) + & - ( u-v-1) * ( (1-v) - v*w * s ) ) / 4 - - y = y + x(3) * ( ( (1+u) * (1+v) - w + u*v*w * s ) + & - ( u+v-1) * ( (1+v) + v*w * s ) ) / 4 - - y = y + x(4) * ( -( (1-u) * (1+v) - w - u*v*w * s ) + & - (-u+v-1) * ( -(1+v) - v*w * s ) ) / 4 - - y = y + x(5) * 0.0d0 - - y = y + x(6) * ( (1-u-w)*(1-v-w) - (1+u-w)*(1-v-w) ) * s / 2 - y = y + x(7) * ( (1+v-w)*(1-v-w) ) * s / 2 - y = y + x(8) * ( (1-u-w)*(1+v-w) - (1+u-w)*(1+v-w) ) * s / 2 - y = y + x(9) * ( -(1+v-w)*(1-v-w) ) * s / 2 - - y = y - x(10) * w * (1-v-w) * s - y = y + x(11) * w * (1-v-w) * s - y = y + x(12) * w * (1+v-w) * s - y = y - x(13) * w * (1+v-w) * s - - RETURN -END IF - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( p(i) >= 1 ) THEN - s = s + p(i) * Coeff(i) * u**(p(i)-1) * v**q(i) * w**r(i) - END IF - END DO - y = y + s*x(n) - END IF - END DO -!------------------------------------------------------------------------------ - END FUNCTION FirstDerivativeInU3D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate v of a quantity x given at element nodes at -!> local coordinate point u,v,w inside the element. Element basis functions -!> are used to compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivativeInV3D( element,x,u,v,w ) RESULT(y) -!------------------------------------------------------------------------------ -! -! DESCRIPTION: -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v,w)/@v -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in v of a quantity x at point u,v,w - ! - - TYPE(Element_t) :: element - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp), DIMENSION(:) :: x - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y - - TYPE(ElementType_t),POINTER :: elt - - INTEGER :: i,j,k,l,n,m - - REAL(KIND=dp) :: s,t - - INTEGER, POINTER :: p(:),q(:), r(:) - REAL(KIND=dp), POINTER :: Coeff(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - elt => element % TYPE - l = elt % BasisFunctionDegree - BasisFunctions => elt % BasisFunctions - -IF ( Elt % ElementCode == 605 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( -(1-u) + u*w * s ) / 4 - y = y + x(2) * ( -(1+u) - u*w * s ) / 4 - y = y + x(3) * ( (1+u) + u*w * s ) / 4 - y = y + x(4) * ( (1-u) - u*w * s ) / 4 - - RETURN -ELSE IF ( Elt % ElementCode == 613 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( -( (1-u) * (1-v) - w + u*v*w * s ) + & - (-u-v-1) * ( -(1-u) + u*w * s ) ) / 4 - - y = y + x(2) * ( -( (1+u) * (1-v) - w - u*v*w * s ) + & - ( u-v-1) * ( -(1+u) - u*w * s ) ) / 4 - - y = y + x(3) * ( ( (1+u) * (1+v) - w + u*v*w * s ) + & - ( u+v-1) * ( (1+u) + u*w * s ) ) / 4 - - y = y + x(4) * ( ( (1-u) * (1+v) - w - u*v*w * s ) + & - (-u+v-1) * ( (1-u) - u*w * s ) ) / 4 - - y = y + x(5) * 0.0d0 - - y = y - x(6) * (1+u-w)*(1-u-w) * s / 2 - y = y + x(7) * ( (1-v-w)*(1+u-w) - (1+v-w)*(1+u-w) ) * s / 2 - y = y + x(8) * (1+u-w)*(1-u-w) * s / 2 - y = y + x(9) * ( (1-v-w)*(1-u-w) - (1+v-w)*(1-u-w) ) * s / 2 - - y = y - x(10) * w * (1-u-w) * s - y = y - x(11) * w * (1+u-w) * s - y = y + x(12) * w * (1+u-w) * s - y = y + x(13) * w * (1-u-w) * s - RETURN -END IF - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( q(i) >= 1 ) THEN - s = s + q(i) * Coeff(i) * u**p(i) * v**(q(i)-1) * w**r(i) - END IF - END DO - y = y + s*x(n) - END IF - END DO - END FUNCTION FirstDerivativeInV3D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivatives with -!> respect to local coordinate w of a quantity x given at element nodes at -!> local coordinate point u,v,w inside the element. Element basis functions -!> are used to compute the value. -!------------------------------------------------------------------------------ - FUNCTION FirstDerivativeInW3D( element,x,u,v,w ) RESULT(y) -!------------------------------------------------------------------------------ -! -! DESCRIPTION: -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v,w)/@w -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in u of a quantity x at point u,v,w - ! - ! - - TYPE(Element_t) :: element - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp), DIMENSION(:) :: x - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: y - - TYPE(ElementType_t),POINTER :: elt - INTEGER :: i,j,k,l,n,m - - REAL(KIND=dp) :: s,t - - INTEGER, POINTER :: p(:),q(:), r(:) - REAL(KIND=dp), POINTER :: Coeff(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) -!------------------------------------------------------------------------------ - elt => element % TYPE - l = elt % BasisFunctionDegree - BasisFunctions => elt % BasisFunctions - -IF ( Elt % ElementCode == 605 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * ( -1 + u*v*s**2 ) / 4 - y = y + x(2) * ( -1 - u*v*s**2 ) / 4 - y = y + x(3) * ( -1 + u*v*s**2 ) / 4 - y = y + x(4) * ( -1 - u*v*s**2 ) / 4 - y = y + x(5) - RETURN -ELSE IF ( Elt % ElementCode == 613 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) - - y = 0.0d0 - y = y + x(1) * (-u-v-1) * ( -1 + u*v*s**2 ) / 4 - y = y + x(2) * ( u-v-1) * ( -1 - u*v*s**2 ) / 4 - y = y + x(3) * ( u+v-1) * ( -1 + u*v*s**2 ) / 4 - y = y + x(4) * (-u+v-1) * ( -1 - u*v*s**2 ) / 4 - - y = y + x(5) * (4*w-1) - - y = y + x(6) * ( ( -(1-u-w)*(1-v-w) - (1+u-w)*(1-v-w) - (1+u-w)*(1-u-w) ) * s + & - ( 1+u-w)*(1-u-w)*(1-v-w) * s**2 ) / 2 - - y = y + x(7) * ( ( -(1-v-w)*(1+u-w) - (1+v-w)*(1+u-w) - (1+v-w)*(1-v-w) ) * s + & - ( 1+v-w)*(1-v-w)*(1+u-w) * s**2 ) / 2 - - y = y + x(8) * ( ( -(1-u-w)*(1+v-w) - (1+u-w)*(1+v-w) - (1+u-w)*(1-u-w) ) * s + & - ( 1+u-w)*(1-u-w)*(1+v-w) * s**2 ) / 2 - - y = y + x(9) * ( ( -(1-v-w)*(1-u-w) - (1+v-w)*(1-u-w) - (1+v-w)*(1-v-w) ) * s + & - ( 1+v-w)*(1-v-w)*(1-u-w) * s**2 ) / 2 - - y = y + x(10) * ( ( (1-u-w) * (1-v-w) - w * (1-v-w) - w * (1-u-w) ) * s + & - w * (1-u-w) * (1-v-w) * s**2 ) - - y = y + x(11) * ( ( (1+u-w) * (1-v-w) - w * (1-v-w) - w * (1+u-w) ) * s + & - w * (1+u-w) * (1-v-w) * s**2 ) - - y = y + x(12) * ( ( (1+u-w) * (1+v-w) - w * (1+v-w) - w * (1+u-w) ) * s + & - w * (1+u-w) * (1+v-w) * s**2 ) - - y = y + x(13) * ( ( (1-u-w) * (1+v-w) - w * (1+v-w) - w * (1-u-w) ) * s + & - w * (1-u-w) * (1+v-w) * s**2 ) - RETURN -END IF - - y = 0.0d0 - DO n = 1,elt % NumberOfNodes - IF ( x(n) /= 0.0d0 ) THEN - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( r(i) >= 1 ) THEN - s = s + r(i) * Coeff(i) * u**p(i) * v**q(i) * w**(r(i)-1) - END IF - END DO - y = y + s*x(n) - END IF - END DO -!------------------------------------------------------------------------------ - END FUNCTION FirstDerivativeInW3D -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE NodalFirstDerivatives3D( y,element,u,v,w ) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = @x(u,v)/@u -! -!------------------------------------------------------------------------------ - ! - ! Return first partial derivative in u of a quantity x at point u,v - ! - - TYPE(Element_t) :: element - REAL(KIND=dp) :: u,v,w,y(:,:) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: s,t,z - - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:),q(:),r(:) - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - INTEGER :: i,n - - REAL(KIND=dp) :: ult(0:6), vlt(0:6), wlt(0:6) - - elt => element % TYPE - BasisFunctions => elt % BasisFunctions - - ult(0) = 1 - ult(1) = u - - vlt(0) = 1 - vlt(1) = v - - wlt(0) = 1 - wlt(1) = w - - DO i=2,elt % BasisFunctionDegree - ult(i) = u**i - vlt(i) = v**i - wlt(i) = w**i - END DO - - DO n = 1,elt % NumberOfNodes - p => BasisFunctions(n) % p - q => BasisFunctions(n) % q - r => BasisFunctions(n) % r - Coeff => BasisFunctions(n) % Coeff - - s = 0.0d0 - t = 0.0d0 - z = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF (p(i)>=1) s = s + p(i)*Coeff(i)*ult(p(i)-1)*vlt(q(i))*wlt(r(i)) - IF (q(i)>=1) t = t + q(i)*Coeff(i)*ult(p(i))*vlt(q(i)-1)*wlt(r(i)) - IF (r(i)>=1) z = z + r(i)*Coeff(i)*ult(p(i))*vlt(q(i))*wlt(r(i)-1) - END DO - y(n,1) = s - y(n,2) = t - y(n,3) = z - END DO - END SUBROUTINE NodalFirstDerivatives3D -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the second partial derivatives with -!> respect to local coordinates of i quantity x given at element nodes at local -!> coordinate point u,v inside the element. Element basis functions are used to -!> compute the value. -!------------------------------------------------------------------------------ - FUNCTION SecondDerivatives3D( element,x,u,v,w ) RESULT(ddx) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: x(:) -! INPUT: Nodal values of the quantity whose partial derivatives we want to know -! -! REAL(KIND=dp) :: u,v -! INPUT: Point at which to evaluate the partial derivative -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: s -! value of the quantity s = @^2x(u,v)/@v^2 -! -!------------------------------------------------------------------------------ - ! - ! Return matrix of second partial derivatives. - ! -!------------------------------------------------------------------------------ - - TYPE(Element_t) :: element - - REAL(KIND=dp), DIMENSION(:) :: x - REAL(KIND=dp) :: u,v,w - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp), DIMENSION (3,3) :: ddx - TYPE(BasisFunctions_t), POINTER :: BasisFunctions(:) - - REAL(KIND=dp), POINTER :: Coeff(:) - INTEGER, POINTER :: p(:), q(:), r(:) - - REAL(KIND=dp) :: s - INTEGER :: i,j,k,l,n,m - -!------------------------------------------------------------------------------ - elt => element % TYPE - k = elt % NumberOfNodes - BasisFunctions => elt % BasisFunctions - - ddx = 0.0d0 - - DO n = 1,k - IF ( x(n) /= 0.0d0 ) THEN - p => elt % BasisFunctions(n) % p - q => elt % BasisFunctions(n) % q - r => elt % BasisFunctions(n) % r - Coeff => elt % BasisFunctions(n) % Coeff -!------------------------------------------------------------------------------ -! @^2x/@u^2 -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( p(i) >= 2 ) THEN - s = s + p(i) * (p(i)-1) * Coeff(i) * u**(p(i)-2) * v**q(i) * w**r(i) - END IF - END DO - ddx(1,1) = ddx(1,1) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@u@v -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( p(i) >= 1 .AND. q(i) >= 1 ) THEN - s = s + p(i) * q(i) * Coeff(i) * u**(p(i)-1) * v**(q(i)-1) * w**r(i) - END IF - END DO - ddx(1,2) = ddx(1,2) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@u@w -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 2,k - IF ( p(i) >= 1 .AND. r(i) >= 1 ) THEN - s = s + p(i) * r(i) * Coeff(i) * u**(p(i)-1) * v**q(i) * w**(r(i)-1) - END IF - END DO - ddx(1,3) = ddx(1,3) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@v^2 -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( q(i) >= 2 ) THEN - s = s + q(i) * (q(i)-1) * Coeff(i) * u**p(i) * v**(q(i)-2) * w**r(i) - END IF - END DO - ddx(2,2) = ddx(2,2) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@v@w -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( q(i) >= 1 .AND. r(i) >= 1 ) THEN - s = s + q(i) * r(i) * Coeff(i) * u**p(i) * v**(q(i)-1) * w**(r(i)-1) - END IF - END DO - ddx(2,3) = ddx(2,3) + s*x(n) - -!------------------------------------------------------------------------------ -! @^2x/@w^2 -!------------------------------------------------------------------------------ - s = 0.0d0 - DO i = 1,BasisFunctions(n) % n - IF ( r(i) >= 2 ) THEN - s = s + r(i) * (r(i)-1) * Coeff(i) * u**p(i) * v**q(i) * w**(r(i)-2) - END IF - END DO - ddx(3,3) = ddx(3,3) + s*x(n) - - END IF - END DO - - ddx(2,1) = ddx(1,2) - ddx(3,1) = ddx(1,3) - ddx(3,2) = ddx(2,3) - - END FUNCTION SecondDerivatives3D -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -!> Return the values of the reference element basis functions. In the case of -!> p-element, the values of the lowest-order basis functions corresponding -!> to the background mesh are returned. -!------------------------------------------------------------------------------ - SUBROUTINE NodalBasisFunctions( n, Basis, element, u, v, w) -!------------------------------------------------------------------------------ - INTEGER :: n !< The number of (background) element nodes - REAL(KIND=dp) :: Basis(:) !< The values of reference element basis - TYPE(Element_t) :: element !< The element structure - REAL(KIND=dp) :: u,v,w !< The coordinates of the reference element point -!------------------------------------------------------------------------------ - INTEGER :: i, q, dim - REAL(KIND=dp) :: NodalBasis(n) - - dim = Element % TYPE % DIMENSION - - IF ( isActivePElement(Element) ) THEN - SELECT CASE(dim) - CASE(1) - CALL NodalBasisFunctions1D( Basis, element, u ) - CASE(2) - IF (isPTriangle(Element)) THEN - DO q=1,n - Basis(q) = TriangleNodalPBasis(q, u, v) - END DO - ELSE IF (isPQuad(Element)) THEN - DO q=1,n - Basis(q) = QuadNodalPBasis(q, u, v) - END DO - END IF - CASE(3) - IF (isPTetra( Element )) THEN - DO q=1,n - Basis(q) = TetraNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPWedge( Element )) THEN - DO q=1,n - Basis(q) = WedgeNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPPyramid( Element )) THEN - DO q=1,n - Basis(q) = PyramidNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPBrick( Element )) THEN - DO q=1,n - Basis(q) = BrickNodalPBasis(q, u, v, w) - END DO - END IF - END SELECT - ELSE - SELECT CASE( dim ) - CASE(1) - CALL NodalBasisFunctions1D( Basis, element, u ) - CASE(2) - CALL NodalBasisFunctions2D( Basis, element, u,v ) - CASE(3) - IF ( Element % TYPE % ElementCode/100==6 ) THEN - NodalBasis=0 - DO q=1,n - NodalBasis(q) = 1.0d0 - Basis(q) = InterpolateInElement3D( element, NodalBasis, u,v,w ) - NodalBasis(q) = 0.0d0 - END DO - ELSE - CALL NodalBasisFunctions3D( Basis, element, u,v,w ) - END IF - END SELECT - END IF -!------------------------------------------------------------------------------ - END SUBROUTINE NodalBasisFunctions -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -!> Return the gradient of the reference element basis functions, with the -!> gradient taken with respect to the reference element coordinates. In the case -!> of p-element, the gradients of the lowest-order basis functions corresponding -!> to the background mesh are returned. -!------------------------------------------------------------------------------ - SUBROUTINE NodalFirstDerivatives( n, dLBasisdx, element, u, v, w) -!------------------------------------------------------------------------------ - INTEGER :: n !< The number of (background) element nodes - REAL(KIND=dp) :: dLBasisdx(:,:) !< The gradient of reference element basis functions - TYPE(Element_t) :: element !< The element structure - REAL(KIND=dp) :: u,v,w !< The coordinates of the reference element point -!------------------------------------------------------------------------------ - INTEGER :: i, q, dim - REAL(KIND=dp) :: NodalBasis(n) -!------------------------------------------------------------------------------ - dim = Element % TYPE % DIMENSION - - IF ( IsActivePElement(Element) ) THEN - SELECT CASE(dim) - CASE(1) - CALL NodalFirstDerivatives1D( dLBasisdx, element, u ) - CASE(2) - IF (isPTriangle(Element)) THEN - DO q=1,n - dLBasisdx(q,1:2) = dTriangleNodalPBasis(q, u, v) - END DO - ELSE IF (isPQuad(Element)) THEN - DO q=1,n - dLBasisdx(q,1:2) = dQuadNodalPBasis(q, u, v) - END DO - END IF - CASE(3) - IF (isPTetra( Element )) THEN - DO q=1,n - dLBasisdx(q,1:3) = dTetraNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPWedge( Element )) THEN - DO q=1,n - dLBasisdx(q,1:3) = dWedgeNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPPyramid( Element )) THEN - DO q=1,n - dLBasisdx(q,1:3) = dPyramidNodalPBasis(q, u, v, w) - END DO - ELSE IF (isPBrick( Element )) THEN - DO q=1,n - dLBasisdx(q,1:3) = dBrickNodalPBasis(q, u, v, w) - END DO - END IF - END SELECT - ELSE - SELECT CASE(dim) - CASE(1) - CALL NodalFirstDerivatives1D( dLBasisdx, element, u ) - CASE(2) - CALL NodalFirstDerivatives2D( dLBasisdx, element, u,v ) - CASE(3) - IF ( Element % TYPE % ElementCode / 100 == 6 ) THEN - NodalBasis=0 - DO q=1,n - NodalBasis(q) = 1.0d0 - dLBasisdx(q,1) = FirstDerivativeInU3D(element,NodalBasis,u,v,w) - dLBasisdx(q,2) = FirstDerivativeInV3D(element,NodalBasis,u,v,w) - dLBasisdx(q,3) = FirstDerivativeInW3D(element,NodalBasis,u,v,w) - NodalBasis(q) = 0.0d0 - END DO - ELSE - CALL NodalFirstDerivatives3D( dLBasisdx, element, u,v,w ) - END IF - END SELECT - END IF -!------------------------------------------------------------------------------ - END SUBROUTINE NodalFirstDerivatives -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Return basis function degrees -!------------------------------------------------------------------------------ - SUBROUTINE ElementBasisDegree( Element, BasisDegree ) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - INTEGER :: BasisDegree(:)!< Degree of each basis function in Basis(:) vector. -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: t,s - LOGICAL :: invert, degrees - INTEGER :: i, j, k, l, q, p, f, n, nb, dim, cdim, locali, localj, & - tmp(4), direction(4) - - TYPE(Element_t) :: Bubble - TYPE(Element_t), POINTER :: Edge, Face -!------------------------------------------------------------------------------ - - n = Element % TYPE % NumberOfNodes - dim = Element % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - - BasisDegree = 0 - BasisDegree(1:n) = Element % Type % BasisFunctionDegree - - IF ( isActivePElement(element) ) THEN - - ! Check for need of P basis degrees and set degree of - ! linear basis if vector asked: - ! --------------------------------------------------- - BasisDegree(1:n) = 1 - q = n - -!------------------------------------------------------------------------------ - SELECT CASE( Element % TYPE % ElementCode ) -!------------------------------------------------------------------------------ - - ! P element code for line element: - ! -------------------------------- - CASE(202) - ! Bubbles of line element - IF (Element % BDOFs > 0) THEN - ! For each bubble in line element get value of basis function - DO i=1, Element % BDOFs - IF (q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 1+i - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for edges and bubbles of triangle - CASE(303) - ! Edges of triangle - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge calculate the value of edge basis function - DO i=1,3 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! For each dof in edge get value of p basis function - DO k=1,Edge % BDOFs - IF (q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Bubbles of p triangle - IF ( Element % BDOFs > 0 ) THEN - ! Get element p - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p = CEILING( ( 3.0d0+SQRT(1.0d0+8.0d0*nb) ) / 2.0d0 - AEPS ) - - DO i = 0,p-3 - DO j = 0,p-i-3 - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 3+i+j - END DO - END DO - END IF -!------------------------------------------------------------------------------ -! P element code for quadrilateral edges and bubbles - CASE(404) - ! Edges of p quadrilateral - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge begin node calculate values of edge functions - DO i=1,4 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - ! For each DOF in edge calculate value of p basis function - DO k=1,Edge % BDOFs - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Bubbles of p quadrilateral - IF ( Element % BDOFs > 0 ) THEN - ! Get element P - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p = CEILING( ( 5.0d0+SQRT(1.0d0+8.0d0*nb) ) / 2.0d0 - AEPS) - - DO i=2,(p-2) - DO j=2,(p-i) - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = i+j - END DO - END DO - END IF -!------------------------------------------------------------------------------ -! P element code for tetrahedron edges, faces and bubbles - CASE(504) - ! Edges of p tetrahedron - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge calculate value of edge functions - DO i=1,6 - Edge => CurrentModel % Solver % Mesh % Edges (Element % EdgeIndexes(i)) - - ! Do not solve edge DOFS if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! For each DOF in edge calculate value of edge functions - ! and their derivatives for edge=i, i=k+1 - DO k=1, Edge % BDOFs - IF (q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of p tetrahedron - IF ( ASSOCIATED( Element % FaceIndexes )) THEN - ! For each face calculate value of face functions - DO F=1,4 - Face => CurrentModel % Solver % Mesh % Faces (Element % FaceIndexes(F)) - - ! Do not solve face DOFs if there is not any - IF (Face % BDOFs <= 0) CYCLE - - ! Get face p - p = Face % PDefs % P - - ! For each DOF in face calculate value of face functions and - ! their derivatives for face=F and index pairs - ! i,j=0,..,p-3, i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF (q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 3+i+j - END DO - END DO - END DO - END IF - - ! Bubbles of p tetrahedron - IF ( Element % BDOFs > 0 ) THEN - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+2 - AEPS) - - DO i=0,p-4 - DO j=0,p-i-4 - DO k=0,p-i-j-4 - IF (q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 4+i+j+k - END DO - END DO - END DO - - END IF -!------------------------------------------------------------------------------ -! P element code for pyramid edges, faces and bubbles - CASE(605) - ! Edges of P Pyramid - IF (ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in wedge, calculate values of edge functions - DO i=1,8 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P Pyramid - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in pyramid, calculate values of face functions - DO F=1,5 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not solve face dofs, if there is not any - IF ( Face % BDOFs <= 0) CYCLE - - ! Get face p - p = Face % PDefs % P - - ! Handle triangle and square faces separately - SELECT CASE(F) - CASE (1) - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = i+j - END DO - END DO - - CASE (2,3,4,5) - ! For each face calculate values of functions from index - ! pairs i,j=0,..,p-3 i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 3+i+j - END DO - END DO - END SELECT - END DO - END IF - - ! Bubbles of P Pyramid - IF (Element % BDOFs > 0) THEN - ! Get element p - p = Element % PDefs % p - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+2 - AEPS) - - ! Calculate value of bubble functions from indexes - ! i,j,k=0,..,p-4 i+j+k=0,..,p-4 - DO i=0,p-4 - DO j=0,p-i-4 - DO k=0,p-i-j-4 - IF ( q >= SIZE(BasisDegree)) CYCLE - q = q + 1 - BasisDegree(q) = 4+i+j+k - END DO - END DO - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for wedge edges, faces and bubbles - CASE(706) - ! Edges of P Wedge - IF (ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in wedge, calculate values of edge functions - DO i=1,9 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - - ! Use basis compatible with pyramid if necessary - ! @todo Correct this! - IF (Edge % PDefs % pyramidQuadEdge) THEN - CALL Fatal('ElementInfo','Pyramid compatible wedge edge basis NIY!') - END IF - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P Wedge - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in wedge, calculate values of face functions - DO F=1,5 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not solve face dofs, if there is not any - IF ( Face % BDOFs <= 0) CYCLE - - p = Face % PDefs % P - - ! Handle triangle and square faces separately - SELECT CASE(F) - CASE (1,2) - ! For each face calculate values of functions from index - ! pairs i,j=0,..,p-3 i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 3+i+j - END DO - END DO - CASE (3,4,5) - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = i+j - END DO - END DO - END SELECT - - END DO - END IF - - ! Bubbles of P Wedge - IF ( Element % BDOFs > 0 ) THEN - ! Get p from element - p = Element % PDefs % P - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+3 - AEPS) - - ! For each bubble calculate value of basis function and its derivative - ! for index pairs i,j=0,..,p-5 k=2,..,p-3 i+j+k=2,..,p-3 - DO i=0,p-5 - DO j=0,p-5-i - DO k=2,p-3-i-j - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 3+i+j+k - END DO - END DO - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for brick edges, faces and bubbles - CASE(808) - ! Edges of P brick - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in brick, calculate values of edge functions - DO i=1,12 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P brick - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in brick, calculate values of face functions - DO F=1,6 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not calculate face values if no dofs - IF (Face % BDOFs <= 0) CYCLE - - ! Get p for face - p = Face % PDefs % P - - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = i+j - END DO - END DO - END DO - END IF - - ! Bubbles of p brick - IF ( Element % BDOFs > 0 ) THEN - ! Get p from bubble DOFs - p = Element % PDefs % P - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+4 - AEPS) - - ! For each bubble calculate value of basis function and its derivative - ! for index pairs i,j,k=2,..,p-4, i+j+k=6,..,p - DO i=2,p-4 - DO j=2,p-i-2 - DO k=2,p-i-j - IF ( q >= SIZE(BasisDegree) ) CYCLE - q = q + 1 - BasisDegree(q) = i+j+k - END DO - END DO - END DO - END IF - - END SELECT - END IF ! P element flag check -!------------------------------------------------------------------------------ - END SUBROUTINE ElementBasisDegree -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Return the referencial description b(f(p)) of the basis function b(x), -!> with f mapping points p on a reference element to points x on a physical -!> element. The referencial description of the spatial gradient field grad b -!> and, if requested, the second spatial derivatives may also be returned. -!> Also return the square root of the determinant of the metric tensor -!> (=sqrt(det(J^TJ))) related to the mapping f. -!------------------------------------------------------------------------------ - RECURSIVE FUNCTION ElementInfo( Element, Nodes, u, v, w, detJ, & - Basis, dBasisdx, ddBasisddx, SecondDerivatives, Bubbles, BasisDegree, & - EdgeBasis, RotBasis, USolver ) RESULT(stat) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Element nodal coordinates. - REAL(KIND=dp) :: u !< 1st local coordinate at which to calculate the basis function. - REAL(KIND=dp) :: v !< 2nd local coordinate. - REAL(KIND=dp) :: w !< 3rd local coordinate. - REAL(KIND=dp) :: detJ !< Square root of determinant of element coordinate system metric - REAL(KIND=dp) :: Basis(:) !< Basis function values at p=(u,v,w) - REAL(KIND=dp), OPTIONAL :: dBasisdx(:,:) !< Global first derivatives of basis functions at (u,v,w) - REAL(KIND=dp), OPTIONAL :: ddBasisddx(:,:,:) !< Global second derivatives of basis functions at (u,v,w) if requested - INTEGER, OPTIONAL :: BasisDegree(:) !< Degree of each basis function in Basis(:) vector. - !! May be used with P element basis functions - LOGICAL, OPTIONAL :: SecondDerivatives !< Are the second derivatives needed? (still present for historical reasons) - TYPE(Solver_t), POINTER, OPTIONAL :: USolver !< The solver used to call the basis functions. - LOGICAL, OPTIONAL :: Bubbles !< Are the bubbles to be evaluated. - REAL(KIND=dp), OPTIONAL :: EdgeBasis(:,:) !< If present, the values of H(curl)-conforming basis functions B(f(p)) - REAL(KIND=dp), OPTIONAL :: RotBasis(:,:) !< The referencial description of the spatial curl of B - LOGICAL :: Stat !< If .FALSE. element is degenerate. -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - TYPE(Solver_t), POINTER :: PSolver => NULL() - REAL(KIND=dp) :: BubbleValue, dBubbledx(3), t, s, LtoGMap(3,3) - LOGICAL :: invert, degrees - INTEGER :: i, j, k, l, q, p, f, n, nb, dim, cdim, locali, localj, & - tmp(4), direction(4) - REAL(KIND=dp) :: LinBasis(8), dLinBasisdx(8,3), ElmMetric(3,3) - - REAL(KIND=dp) :: NodalBasis(Element % TYPE % NumberOfNodes), & - dLBasisdx(MAX(SIZE(Nodes % x),SIZE(Basis)),3) - - TYPE(Element_t) :: Bubble - TYPE(Element_t), POINTER :: Edge, Face - INTEGER :: EdgeBasisDegree - LOGICAL :: PerformPiolaTransform, Found - - SAVE PSolver, EdgeBasisDegree, PerformPiolaTransform -!------------------------------------------------------------------------------ - IF(PRESENT(EdgeBasis)) THEN - IF( PRESENT( USolver ) ) THEN - IF( .NOT. ASSOCIATED( USolver, PSolver ) ) THEN - IF( ListGetLogical(USolver % Values,'Quadratic Approximation', Found ) ) THEN - EdgeBasisDegree = 2 - PerformPiolaTransform = .TRUE. - ELSE - EdgeBasisDegree = 1 - PerformPiolaTransform = ListGetLogical(USolver % Values,'Use Piola Transform', Found ) - END IF - PSolver => USolver - END IF - ELSE - EdgeBasisDegree = 1 - PerformPiolaTransform = .TRUE. - END IF - IF( PerformPiolaTransform ) THEN - stat = EdgeElementInfo(Element,Nodes,u,v,w,detF=Detj,Basis=Basis, & - EdgeBasis=EdgeBasis,RotBasis=RotBasis,dBasisdx=dBasisdx,& - BasisDegree = EdgeBasisDegree, ApplyPiolaTransform = PerformPiolaTransform ) - ELSE - ! Is this really necessary to call in case no piola version? - stat = ElementInfo( Element, Nodes, u, v, w, detJ, Basis, dBasisdx ) - CALL GetEdgeBasis(Element,EdgeBasis,RotBasis,Basis,dBasisdx) - END IF - RETURN - END IF - - stat = .TRUE. - n = Element % TYPE % NumberOfNodes - dim = Element % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - - IF ( Element % TYPE % ElementCode == 101 ) THEN - detJ = 1.0d0 - Basis(1) = 1.0d0 - IF ( PRESENT(dBasisdx) ) dBasisdx(1,:) = 0.0d0 - RETURN - END IF - - Basis = 0.0d0 - CALL NodalBasisFunctions(n, Basis, element, u, v, w) - - dLbasisdx = 0.0d0 - CALL NodalFirstDerivatives(n, dLBasisdx, element, u, v, w) - - q = n - - ! P ELEMENT CODE: - ! --------------- - IF ( isActivePElement(element) ) THEN - - ! Check for need of P basis degrees and set degree of - ! linear basis if vector asked: - ! --------------------------------------------------- - degrees = .FALSE. - IF ( PRESENT(BasisDegree)) THEN - degrees = .TRUE. - BasisDegree = 0 - BasisDegree(1:n) = 1 - END IF - -!------------------------------------------------------------------------------ - SELECT CASE( Element % TYPE % ElementCode ) -!------------------------------------------------------------------------------ - - ! P element code for line element: - ! -------------------------------- - CASE(202) - ! Bubbles of line element - IF (Element % BDOFs > 0) THEN - ! For boundary element integration check direction - invert = .FALSE. - IF ( Element % PDefs % isEdge .AND. & - Element % NodeIndexes(1)>Element % NodeIndexes(2) ) invert = .TRUE. - - ! For each bubble in line element get value of basis function - DO i=1, Element % BDOFs - IF (q >= SIZE(Basis)) CYCLE - q = q + 1 - - Basis(q) = LineBubblePBasis(i+1,u,invert) - dLBasisdx(q,1) = dLineBubblePBasis(i+1,u,invert) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+i - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for edges and bubbles of triangle - CASE(303) - ! Edges of triangle - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge calculate the value of edge basis function - DO i=1,3 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Get local number of edge start and endpoint nodes - tmp(1:2) = getTriangleEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Invert edge for parity if needed - invert = .FALSE. - IF ( Element % NodeIndexes(locali)>Element % NodeIndexes(localj) ) invert=.TRUE. - - ! For each dof in edge get value of p basis function - DO k=1,Edge % BDOFs - IF (q >= SIZE(Basis)) CYCLE - q = q + 1 - - ! Value of basis functions for edge=i and i=k+1 by parity - Basis(q) = TriangleEdgePBasis(i, k+1, u, v, invert) - ! Value of derivative of basis function - dLBasisdx(q,1:2) = dTriangleEdgePBasis(i, k+1, u, v, invert) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Bubbles of p triangle - IF ( Element % BDOFs > 0 ) THEN - ! Get element p - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p = CEILING( ( 3.0d0+SQRT(1.0d0+8.0d0*nb) ) / 2.0d0 - AEPS) - - ! For boundary element direction needs to be calculated - IF (Element % PDefs % isEdge) THEN - direction = 0 - ! Get direction of this face (mask for face = boundary element nodes) - direction(1:3) = getTriangleFaceDirection(Element, [ 1,2,3 ]) - END IF - - DO i = 0,p-3 - DO j = 0,p-i-3 - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! Get bubble basis functions and their derivatives - ! 3d Boundary element has a direction - IF (Element % PDefs % isEdge) THEN - Basis(q) = TriangleEBubblePBasis(i,j,u,v,direction) - dLBasisdx(q,1:2) = dTriangleEBubblePBasis(i,j,u,v,direction) - ELSE - ! 2d element bubbles have no direction - Basis(q) = TriangleBubblePBasis(i,j,u,v) - dLBasisdx(q,1:2) = dTriangleBubblePBasis(i,j,u,v) - END IF - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 3+i+j - END DO - END DO - END IF -!------------------------------------------------------------------------------ -! P element code for quadrilateral edges and bubbles - CASE(404) - ! Edges of p quadrilateral - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge begin node calculate values of edge functions - DO i=1,4 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Choose correct parity by global edge dofs - tmp(1:2) = getQuadEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Invert parity if needed - invert = .FALSE. - IF (Element % NodeIndexes(locali) > Element % NodeIndexes(localj)) invert = .TRUE. - - ! For each DOF in edge calculate value of p basis function - DO k=1,Edge % BDOFs - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! For pyramid square face edges use different basis - IF (Edge % PDefs % pyramidQuadEdge) THEN - Basis(q) = QuadPyraEdgePBasis(i,k+1,u,v,invert) - dLBasisdx(q,1:2) = dQuadPyraEdgePBasis(i,k+1,u,v,invert) - ! Normal case, use basis of quadrilateral - ELSE - ! Get values of basis functions for edge=i and i=k+1 by parity - Basis(q) = QuadEdgePBasis(i,k+1,u,v,invert) - ! Get value of derivatives of basis functions - dLBasisdx(q,1:2) = dQuadEdgePBasis(i,k+1,u,v,invert) - END IF - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Bubbles of p quadrilateral - IF ( Element % BDOFs > 0 ) THEN - ! Get element P - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p = CEILING( ( 5.0d0+SQRT(1.0d0+8.0d0*nb) ) / 2.0d0 - AEPS) - - ! For boundary element direction needs to be calculated - IF (Element % PDefs % isEdge) THEN - direction = 0 - direction = getSquareFaceDirection(Element, [ 1,2,3,4 ]) - END IF - - ! For each bubble calculate value of p basis function - ! and their derivatives for index pairs i,j>=2, i+j=4,...,p - DO i=2,(p-2) - DO j=2,(p-i) - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! Get values of bubble functions - ! 3D boundary elements have a direction - IF (Element % PDefs % isEdge) THEN - Basis(q) = QuadBubblePBasis(i,j,u,v,direction) - dLBasisdx(q,1:2) = dQuadBubblePBasis(i,j,u,v,direction) - ELSE - ! 2d element bubbles have no direction - Basis(q) = QuadBubblePBasis(i,j,u,v) - dLBasisdx(q,1:2) = dQuadBubblePBasis(i,j,u,v) - END IF - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = i+j - END DO - END DO - END IF -!------------------------------------------------------------------------------ -! P element code for tetrahedron edges, faces and bubbles - CASE(504) - ! Edges of p tetrahedron - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge calculate value of edge functions - DO i=1,6 - Edge => CurrentModel % Solver % Mesh % Edges (Element % EdgeIndexes(i)) - - ! Do not solve edge DOFS if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! For each DOF in edge calculate value of edge functions - ! and their derivatives for edge=i, i=k+1 - DO k=1, Edge % BDOFs - IF (q >= SIZE(Basis)) CYCLE - q = q + 1 - - Basis(q) = TetraEdgePBasis(i,k+1,u,v,w, Element % PDefs % TetraType) - dLBasisdx(q,1:3) = dTetraEdgePBasis(i,k+1,u,v,w, Element % PDefs % TetraType) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of p tetrahedron - IF ( ASSOCIATED( Element % FaceIndexes )) THEN - ! For each face calculate value of face functions - DO F=1,4 - Face => CurrentModel % Solver % Mesh % Faces (Element % FaceIndexes(F)) - - ! Do not solve face DOFs if there is not any - IF (Face % BDOFs <= 0) CYCLE - - ! Get face p - p = Face % PDefs % P - - ! For each DOF in face calculate value of face functions and - ! their derivatives for face=F and index pairs - ! i,j=0,..,p-3, i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF (q >= SIZE(Basis)) CYCLE - q = q + 1 - - Basis(q) = TetraFacePBasis(F,i,j,u,v,w, Element % PDefs % TetraType) - dLBasisdx(q,1:3) = dTetraFacePBasis(F,i,j,u,v,w, Element % PDefs % TetraType) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 3+i+j - END DO - END DO - END DO - END IF - - ! Bubbles of p tetrahedron - IF ( Element % BDOFs > 0 ) THEN - p = Element % PDefs % P - - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+2 - AEPS) - - ! For each DOF in bubbles calculate value of bubble functions - ! and their derivatives for index pairs - ! i,j,k=0,..,p-4 i+j+k=0,..,p-4 - DO i=0,p-4 - DO j=0,p-i-4 - DO k=0,p-i-j-4 - IF (q >= SIZE(Basis)) CYCLE - q = q + 1 - - Basis(q) = TetraBubblePBasis(i,j,k,u,v,w) - dLBasisdx(q,1:3) = dTetraBubblePBasis(i,j,k,u,v,w) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 4+i+j+k - END DO - END DO - END DO - - END IF -!------------------------------------------------------------------------------ -! P element code for pyramid edges, faces and bubbles - CASE(605) - ! Edges of P Pyramid - IF (ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in wedge, calculate values of edge functions - DO i=1,8 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! Get local indexes of current edge - tmp(1:2) = getPyramidEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Determine edge direction - invert = .FALSE. - - ! Invert edge if local first node has greater global index than second one - IF ( Element % NodeIndexes(locali) > Element % NodeIndexes(localj) ) invert = .TRUE. - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! Get values of edge basis functions and their derivatives - Basis(q) = PyramidEdgePBasis(i,k+1,u,v,w,invert) - dLBasisdx(q,1:3) = dPyramidEdgePBasis(i,k+1,u,v,w,invert) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P Pyramid - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in pyramid, calculate values of face functions - DO F=1,5 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not solve face dofs, if there is not any - IF ( Face % BDOFs <= 0) CYCLE - - ! Get face p - p = Face % PDefs % P - - ! Handle triangle and square faces separately - SELECT CASE(F) - CASE (1) - direction = 0 - ! Get global direction vector for enforcing parity - tmp(1:4) = getPyramidFaceMap(F) - direction(1:4) = getSquareFaceDirection( Element, tmp(1:4) ) - - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - Basis(q) = PyramidFacePBasis(F,i,j,u,v,w,direction) - dLBasisdx(q,:) = dPyramidFacePBasis(F,i,j,u,v,w,direction) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = i+j - END DO - END DO - - CASE (2,3,4,5) - direction = 0 - ! Get global direction vector for enforcing parity - tmp(1:4) = getPyramidFaceMap(F) - direction(1:3) = getTriangleFaceDirection( Element, tmp(1:3) ) - - ! For each face calculate values of functions from index - ! pairs i,j=0,..,p-3 i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - Basis(q) = PyramidFacePBasis(F,i,j,u,v,w,direction) - dLBasisdx(q,:) = dPyramidFacePBasis(F,i,j,u,v,w,direction) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 3+i+j - END DO - END DO - END SELECT - END DO - END IF - - ! Bubbles of P Pyramid - IF (Element % BDOFs > 0) THEN - ! Get element p - p = Element % PDefs % p - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+2 - AEPS) - - ! Calculate value of bubble functions from indexes - ! i,j,k=0,..,p-4 i+j+k=0,..,p-4 - DO i=0,p-4 - DO j=0,p-i-4 - DO k=0,p-i-j-4 - IF ( q >= SIZE(Basis)) CYCLE - q = q + 1 - - Basis(q) = PyramidBubblePBasis(i,j,k,u,v,w) - dLBasisdx(q,:) = dPyramidBubblePBasis(i,j,k,u,v,w) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 4+i+j+k - END DO - END DO - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for wedge edges, faces and bubbles - CASE(706) - ! Edges of P Wedge - IF (ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in wedge, calculate values of edge functions - DO i=1,9 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! Get local indexes of current edge - tmp(1:2) = getWedgeEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Determine edge direction - invert = .FALSE. - ! Invert edge if local first node has greater global index than second one - IF ( Element % NodeIndexes(locali) > Element % NodeIndexes(localj) ) invert = .TRUE. - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! Use basis compatible with pyramid if necessary - ! @todo Correct this! - IF (Edge % PDefs % pyramidQuadEdge) THEN - CALL Fatal('ElementInfo','Pyramid compatible wedge edge basis NIY!') - END IF - - ! Get values of edge basis functions and their derivatives - Basis(q) = WedgeEdgePBasis(i,k+1,u,v,w,invert) - dLBasisdx(q,1:3) = dWedgeEdgePBasis(i,k+1,u,v,w,invert) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P Wedge - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in wedge, calculate values of face functions - DO F=1,5 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not solve face dofs, if there is not any - IF ( Face % BDOFs <= 0) CYCLE - - p = Face % PDefs % P - - ! Handle triangle and square faces separately - SELECT CASE(F) - CASE (1,2) - direction = 0 - ! Get global direction vector for enforcing parity - tmp(1:4) = getWedgeFaceMap(F) - direction(1:3) = getTriangleFaceDirection( Element, tmp(1:3) ) - - ! For each face calculate values of functions from index - ! pairs i,j=0,..,p-3 i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - Basis(q) = WedgeFacePBasis(F,i,j,u,v,w,direction) - dLBasisdx(q,:) = dWedgeFacePBasis(F,i,j,u,v,w,direction) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 3+i+j - END DO - END DO - CASE (3,4,5) - direction = 0 - ! Get global direction vector for enforcing parity - invert = .FALSE. - tmp(1:4) = getWedgeFaceMap(F) - direction(1:4) = getSquareFaceDirection( Element, tmp(1:4) ) - - ! First and second node must form a face in upper or lower triangle - IF (.NOT. wedgeOrdering(direction)) THEN - invert = .TRUE. - tmp(1) = direction(2) - direction(2) = direction(4) - direction(4) = tmp(1) - END IF - - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - IF (.NOT. invert) THEN - Basis(q) = WedgeFacePBasis(F,i,j,u,v,w,direction) - dLBasisdx(q,:) = dWedgeFacePBasis(F,i,j,u,v,w,direction) - ELSE - Basis(q) = WedgeFacePBasis(F,j,i,u,v,w,direction) - dLBasisdx(q,:) = dWedgeFacePBasis(F,j,i,u,v,w,direction) - END IF - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = i+j - END DO - END DO - END SELECT - - END DO - END IF - - ! Bubbles of P Wedge - IF ( Element % BDOFs > 0 ) THEN - ! Get p from element - p = Element % PDefs % P - nb = MAX( GetBubbleDOFs( Element, p ), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+3 - AEPS) - - ! For each bubble calculate value of basis function and its derivative - ! for index pairs i,j=0,..,p-5 k=2,..,p-3 i+j+k=2,..,p-3 - DO i=0,p-5 - DO j=0,p-5-i - DO k=2,p-3-i-j - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - Basis(q) = WedgeBubblePBasis(i,j,k,u,v,w) - dLBasisdx(q,:) = dWedgeBubblePBasis(i,j,k,u,v,w) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 3+i+j+k - END DO - END DO - END DO - END IF - -!------------------------------------------------------------------------------ -! P element code for brick edges, faces and bubbles - CASE(808) - ! Edges of P brick - IF ( ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in brick, calculate values of edge functions - DO i=1,12 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! Get local indexes of current edge - tmp(1:2) = getBrickEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Determine edge direction - invert = .FALSE. - - ! Invert edge if local first node has greater global index than second one - IF ( Element % NodeIndexes(locali) > Element % NodeIndexes(localj) ) invert = .TRUE. - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - - ! For edges connected to pyramid square face, use different basis - IF (Edge % PDefs % pyramidQuadEdge) THEN - ! Get values of edge basis functions and their derivatives - Basis(q) = BrickPyraEdgePBasis(i,k+1,u,v,w,invert) - dLBasisdx(q,1:3) = dBrickPyraEdgePBasis(i,k+1,u,v,w,invert) - ! Normal case. Use standard brick edge functions - ELSE - ! Get values of edge basis functions and their derivatives - Basis(q) = BrickEdgePBasis(i,k+1,u,v,w,invert) - dLBasisdx(q,1:3) = dBrickEdgePBasis(i,k+1,u,v,w,invert) - END IF - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = 1+k - END DO - END DO - END IF - - ! Faces of P brick - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in brick, calculate values of face functions - DO F=1,6 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not calculate face values if no dofs - IF (Face % BDOFs <= 0) CYCLE - - ! Get p for face - p = Face % PDefs % P - - ! Generate direction vector for this face - tmp(1:4) = getBrickFaceMap(F) - direction(1:4) = getSquareFaceDirection(Element, tmp) - - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - Basis(q) = BrickFacePBasis(F,i,j,u,v,w,direction) - dLBasisdx(q,:) = dBrickFacePBasis(F,i,j,u,v,w,direction) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = i+j - END DO - END DO - END DO - END IF - - ! Bubbles of p brick - IF ( Element % BDOFs > 0 ) THEN - ! Get p from bubble DOFs - p = Element % PDefs % P - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+4 - AEPS) - - - ! For each bubble calculate value of basis function and its derivative - ! for index pairs i,j,k=2,..,p-4, i+j+k=6,..,p - DO i=2,p-4 - DO j=2,p-i-2 - DO k=2,p-i-j - IF ( q >= SIZE(Basis) ) CYCLE - q = q + 1 - Basis(q) = BrickBubblePBasis(i,j,k,u,v,w) - dLBasisdx(q,:) = dBrickBubblePBasis(i,j,k,u,v,w) - - ! Polynomial degree of basis function to vector - IF (degrees) BasisDegree(q) = i+j+k - END DO - END DO - END DO - END IF - - END SELECT - END IF ! P element flag check -!------------------------------------------------------------------------------ - - ! Element (contravariant) metric and square root of determinant - !-------------------------------------------------------------- - IF ( .NOT. ElementMetric( q, Element, Nodes, & - ElmMetric, detJ, dLBasisdx, LtoGMap ) ) THEN - stat = .FALSE. - RETURN - END IF - - ! Get global first derivatives: - !------------------------------ - IF ( PRESENT(dBasisdx) ) THEN - dBasisdx = 0.0d0 - DO i=1,q - DO j=1,cdim - DO k=1,dim - dBasisdx(i,j) = dBasisdx(i,j) + dLBasisdx(i,k)*LtoGMap(j,k) - END DO - END DO - END DO - END IF - - ! Get matrix of second derivatives, if needed: - !--------------------------------------------- - IF ( PRESENT(ddBasisddx) .AND. PRESENT(SecondDerivatives) ) THEN - IF ( SecondDerivatives ) THEN - NodalBasis = 0.0d0 - ddBasisddx(1:n,:,:) = 0.0d0 - DO q=1,n - NodalBasis(q) = 1.0d0 - CALL GlobalSecondDerivatives(Element,Nodes,NodalBasis, & - ddBasisddx(q,:,:),u,v,w,ElmMetric,dLBasisdx ) - NodalBasis(q) = 0.0d0 - END DO - END IF - END IF - -!------------------------------------------------------------------------------ -! Generate bubble basis functions, if requested. Bubble basis is as follows: -! B_i (=(N_(i+n)) = B * N_i, where N_i:s are the nodal basis functions of -! the element, and B the basic bubble, i.e. the product of nodal basis -! functions of the corresponding linear element for triangles and tetras, -! and product of two diagonally opposed nodal basisfunctions of the -! corresponding (bi-,tri-)linear element for 1d-elements, quads and hexas. -!------------------------------------------------------------------------------ - IF ( PRESENT( Bubbles ) ) THEN - Bubble % BDOFs = 0 - NULLIFY( Bubble % PDefs ) - NULLIFY( Bubble % EdgeIndexes ) - NULLIFY( Bubble % FaceIndexes ) - NULLIFY( Bubble % BubbleIndexes ) - - IF ( Bubbles .AND. SIZE(Basis) >= 2*n ) THEN - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(2) - - IF ( Element % TYPE % ElementCode == 202 ) THEN - LinBasis(1:n) = Basis(1:n) - dLinBasisdx(1:n,1:cdim) = dBasisdx(1:n,1:cdim) - ELSE - Bubble % TYPE => GetElementType(202) - - stat = ElementInfo( Bubble, nodes, u, v, w, detJ, & - LinBasis, dLinBasisdx ) - END IF - - BubbleValue = LinBasis(1) * LinBasis(2) - - DO i=1,n - Basis(n+i) = Basis(i) * BubbleValue - DO j=1,cdim - dBasisdx(n+i,j) = dBasisdx(i,j) * BubbleValue - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(1,j) * LinBasis(2) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(2,j) * LinBasis(1) - END DO - END DO - - CASE(3) - - IF ( Element % TYPE % ElementCode == 303 ) THEN - LinBasis(1:n) = Basis(1:n) - dLinBasisdx(1:n,1:cdim) = dBasisdx(1:n,1:cdim) - ELSE - Bubble % TYPE => GetElementType(303) - - stat = ElementInfo( Bubble, nodes, u, v, w, detJ, & - LinBasis, dLinBasisdx ) - END IF - - BubbleValue = LinBasis(1) * LinBasis(2) * LinBasis(3) - - DO i=1,n - Basis(n+i) = Basis(i) * BubbleValue - DO j=1,cdim - dBasisdx(n+i,j) = dBasisdx(i,j) * BubbleValue - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(1,j) * LinBasis(2) * LinBasis(3) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(2,j) * LinBasis(1) * LinBasis(3) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(3,j) * LinBasis(1) * LinBasis(2) - END DO - END DO - - CASE(4) - - IF ( Element % TYPE % ElementCode == 404 ) THEN - LinBasis(1:n) = Basis(1:n) - dLinBasisdx(1:n,1:cdim) = dBasisdx(1:n,1:cdim) - ELSE - Bubble % TYPE => GetElementType(404) - - stat = ElementInfo( Bubble, nodes, u, v, w, detJ, & - LinBasis, dLinBasisdx ) - END IF - - BubbleValue = LinBasis(1) * LinBasis(3) - - DO i=1,n - Basis(n+i) = Basis(i) * BubbleValue - DO j=1,cdim - dBasisdx(n+i,j) = dBasisdx(i,j) * BubbleValue - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(1,j) * LinBasis(3) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(3,j) * LinBasis(1) - END DO - END DO - - CASE(5) - - IF ( Element % TYPE % ElementCode == 504 ) THEN - LinBasis(1:n) = Basis(1:n) - dLinBasisdx(1:n,1:cdim) = dBasisdx(1:n,1:cdim) - ELSE - Bubble % TYPE => GetElementType(504) - - stat = ElementInfo( Bubble, nodes, u, v, w, detJ, & - LinBasis, dLinBasisdx ) - END IF - - BubbleValue = LinBasis(1) * LinBasis(2) * LinBasis(3) * LinBasis(4) - DO i=1,n - Basis(n+i) = Basis(i) * BubbleValue - DO j=1,cdim - dBasisdx(n+i,j) = dBasisdx(i,j) * BubbleValue - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * dLinBasisdx(1,j) * & - LinBasis(2) * LinBasis(3) * LinBasis(4) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * dLinBasisdx(2,j) * & - LinBasis(1) * LinBasis(3) * LinBasis(4) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * dLinBasisdx(3,j) * & - LinBasis(1) * LinBasis(2) * LinBasis(4) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * dLinBasisdx(4,j) * & - LinBasis(1) * LinBasis(2) * LinBasis(3) - END DO - END DO - - CASE(8) - - IF ( Element % TYPE % ElementCode == 808 ) THEN - LinBasis(1:n) = Basis(1:n) - dLinBasisdx(1:n,1:cdim) = dBasisdx(1:n,1:cdim) - ELSE - Bubble % TYPE => GetElementType(808) - - stat = ElementInfo( Bubble, nodes, u, v, w, detJ, & - LinBasis, dLinBasisdx ) - END IF - - BubbleValue = LinBasis(1) * LinBasis(7) - - DO i=1,n - Basis(n+i) = Basis(i) * BubbleValue - DO j=1,cdim - dBasisdx(n+i,j) = dBasisdx(i,j) * BubbleValue - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(1,j) * LinBasis(7) - - dBasisdx(n+i,j) = dBasisdx(n+i,j) + Basis(i) * & - dLinBasisdx(7,j) * LinBasis(1) - END DO - END DO - - CASE DEFAULT - - WRITE( Message, '(a,i4,a)' ) 'Bubbles for element: ', & - Element % TYPE % ElementCode, ' are not implemented.' - CALL Error( 'ElementInfo', Message ) - CALL Fatal( 'ElementInfo', 'Please use p-element basis instead.' ) - - END SELECT - END IF - END IF -!------------------------------------------------------------------------------ - END FUNCTION ElementInfo -!------------------------------------------------------------------------------ - - ! SUBROUTINE ElementInfoVec_InitWork(m, n) - ! IMPLICIT NONE - - ! INTEGER, INTENT(IN) :: m, n - ! INTEGER :: allocstat - - ! allocstat = 0 - ! IF (.NOT. ALLOCATED(BasisWrk)) THEN - ! ALLOCATE(BasisWrk(m,n), & - ! dBasisdxWrk(m,n,3), & - ! LtoGMapsWrk(m,3,3), & - ! DetJWrk(m), & - ! uWrk(m), vWrk(m), wWrk(m), STAT=allocstat) - ! ELSE IF (SIZE(BasisWrk,1) /= m .OR. SIZE(BasisWrk,2) /= n) THEN - ! DEALLOCATE(BasisWrk, dBasisdxWrk, LtoGMapsWrk, DetJWrk, uWrk, vWrk, wWrk) - ! ALLOCATE(BasisWrk(m,n), & - ! dBasisdxWrk(m,n,3), & - ! LtoGMapsWrk(m,3,3), & - ! DetJWrk(m), & - ! uWrk(m), vWrk(m), wWrk(m), STAT=allocstat) - ! END IF - - ! ! Check memory allocation status - ! IF (allocstat /= 0) THEN - ! CALL Error('ElementInfo_InitWork','Storage allocation for local element basis failed') - ! END IF - ! END SUBROUTINE ElementInfoVec_InitWork - - ! SUBROUTINE ElementInfoVec_FreeWork() - ! IMPLICIT NONE - - ! IF (ALLOCATED(BasisWrk)) THEN - ! DEALLOCATE(BasisWrk, dBasisdxWrk, LtoGMapsWrk, DetJWrk, uWrk, vWrk, wWrk) - ! END IF - ! END SUBROUTINE ElementInfoVec_FreeWork - -! ElementInfoVec currently uses only P element definitions for basis -! functions, even for purely nodal elements. Support for standard nodal elements -! will be implemented in the future. -!------------------------------------------------------------------------------ - FUNCTION ElementInfoVec( Element, Nodes, nc, u, v, w, detJ, nbmax, Basis, dBasisdx ) RESULT(retval) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Element nodal coordinates. - INTEGER, INTENT(IN) :: nc !< Number of local coordinates to compute values of the basis function - REAL(KIND=dp), POINTER CONTIG :: u(:) !< 1st local coordinates at which to calculate the basis function. - REAL(KIND=dp), POINTER CONTIG :: v(:) !< 2nd local coordinates. - REAL(KIND=dp), POINTER CONTIG :: w(:) !< 3rd local coordinates. - REAL(KIND=dp) CONTIG, INTENT(OUT) :: detJ(:) !< Square roots of determinants of element coordinate system metric at coordinates - INTEGER, INTENT(IN) :: nbmax !< Maximum number of basis functions to compute - REAL(KIND=dp) CONTIG :: Basis(:,:) !< Basis function values at (u,v,w) - REAL(KIND=dp) CONTIG, OPTIONAL :: dBasisdx(:,:,:) !< Global first derivatives of basis functions at (u,v,w) - LOGICAL :: retval !< If .FALSE. element is degenerate. or if local storage allocation fails - - ! Internal work arrays (always needed) - REAL(KIND=dp) :: uWrk(VECTOR_BLOCK_LENGTH), vWrk(VECTOR_BLOCK_LENGTH), wWrk(VECTOR_BLOCK_LENGTH) - REAL(KIND=dp) :: BasisWrk(VECTOR_BLOCK_LENGTH,nbmax) - REAL(KIND=dp) :: dBasisdxWrk(VECTOR_BLOCK_LENGTH,nbmax,3) - REAL(KIND=dp) :: DetJWrk(VECTOR_BLOCK_LENGTH) - REAL(KIND=dp) :: LtoGMapsWrk(VECTOR_BLOCK_LENGTH,3,3) - - INTEGER :: i -!DIR$ ATTRIBUTES ALIGN:64::uWrk, vWrk, wWrk, BasisWrk, dBasisdxWrk, DetJWrk, LtoGMapsWrk - - !------------------------------------------------------------------------------ - ! Special case, Element: POINT - IF (Element % TYPE % ElementCODE == 101) THEN - DetJ(1:nc) = REAL(1, dp) - Basis(1:nc,1) = REAL(1, dp) - IF (PRESENT(dBasisdx)) THEN - DO i=1,nc - dBasisdx(i,1,1) = REAL(0, dp) - END DO - END IF - retval = .TRUE. - RETURN - END IF - - ! Set up workspace arrays - ! CALL ElementInfoVec_InitWork(VECTOR_BLOCK_LENGTH, nbmax) - IF ( nbmax < Element % TYPE % NumberOfNodes ) THEN - CALL Fatal('ElementInfoVec','Not enough storage to compute local element basis') - END IF - - IF(PRESENT(dBasisdx)) & - dBasisdx = 0._dp ! avoid unitialized stuff depending on coordinate dimension... - - retval = ElementInfoVec_ComputePElementBasis(Element,Nodes,nc,u,v,w,detJ,nbmax,Basis,& - uWrk,vWrk,wWrk,BasisWrk,dBasisdxWrk,DetJWrk,LtoGmapsWrk,dBasisdx) - END FUNCTION ElementInfoVec - - FUNCTION ElementInfoVec_ComputePElementBasis(Element, Nodes, nc, u, v, w, DetJ, nbmax, Basis, & - uWrk, vWrk, wWrk, BasisWrk, dBasisdxWrk, & - DetJWrk, LtoGmapsWrk, dBasisdx) RESULT(retval) - IMPLICIT NONE - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Element nodal coordinates. - INTEGER, INTENT(IN) :: nc !< Number of local coordinates to compute values of the basis function - REAL(KIND=dp), POINTER CONTIG :: u(:) !< 1st local coordinates at which to calculate the basis function. - REAL(KIND=dp), POINTER CONTIG :: v(:) !< 2nd local coordinates. - REAL(KIND=dp), POINTER CONTIG :: w(:) !< 3rd local coordinates. - REAL(KIND=dp) CONTIG, INTENT(OUT) :: detJ(:) !< Square roots of determinants of element coordinate system metric at coordinates - INTEGER, INTENT(IN) :: nbmax !< Maximum number of basis functions to compute - REAL(KIND=dp) CONTIG :: Basis(:,:) !< Basis function values at (u,v,w) - ! Internal work arrays - REAL(KIND=dp) :: uWrk(VECTOR_BLOCK_LENGTH), vWrk(VECTOR_BLOCK_LENGTH), wWrk(VECTOR_BLOCK_LENGTH) - REAL(KIND=dp) :: BasisWrk(VECTOR_BLOCK_LENGTH,nbmax) - REAL(KIND=dp) :: dBasisdxWrk(VECTOR_BLOCK_LENGTH,nbmax,3) - REAL(KIND=dp) :: DetJWrk(VECTOR_BLOCK_LENGTH) - REAL(KIND=dp) :: LtoGMapsWrk(VECTOR_BLOCK_LENGTH,3,3) - REAL(KIND=dp) CONTIG, OPTIONAL :: dBasisdx(:,:,:) !< Global first derivatives of basis functions at (u,v,w) - LOGICAL :: retval !< If .FALSE. element is degenerate. or if local storage allocation fails - - - !------------------------------------------------------------------------------ - ! Local variables - !------------------------------------------------------------------------------ - INTEGER :: EdgeDegree(H1Basis_MaxPElementEdges), & - FaceDegree(H1Basis_MaxPElementFaces), & - EdgeDirection(H1Basis_MaxPElementEdgeNodes,H1Basis_MaxPElementEdges), & - FaceDirection(H1Basis_MaxPElementFaceNodes,H1Basis_MaxPElementFaces) - - INTEGER :: cdim, dim, i, j, k, l, ll, lln, ncl, ip, n, p, nb, & - nbp, nbq, nbdxp, allocstat, ncpad, EdgeMaxDegree, FaceMaxDegree - - - LOGICAL :: invertBubble, elem - -!DIR$ ATTRIBUTES ALIGN:64::EdgeDegree, FaceDegree -!DIR$ ATTRIBUTES ALIGN:64::EdgeDirection, FaceDirection -!DIR$ ASSUME_ALIGNED uWrk:64, vWrk:64, wWrk:64, BasisWrk:64, dBasisdxWrk:64, DetJWrk:64, LtoGMapsWrk:64 - - retval = .TRUE. - n = Element % TYPE % NumberOfNodes - dim = Element % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - - dBasisdxWrk = 0._dp ! avoid unitialized stuff depending on coordinate dimension... - - ! Block the computation for large values of input points - DO ll=1,nc,VECTOR_BLOCK_LENGTH - lln = MIN(ll+VECTOR_BLOCK_LENGTH-1,nc) - ncl = lln-ll+1 - - ! Set number of computed basis functions - nbp = 0 - nbdxp = 0 - - ! Block copy input - uWrk(1:ncl) = u(ll:lln) - IF (cdim > 1) THEN - vWrk(1:ncl) = v(ll:lln) - END IF - IF (cdim > 2) THEN - wWrk(1:ncl) = w(ll:lln) - END IF - - ! Compute local p element basis - SELECT CASE (Element % Type % ElementCode) - ! Element: LINE - CASE (202) - ! Compute nodal basis - CALL H1Basis_LineNodal(ncl, uWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dLineNodal(ncl, uWrk, nbmax, dBasisdxWrk, nbdxp) - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! For first round of blocked loop, compute edge direction - IF (ll==1) THEN - ! Compute P from bubble dofs - P = Element % BDOFS + 1 - - IF (Element % PDefs % isEdge .AND. & - Element % NodeIndexes(1)> Element % NodeIndexes(2)) THEN - invertBubble = .TRUE. - ELSE - invertBubble = .FALSE. - END IF - END IF - - CALL H1Basis_LineBubbleP(ncl, uWrk, P, nbmax, BasisWrk, nbp, invertBubble) - CALL H1Basis_dLineBubbleP(ncl, uWrk, P, nbmax, dBasisdxWrk, nbdxp, invertBubble) - END IF - - ! Element: TRIANGLE - CASE (303) - ! Compute nodal basis - CALL H1Basis_TriangleNodalP(ncl, uWrk, vWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dTriangleNodalP(ncl, uWrk, vWrk, nbmax, dBasisdxWrk, nbdxp) - - IF (ASSOCIATED( Element % EdgeIndexes)) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - CALL GetElementMeshEdgeInfo(CurrentModel % Solver % Mesh, & - Element, EdgeDegree, EdgeDirection, EdgeMaxDegree) - END IF - - ! Compute basis function values - IF (EdgeMaxDegree>1 ) THEN - nbq = nbp + SUM(EdgeDegree(1:3)-1) - IF(nbmax >= nbq ) THEN - CALL H1Basis_TriangleEdgeP(ncl, uWrk, vWrk, EdgeDegree, nbmax, BasisWrk, & - nbp, EdgeDirection) - CALL H1Basis_dTriangleEdgeP(ncl, uWrk, vWrk, EdgeDegree, nbmax, dBasisdxWrk, & - nbdxp, EdgeDirection) - END IF - END IF - END IF - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - ! Compute P from bubble dofs - P = CEILING( ( 3.0d0+SQRT(1.0d0+8.0d0*(Element % BDOFS)) ) / 2.0d0 - AEPS) - - IF (Element % PDefs % isEdge) THEN - ! Get 2D face direction - CALL H1Basis_GetFaceDirection(Element % Type % ElementCode, & - 1, & - Element % NodeIndexes, & - FaceDirection) - END IF - END IF - IF (Element % PDefs % isEdge) THEN - CALL H1Basis_TriangleBubbleP(ncl, uWrk, vWrk, P, nbmax, BasisWrk, nbp, & - FaceDirection(1:3,1)) - CALL H1Basis_dTriangleBubbleP(ncl, uWrk, vWrk, P, nbmax, dBasisdxWrk, nbdxp, & - FaceDirection(1:3,1)) - ELSE - CALL H1Basis_TriangleBubbleP(ncl, uWrk, vWrk, P, nbmax, BasisWrk, nbp) - CALL H1Basis_dTriangleBubbleP(ncl, uWrk, vWrk, P, nbmax, dBasisdxWrk, nbdxp) - END IF - END IF - - ! QUADRILATERAL - CASE (404) - ! Compute nodal basis - CALL H1Basis_QuadNodal(ncl, uWrk, vWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dQuadNodal(ncl, uWrk, vWrk, nbmax, dBasisdxWrk, nbdxp) - - IF (ASSOCIATED( Element % EdgeIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - CALL GetElementMeshEdgeInfo(CurrentModel % Solver % Mesh, & - Element, EdgeDegree, EdgeDirection, EdgeMaxDegree) - END IF - - ! Compute basis function values - IF (EdgeMaxDegree > 1) THEN - nbq = nbp + SUM(EdgeDegree(1:4)-1) - IF(nbmax >= nbq) THEN - CALL H1Basis_QuadEdgeP(ncl, uWrk, vWrk, EdgeDegree, nbmax, BasisWrk, nbp, & - EdgeDirection) - CALL H1Basis_dQuadEdgeP(ncl, uWrk, vWrk, EdgeDegree, nbmax, dBasisdxWrk, nbdxp, & - EdgeDirection) - END IF - END IF - END IF - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - ! Compute P from bubble dofs - P = CEILING( ( 5.0d0+SQRT(1.0d0+8.0d0*(Element % BDOFS)) ) / 2.0d0 - AEPS ) - - IF (Element % PDefs % isEdge) THEN - ! Get 2D face direction - CALL H1Basis_GetFaceDirection(Element % Type % ElementCode, & - 1, & - Element % NodeIndexes, & - FaceDirection) - END IF - END IF - - IF (Element % PDefs % isEdge) THEN - CALL H1Basis_QuadBubbleP(ncl, uWrk, vWrk, P, nbmax, BasisWrk, nbp, & - FaceDirection(1:4,1)) - CALL H1Basis_dQuadBubbleP(ncl, uWrk, vWrk, P, nbmax, dBasisdxWrk, nbdxp, & - FaceDirection(1:4,1)) - ELSE - CALL H1Basis_QuadBubbleP(ncl, uWrk, vWrk, P, nbmax, BasisWrk, nbp) - CALL H1Basis_dQuadBubbleP(ncl, uWrk, vWrk, P, nbmax, dBasisdxWrk, nbdxp) - END IF - END IF - - ! TETRAHEDRON - CASE (504) - ! Compute nodal basis - CALL H1Basis_TetraNodalP(ncl, uWrk, vWrk, wWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dTetraNodalP(ncl, uWrk, vWrk, wWrk, nbmax, dBasisdxWrk, nbdxp) - - IF (ASSOCIATED( Element % EdgeIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - ! Get polynomial degree of each edge - EdgeMaxDegree = 0 - IF( CurrentModel % Solver % Mesh % MaxEdgeDofs == 0 ) THEN - CONTINUE - ELSE IF (CurrentModel % Solver % Mesh % MinEdgeDOFs == & - CurrentModel % Solver % Mesh % MaxEdgeDOFs) THEN - EdgeMaxDegree = Element % BDOFs+1 - EdgeDegree(1:Element % Type % NumberOfFaces) = EdgeMaxDegree - ELSE - DO i=1,6 - EdgeDegree(i) = CurrentModel % Solver % & - Mesh % Edges( Element % EdgeIndexes(i) ) % BDOFs + 1 - EdgeMaxDegree = MAX(EdgeDegree(i),EdgeMaxDegree) - END DO - END IF - - ! Tetrahedral directions are enforced by tetra element types - IF (EdgeMaxDegree > 1) THEN - CALL H1Basis_GetTetraEdgeDirection(Element % PDefs % TetraType, EdgeDirection) - END IF - END IF - - ! Compute basis function values - IF (EdgeMaxDegree > 1) THEN - nbq = nbp + SUM(EdgeDegree(1:6)-1) - IF(nbmax >= nbq) THEN - CALL H1Basis_TetraEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, BasisWrk, nbp, & - EdgeDirection) - CALL H1Basis_dTetraEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, dBasisdxWrk, nbdxp, & - EdgeDirection) - END IF - END IF - END IF - - IF (ASSOCIATED( Element % FaceIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! face directions - IF (ll==1) THEN - ! Get polynomial degree of each face - FaceMaxDegree = 0 - - IF( CurrentModel % Solver % Mesh % MaxFaceDofs == 0 ) THEN - CONTINUE - ELSE IF (CurrentModel % Solver % Mesh % MinFaceDOFs == & - CurrentModel % Solver % Mesh % MaxFaceDOFs) THEN - FaceMaxDegree = CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(1) ) % PDefs % P - FaceDegree(1:Element % Type % NumberOfFaces) = FaceMaxDegree - ELSE - DO i=1,4 - IF (CurrentModel % Solver % Mesh % & - Faces( Element % FaceIndexes(i) ) % BDOFs /= 0) THEN - FaceDegree(i) = CurrentModel % Solver % Mesh % & - Faces( Element % FaceIndexes(i) ) % PDefs % P - FaceMaxDegree = MAX(FaceDegree(i), FaceMaxDegree) - ELSE - FaceDegree(i) = 0 - END IF - END DO - END IF - - IF (FaceMaxDegree > 1) THEN - CALL H1Basis_GetTetraFaceDirection(Element % PDefs % TetraType, FaceDirection) - END IF - END IF - - ! Compute basis function values - IF (FaceMaxDegree>1 ) THEN - nbq = nbp - DO i=1,4 - DO j=0,FaceDegree(i) - nbq = nbq + MAX(FaceDegree(i)-j-2,0) - END DO - END DO - - IF (nbmax >= nbq ) THEN - CALL H1Basis_TetraFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, BasisWrk, nbp, & - FaceDirection) - CALL H1Basis_dTetraFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, dBasisdxWrk, nbdxp, & - FaceDirection) - END IF - END IF - END IF - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! Compute P based on bubble dofs - nb = Element % BDOFs - p = CEILING( 1/3._dp*(81*nb+3*SQRT(-3._dp+729*nb**2))**(1/3._dp) + & - 1d0/(81*nb+3*SQRT(-3._dp+729*nb**2))**(1/3._dp)+2 - AEPS ) - - CALL H1Basis_TetraBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, BasisWrk, nbp) - CALL H1Basis_dTetraBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, dBasisdxWrk, nbdxp) - END IF - - ! TEMPORARY NONVECTORIZED PYRAMID - CASE (605) -BLOCK - INTEGER :: F, locali, localj, nb, q, tmp(4), direction(4) - LOGICAL :: invert - TYPE(Element_t), POINTER :: Face, Edge - - dBasisdxWrk(1:ncl,:,:) = 0.0d0 - BasisWrk(1:ncl,:) = 0.0d0 - DO l=1,ncl - CALL NodalBasisFunctions(5, BasisWrk(l,:), element, uWrk(l), vWrk(l), wWrk(l)) - CALL NodalFirstDerivatives(5, dBasisdxWrk(l,:,:), element, uWrk(l), vWrk(l), wWrk(l) ) - - q = 5 - - ! Edges of P Pyramid - IF (ASSOCIATED( Element % EdgeIndexes ) ) THEN - ! For each edge in wedge, calculate values of edge functions - DO i=1,8 - Edge => CurrentModel % Solver % Mesh % Edges( Element % EdgeIndexes(i) ) - - ! Do not solve edge dofs, if there is not any - IF (Edge % BDOFs <= 0) CYCLE - - ! Get local indexes of current edge - tmp(1:2) = getPyramidEdgeMap(i) - locali = tmp(1) - localj = tmp(2) - - ! Determine edge direction - invert = .FALSE. - - ! Invert edge if local first node has greater global index than second one - IF ( Element % NodeIndexes(locali) > Element % NodeIndexes(localj) ) invert = .TRUE. - - ! For each DOF in edge calculate values of edge functions - ! and their derivatives for edge=i and i=k+1 - DO k=1,Edge % BDOFs - IF ( q >= SIZE(BasisWrk,2) ) CYCLE - q = q + 1 - - ! Get values of edge basis functions and their derivatives - BasisWrk(l,q) = PyramidEdgePBasis(i,k+1,uwrk(l),vwrk(l),wwrk(l),invert) - dBasisdxWrk(l,q,1:3) = dPyramidEdgePBasis(i,k+1,uwrk(l),vwrk(l),wwrk(l),invert) - END DO - END DO - END IF - - ! Faces of P Pyramid - IF ( ASSOCIATED( Element % FaceIndexes ) ) THEN - ! For each face in pyramid, calculate values of face functions - DO F=1,5 - Face => CurrentModel % Solver % Mesh % Faces( Element % FaceIndexes(F) ) - - ! Do not solve face dofs, if there is not any - IF ( Face % BDOFs <= 0) CYCLE - - ! Get face p - p = Face % PDefs % P - - ! Handle triangle and square faces separately - SELECT CASE(F) - CASE (1) - direction = 0 - ! Get global direction vector for enforcing parity - tmp(1:4) = getPyramidFaceMap(F) - direction(1:4) = getSquareFaceDirection( Element, tmp(1:4) ) - - ! For each face calculate values of functions from index - ! pairs i,j=2,..,p-2 i+j=4,..,p - DO i=2,p-2 - DO j=2,p-i - IF ( q >= SIZE(BasisWrk,2) ) CYCLE - q = q + 1 - - BasisWrk(l,q) = PyramidFacePBasis(F,i,j,uwrk(l),vwrk(l),wwrk(l),direction) - dBasisdxWrk(l,q,:) = dPyramidFacePBasis(F,i,j,uwrk(l),vwrk(l),wwrk(l),direction) - END DO - END DO - - CASE (2,3,4,5) - direction = 0 - ! Get global direction vector for enforcing parity - tmp(1:4) = getPyramidFaceMap(F) - direction(1:3) = getTriangleFaceDirection( Element, tmp(1:3) ) - - ! For each face calculate values of functions from index - ! pairs i,j=0,..,p-3 i+j=0,..,p-3 - DO i=0,p-3 - DO j=0,p-i-3 - IF ( q >= SIZE(BasisWrk,2) ) CYCLE - q = q + 1 - - BasisWrk(l,q) = PyramidFacePBasis(F,i,j,uwrk(l),vwrk(l),wwrk(l),direction) - dBasisdxWrk(l,q,:) = dPyramidFacePBasis(F,i,j,uwrk(l),vwrk(l),wwrk(l),direction) - END DO - END DO - END SELECT - END DO - END IF - - ! Bubbles of P Pyramid - IF (Element % BDOFs > 0) THEN - ! Get element p - p = Element % PDefs % p - nb = MAX( GetBubbleDOFs(Element, p), Element % BDOFs ) - p=CEILING(1/3d0*(81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+1d0/ & - (81*nb+3*SQRT(-3d0+729*nb**2))**(1/3d0)+2 - AEPS) - - ! Calculate value of bubble functions from indexes - ! i,j,k=0,..,p-4 i+j+k=0,..,p-4 - DO i=0,p-4 - DO j=0,p-i-4 - DO k=0,p-i-j-4 - IF ( q >= SIZE(BasisWrk,2)) CYCLE - q = q + 1 - - BasisWrk(l,q) = PyramidBubblePBasis(i,j,k,uwrk(l),vwrk(l),wwrk(l)) - dBasisdxWrk(l,q,:) = dPyramidBubblePBasis(i,j,k,uwrk(l),vwrk(l),wwrk(l)) - END DO - END DO - END DO - END IF - END DO - - nbp = q -!------------------------------------------------------------------------------ -END BLOCK - - - ! WEDGE - CASE (706) - ! Compute nodal basis - CALL H1Basis_WedgeNodalP(ncl, uWrk, vWrk, wWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dWedgeNodalP(ncl, uWrk, vWrk, wWrk, nbmax, dBasisdxWrk, nbdxp) - - IF (ASSOCIATED( Element % EdgeIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - CALL GetElementMeshEdgeInfo(CurrentModel % Solver % Mesh, & - Element, EdgeDegree, EdgeDirection, EdgeMaxDegree) - END IF - - ! Compute basis function values - IF (EdgeMaxDegree > 1)THEN - nbq = nbp+SUM(EdgeDegree(1:9)-1) - IF(nbmax >= nbq) THEN - CALL H1Basis_WedgeEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, BasisWrk, nbp, & - EdgeDirection) - CALL H1Basis_dWedgeEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, dBasisdxWrk, nbdxp, & - EdgeDirection) - END IF - END IF - END IF - - IF (ASSOCIATED( Element % FaceIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! face directions - IF (ll==1) THEN - CALL GetElementMeshFaceInfo(CurrentModel % Solver % Mesh, & - Element, FaceDegree, FaceDirection, FaceMaxDegree) - END IF - - ! Compute basis function values - IF (FaceMaxDegree > 1 ) THEN - nbq = nbp - ! Triangle faces - DO i=1,2 - DO j=0,FaceDegree(i)-3 - nbq = nbq + MAX(FaceDegree(i)-j-2,0) - END DO - END DO - ! Square faces - DO i=3,5 - DO j=2,FaceDegree(i)-2 - nbq = nbq + MAX(FaceDegree(i)-j-1,0) - END DO - END DO - - IF(nbmax >= nbq) THEN - CALL H1Basis_WedgeFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, BasisWrk, nbp, & - FaceDirection) - CALL H1Basis_dWedgeFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, dBasisdxWrk, nbdxp, & - FaceDirection) - END IF - END IF - END IF - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! Compute P from bubble dofs - P=CEILING(1/3d0*(81*(Element % BDOFS) + & - 3*SQRT(-3d0+729*(Element % BDOFS)**2))**(1/3d0) + & - 1d0/(81*(Element % BDOFS)+ & - 3*SQRT(-3d0+729*(Element % BDOFS)**2))**(1/3d0)+3 - AEPS) - - CALL H1Basis_WedgeBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, BasisWrk, nbp) - CALL H1Basis_dWedgeBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, dBasisdxWrk, nbdxp) - END IF - - ! HEXAHEDRON - CASE (808) - ! Compute local basis - CALL H1Basis_BrickNodal(ncl, uWrk, vWrk, wWrk, nbmax, BasisWrk, nbp) - ! Compute local first derivatives - CALL H1Basis_dBrickNodal(ncl, uWrk, vWrk, wWrk, nbmax, dBasisdxWrk, nbdxp) - - IF (ASSOCIATED( Element % EdgeIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! edge directions - IF (ll==1) THEN - CALL GetElementMeshEdgeInfo(CurrentModel % Solver % Mesh, & - Element, EdgeDegree, EdgeDirection, EdgeMaxDegree) - END IF - - ! Compute basis function values - IF (EdgeMaxDegree > 1) THEN - nbq = nbp + SUM(EdgeDegree(1:12)-1) - IF(nbmax >= nbq) THEN - CALL H1Basis_BrickEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, BasisWrk, nbp, & - EdgeDirection) - CALL H1Basis_dBrickEdgeP(ncl, uWrk, vWrk, wWrk, EdgeDegree, nbmax, dBasisdxWrk, nbdxp, & - EdgeDirection) - END IF - END IF - END IF - - - IF (ASSOCIATED( Element % FaceIndexes )) THEN - ! For first round of blocked loop, compute polynomial degrees and - ! face directions - IF (ll==1) THEN - CALL GetElementMeshFaceInfo(CurrentModel % Solver % Mesh, & - Element, FaceDegree, FaceDirection, FaceMaxDegree) - END IF - - ! Compute basis function values - IF (FaceMaxDegree > 1) THEN - nbq = nbp - DO i=1,6 - DO j=2,FaceDegree(i) - nbq = nbq + MAX(FaceDegree(i)-j-1,0) - END DO - END DO - - IF(nbmax >= nbq) THEN - CALL H1Basis_BrickFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, BasisWrk, nbp, & - FaceDirection) - CALL H1Basis_dBrickFaceP(ncl, uWrk, vWrk, wWrk, FaceDegree, nbmax, dBasisdxWrk, nbdxp, & - FaceDirection) - END IF - END IF - END IF - - - ! Element bubble functions - IF (Element % BDOFS > 0) THEN - ! Compute P from bubble dofs - P=CEILING(1/3d0*(81*Element % BDOFS + & - 3*SQRT(-3d0+729*Element % BDOFS**2))**(1/3d0) + & - 1d0/(81*Element % BDOFS+3*SQRT(-3d0+729*Element % BDOFS**2))**(1/3d0)+4 - AEPS) - CALL H1Basis_BrickBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, BasisWrk, nbp) - CALL H1Basis_dBrickBubbleP(ncl, uWrk, vWrk, wWrk, P, nbmax, dBasisdxWrk, nbdxp) - END IF - - - CASE DEFAULT - WRITE( Message, '(a,i4,a)' ) 'Vectorized basis for element: ', & - Element % TYPE % ElementCode, ' not implemented.' - CALL Error( 'ElementInfoVec', Message ) - CALL Fatal( 'ElementInfoVec', 'ElementInfoVec is still does not include pyramids.' ) - END SELECT - - ! Copy basis function values to global array - DO j=1,nbp - DO i=1,ncl - Basis(i+ll-1,j)=BasisWrk(i,j) - END DO - END DO - - !-------------------------------------------------------------- - ! Element (contravariant) metric and square root of determinant - !-------------------------------------------------------------- - elem = ElementMetricVec( Element, Nodes, ncl, nbp, DetJWrk, & - nbmax, dBasisdxWrk, LtoGMapsWrk ) - IF (.NOT. elem) THEN - retval = .FALSE. - RETURN - END IF - - !_ELMER_OMP_SIMD - DO i=1,ncl - DetJ(i+ll-1)=DetJWrk(i) - END DO - - ! Get global basis functions - !-------------------------------------------------------------- - ! First derivatives - IF (PRESENT(dBasisdx)) THEN -!DIR$ FORCEINLINE - CALL ElementInfoVec_ElementBasisToGlobal(ncl, nbp, nbmax, dBasisdxWrk, dim, cdim, LtoGMapsWrk, ll, dBasisdx) - END IF - END DO ! Block over Gauss points - - CONTAINS - - SUBROUTINE GetElementMeshEdgeInfo(Mesh, Element, EdgeDegree, EdgeDirection, EdgeMaxDegree) - IMPLICIT NONE - - TYPE(Mesh_t), INTENT(IN) :: Mesh - TYPE(Element_t), INTENT(IN) :: Element - INTEGER, INTENT(OUT) :: EdgeDegree(H1Basis_MaxPElementEdges), & - EdgeDirection(H1Basis_MaxPElementEdgeNodes,H1Basis_MaxPElementEdges) - INTEGER, INTENT(OUT) :: EdgeMaxDegree - INTEGER :: i - - EdgeMaxDegree = 0 - - IF( Mesh % MaxEdgeDofs == 0 ) THEN - CONTINUE - - ELSE IF (Mesh % MinEdgeDOFs == Mesh % MaxEdgeDOFs) THEN - EdgeDegree(1:Element % Type % NumberOfEdges) = Mesh % MaxEdgeDOFs + 1 - EdgeMaxDegree = Mesh % MaxEdgeDOFs + 1 - ELSE - ! Get polynomial degree of each edge separately -!DIR$ LOOP COUNT MAX=12 - DO i=1,Element % Type % NumberOfEdges - EdgeDegree(i) = Mesh % Edges( Element % EdgeIndexes(i) ) % BDOFs + 1 - EdgeMaxDegree = MAX(EdgeDegree(i), EdgeMaxDegree) - END DO - END IF - - ! Get edge directions if needed - IF (EdgeMaxDegree > 1) THEN - CALL H1Basis_GetEdgeDirection(Element % Type % ElementCode, & - Element % Type % NumberOfEdges, & - Element % NodeIndexes, & - EdgeDirection) - END IF - END SUBROUTINE GetElementMeshEdgeInfo - - SUBROUTINE GetElementMeshFaceInfo(Mesh, Element, FaceDegree, FaceDirection, FaceMaxDegree) - IMPLICIT NONE - - TYPE(Mesh_t), INTENT(IN) :: Mesh - TYPE(Element_t), INTENT(IN) :: Element - INTEGER, INTENT(OUT) :: FaceDegree(H1Basis_MaxPElementFaces), & - FaceDirection(H1Basis_MaxPElementFaceNodes,H1Basis_MaxPElementFaces) - INTEGER, INTENT(OUT) :: FaceMaxDegree - INTEGER :: i - - ! Get polynomial degree of each face - FaceMaxDegree = 0 - - IF( Mesh % MaxFaceDofs == 0 ) THEN - CONTINUE - - ELSE IF (Mesh % MinFaceDOFs == Mesh % MaxFaceDOFs) THEN - FaceMaxDegree = Mesh % Faces( Element % FaceIndexes(1) ) % PDefs % P - FaceDegree(1:Element % Type % NumberOfFaces) = FaceMaxDegree - ELSE -!DIR$ LOOP COUNT MAX=6 - DO i=1,Element % Type % NumberOfFaces - IF (Mesh % Faces( Element % FaceIndexes(i) ) % BDOFs /= 0) THEN - FaceDegree(i) = Mesh % Faces( Element % FaceIndexes(i) ) % PDefs % P - FaceMaxDegree = MAX(FaceDegree(i), FaceMaxDegree) - ELSE - FaceDegree(i) = 0 - END IF - END DO - END IF - - ! Get face directions - IF (FaceMaxDegree > 1) THEN - CALL H1Basis_GetFaceDirection(Element % Type % ElementCode, & - Element % Type % NumberOfFaces, & - Element % NodeIndexes, & - FaceDirection) - END IF - END SUBROUTINE GetElementMeshFaceInfo -!------------------------------------------------------------------------------ - END FUNCTION ElementInfoVec_ComputePElementBasis -!------------------------------------------------------------------------------ - - SUBROUTINE ElementInfoVec_ElementBasisToGlobal(npts, nbasis, nbmax, dLBasisdx, dim, cdim, LtoGMap, offset, dBasisdx) - IMPLICIT NONE - - INTEGER, INTENT(IN) :: npts - INTEGER, INTENT(IN) :: nbasis - INTEGER, INTENT(IN) :: nbmax - REAL(KIND=dp), INTENT(IN) :: dLBasisdx(VECTOR_BLOCK_LENGTH,nbmax,3) - INTEGER, INTENT(IN) :: dim - INTEGER, INTENT(IN) :: cdim - REAL(KIND=dp), INTENT(IN) :: LtoGMap(VECTOR_BLOCK_LENGTH,3,3) - INTEGER, INTENT(IN) :: offset - REAL(KIND=dp) CONTIG :: dBasisdx(:,:,:) - - INTEGER :: i, j, l -!DIR$ ASSUME_ALIGNED dLBasisdx:64, LtoGMap:64 - - ! Map local basis function to global - SELECT CASE (dim) - CASE(1) - !DIR$ LOOP COUNT MAX=3 - DO j=1,cdim - DO i=1,nbasis - !_ELMER_OMP_SIMD - DO l=1,npts - dBasisdx(l+offset-1,i,j) = dLBasisdx(l,i,1)*LtoGMap(l,j,1) - END DO - END DO - END DO - CASE(2) - !DIR$ LOOP COUNT MAX=3 - DO j=1,cdim - DO i=1,nbasis - !_ELMER_OMP_SIMD - DO l=1,npts - ! Map local basis function to global - dBasisdx(l+offset-1,i,j) = dLBasisdx(l,i,1)*LtoGMap(l,j,1)+ & - dLBasisdx(l,i,2)*LtoGMap(l,j,2) - END DO - END DO - END DO - CASE(3) - !DIR$ LOOP COUNT MAX=3 - DO j=1,cdim - DO i=1,nbasis - !_ELMER_OMP_SIMD - DO l=1,npts - ! Map local basis function to global - dBasisdx(l+offset-1,i,j) = dLBasisdx(l,i,1)*LtoGMap(l,j,1)+ & - dLBasisdx(l,i,2)*LtoGMap(l,j,2)+ & - dLBasisdx(l,i,3)*LtoGMap(l,j,3) - END DO - END DO - END DO - END SELECT - - END SUBROUTINE ElementInfoVec_ElementBasisToGlobal - - -!------------------------------------------------------------------------------ -!> Returns just the size of the element at its center. -!> providing a more economical way than calling ElementInfo. -!------------------------------------------------------------------------------ - FUNCTION ElementSize( Element, Nodes ) RESULT ( detJ ) - - TYPE(Element_t) :: Element - TYPE(Nodes_t) :: Nodes - REAL(KIND=dp) :: detJ - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp), ALLOCATABLE :: Basis(:) - INTEGER :: n,family - LOGICAL :: Stat - - - family = Element % TYPE % ElementCode / 100 - n = Element % TYPE % NumberOfNodes - ALLOCATE( Basis(n) ) - - SELECT CASE ( family ) - - CASE ( 1 ) - DetJ = 1.0_dp - RETURN - - CASE ( 2 ) - u = 0.0_dp - v = 0.0_dp - - CASE ( 3 ) - u = 0.5_dp - v = 0.5_dp - - CASE ( 4 ) - u = 0.0_dp - v = 0.0_dp - - CASE ( 5 ) - u = 0.5_dp - v = 0.5_dp - w = 0.5_dp - - CASE ( 8 ) - u = 0.0_dp - v = 0.0_dp - w = 0.0_dp - - CASE DEFAULT - CALL Fatal('ElementSize','Not implemented for elementtype') - - END SELECT - - Stat = ElementInfo( Element, Nodes, u, v, w, detJ, Basis ) - - END FUNCTION ElementSize -!------------------------------------------------------------------------------ - - -!---------------------------------------------------------------------------------- -!> Return H(div)-conforming face element basis function values and their divergence -!> with respect to the reference element coordinates at a given point on the -!> reference element. Here the basis for a real element K is constructed by -!> transforming the basis functions defined on the reference element k via the -!> Piola transformation. The data for performing the Piola transformation is also returned. -!> Note that the reference element is chosen as in the p-approximation so that -!> the reference element edges/faces have the same length/area. This choice simplifies -!> the associated assembly procedure. -!> With giving the optional argument ApplyPiolaTransform = .TRUE., this function -!> also performs the Piola transform, so that the basis functions and their spatial -!> div as defined on the physical element are returned. -!> The implementation is not yet complete as all element shapes are not supported. -!--------------------------------------------------------------------------------- - RECURSIVE FUNCTION FaceElementInfo( Element, Nodes, u, v, w, F, detF, & - Basis, FBasis, DivFBasis, BDM, Dual, BasisDegree, ApplyPiolaTransform) RESULT(stat) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Data corresponding to the classic element nodes - REAL(KIND=dp) :: u !< 1st reference element coordinate at which the basis functions are evaluated - REAL(KIND=dp) :: v !< 2nd reference element coordinate - REAL(KIND=dp) :: w !< 3rd reference element coordinate - REAL(KIND=dp), OPTIONAL :: F(3,3) !< The gradient F=Grad f, with f the element map f:k->K - REAL(KIND=dp) :: detF !< The determinant of the gradient matrix F - REAL(KIND=dp) :: Basis(:) !< Standard nodal basis functions evaluated at (u,v,w) - REAL(KIND=dp) :: FBasis(:,:) !< Face element basis functions b spanning the reference element space - REAL(KIND=dp), OPTIONAL :: DivFBasis(:) !< The divergence of basis functions with respect to the local coordinates - LOGICAL, OPTIONAL :: BDM !< If .TRUE., a basis for BDM space is constructed - LOGICAL, OPTIONAL :: Dual !< If .TRUE., create an alternate dual basis - INTEGER, OPTIONAL :: BasisDegree(:) !< This a dummy parameter at the moment - LOGICAL, OPTIONAL :: ApplyPiolaTransform !< If .TRUE., perform the Piola transform so that, instead of b - !< and Div b, return B(f(p)) and (div B)(f(p)) with B(x) the basis - !< functions on the physical element and div the spatial divergence operator. - LOGICAL :: Stat !< Should be .FALSE. for a degenerate element but this is not yet checked -!----------------------------------------------------------------------------------------------------------------- -! Local variables -!------------------------------------------------------------------------------------------------------------ - TYPE(Mesh_t), POINTER :: Mesh - INTEGER, POINTER :: EdgeMap(:,:), FaceMap(:,:), Ind(:) - INTEGER :: SquareFaceMap(4) - INTEGER :: DOFs - INTEGER :: n, dim, q, i, j, k, ni, nj, nk, I1, I2 - INTEGER :: FDofMap(4,3), DofsPerFace, FaceIndices(4) - REAL(KIND=dp) :: LF(3,3) - REAL(KIND=dp) :: DivBasis(12) ! Note the hard-coded size, alter if new elements are added - REAL(KIND=dp) :: dLbasisdx(MAX(SIZE(Nodes % x),SIZE(Basis)),3), S, D1, D2 - REAL(KIND=dp) :: BDMBasis(12,3), BDMDivBasis(12), WorkBasis(2,3), WorkDivBasis(2) - - LOGICAL :: RevertSign(4), CreateBDMBasis, Parallel - LOGICAL :: CreateDualBasis - LOGICAL :: PerformPiolaTransform -!----------------------------------------------------------------------------------------------------- - Mesh => CurrentModel % Solver % Mesh - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - - !-------------------------------------------------------------------- - ! Check whether BDM or dual basis functions should be created and - ! whether the Piola transform is already applied within this function. - !--------------------------------------------------------------------- - CreateBDMBasis = .FALSE. - IF ( PRESENT(BDM) ) CreateBDMBasis = BDM - CreateDualBasis = .FALSE. - IF ( PRESENT(Dual) ) CreateDualBasis = Dual - PerformPiolaTransform = .FALSE. - IF ( PRESENT(ApplyPiolaTransform) ) PerformPiolaTransform = ApplyPiolaTransform - !----------------------------------------------------------------------------------------------------- - stat = .TRUE. - Basis = 0.0d0 - FBasis = 0.0d0 - DivFBasis = 0.0d0 - DivBasis = 0.0d0 - LF = 0.0d0 - - dLbasisdx = 0.0d0 - n = Element % TYPE % NumberOfNodes - dim = Element % TYPE % DIMENSION - - IF ( Element % TYPE % ElementCode == 101 ) THEN - detF = 1.0d0 - Basis(1) = 1.0d0 - RETURN - END IF - - !----------------------------------------------------------------------- - ! The standard nodal basis functions on the reference element and - ! their derivatives with respect to the local coordinates. These define - ! the mapping of the reference element to an actual element on the - ! background mesh but are not the basis functions for face element approximation. - ! Remark: Using reference elements having the faces of the same area - ! simplifies the implementation of element assembly procedures. - !----------------------------------------------------------------------- - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3) - DO q=1,n - Basis(q) = TriangleNodalPBasis(q, u, v) - dLBasisdx(q,1:2) = dTriangleNodalPBasis(q, u, v) - END DO - CASE(4) - DO q=1,n - Basis(q) = QuadNodalPBasis(q, u, v) - dLBasisdx(q,1:2) = dQuadNodalPBasis(q, u, v) - END DO - CASE(5) - DO q=1,n - Basis(q) = TetraNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dTetraNodalPBasis(q, u, v, w) - END DO - CASE DEFAULT - CALL Fatal('ElementDescription::FaceElementInfo','Unsupported element type') - END SELECT - - !----------------------------------------------------------------------- - ! Get data for performing the Piola transformation... - !----------------------------------------------------------------------- - stat = PiolaTransformationData(n, Element, Nodes, LF, detF, dLBasisdx) - !------------------------------------------------------------------------ - ! ... in order to define the basis for the element space X(K) via - ! applying the Piola transformation as - ! X(K) = { B | B = 1/(det F) F b(f^{-1}(x)) } - ! with b giving the face element basis function on the reference element k, - ! f mapping k to the actual element K, i.e. K = f(k) and F = Grad f. This - ! function returns the local basis functions b and their divergence (with respect - ! to local coordinates) evaluated at the integration point. The effect of - ! the Piola transformation need to be considered when integrating, so we - ! shall return also the values of F and det F. - ! - ! The construction of face element bases could be done in an alternate way for - ! triangles and tetrahedra, while the chosen approach has the benefit that - ! it generalizes to other cases. For example general quadrilaterals may now - ! be handled in the same way. - !--------------------------------------------------------------------------- - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3) - !---------------------------------------------------------------- - ! Note that the global orientation of face normal is taken to be - ! n = t x e_z where the tangent vector t is aligned with - ! the element edge and points towards the node that has - ! a larger global index. - !--------------------------------------------------------------- - EdgeMap => GetEdgeMap(3) - !EdgeMap => GetEdgeMap(GetElementFamily(Element)) - - !----------------------------------------------------------------------------------- - ! Check first whether a sign reversion will be needed as face dofs have orientation. - !----------------------------------------------------------------------------------- - CALL FaceElementOrientation(Element, RevertSign) - - IF (CreateBDMBasis) THEN - !---------------------------------------------------------------------------- - ! This is for the BDM space of degree k=1. - !---------------------------------------------------------------------------- - DOFs = 6 - DofsPerFace = 2 - !---------------------------------------------------------------------------- - ! First tabulate the basis functions in the default order. - !---------------------------------------------------------------------------- - ! Two basis functions defined on face 12: - !------------------------------------------------- - FBasis(1,1) = sqrt(3.0d0)/6.0d0 * (-sqrt(3.0d0) + u + v) - FBasis(1,2) = sqrt(3.0d0)/6.0d0 * (-sqrt(3.0d0) + 3.0d0 * u + v) - DivBasis(1) = sqrt(3.0d0)/3.0d0 - - FBasis(2,1) = sqrt(3.0d0)/6.0d0 * (sqrt(3.0d0) + u - v) - FBasis(2,2) = sqrt(3.0d0)/6.0d0 * (-sqrt(3.0d0) - 3.0d0 * u + v) - DivBasis(2) = sqrt(3.0d0)/3.0d0 - - ! Two basis functions defined on face 23: - - FBasis(3,1) = 1.0d0/(3.0d0+sqrt(3.0d0)) * (2.0d0+sqrt(3.0d0)+(2.0d0+sqrt(3.0d0))*u-(1.0d0+sqrt(3.0d0))*v) - FBasis(3,2) = 1.0d0/6.0d0 * ( -3.0d0+sqrt(3.0d0) ) * v - DivBasis(3) = sqrt(3.0d0)/3.0d0 - - FBasis(4,1) = 1.0d0/6.0d0 * (-3.0d0+sqrt(3.0d0)+(-3.0d0+sqrt(3.0d0))*u + 2.0d0*sqrt(3.0d0)*v) - FBasis(4,2) = 1.0d0/6.0d0 * ( 3.0d0+sqrt(3.0d0) ) * v - DivBasis(4) = sqrt(3.0d0)/3.0d0 - - - ! Two basis functions defined on face 31: - - FBasis(5,1) = 1.0d0/( 3.0d0+sqrt(3.0d0) ) * ( 1.0d0 - u - v - sqrt(3.0d0)*v ) - FBasis(5,2) = ( 3.0d0+2.0d0*sqrt(3.0d0) ) * v /(3.0d0*(1.0d0+sqrt(3.0d0))) - DivBasis(5) = sqrt(3.0d0)/3.0d0 - - FBasis(6,1) = 1.0d0/6.0d0 * (-3.0d0-sqrt(3.0d0)+(3.0d0+sqrt(3.0d0))*u + 2.0d0*sqrt(3.0d0)*v) - FBasis(6,2) = 1.0d0/6.0d0 * ( -3.0d0+sqrt(3.0d0) ) * v - DivBasis(6) = sqrt(3.0d0)/3.0d0 - - !----------------------------------------------------- - ! Now do the reordering and sign reversion: - !----------------------------------------------------- - DO q=1,3 - IF (RevertSign(q)) THEN - DO j=1,DofsPerFace - i = (q-1)*DofsPerFace + j - WorkBasis(j,1:2) = FBasis(i,1:2) - WorkDivBasis(j) = DivBasis(i) - END DO - i = 2*q - 1 - FBasis(i,1:2) = -WorkBasis(2,1:2) - DivBasis(i) = -WorkDivBasis(2) - i = 2*q - FBasis(i,1:2) = -WorkBasis(1,1:2) - DivBasis(i) = -WorkDivBasis(1) - END IF - END DO - - ELSE - DOFs = 3 - - FBasis(1,1) = SQRT(3.0d0)/6.0d0 * u - FBasis(1,2) = -0.5d0 + SQRT(3.0d0)/6.0d0 * v - DivBasis(1) = SQRT(3.0d0)/3.0d0 - IF (RevertSign(1)) THEN - FBasis(1,:) = -FBasis(1,:) - DivBasis(1) = -DivBasis(1) - END IF - - FBasis(2,1) = SQRT(3.0d0)/6.0d0 * (1.0d0 + u) - FBasis(2,2) = SQRT(3.0d0)/6.0d0 * v - DivBasis(2) = SQRT(3.0d0)/3.0d0 - IF (RevertSign(2)) THEN - FBasis(2,:) = -FBasis(2,:) - DivBasis(2) = -DivBasis(2) - END IF - - FBasis(3,1) = SQRT(3.0d0)/6.0d0 * (-1.0d0 + u) - FBasis(3,2) = SQRT(3.0d0)/6.0d0 * v - DivBasis(3) = SQRT(3.0d0)/3.0d0 - IF (RevertSign(3)) THEN - FBasis(3,:) = -FBasis(3,:) - DivBasis(3) = -DivBasis(3) - END IF - - END IF - - CASE(4) - DOFs = 6 - !-------------------------------------------------------------------- - ! Quadrilateral Arnold-Boffi-Falk (ABF) element basis of degree k=0 - !-------------------------------------------------------------------- - EdgeMap => GetEdgeMap(4) - SquareFaceMap(:) = (/ 1,2,3,4 /) - Ind => Element % Nodeindexes - - IF (.NOT. CreateDualBasis) THEN - !------------------------------------------------- - ! Four basis functions defined on the edges - !------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - FBasis(1,1) = 0.0d0 - FBasis(1,2) = -((-1.0d0 + v)*v)/4.0d0 - DivBasis(1) = (1.0d0 - 2*v)/4.0d0 - IF (nj This function returns data for performing the Piola transformation -!------------------------------------------------------------------------------------------------ - FUNCTION PiolaTransformationData(nn,Element,Nodes,F,DetF,dLBasisdx) RESULT(Success) -!------------------------------------------------------------------------------------------------- - INTEGER :: nn !< The number of classic nodes used in the element mapping - TYPE(Element_t) :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Data corresponding to the classic element nodes - REAL(KIND=dp) :: F(:,:) !< The gradient of the element mapping - REAL(KIND=dp) :: DetF !< The determinant of the gradient matrix (or the Jacobian matrix) - REAL(KIND=dp) :: dLBasisdx(:,:) !< Derivatives of nodal basis functions with respect to local coordinates - LOGICAL :: Success !< Could and should return .FALSE. if the element is degenerate -!----------------------------------------------------------------------------------------------------- -! Local variables -!------------------------------------------------------------------------------------------------- - REAL(KIND=dp), DIMENSION(:), POINTER :: x,y,z - INTEGER :: cdim,dim,n,i -!------------------------------------------------------------------------------------------------- - x => Nodes % x - y => Nodes % y - z => Nodes % z - - ! cdim = CoordinateSystemDimension() - n = MIN( SIZE(x), nn ) - dim = Element % TYPE % DIMENSION - - !------------------------------------------------------------------------------ - ! The gradient of the element mapping K = f(k), with k the reference element - !------------------------------------------------------------------------------ - F = 0.0d0 - DO i=1,dim - F(1,i) = SUM( x(1:n) * dLBasisdx(1:n,i) ) - F(2,i) = SUM( y(1:n) * dLBasisdx(1:n,i) ) - !IF (dim == 3) & - ! In addition to the case dim = 3, the following entries may be useful - ! with dim=2 when natural BCs in 3-D are handled. - F(3,i) = SUM( z(1:n) * dLBasisdx(1:n,i) ) - END DO - - SELECT CASE( dim ) - CASE (2) - DetF = F(1,1)*F(2,2) - F(1,2)*F(2,1) - CASE(3) - DetF = F(1,1) * ( F(2,2)*F(3,3) - F(2,3)*F(3,2) ) + & - F(1,2) * ( F(2,3)*F(3,1) - F(2,1)*F(3,3) ) + & - F(1,3) * ( F(2,1)*F(3,2) - F(2,2)*F(3,1) ) - END SELECT - - success = .TRUE. -!------------------------------------------------ - END FUNCTION PiolaTransformationData -!------------------------------------------------ - -!----------------------------------------------------------------------------------- -!> Get information about whether a sign reversion will be needed to obtain right -!> DOFs for face (vector) elements. If the sign is not reverted, the positive value of -!> the degree of freedom produces positive outward flux from the element through -!> the face handled. -!----------------------------------------------------------------------------------- -SUBROUTINE FaceElementOrientation(Element, RevertSign, FaceIndex, Nodes) -!----------------------------------------------------------------------------------- - IMPLICIT NONE - - TYPE(Element_t), INTENT(IN) :: Element !< A 3-D/2-D element having 2-D/1-D faces - LOGICAL, INTENT(OUT) :: RevertSign(:) !< Face-wise information about the sign reversions - INTEGER, OPTIONAL, INTENT(IN) :: FaceIndex !< Check just one face that is specified here - TYPE(Nodes_t), OPTIONAL :: Nodes !< An inactive variable related to code verification -!----------------------------------------------------------------------------------- - TYPE(Mesh_t), POINTER :: Mesh - LOGICAL :: Parallel - - INTEGER, POINTER :: FaceMap(:,:), Ind(:) - INTEGER, TARGET :: TetraFaceMap(4,3) - INTEGER :: FaceIndices(4) - INTEGER :: j, q, first_face, last_face - - ! Some inactive variables that were used in the code verification - LOGICAL :: RevertSign2(4), CheckSignReversions - INTEGER :: i, k, A, B, C, D - REAL(KIND=dp) :: t1(3), t2(3), m(3), e(3) -!----------------------------------------------------------------------------------- - RevertSign(:) = .FALSE. - - IF (PRESENT(FaceIndex)) THEN - first_face = FaceIndex - last_face = FaceIndex - ELSE - first_face = 1 - END IF - - Mesh => CurrentModel % Solver % Mesh - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - Ind => Element % NodeIndexes - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3) - FaceMap => GetEdgeMap(3) - - IF (.NOT. PRESENT(FaceIndex)) last_face = 3 - IF (SIZE(RevertSign) < last_face) CALL Fatal('FaceElementOrientation', & - 'Too small array for listing element faces') - - DO q=first_face,last_face - DO j=1,2 - FaceIndices(j) = Ind(FaceMap(q,j)) - END DO - IF (Parallel) THEN - DO j=1,2 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - - IF (FaceIndices(2) < FaceIndices(1)) RevertSign(q) = .TRUE. - END DO - - CASE(5) - TetraFaceMap(1,:) = (/ 2, 1, 3 /) - TetraFaceMap(2,:) = (/ 1, 2, 4 /) - TetraFaceMap(3,:) = (/ 2, 3, 4 /) - TetraFaceMap(4,:) = (/ 3, 1, 4 /) - - FaceMap => TetraFaceMap - - IF (.NOT. PRESENT(FaceIndex)) last_face = 4 - IF (SIZE(RevertSign) < last_face) CALL Fatal('FaceElementOrientation', & - 'Too small array for listing element faces') - - DO q=first_face,last_face - DO j=1,3 - FaceIndices(j) = Ind(FaceMap(q,j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - - IF ( (FaceIndices(1) < FaceIndices(2)) .AND. (FaceIndices(1) < FaceIndices(3)) ) THEN - IF (FaceIndices(3) < FaceIndices(2)) THEN - RevertSign(q) = .TRUE. - END IF - ELSE IF ( ( FaceIndices(2) < FaceIndices(1) ) .AND. ( FaceIndices(2) < FaceIndices(3) ) ) THEN - IF ( FaceIndices(1) < FaceIndices(3) ) THEN - RevertSign(q) = .TRUE. - END IF - ELSE - IF ( FaceIndices(2) < FaceIndices(1) ) THEN - RevertSign(q) = .TRUE. - END IF - END IF - END DO - - !---------------------------------------------------------------------- - ! Another way for finding sign reversions in the case of tetrahedron. - ! This code is retained here, although it was used for verification purposes... - !---------------------------------------------------------------------- - CheckSignReversions = .FALSE. - IF (CheckSignReversions) THEN - DO q=1,4 - RevertSign2(q) = .FALSE. - i = FaceMap(q,1) - j = FaceMap(q,2) - k = FaceMap(q,3) - - IF ( ( Ind(i) < Ind(j) ) .AND. ( Ind(i) < Ind(k) ) ) THEN - A = i - IF (Ind(j) < Ind(k)) THEN - B = j - C = k - ELSE - B = k - C = j - END IF - ELSE IF ( ( Ind(j) < Ind(i) ) .AND. ( Ind(j) < Ind(k) ) ) THEN - A = j - IF (Ind(i) < Ind(k)) THEN - B = i - C = k - ELSE - B = k - C = i - END IF - ELSE - A = k - IF (Ind(i) < Ind(j)) THEN - B = i - C = j - ELSE - B = j - C = i - END IF - END IF - - t1(1) = Nodes % x(B) - Nodes % x(A) - t1(2) = Nodes % y(B) - Nodes % y(A) - t1(3) = Nodes % z(B) - Nodes % z(A) - - t2(1) = Nodes % x(C) - Nodes % x(A) - t2(2) = Nodes % y(C) - Nodes % y(A) - t2(3) = Nodes % z(C) - Nodes % z(A) - - m(1:3) = CrossProduct(t1,t2) - - SELECT CASE(q) - CASE(1) - D = 4 - CASE(2) - D = 3 - CASE(3) - D = 1 - CASE(4) - D = 2 - END SELECT - - e(1) = Nodes % x(D) - Nodes % x(A) - e(2) = Nodes % y(D) - Nodes % y(A) - e(3) = Nodes % z(D) - Nodes % z(A) - - IF ( SUM(m(1:3) * e(1:3)) > 0.0d0 ) RevertSign2(q) = .TRUE. - - END DO - - IF ( ANY(RevertSign(1:4) .NEQV. RevertSign2(1:4)) ) THEN - PRINT *, 'CONFLICTING SIGN REVERSIONS SUGGESTED' - PRINT *, RevertSign(1:4) - PRINT *, RevertSign2(1:4) - STOP - END IF - END IF - - CASE DEFAULT - CALL Fatal('FaceElementOrientation', 'Unsupported element family') - END SELECT -!----------------------------------------------------------------------------------- -END SUBROUTINE FaceElementOrientation -!----------------------------------------------------------------------------------- - -!----------------------------------------------------------------------------------- -!> This subroutine produces information about how the basis functions of face (vector) -!> elements have to be reordered to conform with the global ordering convention. -!> Currently this can handle only the tetrahedron of Nedelec's second family. -!----------------------------------------------------------------------------------- -SUBROUTINE FaceElementBasisOrdering(Element, FDofMap, FaceIndex) -!----------------------------------------------------------------------------------- - IMPLICIT NONE - - TYPE(Element_t), INTENT(IN) :: Element !< A 3-D element having 2-D faces - INTEGER, INTENT(OUT) :: FDofMap(4,3) !< Face-wise information for the basis permutation - INTEGER, OPTIONAL, INTENT(IN) :: FaceIndex !< Check just one face that is specified here -!----------------------------------------------------------------------------------- - TYPE(Mesh_t), POINTER :: Mesh - LOGICAL :: Parallel - INTEGER, POINTER :: FaceMap(:,:), Ind(:) - INTEGER, TARGET :: TetraFaceMap(4,3), FaceIndices(4) - INTEGER :: j, q, first_face, last_face -!----------------------------------------------------------------------------------- - FDofMap(4,3) = 0 - - IF (PRESENT(FaceIndex)) THEN - first_face = FaceIndex - last_face = FaceIndex - ELSE - first_face = 1 - END IF - - Mesh => CurrentModel % Solver % Mesh - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - Ind => Element % NodeIndexes - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(5) - TetraFaceMap(1,:) = (/ 2, 1, 3 /) - TetraFaceMap(2,:) = (/ 1, 2, 4 /) - TetraFaceMap(3,:) = (/ 2, 3, 4 /) - TetraFaceMap(4,:) = (/ 3, 1, 4 /) - - FaceMap => TetraFaceMap - - IF (.NOT. PRESENT(FaceIndex)) last_face = 4 - - DO q=first_face,last_face - DO j=1,3 - FaceIndices(j) = Ind(FaceMap(q,j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - - IF ( ( FaceIndices(1) < FaceIndices(2) ) .AND. ( FaceIndices(1) < FaceIndices(3) ) ) THEN - FDofMap(q,1) = 1 - IF (FaceIndices(2) < FaceIndices(3)) THEN - FDofMap(q,2) = 2 - FDofMap(q,3) = 3 - ELSE - FDofMap(q,2) = 3 - FDofMap(q,3) = 2 - END IF - ELSE IF ( ( FaceIndices(2) < FaceIndices(1) ) .AND. ( FaceIndices(2) < FaceIndices(3) ) ) THEN - FDofMap(q,1) = 2 - IF (FaceIndices(1) < FaceIndices(3)) THEN - FDofMap(q,2) = 1 - FDofMap(q,3) = 3 - ELSE - FDofMap(q,2) = 3 - FDofMap(q,3) = 1 - END IF - ELSE - FDofMap(q,1) = 3 - IF (FaceIndices(1) < FaceIndices(2)) THEN - FDofMap(q,2) = 1 - FDofMap(q,3) = 2 - ELSE - FDofMap(q,2) = 2 - FDofMap(q,3) = 1 - END IF - END IF - END DO - - CASE DEFAULT - CALL Fatal('FaceElementBasisOrdering', 'Unsupported element family') - END SELECT -!----------------------------------------------------------------------------------- -END SUBROUTINE FaceElementBasisOrdering -!----------------------------------------------------------------------------------- - -!------------------------------------------------------------------------------ -!> Perform the cross product of two vectors -!------------------------------------------------------------------------------ - FUNCTION CrossProduct( v1, v2 ) RESULT( v3 ) -!------------------------------------------------------------------------------ - IMPLICIT NONE - REAL(KIND=dp) :: v1(3), v2(3), v3(3) - v3(1) = v1(2)*v2(3) - v1(3)*v2(2) - v3(2) = -v1(1)*v2(3) + v1(3)*v2(1) - v3(3) = v1(1)*v2(2) - v1(2)*v2(1) -!------------------------------------------------------------------------------ - END FUNCTION CrossProduct -!------------------------------------------------------------------------------ - - -!---------------------------------------------------------------------------------- -!> Return H(curl)-conforming edge element basis function values and their Curl -!> with respect to the reference element coordinates at a given point on the -!> reference element. Here the basis for a real element K is constructed by -!> transforming the basis functions defined on the reference element k via a version -!> of the Piola transformation designed for functions in H(curl). This construction -!> differs from the approach taken in the alternate subroutine GetEdgeBasis, which -!> does not make reference to the Piola transformation and hence may have limitations -!> in its extendability. The data for performing the Piola transformation is also returned. -!> Note that the reference element is chosen as in the p-approximation so that -!> the reference element edges/faces have the same length/area. This choice simplifies -!> the associated assembly procedure. -!> With giving the optional argument ApplyPiolaTransform = .TRUE., this function -!> also performs the Piola transform, so that the basis functions and their spatial -!> curl as defined on the physical element are returned. -!> In the lowest-order case this function returns the basis functions belonging -!> to the optimal family which is not subject to degradation of convergence on -!> meshes consisting of non-affine physical elements. The second-order elements -!> are members of the Nedelec's first family and are constructed in a hierarchic -!> fashion (the lowest-order basis functions give a partial construction of -!> the second-order basis). -!--------------------------------------------------------------------------------- - FUNCTION EdgeElementInfo( Element, Nodes, u, v, w, F, G, detF, & - Basis, EdgeBasis, RotBasis, dBasisdx, SecondFamily, BasisDegree, & - ApplyPiolaTransform, ReadyEdgeBasis, ReadyRotBasis, & - TangentialTrMapping) RESULT(stat) -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Data corresponding to the classic element nodes - REAL(KIND=dp) :: u !< 1st reference element coordinate at which the basis functions are evaluated - REAL(KIND=dp) :: v !< 2nd local coordinate - REAL(KIND=dp) :: w !< 3rd local coordinate - REAL(KIND=dp), OPTIONAL :: F(3,3) !< The gradient F=Grad f, with f the element map f:k->K - REAL(KIND=dp), OPTIONAL :: G(3,3) !< The transpose of the inverse of the gradient F - REAL(KIND=dp) :: detF !< The determinant of the gradient matrix F - REAL(KIND=dp) :: Basis(:) !< H1-conforming basis functions evaluated at (u,v,w) - REAL(KIND=dp) :: EdgeBasis(:,:) !< The basis functions b spanning the reference element space - REAL(KIND=dp), OPTIONAL :: RotBasis(:,:) !< The Curl of the edge basis functions with respect to the local coordinates - REAL(KIND=dp), OPTIONAL :: dBasisdx(:,:) !< The first derivatives of the H1-conforming basis functions at (u,v,w) - LOGICAL, OPTIONAL :: SecondFamily !< If .TRUE., a Nedelec basis of the second kind is returned (only simplicial elements) - INTEGER, OPTIONAL :: BasisDegree !< The approximation degree 2 is also supported - LOGICAL, OPTIONAL :: ApplyPiolaTransform !< If .TRUE., perform the Piola transform so that, instead of b - !< and Curl b, return B(f(p)) and (curl B)(f(p)) with B(x) the basis - !< functions on the physical element and curl the spatial curl operator. - !< In this case the absolute value of detF is returned. - REAL(KIND=dp), OPTIONAL :: ReadyEdgeBasis(:,:) !< A pretabulated edge basis function can be given - REAL(KIND=dp), OPTIONAL :: ReadyRotBasis(:,:) !< The preretabulated Curl of the edge basis function - LOGICAL, OPTIONAL :: TangentialTrMapping !< To return b x n, with n=(0,0,1) the normal to the 2D reference element. - !< The Piola transform is then the usual div-conforming version. - LOGICAL :: Stat !< .FALSE. for a degenerate element -!----------------------------------------------------------------------------------------------------------------- -! Local variables -!------------------------------------------------------------------------------------------------------------ - TYPE(Mesh_t), POINTER :: Mesh - INTEGER :: n, dim, cdim, q, i, j, k, l, ni, nj, A, I1, I2, FaceIndices(4) - REAL(KIND=dp) :: dLbasisdx(MAX(SIZE(Nodes % x),SIZE(Basis)),3), WorkBasis(4,3), WorkCurlBasis(4,3) - REAL(KIND=dp) :: D1, D2, B(3), curlB(3), GT(3,3), LG(3,3), LF(3,3) - REAL(KIND=dp) :: ElmMetric(3,3), detJ, CurlBasis(54,3) - REAL(KIND=dp) :: t(3), s(3), v1, v2, v3, h1, h2, h3, dh1, dh2, dh3, grad(2) - REAL(KIND=dp) :: LBasis(Element % TYPE % NumberOfNodes), Beta(4), EdgeSign(16) - LOGICAL :: Create2ndKindBasis, PerformPiolaTransform, UsePretabulatedBasis, Parallel - LOGICAL :: SecondOrder, ApplyTraceMapping, Found - INTEGER, POINTER :: EdgeMap(:,:), Ind(:) - INTEGER :: TriangleFaceMap(3), SquareFaceMap(4), BrickFaceMap(6,4), PrismSquareFaceMap(3,4), DOFs -!---------------------------------------------------------------------------------------------------------- - - Mesh => CurrentModel % Solver % Mesh - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - - stat = .TRUE. - Basis = 0.0d0 - EdgeBasis = 0.0d0 - WorkBasis = 0.0d0 - CurlBasis = 0.0d0 - LG = 0.0d0 - !-------------------------------------------------------------------------------------------- - ! Check whether ready edge basis function values are available to reduce computation. - ! If they are available, this function is used primarily to obtain the Piola transformation. - !-------------------------------------------------------------------------------------------- - UsePretabulatedBasis = .FALSE. - IF ( PRESENT(ReadyEdgeBasis) .AND. PRESENT(ReadyRotBasis) ) UsePretabulatedBasis = .TRUE. - !------------------------------------------------------------------------------------------ - ! Check whether the Nedelec basis functions of the second kind or higher order basis - ! functions should be created and whether the Piola transform is already applied within - ! this function. - !------------------------------------------------------------------------------------------ - Create2ndKindBasis = .FALSE. - IF ( PRESENT(SecondFamily) ) Create2ndKindBasis = SecondFamily - SecondOrder = .FALSE. - IF ( PRESENT(BasisDegree) ) THEN - SecondOrder = BasisDegree > 1 - END IF - PerformPiolaTransform = .FALSE. - IF ( PRESENT(ApplyPiolaTransform) ) PerformPiolaTransform = ApplyPiolaTransform - - ApplyTraceMapping = .FALSE. - IF ( PRESENT(TangentialTrMapping) ) ApplyTraceMapping = TangentialTrMapping - !------------------------------------------------------------------------------------------- - dLbasisdx = 0.0d0 - n = Element % TYPE % NumberOfNodes - dim = Element % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - - IF ( Element % TYPE % ElementCode == 101 ) THEN - detF = 1.0d0 - Basis(1) = 1.0d0 - IF ( PRESENT(dBasisdx) ) dBasisdx(1,:) = 0.0d0 - RETURN - END IF - - IF (cdim == 2 .AND. dim==1) THEN - CALL Warn('EdgeElementInfo', 'Traces of 2-D edge elements have not been implemented yet') - RETURN - END IF - - !----------------------------------------------------------------------- - ! The standard nodal basis functions on the reference element and - ! their derivatives with respect to the local coordinates. These define - ! the mapping of the reference element to an actual element on the background - ! mesh but are not the basis functions for the edge element approximation. - ! Remark: Using reference elements having the edges of the same length - ! simplifies the implementation of element assembly procedures. - !----------------------------------------------------------------------- - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3) - IF (SecondOrder) THEN - ! DOFs is the number of H(curl)-conforming basis functions: - DOFs = 8 - IF (n == 6) THEN - ! Here the element of the background mesh is of type 306. - ! The Lagrange interpolation basis on the p-approximation reference element: - Basis(1) = (3.0d0*u**2 + v*(-Sqrt(3.0d0) + v) + u*(-3.0d0 + 2.0d0*Sqrt(3.0d0)*v))/6.0d0 - dLBasisdx(1,1) = -0.5d0 + u + v/Sqrt(3.0d0) - dLBasisdx(1,2) = (-Sqrt(3.0d0) + 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v)/6.0d0 - Basis(2) = (3.0d0*u**2 + v*(-Sqrt(3.0d0) + v) + u*(3.0d0 - 2.0d0*Sqrt(3.0d0)*v))/6.0d0 - dLBasisdx(2,1) = 0.5d0 + u - v/Sqrt(3.d0) - dLBasisdx(2,2) = (-Sqrt(3.0d0) - 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v)/6.0d0 - Basis(3) = (v*(-Sqrt(3.0d0) + 2.0d0*v))/3.0d0 - dLBasisdx(3,1) = 0.0d0 - dLBasisdx(3,2) = -(1.0d0/Sqrt(3.0d0)) + (4.0d0*v)/3.0d0 - Basis(4) = (3.0d0 - 3.0d0*u**2 - 2.0d0*Sqrt(3.0d0)*v + v**2)/3.0d0 - dLBasisdx(4,1) = -2.0d0*u - dLBasisdx(4,2) = (-2.0d0*(Sqrt(3.0d0) - v))/3.0d0 - Basis(5) = (2.0d0*(Sqrt(3.0d0) + Sqrt(3.0d0)*u - v)*v)/3.0d0 - dLBasisdx(5,1) = (2.0d0*v)/Sqrt(3.0d0) - dLBasisdx(5,2) = (2.0d0*(Sqrt(3.0d0) + Sqrt(3.0d0)*u - 2.0d0*v))/3.0d0 - Basis(6) = (-2.0d0*v*(-Sqrt(3.0d0) + Sqrt(3.0d0)*u + v))/3.0d0 - dLBasisdx(6,1) = (-2.0d0*v)/Sqrt(3.0d0) - dLBasisdx(6,2) = (-2.0d0*(-Sqrt(3.0d0) + Sqrt(3.0d0)*u + 2.0d0*v))/3.0d0 - ELSE - ! Here the element of the background mesh is of type 303: - DO q=1,3 - Basis(q) = TriangleNodalPBasis(q, u, v) - dLBasisdx(q,1:2) = dTriangleNodalPBasis(q, u, v) - END DO - END IF - ELSE - DO q=1,n - Basis(q) = TriangleNodalPBasis(q, u, v) - dLBasisdx(q,1:2) = dTriangleNodalPBasis(q, u, v) - END DO - IF (Create2ndKindBasis) THEN - DOFs = 6 - ELSE - DOFs = 3 - END IF - END IF - CASE(4) - IF (SecondOrder) THEN - ! The second-order quad from the Nedelec's first family: affine physical elements may be needed - DOFs = 12 - ELSE - ! The lowest-order quad from the optimal family (ABF_0) - DOFs = 6 - END IF - IF (n>4) THEN - ! Here the background mesh is supposed to be of type 408/409 - CALL NodalBasisFunctions2D(Basis, Element, u, v) - CALL NodalFirstDerivatives(n, dLBasisdx, Element, u, v, w) - ELSE - ! Here the background mesh is of type 404 - DO q=1,4 - Basis(q) = QuadNodalPBasis(q, u, v) - dLBasisdx(q,1:2) = dQuadNodalPBasis(q, u, v) - END DO - END IF - CASE(5) - IF (SecondOrder) THEN - DOFs = 20 - IF (n == 10) THEN - ! Here the element of the background mesh is of type 510. - ! The Lagrange interpolation basis on the p-approximation reference element: - Basis(1) = (6.0d0*u**2 - 2.0d0*Sqrt(3.0d0)*v + 2.0d0*v**2 - Sqrt(6.0d0)*w + 2.0d0*Sqrt(2.0d0)*v*w + & - w**2 + 2.0d0*u*(-3.0d0 + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/12.0d0 - dLBasisdx(1,1) = -0.5d0 + u + v/Sqrt(3.0d0) + w/Sqrt(6.0d0) - dLBasisdx(1,2) = (-Sqrt(3.0d0) + 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v + Sqrt(2.0d0)*w)/6.0d0 - dLBasisdx(1,3) = (-Sqrt(6.0d0) + 2.0d0*Sqrt(6.0d0)*u + 2.0d0*Sqrt(2.0d0)*v + 2.0d0*w)/12.0d0 - Basis(2) = (6.0d0*u**2 - 2.0d0*Sqrt(3.0d0)*v + 2.0d0*v**2 - Sqrt(6.0d0)*w + 2.0d0*Sqrt(2.0d0)*v*w + & - w**2 - 2.0d0*u*(-3.0d0 + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/12.0d0 - dLBasisdx(2,1) = 0.5d0 + u - v/Sqrt(3.0d0) - w/Sqrt(6.0d0) - dLBasisdx(2,2) = (-Sqrt(3.0d0) - 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v + Sqrt(2.0d0)*w)/6.0d0 - dLBasisdx(2,3) = (-Sqrt(6.0d0) - 2.0d0*Sqrt(6.0d0)*u + 2.0d0*Sqrt(2.0d0)*v + 2.0d0*w)/12.0d0 - Basis(3) = (8.0d0*v**2 + w*(Sqrt(6.0d0) + w) - 4.0d0*v*(Sqrt(3.0d0) + Sqrt(2.0d0)*w))/12.0d0 - dLBasisdx(3,1) = 0.0d0 - dLBasisdx(3,2) = (-Sqrt(3.0d0) + 4.0d0*v - Sqrt(2.0d0)*w)/3.0d0 - dLBasisdx(3,3) = (Sqrt(6.0d0) - 4.0d0*Sqrt(2.0d0)*v + 2.0d0*w)/12.0d0 - Basis(4) = (w*(-Sqrt(6.0d0) + 3.0d0*w))/4.0d0 - dLBasisdx(4,1) = 0.0d0 - dLBasisdx(4,2) = 0.0d0 - dLBasisdx(4,3) = (-Sqrt(6.0d0) + 6.0d0*w)/4.0d0 - Basis(5) = (6.0d0 - 6.0d0*u**2 - 4.0d0*Sqrt(3.0d0)*v + 2.0d0*v**2 - 2.0d0*Sqrt(6.0d0)*w + & - 2.0d0*Sqrt(2.0d0)*v*w + w**2)/6.0d0 - dLBasisdx(5,1) = -2.0d0*u - dLBasisdx(5,2) = (-2.0d0*Sqrt(3.0d0) + 2.0d0*v + Sqrt(2.0d0)*w)/3.0d0 - dLBasisdx(5,3) = (-Sqrt(6.0d0) + Sqrt(2.0d0)*v + w)/3.0d0 - Basis(6) = (-4.0d0*v**2 + w*(-Sqrt(6.0d0) - Sqrt(6.0d0)*u + w) + v*(4.0d0*Sqrt(3.0d0) + & - 4.0d0*Sqrt(3.0d0)*u - Sqrt(2.0d0)*w))/6.0d0 - dLBasisdx(6,1) = (2.0d0*v)/Sqrt(3.0d0) - w/Sqrt(6.0d0) - dLBasisdx(6,2) = (4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u - 8.0d0*v - Sqrt(2.0d0)*w)/6.0d0 - dLBasisdx(6,3) = (-Sqrt(6.0d0) - Sqrt(6.0d0)*u - Sqrt(2.0d0)*v + 2.0d0*w)/6.0d0 - Basis(7) = (-4.0d0*v**2 + w*(-Sqrt(6.0d0) + Sqrt(6.0d0)*u + w) - & - v*(-4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u + Sqrt(2.0d0)*w))/6.0d0 - dLBasisdx(7,1) = (-2.0d0*v)/Sqrt(3.0d0) + w/Sqrt(6.0d0) - dLBasisdx(7,2) = (4.0d0*Sqrt(3.0d0) - 4.0d0*Sqrt(3.0d0)*u - 8.0d0*v - Sqrt(2.0d0)*w)/6.0d0 - dLBasisdx(7,3) = (-Sqrt(6.0d0) + Sqrt(6.0d0)*u - Sqrt(2.0d0)*v + 2.0d0*w)/6.0d0 - Basis(8) = -(w*(-Sqrt(6.0d0) + Sqrt(6.0d0)*u + Sqrt(2.0d0)*v + w))/2.0d0 - dLBasisdx(8,1) = -(Sqrt(1.5d0)*w) - dLBasisdx(8,2) = -(w/Sqrt(2.0d0)) - dLBasisdx(8,3) = (Sqrt(6.0d0) - Sqrt(6.0d0)*u - Sqrt(2.0d0)*v - 2.0d0*w)/2.0d0 - Basis(9) = ((Sqrt(6.0d0) + Sqrt(6.0d0)*u - Sqrt(2.0d0)*v - w)*w)/2.0d0 - dLBasisdx(9,1) = Sqrt(1.5d0)*w - dLBasisdx(9,2) = -(w/Sqrt(2.0d0)) - dLBasisdx(9,3) = (Sqrt(6.0d0) + Sqrt(6.0d0)*u - Sqrt(2.0d0)*v - 2.0d0*w)/2.0d0 - Basis(10) = Sqrt(2.0d0)*v*w - w**2/2.0d0 - dLBasisdx(10,1) = 0.0d0 - dLBasisdx(10,2) = Sqrt(2.0d0)*w - dLBasisdx(10,3) = Sqrt(2.0d0)*v - w - ELSE - ! Here the element of the background mesh is of type 504: - DO q=1,4 - Basis(q) = TetraNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dTetraNodalPBasis(q, u, v, w) - END DO - END IF - ELSE - DO q=1,n - Basis(q) = TetraNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dTetraNodalPBasis(q, u, v, w) - END DO - IF (Create2ndKindBasis) THEN - DOFs = 12 - ELSE - DOFs = 6 - END IF - END IF - CASE(6) - IF (SecondOrder) THEN - ! The second-order pyramid from the Nedelec's first family - DOFs = 31 - ELSE - ! The lowest-order pyramid from the optimal family - DOFs = 10 - END IF - - IF (n==13) THEN - ! Here the background mesh is supposed to be of type 613. The difference between the standard - ! reference element and the p-reference element can be taken into account by a simple scaling: - CALL NodalBasisFunctions3D(Basis, Element, u, v, sqrt(2.0d0)*w) - CALL NodalFirstDerivatives(n, dLBasisdx, Element, u, v, sqrt(2.0d0)*w) - dLBasisdx(1:n,3) = sqrt(2.0d0) * dLBasisdx(1:n,3) - ELSE - ! Background mesh elements of the type 605: - DO q=1,n - Basis(q) = PyramidNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dPyramidNodalPBasis(q, u, v, w) - END DO - END IF - - CASE(7) - IF (SecondOrder) THEN - ! The second-order prism from the Nedelec's first family: affine physical elements may be needed - DOFs = 36 - ELSE - ! The lowest-order prism from the optimal family - DOFs = 15 - END IF - - IF (n==15) THEN - ! Here the background mesh is of type 715. - ! The Lagrange interpolation basis on the p-approximation reference element: - - h1 = -0.5d0*w + 0.5d0*w**2 - h2 = 0.5d0*w + 0.5d0*w**2 - h3 = 1.0d0 - w**2 - dh1 = -0.5d0 + w - dh2 = 0.5d0 + w - dh3 = -2.0d0 * w - - WorkBasis(1,1) = (3.0d0*u**2 + v*(-Sqrt(3.0d0) + v) + u*(-3.0d0 + 2.0d0*Sqrt(3.0d0)*v))/6 - grad(1) = -0.5d0 + u + v/Sqrt(3.0d0) - grad(2) = (-Sqrt(3.0d0) + 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v)/6.0d0 - Basis(1) = WorkBasis(1,1) * h1 - dLBasisdx(1,1:2) = grad(1:2) * h1 - dLBasisdx(1,3) = WorkBasis(1,1) * dh1 - Basis(4) = WorkBasis(1,1) * h2 - dLBasisdx(4,1:2) = grad(1:2) * h2 - dLBasisdx(4,3) = WorkBasis(1,1) * dh2 - Basis(13) = WorkBasis(1,1) * h3 - dLBasisdx(13,1:2) = grad(1:2) * h3 - dLBasisdx(13,3) = WorkBasis(1,1) * dh3 - - WorkBasis(1,1) = (3.0d0*u**2 + v*(-Sqrt(3.0d0) + v) + u*(3.0d0 - 2.0d0*Sqrt(3.0d0)*v))/6.0d0 - grad(1) = 0.5d0 + u - v/Sqrt(3.d0) - grad(2) = (-Sqrt(3.0d0) - 2.0d0*Sqrt(3.0d0)*u + 2.0d0*v)/6.0d0 - Basis(2) = WorkBasis(1,1) * h1 - dLBasisdx(2,1:2) = grad(1:2) * h1 - dLBasisdx(2,3) = WorkBasis(1,1) * dh1 - Basis(5) = WorkBasis(1,1) * h2 - dLBasisdx(5,1:2) = grad(1:2) * h2 - dLBasisdx(5,3) = WorkBasis(1,1) * dh2 - Basis(14) = WorkBasis(1,1) * h3 - dLBasisdx(14,1:2) = grad(1:2) * h3 - dLBasisdx(14,3) = WorkBasis(1,1) * dh3 - - WorkBasis(1,1) = (v*(-Sqrt(3.0d0) + 2.0d0*v))/3.0d0 - grad(1) = 0.0d0 - grad(2) = -(1.0d0/Sqrt(3.0d0)) + (4.0d0*v)/3.0d0 - Basis(3) = WorkBasis(1,1) * h1 - dLBasisdx(3,1:2) = grad(1:2) * h1 - dLBasisdx(3,3) = WorkBasis(1,1) * dh1 - Basis(6) = WorkBasis(1,1) * h2 - dLBasisdx(6,1:2) = grad(1:2) * h2 - dLBasisdx(6,3) = WorkBasis(1,1) * dh2 - Basis(15) = WorkBasis(1,1) * h3 - dLBasisdx(15,1:2) = grad(1:2) * h3 - dLBasisdx(15,3) = WorkBasis(1,1) * dh3 - - h1 = 0.5d0 * (1.0d0 - w) - dh1 = -0.5d0 - h2 = 0.5d0 * (1.0d0 + w) - dh2 = 0.5d0 - - WorkBasis(1,1) = (3.0d0 - 3.0d0*u**2 - 2.0d0*Sqrt(3.0d0)*v + v**2)/3.0d0 - grad(1) = -2.0d0*u - grad(2) = (-2.0d0*(Sqrt(3.0d0) - v))/3.0d0 - Basis(7) = WorkBasis(1,1) * h1 - dLBasisdx(7,1:2) = grad(1:2) * h1 - dLBasisdx(7,3) = WorkBasis(1,1) * dh1 - Basis(10) = WorkBasis(1,1) * h2 - dLBasisdx(10,1:2) = grad(1:2) * h2 - dLBasisdx(10,3) = WorkBasis(1,1) * dh2 - - WorkBasis(1,1) = (2.0d0*(Sqrt(3.0d0) + Sqrt(3.0d0)*u - v)*v)/3.0d0 - grad(1) = (2.0d0*v)/Sqrt(3.0d0) - grad(2) = (2.0d0*(Sqrt(3.0d0) + Sqrt(3.0d0)*u - 2.0d0*v))/3.0d0 - Basis(8) = WorkBasis(1,1) * h1 - dLBasisdx(8,1:2) = grad(1:2) * h1 - dLBasisdx(8,3) = WorkBasis(1,1) * dh1 - Basis(11) = WorkBasis(1,1) * h2 - dLBasisdx(11,1:2) = grad(1:2) * h2 - dLBasisdx(11,3) = WorkBasis(1,1) * dh2 - - WorkBasis(1,1) = (-2.0d0*v*(-Sqrt(3.0d0) + Sqrt(3.0d0)*u + v))/3.0d0 - grad(1) = (-2.0d0*v)/Sqrt(3.0d0) - grad(2) = (-2.0d0*(-Sqrt(3.0d0) + Sqrt(3.0d0)*u + 2.0d0*v))/3.0d0 - Basis(9) = WorkBasis(1,1) * h1 - dLBasisdx(9,1:2) = grad(1:2) * h1 - dLBasisdx(9,3) = WorkBasis(1,1) * dh1 - Basis(12) = WorkBasis(1,1) * h2 - dLBasisdx(12,1:2) = grad(1:2) * h2 - dLBasisdx(12,3) = WorkBasis(1,1) * dh2 - ELSE - ! Here the background mesh is of type 706 - DO q=1,n - Basis(q) = WedgeNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dWedgeNodalPBasis(q, u, v, w) - END DO - END IF - CASE(8) - IF (SecondOrder) THEN - ! The second-order brick from the Nedelec's first family: affine physical elements may be needed - DOFs = 54 - ELSE - ! The lowest-order brick from the optimal family - DOFs = 27 - END IF - IF (n>8) THEN - ! Here the background mesh is supposed to be of type 820/827 - CALL NodalBasisFunctions3D(Basis, Element, u, v, w) - CALL NodalFirstDerivatives(n, dLBasisdx, Element, u, v, w) - ELSE - ! Here the background mesh is of type 808 - DO q=1,n - Basis(q) = BrickNodalPBasis(q, u, v, w) - dLBasisdx(q,1:3) = dBrickNodalPBasis(q, u, v, w) - END DO - END IF - CASE DEFAULT - CALL Fatal('ElementDescription::EdgeElementInfo','Unsupported element type') - END SELECT - - !----------------------------------------------------------------------- - ! Get data for performing the Piola transformation... - !----------------------------------------------------------------------- - stat = PiolaTransformationData(n, Element, Nodes, LF, detF, dLBasisdx) - !------------------------------------------------------------------------ - ! ... in order to define the basis for the element space X(K) via - ! applying a version of the Piola transformation as - ! X(K) = { B | B = F^{-T}(f^{-1}(x)) b(f^{-1}(x)) } - ! with b giving the edge basis function on the reference element k, - ! f mapping k to the actual element K, i.e. K = f(k) and F = Grad f. This - ! function returns the local basis functions b and their Curl (with respect - ! to local coordinates) evaluated at the integration point. The effect of - ! the Piola transformation need to be considered when integrating, so we - ! shall return also the values of F, G=F^{-T} and det F. - ! - ! The construction of edge element bases could be done in an alternate way for - ! triangles and tetrahedra, while the chosen approach has the benefit that - ! it generalizes to other cases. For example general quadrilaterals may now - ! be handled in the same way. - !--------------------------------------------------------------------------- - IF (cdim == dim) THEN - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3,4) - LG(1,1) = 1.0d0/detF * LF(2,2) - LG(1,2) = -1.0d0/detF * LF(1,2) - LG(2,1) = -1.0d0/detF * LF(2,1) - LG(2,2) = 1.0d0/detF * LF(1,1) - CASE(5,6,7,8) - CALL InvertMatrix3x3(LF,LG,detF) - CASE DEFAULT - CALL Fatal('ElementDescription::EdgeElementInfo','Unsupported element type') - END SELECT - LG(1:dim,1:dim) = TRANSPOSE( LG(1:dim,1:dim) ) - END IF - - IF (UsePretabulatedBasis) THEN - DO i=1,DOFs - EdgeBasis(i,1:3) = ReadyEdgeBasis(i,1:3) - CurlBasis(i,1:3) = ReadyRotBasis(i,1:3) - END DO - ELSE - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3) - !-------------------------------------------------------------- - ! This branch is for handling triangles. Note that - ! the global orientation of the edge tangent t is defined such that - ! t points towards the node that has a larger global index. - !-------------------------------------------------------------- - EdgeMap => GetEdgeMap(3) - !EdgeMap => GetEdgeMap(GetElementFamily(Element)) - - IF (Create2ndKindBasis) THEN - !------------------------------------------------- - ! Two basis functions defined on the edge 12. - !------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj Element % Nodeindexes - - DO j=1,3 - FaceIndices(j) = Ind(TriangleFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL TriangleFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - WorkBasis(1,1) = ((Sqrt(3.0d0) - v)*v)/6.0d0 - WorkBasis(1,2) = (u*v)/6.0d0 - WorkCurlBasis(1,3) = (-Sqrt(3.0d0) + 3.0d0*v)/6.0d0 - WorkBasis(2,1) = (v*(1.0d0 + u - v/Sqrt(3.0d0)))/(4.0d0*Sqrt(3.0d0)) - WorkBasis(2,2) = ((-1.0d0 + u)*(-3.0d0 - 3.0d0*u + Sqrt(3.0d0)*v))/(12.0d0*Sqrt(3.0d0)) - WorkCurlBasis(2,3) =(-Sqrt(3.0d0) - 3.0d0*Sqrt(3.0d0)*u + 3.0d0*v)/12.0d0 - WorkBasis(3,1) = (v*(-3.0d0 + 3.0d0*u + Sqrt(3.0d0)*v))/(12.0d0*Sqrt(3.0d0)) - WorkBasis(3,2) = -((1.0d0 + u)*(-3.0d0 + 3.0d0*u + Sqrt(3.0d0)*v))/(12.0d0*Sqrt(3.0d0)) - WorkCurlBasis(3,3) = (Sqrt(3.0d0) - 3.0d0*Sqrt(3.0d0)*u - 3.0d0*v)/12.0d0 - - EdgeBasis(7,:) = D1 * WorkBasis(I1,:) - CurlBasis(7,3) = D1 * WorkCurlBasis(I1,3) - EdgeBasis(8,:) = D2 * WorkBasis(I2,:) - CurlBasis(8,3) = D2 * WorkCurlBasis(I2,3) - - END IF - END IF - - CASE(4) - !-------------------------------------------------------------- - ! This branch is for handling quadrilaterals - !-------------------------------------------------------------- - EdgeMap => GetEdgeMap(4) - IF (SecondOrder) THEN - !--------------------------------------------------------------- - ! The second-order element from the Nedelec's first family with - ! a hierarchic basis. This element may not be optimally accurate - ! if the physical element is not affine. - ! First, the eight basis functions associated with the edges: - !-------------------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - EdgeBasis(1,1) = 0.1D1 / 0.4D1 - v / 0.4D1 - CurlBasis(1,3) = 0.1D1 / 0.4D1 - IF (nj Element % Nodeindexes - - WorkBasis = 0.0d0 - WorkCurlBasis = 0.0d0 - - WorkBasis(1,1) = 0.2D1 * (0.1D1 / 0.2D1 - v / 0.2D1) * (0.1D1 / 0.2D1 + v / 0.2D1) - WorkCurlBasis(1,3) = v - WorkBasis(2,1) = 0.12D2 * u * (0.1D1 / 0.2D1 - v / 0.2D1) * (0.1D1 / 0.2D1 + v / 0.2D1) - WorkCurlBasis(2,3) = 0.6D1 * u * (0.1D1 / 0.2D1 + v / 0.2D1) - & - 0.6D1 * u * (0.1D1 / 0.2D1 - v / 0.2D1) - - WorkBasis(3,2) = 0.2D1 * (0.1D1 / 0.2D1 - u / 0.2D1) * (0.1D1 / 0.2D1 + u / 0.2D1) - WorkCurlBasis(3,3) = -u - WorkBasis(4,2) = 0.12D2 * v * (0.1D1 / 0.2D1 - u / 0.2D1) * (0.1D1 / 0.2D1 + u / 0.2D1) - WorkCurlBasis(4,3) = -0.6D1 * v * (0.1D1 / 0.2D1 + u / 0.2D1) + & - 0.6D1 * v * (0.1D1 / 0.2D1 - u / 0.2D1) - - DO j=1,4 - FaceIndices(j) = Ind(SquareFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,4 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL SquareFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - EdgeBasis(9,:) = D1 * WorkBasis(2*(I1-1)+1,:) - CurlBasis(9,:) = D1 * WorkCurlBasis(2*(I1-1)+1,:) - EdgeBasis(10,:) = WorkBasis(2*(I1-1)+2,:) - CurlBasis(10,:) = WorkCurlBasis(2*(I1-1)+2,:) - EdgeBasis(11,:) = D2 * WorkBasis(2*(I2-1)+1,:) - CurlBasis(11,:) = D2 * WorkCurlBasis(2*(I2-1)+1,:) - EdgeBasis(12,:) = WorkBasis(2*(I2-1)+2,:) - CurlBasis(12,:) = WorkCurlBasis(2*(I2-1)+2,:) - - ELSE - !------------------------------------------------------ - ! The Arnold-Boffi-Falk element of degree k=0 which is - ! a member of the optimal edge element family. - ! First, four basis functions defined on the edges - !------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - EdgeBasis(1,1) = ((-1.0d0 + v)*v)/4.0d0 - EdgeBasis(1,2) = 0.0d0 - CurlBasis(1,3) = (1.0d0 - 2*v)/4.0d0 - IF (nj Element % Nodeindexes - - WorkBasis(1,:) = 0.0d0 - WorkBasis(2,:) = 0.0d0 - WorkCurlBasis(1,:) = 0.0d0 - WorkCurlBasis(2,:) = 0.0d0 - - WorkBasis(1,1) = (1.0d0 - v**2)/2.0d0 - WorkBasis(1,2) = 0.0d0 - WorkCurlBasis(1,3) = v - - WorkBasis(2,1) = 0.0d0 - WorkBasis(2,2) = (1.0d0 - u**2)/2.0d0 - WorkCurlBasis(2,3) = -u - - DO j=1,4 - FaceIndices(j) = Ind(SquareFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,4 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL SquareFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - EdgeBasis(5,:) = D1 * WorkBasis(I1,:) - CurlBasis(5,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(6,:) = D2 * WorkBasis(I2,:) - CurlBasis(6,:) = D2 * WorkCurlBasis(I2,:) - END IF - - CASE(5) - !-------------------------------------------------------------- - ! This branch is for handling tetrahedra - !-------------------------------------------------------------- - EdgeMap => GetEdgeMap(5) - - IF (Create2ndKindBasis) THEN - !------------------------------------------------- - ! Two basis functions defined on the edge 12. - !------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj Element % Nodeindexes - - DO j=1,3 - FaceIndices(j) = Ind(TriangleFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL TriangleFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - WorkBasis(1,1) = ((4.0d0*v - Sqrt(2.0d0)*w)*& - (-6.0d0 + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(48.0d0*Sqrt(3.0d0)) - WorkBasis(1,2) = -(u*(4.0d0*v - Sqrt(2.0d0)*w))/24.0d0 - WorkBasis(1,3) = (u*(-2.0d0*Sqrt(2.0d0)*v + w))/24.0d0 - WorkCurlBasis(1,1) = -u/(4.0d0*Sqrt(2.0d0)) - WorkCurlBasis(1,2) = (Sqrt(6.0d0) + 3.0d0*Sqrt(2.0d0)*v - 3.0d0*w)/24.0d0 - WorkCurlBasis(1,3) = (Sqrt(3.0d0) - 3.0d0*v)/6.0d0 - - WorkBasis(2,1) = ((4.0d0*v - Sqrt(2.0d0)*w)*(-6.0d0 + 6.0d0*u + & - 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(96.0d0*Sqrt(3.0d0)) - WorkBasis(2,2) = -((4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u - 3.0d0*Sqrt(2.0d0)*w)*& - (-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/288.0d0 - WorkBasis(2,3) = ((Sqrt(3.0d0) + Sqrt(3.0d0)*u - 3.0d0*v)*& - (-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(144.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,1) = -(-6.0d0 + 2.0d0*u + 2.0d0*Sqrt(3.0d0)*v + & - Sqrt(6.0d0)*w)/(16.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,2) = (2.0d0*Sqrt(3.0d0) - 6.0d0*Sqrt(3.0d0)*u + & - 6.0d0*v - 3.0d0*Sqrt(2.0d0)*w)/(48.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,3) = (Sqrt(3.0d0) - 3.0d0*Sqrt(3.0d0)*u - 3.0d0*v)/12.0d0 - - WorkBasis(3,1) = -((4.0d0*v - Sqrt(2.0d0)*w)*(-6.0d0 - 6.0d0*u + & - 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(96.0d0*Sqrt(3.0d0)) - WorkBasis(3,2) = ((-4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u + 3.0d0*Sqrt(2.0d0)*w)* & - (-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/288.0d0 - WorkBasis(3,3) = -((-Sqrt(3.0d0) + Sqrt(3.0d0)*u + 3.0d0*v)* & - (-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(144.0d0*Sqrt(2.0d0)) - WorkCurlBasis(3,1) = -(-6.0d0 - 2.0d0*u + 2.0d0*Sqrt(3.0d0)*v + & - Sqrt(6.0d0)*w)/(16.0d0*Sqrt(2.0d0)) - WorkCurlBasis(3,2) = (-2.0d0*Sqrt(3.0d0) - 6.0d0*Sqrt(3.0d0)*u - 6.0d0*v + & - 3.0d0*Sqrt(2.0d0)*w)/(48.0d0*Sqrt(2.0d0)) - WorkCurlBasis(3,3) = (-Sqrt(3.0d0) - 3.0d0*Sqrt(3.0d0)*u + 3.0d0*v)/12.0d0 - - EdgeBasis(13,:) = D1 * WorkBasis(I1,:) - CurlBasis(13,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(14,:) = D2 * WorkBasis(I2,:) - CurlBasis(14,:) = D2 * WorkCurlBasis(I2,:) - - !------------------------------------------------- - ! Two basis functions defined on the face 124: - !------------------------------------------------- - TriangleFaceMap(:) = (/ 1,2,4 /) - Ind => Element % Nodeindexes - - DO j=1,3 - FaceIndices(j) = Ind(TriangleFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL TriangleFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - WorkBasis(1,1) = -(w*(-6.0d0 + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(8.0d0*Sqrt(6.0d0)) - WorkBasis(1,2) = (u*w)/(4.0d0*Sqrt(2.0d0)) - WorkBasis(1,3) = (u*w)/8.0d0 - WorkCurlBasis(1,1) = -u/(4.0d0*Sqrt(2.0d0)) - WorkCurlBasis(1,2) = (Sqrt(6.0d0) - Sqrt(2.0d0)*v - 3.0d0*w)/8.0d0 - WorkCurlBasis(1,3) = w/(2.0d0*Sqrt(2.0d0)) - - WorkBasis(2,1) = -(w*(-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + & - Sqrt(6.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(2,2) = (w*(1.0d0 + u - v/Sqrt(3.0d0) - w/Sqrt(6.0d0)))/(8.0d0*Sqrt(2.0d0)) - WorkBasis(2,3) = ((-Sqrt(3.0d0) + Sqrt(3.0d0)*u + v)* & - (-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(48.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,1) = (-3.0d0*Sqrt(2.0d0) - Sqrt(2.0d0)*u + Sqrt(6.0d0)*v + Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(2,2) = (Sqrt(6.0d0) + 3.0d0*Sqrt(6.0d0)*u - Sqrt(2.0d0)*v - 3.0d0*w)/16.0d0 - WorkCurlBasis(2,3) = w/(4.0d0*Sqrt(2.0d0)) - - WorkBasis(3,1) = (w*(-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(3,2) = -(w*(-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(48.0d0*Sqrt(2.0d0)) - WorkBasis(3,3) = -((Sqrt(6.0d0) + Sqrt(6.0d0)*u - Sqrt(2.0d0)*v)*& - (-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/96.0d0 - WorkCurlBasis(3,1) = (-3.0d0*Sqrt(2.0d0) + Sqrt(2.0d0)*u + Sqrt(6.0d0)*v + Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(3,2) = (-Sqrt(6.0d0) + 3.0d0*Sqrt(6.0d0)*u + Sqrt(2.0d0)*v + 3.0d0*w)/16.0d0 - WorkCurlBasis(3,3) = -w/(4.0d0*Sqrt(2.0d0)) - - EdgeBasis(15,:) = D1 * WorkBasis(I1,:) - CurlBasis(15,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(16,:) = D2 * WorkBasis(I2,:) - CurlBasis(16,:) = D2 * WorkCurlBasis(I2,:) - - !------------------------------------------------- - ! Two basis functions defined on the face 234: - !------------------------------------------------- - TriangleFaceMap(:) = (/ 2,3,4 /) - Ind => Element % Nodeindexes - - DO j=1,3 - FaceIndices(j) = Ind(TriangleFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL TriangleFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - WorkBasis(1,1) = (w*(-2.0d0*Sqrt(2.0d0)*v + w))/16.0d0 - WorkBasis(1,2) = (w*(4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u - & - 3.0d0*Sqrt(2.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(1,3) = -((1.0d0 + u - Sqrt(3.0d0)*v)*w)/16.0d0 - WorkCurlBasis(1,1) = (-2.0d0*Sqrt(2.0d0) - 2.0d0*Sqrt(2.0d0)*u + 3.0d0*Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(1,2) = (-2.0d0*Sqrt(2.0d0)*v + 3.0d0*w)/16.0d0 - WorkCurlBasis(1,3) = w/(2.0d0*Sqrt(2.0d0)) - - WorkBasis(2,1) = (w*(-2.0d0*Sqrt(2.0d0)*v + w))/16.0d0 - WorkBasis(2,2) = -(w*(-4.0d0*v + Sqrt(2.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(2,3) = -((Sqrt(6.0d0) + Sqrt(6.0d0)*u - Sqrt(2.0d0)*v)*& - (-4.0d0*v + Sqrt(2.0d0)*w))/(32.0d0*Sqrt(3.0d0)) - WorkCurlBasis(2,1) = (2.0d0*Sqrt(2.0d0) + 2.0d0*Sqrt(2.0d0)*u - & - 2.0d0*Sqrt(6.0d0)*v + Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(2,2) = (-4.0d0*Sqrt(2.0d0)*v + 3.0d0*w)/16.0d0 - WorkCurlBasis(2,3) = w/(4.0d0*Sqrt(2.0d0)) - - WorkBasis(3,1) = 0.0d0 - WorkBasis(3,2) = (w*(-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(24.0d0*Sqrt(2.0d0)) - WorkBasis(3,3) = -(v*(-6.0d0 - 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(24.0d0*Sqrt(2.0d0)) - WorkCurlBasis(3,1) = (2.0d0*Sqrt(2.0d0) + 2.0d0*Sqrt(2.0d0)*u - Sqrt(6.0d0)*v - Sqrt(3.0d0)*w)/8.0d0 - WorkCurlBasis(3,2) = -v/(4.0d0*Sqrt(2.0d0)) - WorkCurlBasis(3,3) = -w/(4.0d0*Sqrt(2.0d0)) - - EdgeBasis(17,:) = D1 * WorkBasis(I1,:) - CurlBasis(17,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(18,:) = D2 * WorkBasis(I2,:) - CurlBasis(18,:) = D2 * WorkCurlBasis(I2,:) - - !------------------------------------------------- - ! Two basis functions defined on the face 314: - !------------------------------------------------- - TriangleFaceMap(:) = (/ 3,1,4 /) - Ind => Element % Nodeindexes - - DO j=1,3 - FaceIndices(j) = Ind(TriangleFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,3 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL TriangleFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - WorkBasis(1,1) = (w*(-2.0d0*Sqrt(2.0d0)*v + w))/16.0d0 - WorkBasis(1,2) = (w*(-4.0d0*Sqrt(3.0d0) + 4.0d0*Sqrt(3.0d0)*u + & - 3.0d0*Sqrt(2.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(1,3) = -((-1.0d0 + u + Sqrt(3.0d0)*v)*w)/16.0d0 - WorkCurlBasis(1,1) = (2.0d0*Sqrt(2.0d0) - 2.0d0*Sqrt(2.0d0)*u - 3.0d0*Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(1,2) = (-2.0d0*Sqrt(2.0d0)*v + 3.0d0*w)/16.0d0 - WorkCurlBasis(1,3) = w/(2.0d0*Sqrt(2.0d0)) - - WorkBasis(2,1) = 0.0d0 - WorkBasis(2,2) = (w*(-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(24.0d0*Sqrt(2.0d0)) - WorkBasis(2,3) = -(v*(-6.0d0 + 6.0d0*u + 2.0d0*Sqrt(3.0d0)*v + Sqrt(6.0d0)*w))/(24.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,1) = (2.0d0*Sqrt(2.0d0) - 2.0d0*Sqrt(2.0d0)*u - Sqrt(6.0d0)*v - Sqrt(3.0d0)*w)/8.0d0 - WorkCurlBasis(2,2) = v/(4.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,3) = w/(4.0d0*Sqrt(2.0d0)) - - WorkBasis(3,1) = ((2.0d0*Sqrt(2.0d0)*v - w)*w)/16.0d0 - WorkBasis(3,2) = -(w*(-4.0d0*v + Sqrt(2.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkBasis(3,3) = ((-Sqrt(3.0d0) + Sqrt(3.0d0)*u + v)*& - (-4.0d0*v + Sqrt(2.0d0)*w))/(16.0d0*Sqrt(6.0d0)) - WorkCurlBasis(3,1) = (2.0d0*Sqrt(2.0d0) - 2.0d0*Sqrt(2.0d0)*u - & - 2.0d0*Sqrt(6.0d0)*v + Sqrt(3.0d0)*w)/16.0d0 - WorkCurlBasis(3,2) = (4.0d0*Sqrt(2.0d0)*v - 3.0d0*w)/16.0d0 - WorkCurlBasis(3,3) = -w/(4.0d0*Sqrt(2.0d0)) - - EdgeBasis(19,:) = D1 * WorkBasis(I1,:) - CurlBasis(19,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(20,:) = D2 * WorkBasis(I2,:) - CurlBasis(20,:) = D2 * WorkCurlBasis(I2,:) - END IF - END IF - - CASE(6) - !-------------------------------------------------------------- - ! This branch is for handling pyramidic elements - !-------------------------------------------------------------- - EdgeMap => GetEdgeMap(6) - Ind => Element % Nodeindexes - - IF (SecondOrder) THEN - EdgeSign = 1.0d0 - - LBasis(1) = 0.1D1 / 0.4D1 - u / 0.4D1 - v / 0.4D1 - w * sqrt(0.2D1) / 0.8D1 + & - u * v / ( (0.1D1 - w * sqrt(0.2D1) / 0.2D1) * 0.4D1 ) - LBasis(2) = 0.1D1 / 0.4D1 + u / 0.4D1 - v / 0.4D1 - w * sqrt(0.2D1) / 0.8D1 - & - u * v / ( (0.1D1 - w * sqrt(0.2D1) / 0.2D1) * 0.4D1 ) - LBasis(3) = 0.1D1 / 0.4D1 + u / 0.4D1 + v / 0.4D1 - w * sqrt(0.2D1) / 0.8D1 + & - u * v / ( (0.1D1 - w * sqrt(0.2D1) / 0.2D1) * 0.4D1 ) - LBasis(4) = 0.1D1 / 0.4D1 - u / 0.4D1 + v / 0.4D1 - w * sqrt(0.2D1) / 0.8D1 - & - u * v / ( (0.1D1 - w * sqrt(0.2D1) / 0.2D1) * 0.4D1 ) - LBasis(5) = w * sqrt(0.2D1) / 0.2D1 - - Beta(1) = 0.1D1 / 0.2D1 - u / 0.2D1 - w * sqrt(0.2D1) / 0.4D1 - Beta(2) = 0.1D1 / 0.2D1 - v / 0.2D1 - w * sqrt(0.2D1) / 0.4D1 - Beta(3) = 0.1D1 / 0.2D1 + u / 0.2D1 - w * sqrt(0.2D1) / 0.4D1 - Beta(4) = 0.1D1 / 0.2D1 + v / 0.2D1 - w * sqrt(0.2D1) / 0.4D1 - - ! Edge 12: - !-------------------------------------------------------------- - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - EdgeBasis(1,1) = 0.1D1 / 0.4D1 - v / 0.4D1 - w * sqrt(0.2D1) / 0.8D1 - EdgeBasis(1,2) = 0.0d0 - EdgeBasis(1,3) = sqrt(0.2D1) * u * (w * sqrt(0.2D1) + 2.0D0 * v - 0.2D1) / & - ((w * sqrt(0.2D1) - 0.2D1) * 0.8D1) - CurlBasis(1,1) = sqrt(0.2D1) * u / ((w * sqrt(0.2D1) - 0.2D1) * 0.4D1) - CurlBasis(1,2) = -sqrt(0.2D1) / 0.8D1 - sqrt(0.2D1) * (w * sqrt(0.2D1) + 2.0D0 * v - 0.2D1) / & - ( (w * sqrt(0.2D1) - 0.2D1) * 0.8D1 ) - CurlBasis(1,3) = 0.1D1 / 0.4D1 - IF (nj Element % Nodeindexes - - WorkBasis(1,1) = (2.0d0 - 2*v**2 - 2*Sqrt(2.0d0)*w + w**2)/(4.0d0 - 2*Sqrt(2.0d0)*w) - WorkBasis(1,2) = 0.0d0 - WorkBasis(1,3) = (u*(1.0d0 - (4*v**2)/(-2.0d0 + Sqrt(2.0d0)*w)**2))/(2.0d0*Sqrt(2.0d0)) - WorkCurlBasis(1,1) = (-2*Sqrt(2.0d0)*u*v)/(-2.0d0 + Sqrt(2.0d0)*w)**2 - WorkCurlBasis(1,2) = (-2*Sqrt(2.0d0) + 4*w - Sqrt(2.0d0)*w**2)/(-2.0d0 + Sqrt(2.0d0)*w)**2 - WorkCurlBasis(1,3) = (2.0d0*v)/(2.0d0 - Sqrt(2.0d0)*w) - - WorkBasis(2,1) = 0.0d0 - WorkBasis(2,2) = (2.0d0 - 2*u**2 - 2*Sqrt(2.0d0)*w + w**2)/(4.0d0 - 2*Sqrt(2.0d0)*w) - WorkBasis(2,3) = (v*(1.0d0 - (4*u**2)/(-2.0d0 + Sqrt(2.0d0)*w)**2))/(2.0d0*Sqrt(2.0d0)) - WorkCurlBasis(2,1) = (2*Sqrt(2.0d0) - 4*w + Sqrt(2.0d0)*w**2)/(-2.0d0 + Sqrt(2.0d0)*w)**2 - WorkCurlBasis(2,2) = (2*Sqrt(2.0d0)*u*v)/(-2.0d0 + Sqrt(2.0d0)*w)**2 - WorkCurlBasis(2,3) = (2*u)/(-2.0d0 + Sqrt(2.0d0)*w) - - ! ------------------------------------------------------------------- - ! Finally apply an order change and sign reversions if needed. - ! ------------------------------------------------------------------- - DO j=1,4 - FaceIndices(j) = Ind(SquareFaceMap(j)) - END DO - IF (Parallel) THEN - DO j=1,4 - FaceIndices(j) = Mesh % ParallelInfo % GlobalDOFs(FaceIndices(j)) - END DO - END IF - CALL SquareFaceDofsOrdering(I1,I2,D1,D2,FaceIndices) - - EdgeBasis(9,:) = D1 * WorkBasis(I1,:) - CurlBasis(9,:) = D1 * WorkCurlBasis(I1,:) - EdgeBasis(10,:) = D2 * WorkBasis(I2,:) - CurlBasis(10,:) = D2 * WorkCurlBasis(I2,:) - END IF - - CASE(7) - !-------------------------------------------------------------- - ! This branch is for handling prismatic (or wedge) elements - !-------------------------------------------------------------- - EdgeMap => GetEdgeMap(7) - Ind => Element % Nodeindexes - - IF (SecondOrder) THEN - !--------------------------------------------------------------- - ! The second-order element from the Nedelec's first family - ! (note that the lowest-order prism element is from a different - ! family). This element may not be optimally accurate if - ! the physical element is not affine. - !-------------------------------------------------------------- - h1 = 0.5d0 * (1-w) - dh1 = -0.5d0 - h2 = 0.5d0 * (1+w) - dh2 = 0.5d0 - h3 = h1 * h2 - dh3 = -0.5d0 * w - - ! --------------------------------------------------------- - ! The first and fourth edges ... - !-------------------------------------------------------- - ! The corresponding basis functions for the triangle: - !-------------------------------------------------------- - WorkBasis(1,1) = (3.0d0 - Sqrt(3.0d0)*v)/6.0d0 - WorkBasis(1,2) = u/(2.0d0*Sqrt(3.0d0)) - WorkCurlBasis(1,3) = 1.0d0/Sqrt(3.0d0) - WorkBasis(2,1) = -(u*(-3.0d0 + Sqrt(3.0d0)*v))/2.0d0 - WorkBasis(2,2) = (Sqrt(3.0d0)*u**2)/2.0d0 - WorkCurlBasis(2,3) = (3.0d0*Sqrt(3.0d0)*u)/2.0d0 - - i = EdgeMap(1,1) - j = EdgeMap(1,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - EdgeBasis(1,1:2) = WorkBasis(1,1:2) * h1 - CurlBasis(1,1) = -WorkBasis(1,2) * dh1 - CurlBasis(1,2) = WorkBasis(1,1) * dh1 - CurlBasis(1,3) = WorkCurlBasis(1,3) * h1 - EdgeBasis(2,1:2) = WorkBasis(2,1:2) * h1 - CurlBasis(2,1) = -WorkBasis(2,2) * dh1 - CurlBasis(2,2) = WorkBasis(2,1) * dh1 - CurlBasis(2,3) = WorkCurlBasis(2,3) * h1 - IF (nj GetEdgeMap(8) - Ind => Element % Nodeindexes - - IF (SecondOrder) THEN - !--------------------------------------------------------------- - ! The second-order element from the Nedelec's first family - ! (note that the lowest-order brick element is from a different - ! family). This element may not be optimally accurate if - ! the physical element is not affine. - !-------------------------------------------------------------- - - ! Edges 12 and 43 ... - DO q=1,2 - k = 2*q-1 ! Edge number k: 1 ~ 12 and 3 ~ 43 - i = EdgeMap(k,1) - j = EdgeMap(k,2) - ni = Element % NodeIndexes(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Element % NodeIndexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - - EdgeBasis(2*(k-1)+1,1) = 0.5d0 * LineNodalPBasis(1,w) * LineNodalPBasis(q,v) - CurlBasis(2*(k-1)+1,2) = 0.5d0 * (-0.5d0) * LineNodalPBasis(q,v) - CurlBasis(2*(k-1)+1,3) = -0.5d0 * LineNodalPBasis(1,w) * dLineNodalPBasis(q,v) - EdgeBasis(2*(k-1)+2,1) = 1.5d0 * LineNodalPBasis(1,w) * u * LineNodalPBasis(q,v) - CurlBasis(2*(k-1)+2,2) = 1.5d0 * (-0.5d0) * u * LineNodalPBasis(q,v) - CurlBasis(2*(k-1)+2,3) = -1.5d0 * LineNodalPBasis(1,w) * u * dLineNodalPBasis(q,v) - IF (nj b x n. The resulting field transforms under the usual - ! Piola transform (like div-conforming field). For a general surface element - ! embedded in 3D we return B(f(p))=1/sqrt(a) F(b x n) where a is the determinant of - ! the metric tensor, F=[a1 a2] with a1 and a2 surface basis vectors and (b x n) is - ! considered to be 2-vector (the trivial component ignored). Note that asking simultaneously - ! for the curl of the basis is not an expected combination. - DO j=1,DOFs - WorkBasis(1,1:2) = EdgeBasis(j,1:2) - EdgeBasis(j,1) = WorkBasis(1,2) - EdgeBasis(j,2) = -WorkBasis(1,1) - END DO - IF (PerformPiolaTransform) THEN - DO j=1,DOFs - DO k=1,cdim - B(k) = SUM( LF(k,1:dim) * EdgeBasis(j,1:dim) ) / DetJ - END DO - EdgeBasis(j,1:cdim) = B(1:cdim) - END DO - END IF - ELSE - IF (PerformPiolaTransform) THEN - DO j=1,DOFs - DO k=1,cdim - B(k) = SUM( LG(k,1:dim) * EdgeBasis(j,1:dim) ) - END DO - EdgeBasis(j,1:cdim) = B(1:cdim) - ! The returned spatial curl in the case cdim=3 and dim=2 handled here - ! has limited usability. This handles only a transformation of - ! the type x_3 = p_3: - CurlBasis(j,3) = 1.0d0/DetJ * CurlBasis(j,3) - END DO - END IF - END IF - - ! Make the returned value DetF to act as a metric term for integration - ! over the volume of the element: - DetF = DetJ - - ! ---------------------------------------------------------------------- - ! Get global first derivatives of the nodal basis functions if wanted: - ! ---------------------------------------------------------------------- - IF ( PRESENT(dBasisdx) ) THEN - dBasisdx = 0.0d0 - DO i=1,n - DO j=1,cdim - DO k=1,dim - dBasisdx(i,j) = dBasisdx(i,j) + dLBasisdx(i,k)*LG(j,k) - END DO - END DO - END DO - END IF - - END IF - - IF(PRESENT(F)) F = LF - IF(PRESENT(G)) G = LG - IF(PRESENT(RotBasis)) RotBasis(1:DOFs,:) = CurlBasis(1:DOFs,:) -!----------------------------------------------------------------------------- - END FUNCTION EdgeElementInfo -!------------------------------------------------------------------------------ - - - -!---------------------------------------------------------------------------- - SUBROUTINE TriangleFaceDofsOrdering(I1,I2,D1,D2,Ind) -!----------------------------------------------------------------------------- -! This is used for selecting what additional basis functions are associated -! with a triangular face in the case of second-order approximation. -! ---------------------------------------------------------------------------- - INTEGER :: I1, I2, Ind(4) - REAL(KIND=dp) :: D1, D2 -!--------------------------------------------------------------------------- - INTEGER :: k, A -! -------------------------------------------------------------------------- - D1 = 1.0d0 - D2 = 1.0d0 - IF ( Ind(1) < Ind(2) ) THEN - k = 1 - ELSE - k = 2 - END IF - IF ( Ind(k) > Ind(3) ) THEN - k = 3 - END IF - A = k - - SELECT CASE(A) - CASE(1) - IF (Ind(3) > Ind(2)) THEN - ! C = 3 - I1 = 1 - I2 = 2 - ELSE - ! C = 2 - I1 = 2 - I2 = 1 - END IF - CASE(2) - IF (Ind(3) > Ind(1)) THEN - ! C = 3 - I1 = 1 - I2 = 3 - D1 = -1.0d0 - ELSE - ! C = 1 - I1 = 3 - I2 = 1 - D2 = -1.0d0 - END IF - CASE(3) - IF (Ind(2) > Ind(1)) THEN - ! C = 2 - I1 = 2 - I2 = 3 - ELSE - ! C = 1 - I1 = 3 - I2 = 2 - END IF - D1 = -1.0d0 - D2 = -1.0d0 - CASE DEFAULT - CALL Fatal('ElementDescription::TriangleFaceDofsOrdering','Erratic square face Indices') - END SELECT -!--------------------------------------------------------- - END SUBROUTINE TriangleFaceDofsOrdering -!----------------------------------------------------------- - - -!------------------------------------------------------------- - SUBROUTINE TriangleFaceDofsOrdering2(t,s,Ind) -!------------------------------------------------------------------------------- -! Returns two unit vectors t and s for spanning constant vector fields -! defined on a triangular face. As a rule for orientation, the vector t is defined -! as t = Grad L_B - Grad L_A where L_A and L_B are the Lagrange basis functions -! associated with the nodes that has the smallest global indices A and B (A Ind(3) ) THEN - k = 3 - END IF - A = k - - SELECT CASE(A) - CASE(1) - IF ( Ind(2) < Ind(3) ) THEN ! B=2, tangent = AB = 12 - t(1) = 1.0d0 - t(2) = 0.0 - s(1) = 0.0d0 - s(2) = 1.0d0 - ELSE ! B=3, tangent = AB = 13 - t(1) = 0.5d0 - t(2) = Sqrt(3.0d0)/2.0d0 - s(1) = Sqrt(3.0d0)/2.0d0 - s(2) = -0.5d0 - END IF - CASE(2) - IF ( Ind(1) < Ind(3) ) THEN ! B=1, tangent = AB = 21 - t(1) = -1.0d0 - t(2) = 0.0 - s(1) = 0.0d0 - s(2) = 1.0d0 - ELSE ! B=3, tangent = AB = 23 - t(1) = -0.5d0 - t(2) = Sqrt(3.0d0)/2.0d0 - s(1) = -Sqrt(3.0d0)/2.0d0 - s(2) = -0.5d0 - END IF - CASE(3) - IF ( Ind(1) < Ind(2) ) THEN ! B=1, tangent = AB = 31 - t(1) = -0.5d0 - t(2) = -Sqrt(3.0d0)/2.0d0 - s(1) = Sqrt(3.0d0)/2.0d0 - s(2) = -0.5d0 - ELSE ! B=2, tangent = AB = 32 - t(1) = 0.5d0 - t(2) = -Sqrt(3.0d0)/2.0d0 - s(1) = -Sqrt(3.0d0)/2.0d0 - s(2) = -0.5d0 - END IF - CASE DEFAULT - CALL Fatal('ElementDescription::TriangleFaceDofsOrdering','Erratic square face Indices') - END SELECT -!--------------------------------------------------------- - END SUBROUTINE TriangleFaceDofsOrdering2 -!----------------------------------------------------------- - - -!--------------------------------------------------------- - SUBROUTINE SquareFaceDofsOrdering(I1,I2,D1,D2,Ind) -!----------------------------------------------------------- - INTEGER :: I1, I2, Ind(4) - REAL(KIND=dp) :: D1, D2 -!---------------------------------------------------------- - INTEGER :: i, j, k, l, A -! ------------------------------------------------------------------- -! Find input for applying an order change and sign reversions to two -! basis functions associated with a square face. To this end, -! find nodes A, B, C such that A has the minimal global index, -! AB and AC are edges, with C having the largest global index. -! Then AB gives the positive direction for the first face DOF and -! AC gives the positive direction for the second face DOF. -! REMARK: This convention must be followed when creating basis -! functions for other element types which are intended to be compatible -! with the element type to which this rule is applied. -! ------------------------------------------------------------------- - i = 1 - j = 2 - IF ( Ind(i) < Ind(j) ) THEN - k = i - ELSE - k = j - END IF - i = 4 - j = 3 - IF ( Ind(i) < Ind(j) ) THEN - l = i - ELSE - l = j - END IF - IF ( Ind(k) > Ind(l) ) THEN - k = l - END IF - A = k - - SELECT CASE(A) - CASE(1) - IF ( Ind(2) < Ind(4) ) THEN - I1 = 1 - I2 = 2 - D1 = 1.0d0 - D2 = 1.0d0 - ELSE - I1 = 2 - I2 = 1 - D1 = 1.0d0 - D2 = 1.0d0 - END IF - CASE(2) - IF ( Ind(3) < Ind(1) ) THEN - I1 = 2 - I2 = 1 - D1 = 1.0d0 - D2 = -1.0d0 - ELSE - I1 = 1 - I2 = 2 - D1 = -1.0d0 - D2 = 1.0d0 - END IF - CASE(3) - IF ( Ind(4) < Ind(2) ) THEN - I1 = 1 - I2 = 2 - D1 = -1.0d0 - D2 = -1.0d0 - ELSE - I1 = 2 - I2 = 1 - D1 = -1.0d0 - D2 = -1.0d0 - END IF - CASE(4) - IF ( Ind(1) < Ind(3) ) THEN - I1 = 2 - I2 = 1 - D1 = -1.0d0 - D2 = 1.0d0 - ELSE - I1 = 1 - I2 = 2 - D1 = 1.0d0 - D2 = -1.0d0 - END IF - CASE DEFAULT - CALL Fatal('ElementDescription::SquareFaceDofsOrdering','Erratic square face Indices') - END SELECT -!---------------------------------------------------------- - END SUBROUTINE SquareFaceDofsOrdering -!---------------------------------------------------------- - -!---------------------------------------------------------------------------------- -!> Returns data for rearranging H(curl)-conforming basis functions so that -!> compatibility with the convention for defining global DOFs is attained. -!> If n basis function value have already been tabulated in the default order -!> as BasisArray(1:n,:), then SignVec(1:n) * BasisArray(PermVec(1:n),:) gives -!> the basis vector values corresponding to the global DOFs. -!> TO DO: support for second-order basis functions, triangles and quads missing -!------------------------------------------------------------------------------------ - SUBROUTINE ReorderingAndSignReversionsData(Element,Nodes,PermVec,SignVec) -!------------------------------------------------------------------------------------- - IMPLICIT NONE - - TYPE(Element_t), TARGET :: Element !< Element structure - TYPE(Nodes_t) :: Nodes !< Data corresponding to the classic element nodes - INTEGER :: PermVec(:) !< At exit the permution vector for performing reordering - REAL(KIND=dp) :: SignVec(:) !< At exit the vector for performing sign changes -!--------------------------------------------------------------------------------------------------- - TYPE(Mesh_t), POINTER :: Mesh - INTEGER, POINTER :: EdgeMap(:,:), Ind(:) - INTEGER :: SquareFaceMap(4), BrickFaceMap(6,4), PrismSquareFaceMap(3,4), DOFs, i, j, k - INTEGER :: FaceIndices(4), I1, I2, ni, nj - REAL(KIND=dp) :: D1, D2 - LOGICAL :: Parallel -!--------------------------------------------------------------------------------------------------- - Mesh => CurrentModel % Solver % Mesh - !Parallel = ParEnv % PEs>1 - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - - SignVec = 1.0d0 - Ind => Element % Nodeindexes - - SELECT CASE( Element % TYPE % ElementCode / 100 ) - !CASE(3) needs to be done - - !CASE(4) needs to be done - - CASE(5) - ! NOTE: The Nedelec second family is not yet supported - EdgeMap => GetEdgeMap(5) - DO k=1,6 - i = EdgeMap(k,1) - j = EdgeMap(k,2) - ni = Ind(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Ind(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj GetEdgeMap(6) - DO k=1,8 - i = EdgeMap(k,1) - j = EdgeMap(k,2) - ni = Ind(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Ind(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj GetEdgeMap(7) - DO k=1,9 - i = EdgeMap(k,1) - j = EdgeMap(k,2) - ni = Ind(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Ind(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj GetEdgeMap(8) - DO k=1,12 - i = EdgeMap(k,1) - j = EdgeMap(k,2) - ni = Ind(i) - IF (Parallel) ni=Mesh % ParallelInfo % GlobalDOFs(ni) - nj = Ind(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - IF (nj This subroutine contains an older design for providing edge element basis functions -!> of the lowest-degree. Obtaining optimal accuracy with these elements may require that -!> the element map is affine, while the edge basis functions given by the newer design -!> (the function EdgeElementInfo) should also work on general meshes. -!------------------------------------------------------------------------ - SUBROUTINE GetEdgeBasis( Element, WBasis, RotWBasis, Basis, dBasisdx ) -!------------------------------------------------------------------------ - TYPE(Element_t),TARGET :: Element - REAL(KIND=dp) :: WBasis(:,:), RotWBasis(:,:), Basis(:), dBasisdx(:,:) -!------------------------------------------------------------------------ - TYPE(Element_t),POINTER :: Edge - TYPE(Mesh_t), POINTER :: Mesh - TYPE(Nodes_t), SAVE :: Nodes - REAL(KIND=dp) :: u,v,w,dudx(3,3),du(3),Base,dBase(3),tBase(3), & - rBase(3),triBase(3),dtriBase(3,3), G(3,3), F(3,3), detF, detG, & - EdgeBasis(8,3), CurlBasis(8,3) - LOGICAL :: Parallel,stat - INTEGER :: i,j,k,n,nj,nk,i1,i2 - INTEGER, POINTER :: EdgeMap(:,:) -!------------------------------------------------------------------------ - Mesh => CurrentModel % Solver % Mesh - Parallel = ASSOCIATED(Mesh % ParallelInfo % Interface) - - IF (Element % TYPE % BasisFunctionDegree>1) THEN - CALL Fatal('GetEdgeBasis',"Can't handle but linear elements, sorry.") - END IF - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(4,7,8) - n = Element % TYPE % NumberOfNodes - u = SUM(Basis(1:n)*Element % TYPE % NodeU(1:n)) - v = SUM(Basis(1:n)*Element % TYPE % NodeV(1:n)) - w = SUM(Basis(1:n)*Element % TYPE % NodeW(1:n)) - - dudx(1,:) = MATMUL(Element % TYPE % NodeU(1:n),dBasisdx(1:n,:)) - dudx(2,:) = MATMUL(Element % TYPE % NodeV(1:n),dBasisdx(1:n,:)) - dudx(3,:) = MATMUL(Element % TYPE % NodeW(1:n),dBasisdx(1:n,:)) - - triBase(1) = 1-u-v - triBase(2) = u - triBase(3) = v - - dtriBase(1,:) = -dudx(1,:)-dudx(2,:) - dtriBase(2,:) = dudx(1,:) - dtriBase(3,:) = dudx(2,:) - CASE(6) - n = Element % TYPE % NumberOfNodes - u = SUM(Basis(1:n)*Element % TYPE % NodeU(1:n)) - v = SUM(Basis(1:n)*Element % TYPE % NodeV(1:n)) - w = SUM(Basis(1:n)*Element % TYPE % NodeW(1:n)) - - G(1,:) = MATMUL(Element % TYPE % NodeU(1:n),dBasisdx(1:n,:)) - G(2,:) = MATMUL(Element % TYPE % NodeV(1:n),dBasisdx(1:n,:)) - G(3,:) = MATMUL(Element % TYPE % NodeW(1:n),dBasisdx(1:n,:)) - - detG = G(1,1) * ( G(2,2)*G(3,3) - G(2,3)*G(3,2) ) + & - G(1,2) * ( G(2,3)*G(3,1) - G(2,1)*G(3,3) ) + & - G(1,3) * ( G(2,1)*G(3,2) - G(2,2)*G(3,1) ) - detF = 1.0d0/detG - CALL InvertMatrix3x3(G,F,detG) - - !------------------------------------------------------------ - ! The basis functions spanning the reference element space and - ! their Curl with respect to the local coordinates - ! ------------------------------------------------------------ - EdgeBasis(1,1) = (1.0d0 - v - w)/4.0d0 - EdgeBasis(1,2) = 0.0d0 - EdgeBasis(1,3) = (u*(-1.0d0 + v + w))/(4.0d0*(-1.0d0 + w)) - CurlBasis(1,1) = u/(4.0d0*(-1.0d0 + w)) - CurlBasis(1,2) = -(-2.0d0 + v + 2.0d0*w)/(4.0d0*(-1.0d0 + w)) - CurlBasis(1,3) = 0.25d0 - - EdgeBasis(2,1) = 0.0d0 - EdgeBasis(2,2) = (1.0d0 + u - w)/4.0d0 - EdgeBasis(2,3) = (v*(1.0d0 + u - w))/(4.0d0 - 4.0d0*w) - CurlBasis(2,1) = (2.0d0 + u - 2.0d0*w)/(4.0d0 - 4.0d0*w) - CurlBasis(2,2) = v/(4.0d0*(-1.0d0 + w)) - CurlBasis(2,3) = 0.25d0 - - EdgeBasis(3,1) = (1.0d0 + v - w)/4.0d0 - EdgeBasis(3,2) = 0.0d0 - EdgeBasis(3,3) = (u*(1.0d0 + v - w))/(4.0d0 - 4.0d0*w) - CurlBasis(3,1) = u/(4.0d0 - 4.0d0*w) - CurlBasis(3,2) = (2.0d0 + v - 2.0d0*w)/(4.0d0*(-1.0d0 + w)) - CurlBasis(3,3) = -0.25d0 - - EdgeBasis(4,1) = 0.0d0 - EdgeBasis(4,2) = (1.0d0 - u - w)/4.0d0 - EdgeBasis(4,3) = (v*(-1.0d0 + u + w))/(4.0d0*(-1.0d0 + w)) - CurlBasis(4,1) = (-2.0d0 + u + 2.0d0*w)/(4.0d0*(-1.0d0 + w)) - CurlBasis(4,2) = v/(4.0d0 - 4.0d0*w) - CurlBasis(4,3) = -0.25d0 - - EdgeBasis(5,1) = (w*(-1.0d0 + v + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(5,2) = (w*(-1.0d0 + u + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(5,3) = (-((-1.0d0 + v)*(-1.0d0 + w)**2) + u*(v - (-1.0d0 + w)**2 - 2.0d0*v*w))/& - (4.0d0*(-1.0d0 + w)**2) - CurlBasis(5,1) = -(-1.0d0 + u + w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(5,2) = (-1.0d0 + v + w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(5,3) = 0.0d0 - - EdgeBasis(6,1) = -(w*(-1.0d0 + v + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(6,2) = (w*(-1.0d0 - u + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(6,3) = (-((-1.0d0 + v)*(-1.0d0 + w)**2) + u*((-1.0d0 + w)**2 + v*(-1.0d0 + 2.0d0*w)))/& - (4.0d0*(-1.0d0 + w)**2) - CurlBasis(6,1) = (1.0d0 + u - w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(6,2) = -(-1.0d0 + v + w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(6,3) = 0.0d0 - - EdgeBasis(7,1) = ((1.0d0 + v - w)*w)/(4.0d0*(-1.0d0 + w)) - EdgeBasis(7,2) = ((1.0d0 + u - w)*w)/(4.0d0*(-1.0d0 + w)) - EdgeBasis(7,3) = ((1.0d0 + v)*(-1.0d0 + w)**2 + u*(v + (-1.0d0 + w)**2 - 2.0d0*v*w))/& - (4.0d0*(-1.0d0 + w)**2) - CurlBasis(7,1) = (1.0d0 + u - w)/(2.0d0 - 2.0d0*w) - CurlBasis(7,2) = (1.0d0 + v - w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(7,3) = 0.0d0 - - EdgeBasis(8,1) = (w*(-1.0d0 - v + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(8,2) = -(w*(-1.0d0 + u + w))/(4.0d0*(-1.0d0 + w)) - EdgeBasis(8,3) = ((1.0d0 + v)*(-1.0d0 + w)**2 - u*(v + (-1.0d0 + w)**2 - 2.0d0*v*w))/& - (4.0d0*(-1.0d0 + w)**2) - CurlBasis(8,1) = (-1.0d0 + u + w)/(2.0d0*(-1.0d0 + w)) - CurlBasis(8,2) = (1.0d0 + v - w)/(2.0d0 - 2.0d0*w) - CurlBasis(8,3) = 0.0d0 - - END SELECT - - EdgeMap => GetEdgeMap(Element % TYPE % ElementCode / 100) - DO i=1,SIZE(Edgemap,1) - j = EdgeMap(i,1); k = EdgeMap(i,2) - - nj = Element % Nodeindexes(j) - IF (Parallel) nj=Mesh % ParallelInfo % GlobalDOFs(nj) - nk = Element % Nodeindexes(k) - IF (Parallel) nk=Mesh % ParallelInfo % GlobalDOFs(nk) - - SELECT CASE(Element % TYPE % ElementCode / 100) - CASE(3,5) - WBasis(i,:) = Basis(j)*dBasisdx(k,:) - Basis(k)*dBasisdx(j,:) - - RotWBasis(i,1) = 2.0_dp * ( dBasisdx(j,2) * dBasisdx(k,3) - & - dBasisdx(j,3) * dBasisdx(k,2) ) - RotWBasis(i,2) = 2.0_dp * ( dBasisdx(j,3) * dBasisdx(k,1) - & - dBasisdx(j,1) * dBasisdx(k,3) ) - RotWBasis(i,3) = 2.0_dp * ( dBasisdx(j,1) * dBasisdx(k,2) - & - dBasisdx(j,2) * dBasisdx(k,1) ) - - CASE(6) - !----------------------------------------------------------------------- - ! Create the referential description of basis functions and their - ! spatial curl on the physical element via applying the Piola transform: - !----------------------------------------------------------------------- - DO k=1,3 - WBasis(i,k) = SUM( G(1:3,k) * EdgeBasis(i,1:3) ) - END DO - DO k=1,3 - RotWBasis(i,k) = 1.0d0/DetF * SUM( F(k,1:3) * CurlBasis(i,1:3) ) - END DO - - CASE(7) - SELECT CASE(i) - CASE(1) - j=1;k=2; Base=(1-w)/2; dBase=-dudx(3,:)/2 - CASE(2) - j=2;k=3; Base=(1-w)/2; dBase=-dudx(3,:)/2 - CASE(3) - j=3;k=1; Base=(1-w)/2; dBase=-dudx(3,:)/2 - CASE(4) - j=1;k=2; Base=(1+w)/2; dBase= dudx(3,:)/2 - CASE(5) - j=2;k=3; Base=(1+w)/2; dBase= dudx(3,:)/2 - CASE(6) - j=3;k=1; Base=(1+w)/2; dBase= dudx(3,:)/2 - CASE(7) - Base=triBase(1); dBase=dtriBase(1,:); du=dudx(3,:)/2 - CASE(8) - Base=triBase(2); dBase=dtriBase(2,:); du=dudx(3,:)/2 - CASE(9) - Base=triBase(3); dBase=dtriBase(3,:); du=dudx(3,:)/2 - END SELECT - - IF(i<=6) THEN - tBase = (triBase(j)*dtriBase(k,:)-triBase(k)*dtriBase(j,:)) - rBase(1) = 2*Base*(dtriBase(j,2)*dtriBase(k,3)-dtriBase(k,2)*dtriBase(j,3)) + & - dBase(2)*tBase(3) - dBase(3)*tBase(2) - - rBase(2) = 2*Base*(dtriBase(j,3)*dtriBase(k,1)-dtriBase(k,3)*dtriBase(j,1)) + & - dBase(3)*tBase(1) - dBase(1)*tBase(3) - - rBase(3) = 2*Base*(dtriBase(j,1)*dtriBase(k,2)-dtriBase(k,1)*dtriBase(j,2)) + & - dBase(1)*tBase(2) - dBase(2)*tBase(1) - - RotWBasis(i,:)=rBase - WBasis(i,:)=tBase*Base - ELSE - WBasis(i,:)=Base*du - RotWBasis(i,1)=(dBase(2)*du(3) - dBase(3)*du(2)) - RotWBasis(i,2)=(dBase(3)*du(1) - dBase(1)*du(3)) - RotWBasis(i,3)=(dBase(1)*du(2) - dBase(2)*du(1)) - END IF - CASE(4) - SELECT CASE(i) - CASE(1) - du=dudx(1,:); Base=(1-v)*(1-w) - dBase(:)=-dudx(2,:)*(1-w)-(1-v)*dudx(3,:) - CASE(2) - du=dudx(2,:); Base=(1+u)*(1-w) - dBase(:)= dudx(1,:)*(1-w)-(1+u)*dudx(3,:) - CASE(3) - du=-dudx(1,:); Base=(1+v)*(1-w) - dBase(:)= dudx(2,:)*(1-w)-(1+v)*dudx(3,:) - CASE(4) - du=-dudx(2,:); Base=(1-u)*(1-w) - dBase(:)=-dudx(1,:)*(1-w)-(1-u)*dudx(3,:) - END SELECT - - wBasis(i,:) = Base*du/n - RotWBasis(i,1)=(dBase(2)*du(3) - dBase(3)*du(2))/n - RotWBasis(i,2)=(dBase(3)*du(1) - dBase(1)*du(3))/n - RotWBasis(i,3) = (dBase(1)*du(2) - dBase(2)*du(1))/n - CASE(8) - SELECT CASE(i) - CASE(1) - du=dudx(1,:); Base=(1-v)*(1-w) - dBase(:)=-dudx(2,:)*(1-w)-(1-v)*dudx(3,:) - CASE(2) - du=dudx(2,:); Base=(1+u)*(1-w) - dBase(:)= dudx(1,:)*(1-w)-(1+u)*dudx(3,:) - CASE(3) - du=dudx(1,:); Base=(1+v)*(1-w) - dBase(:)= dudx(2,:)*(1-w)-(1+v)*dudx(3,:) - CASE(4) - du=dudx(2,:); Base=(1-u)*(1-w) - dBase(:)=-dudx(1,:)*(1-w)-(1-u)*dudx(3,:) - CASE(5) - du=dudx(1,:); Base=(1-v)*(1+w) - dBase(:)=-dudx(2,:)*(1+w)+(1-v)*dudx(3,:) - CASE(6) - du=dudx(2,:); Base=(1+u)*(1+w) - dBase(:)= dudx(1,:)*(1+w)+(1+u)*dudx(3,:) - CASE(7) - du=dudx(1,:); Base=(1+v)*(1+w) - dBase(:)= dudx(2,:)*(1+w)+(1+v)*dudx(3,:) - CASE(8) - du=dudx(2,:); Base=(1-u)*(1+w) - dBase(:)=-dudx(1,:)*(1+w)+(1-u)*dudx(3,:) - CASE(9) - du=dudx(3,:); Base=(1-u)*(1-v) - dBase(:)=-dudx(1,:)*(1-v)-(1-u)*dudx(2,:) - CASE(10) - du=dudx(3,:); Base=(1+u)*(1-v) - dBase(:)= dudx(1,:)*(1-v)-(1+u)*dudx(2,:) - CASE(11) - du=dudx(3,:); Base=(1+u)*(1+v) - dBase(:)= dudx(1,:)*(1+v)+(1+u)*dudx(2,:) - CASE(12) - du=dudx(3,:); Base=(1-u)*(1+v) - dBase(:)=-dudx(1,:)*(1+v)+(1-u)*dudx(2,:) - END SELECT - - wBasis(i,:)=Base*du/n - RotWBasis(i,1)=(dBase(2)*du(3) - dBase(3)*du(2))/n - RotWBasis(i,2)=(dBase(3)*du(1) - dBase(1)*du(3))/n - RotWBasis(i,3)=(dBase(1)*du(2) - dBase(2)*du(1))/n - CASE DEFAULT - CALL Fatal( 'Edge Basis', 'Not implemented for this element type.') - END SELECT - - IF( nk < nj ) THEN - WBasis(i,:) = -WBasis(i,:); RotWBasis(i,:) = -RotWBasis(i,:) - END IF - END DO -!------------------------------------------------------------------------------ - END SUBROUTINE GetEdgeBasis -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Compute contravariant metric tensor (=J^TJ)^-1 of element coordinate -!> system, and square root of determinant of covariant metric tensor -!> (=sqrt(det(J^TJ))) -!------------------------------------------------------------------------------ - FUNCTION ElementMetric(nDOFs,Elm,Nodes,Metric,DetG,dLBasisdx,LtoGMap) RESULT(Success) -!------------------------------------------------------------------------------ - INTEGER :: nDOFs !< Number of active nodes in element - TYPE(Element_t) :: Elm !< Element structure - TYPE(Nodes_t) :: Nodes !< Element nodal coordinates - REAL(KIND=dp) :: Metric(:,:) !< Contravariant metric tensor - REAL(KIND=dp) :: dLBasisdx(:,:) !< Derivatives of element basis function with respect to local coordinates - REAL(KIND=dp) :: DetG !< SQRT of determinant of metric tensor - REAL(KIND=dp) :: LtoGMap(3,3) !< Transformation to obtain the referencial description of the spatial gradient - LOGICAL :: Success !< Returns .FALSE. if element is degenerate -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: dx(3,3),G(3,3),GI(3,3),s - REAL(KIND=dp), DIMENSION(:), POINTER :: x,y,z - INTEGER :: GeomId - - INTEGER :: cdim,dim,i,j,k,n -!------------------------------------------------------------------------------ - success = .TRUE. - - x => Nodes % x - y => Nodes % y - z => Nodes % z - - cdim = CoordinateSystemDimension() - n = MIN( SIZE(x), nDOFs ) - dim = elm % TYPE % DIMENSION - -!------------------------------------------------------------------------------ -! Partial derivatives of global coordinates with respect to local coordinates -!------------------------------------------------------------------------------ - DO i=1,dim - dx(1,i) = SUM( x(1:n) * dLBasisdx(1:n,i) ) - dx(2,i) = SUM( y(1:n) * dLBasisdx(1:n,i) ) - dx(3,i) = SUM( z(1:n) * dLBasisdx(1:n,i) ) - END DO -!------------------------------------------------------------------------------ -! Compute the covariant metric tensor of the element coordinate system -!------------------------------------------------------------------------------ - DO i=1,dim - DO j=1,dim - s = 0.0d0 - DO k=1,cdim - s = s + dx(k,i)*dx(k,j) - END DO - G(i,j) = s - END DO - END DO -!------------------------------------------------------------------------------ -! Convert the metric to contravariant base, and compute the SQRT(DetG) -!------------------------------------------------------------------------------ - SELECT CASE( dim ) -!------------------------------------------------------------------------------ -! Line elements -!------------------------------------------------------------------------------ - CASE (1) - DetG = G(1,1) - - IF ( DetG <= TINY( DetG ) ) GOTO 100 - - Metric(1,1) = 1.0d0 / DetG - DetG = SQRT( DetG ) - -!------------------------------------------------------------------------------ -! Surface elements -!------------------------------------------------------------------------------ - CASE (2) - DetG = ( G(1,1)*G(2,2) - G(1,2)*G(2,1) ) - - IF ( DetG <= TINY( DetG ) ) GOTO 100 - - Metric(1,1) = G(2,2) / DetG - Metric(1,2) = -G(1,2) / DetG - Metric(2,1) = -G(2,1) / DetG - Metric(2,2) = G(1,1) / DetG - DetG = SQRT(DetG) - -!------------------------------------------------------------------------------ -! Volume elements -!------------------------------------------------------------------------------ - CASE (3) - DetG = G(1,1) * ( G(2,2)*G(3,3) - G(2,3)*G(3,2) ) + & - G(1,2) * ( G(2,3)*G(3,1) - G(2,1)*G(3,3) ) + & - G(1,3) * ( G(2,1)*G(3,2) - G(2,2)*G(3,1) ) - - IF ( DetG <= TINY( DetG ) ) GOTO 100 - - CALL InvertMatrix3x3( G,GI,detG ) - Metric = GI - DetG = SQRT(DetG) - END SELECT - -!-------------------------------------------------------------------------------------- -! Construct a transformation X = LtoGMap such that (grad B)(f(p)) = X(p) Grad b(p), -! with Grad the gradient with respect to the reference element coordinates p and -! the referencial description of the spatial field B(x) satisfying B(f(p)) = b(p). -! If cdim > dim (e.g. a surface embedded in the 3-dimensional space), X is -! the pseudo-inverse of (Grad f)^{T}. -!------------------------------------------------------------------------------- - DO i=1,cdim - DO j=1,dim - s = 0.0d0 - DO k=1,dim - s = s + dx(i,k) * Metric(k,j) - END DO - LtoGMap(i,j) = s - END DO - END DO - -! Return here also implies success = .TRUE. - RETURN - - -100 Success = .FALSE. - WRITE( Message,'(A,I0,A,I0)') 'Degenerate ',dim,'D element: ',Elm % ElementIndex - CALL Error( 'ElementMetric', Message ) - - IF( ASSOCIATED( Elm % BoundaryInfo ) ) THEN - WRITE( Message,'(A,I0,A,ES12.3)') 'Boundary Id: ',Elm % BoundaryInfo % Constraint,' DetG:',DetG - ELSE - WRITE( Message,'(A,I0,A,ES12.3)') 'Body Id: ',Elm % BodyId,' DetG:',DetG - END IF - CALL Info( 'ElementMetric', Message, Level=3 ) - - DO i=1,n - WRITE( Message,'(A,I0,A,3ES12.3)') 'Node: ',i,' Coord:',x(i),y(i),z(i) - CALL Info( 'ElementMetric', Message, Level=3 ) - END DO - DO i=2,n - WRITE( Message,'(A,I0,A,3ES12.3)') 'Node: ',i,' dCoord:',& - x(i)-x(1),y(i)-y(1),z(i)-z(1) - CALL Info( 'ElementMetric', Message, Level=3 ) - END DO - IF ( cdim < dim ) THEN - WRITE( Message,'(A,I0,A,I0)') 'Element dim larger than meshdim: ',dim,' vs. ',cdim - CALL Info( 'ElementMetric', Message, Level=3 ) - END IF - -!------------------------------------------------------------------------------ - END FUNCTION ElementMetric -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ - FUNCTION ElementMetricVec( Elm, Nodes, nc, ndof, DetJ, nbmax, dLBasisdx, LtoGMap) RESULT(AllSuccess) -!------------------------------------------------------------------------------ - TYPE(Element_t) :: Elm !< Element structure - TYPE(Nodes_t) :: Nodes !< element nodal coordinates - INTEGER, INTENT(IN) :: nc !< Number of points to map - INTEGER :: ndof !< Number of active nodes in element - REAL(KIND=dp) :: DetJ(VECTOR_BLOCK_LENGTH) !< SQRT of determinant of element coordinate metric at each point - INTEGER, INTENT(IN) :: nbmax !< Maximum total number of basis functions in local basis - REAL(KIND=dp) :: dLBasisdx(VECTOR_BLOCK_LENGTH,nbmax,3) !< Derivatives of element basis function with - !< respect to local coordinates at each point - REAL(KIND=dp) :: LtoGMap(VECTOR_BLOCK_LENGTH,3,3) !< Mapping between local and global coordinates - LOGICAL :: AllSuccess !< Returns .FALSE. if some point in element is degenerate -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: dx(VECTOR_BLOCK_LENGTH,3,3) - REAL(KIND=dp) :: Metric(VECTOR_BLOCK_LENGTH,6), & - G(VECTOR_BLOCK_LENGTH,6) ! Symmetric Metric(nc,3,3) and G(nc,3,3) - - REAL(KIND=dp) :: s - INTEGER :: cdim,dim,i,j,k,l,n,ip, jj, kk - INTEGER :: ldbasis, ldxyz, utind -!DIR$ ATTRIBUTES ALIGN:64::Metric -!DIR$ ATTRIBUTES ALIGN:64::dx -!DIR$ ATTRIBUTES ALIGN:64::G -!DIR$ ASSUME_ALIGNED dLBasisdx:64, LtoGMap:64, DetJ:64 - !------------------------------------------------------------------------------ - AllSuccess = .TRUE. - - ! Coordinates (single array) - n = MIN( SIZE(Nodes % x, 1), ndof ) - - ! Dimensions (coordinate system and element) - cdim = CoordinateSystemDimension() - dim = elm % TYPE % DIMENSION - - ! Leading dimensions for local basis and coordinate arrays - ldbasis = SIZE(dLBasisdx, 1) - ldxyz = SIZE(Nodes % xyz, 1) - - ! For linear, extruded and otherwise regular elements mapping has to be computed - ! only once, the problem is to identify these cases... - !------------------------------------------------------------------------------ - ! Partial derivatives of global coordinates with respect to local coordinates - !------------------------------------------------------------------------------ - ! Avoid DGEMM calls for nc small - IF (nc < VECTOR_SMALL_THRESH) THEN - DO l=1,dim - DO j=1,3 - dx(1:nc,j,l)=REAL(0,dp) - DO k=1,n -!DIR$ UNROLL - DO i=1,nc - dx(i,j,l)=dx(i,j,l)+dLBasisdx(i,k,l)*Nodes % xyz(k,j) - END DO - END DO - END DO - END DO - ELSE - DO i=1,dim - CALL DGEMM('N','N',nc, 3, n, & - REAL(1,dp), dLbasisdx(1,1,i), ldbasis, & - Nodes % xyz, ldxyz, REAL(0, dp), dx(1,1,i), VECTOR_BLOCK_LENGTH) - END DO - END IF - !------------------------------------------------------------------------------ - ! Compute the covariant metric tensor of the element coordinate system (symmetric) - !------------------------------------------------------------------------------ - ! Linearized upper triangular indices for accesses to G - ! | (1,1) (1,2) (1,3) | = | 1 2 4 | - ! | (2,2) (2,3) | | 3 5 | - ! | (3,3) | | 6 | - ! G is symmetric, compute only the upper triangular part of G=dx^Tdx -!DIR$ LOOP COUNT MAX=3 - DO j=1,dim -!DIR$ LOOP COUNT MAX=3 - DO i=1,j -!DIR$ INLINE - utind = GetSymmetricIndex(i,j) - SELECT CASE (cdim) - CASE(1) - !_ELMER_OMP_SIMD - DO l=1,nc - G(l,utind)=dx(l,1,i)*dx(l,1,j) - END DO - CASE(2) - !_ELMER_OMP_SIMD - DO l=1,nc - G(l,utind)=dx(l,1,i)*dx(l,1,j)+dx(l,2,i)*dx(l,2,j) - END DO - CASE(3) - !_ELMER_OMP_SIMD - DO l=1,nc - G(l,utind)=dx(l,1,i)*dx(l,1,j)+dx(l,2,i)*dx(l,2,j)+dx(l,3,i)*dx(l,3,j) - END DO - END SELECT - END DO - END DO - - !------------------------------------------------------------------------------ - ! Convert the metric to contravariant base, and compute the SQRT(DetG) - !------------------------------------------------------------------------------ - SELECT CASE( dim ) - !------------------------------------------------------------------------------ - ! Line elements - !------------------------------------------------------------------------------ - CASE (1) - ! Determinants - ! DetJ(1:nc) = G(1:nc,1,1) - DetJ(1:nc) = G(1:nc,1) - - DO i=1,nc - IF (DetJ(i) <= TINY(REAL(1,dp))) THEN - AllSuccess = .FALSE. - EXIT - END IF - END DO - - IF (AllSuccess) THEN - !_ELMER_OMP_SIMD - DO i=1,nc - ! Metric(i,1,1) = REAL(1,dp)/DetJ(i) - Metric(i,1) = REAL(1,dp)/DetJ(i) - END DO - !_ELMER_OMP_SIMD - DO i=1,nc - DetJ(i) = SQRT( DetJ(i)) - END DO - END IF - - - !------------------------------------------------------------------------------ - ! Surface elements - !------------------------------------------------------------------------------ - CASE (2) - ! Determinants - !_ELMER_OMP_SIMD - DO i=1,nc - ! DetJ(i) = ( G(i,1,1)*G(i,2,2) - G(i,1,2)*G(i,2,1) ) - ! G is symmetric - DetJ(i) = G(i,1)*G(i,3)-G(i,2)*G(i,2) - END DO - - DO i=1,nc - IF (DetJ(i) <= TINY(REAL(1,dp))) THEN - AllSuccess = .FALSE. - EXIT - END IF - END DO - - IF (AllSuccess) THEN - ! Since G=G^T, it holds G^{-1}=(G^T)^{-1} - !_ELMER_OMP_SIMD - DO i=1,nc - s = REAL(1,dp)/DetJ(i) - ! G is symmetric - ! All in one go, with redundancies eliminated - Metric(i,1) = s*G(i,3) - Metric(i,2) = -s*G(i,2) - Metric(i,3) = s*G(i,1) - END DO - !_ELMER_OMP_SIMD - DO i=1,nc - DetJ(i) = SQRT(DetJ(i)) - END DO - - END IF - !------------------------------------------------------------------------------ - ! Volume elements - !------------------------------------------------------------------------------ - CASE (3) - ! Determinants - !_ELMER_OMP_SIMD - DO i=1,nc - ! DetJ(i) = G(i,1,1) * ( G(i,2,2)*G(i,3,3) - G(i,2,3)*G(i,3,2) ) + & - ! G(i,1,2) * ( G(i,2,3)*G(i,3,1) - G(i,2,1)*G(i,3,3) ) + & - ! G(i,1,3) * ( G(i,2,1)*G(i,3,2) - G(i,2,2)*G(i,3,1) ) - ! G is symmetric - DetJ(i) = G(i,1)*(G(i,3)*G(i,6)-G(i,5)*G(i,5)) + & - G(i,2)*(G(i,5)*G(i,4)-G(i,2)*G(i,6)) + & - G(i,4)*(G(i,2)*G(i,5)-G(i,3)*G(i,4)) - END DO - - DO i=1,nc - IF (DetJ(i) <= TINY(REAL(1,dp))) THEN - AllSuccess = .FALSE. - EXIT - END IF - END DO - - IF (AllSuccess) THEN - ! Since G=G^T, it holds G^{-1}=(G^T)^{-1} - !_ELMER_OMP_SIMD - DO i=1,nc - s = REAL(1,dp) / DetJ(i) - ! Metric(i,1,1) = s * (G(i,2,2)*G(i,3,3) - G(i,3,2)*G(i,2,3)) - ! Metric(i,2,1) = -s * (G(i,2,1)*G(i,3,3) - G(i,3,1)*G(i,2,3)) - ! Metric(i,3,1) = s * (G(i,2,1)*G(i,3,2) - G(i,3,1)*G(i,2,2)) - ! G is symmetric - - ! All in one go, with redundancies eliminated - Metric(i,1)= s*(G(i,3)*G(i,6)-G(i,5)*G(i,5)) - Metric(i,2)=-s*(G(i,2)*G(i,6)-G(i,4)*G(i,5)) - Metric(i,3)= s*(G(i,1)*G(i,6)-G(i,4)*G(i,4)) - Metric(i,4)= s*(G(i,2)*G(i,5)-G(i,3)*G(i,4)) - Metric(i,5)=-s*(G(i,1)*G(i,5)-G(i,2)*G(i,4)) - Metric(i,6)= s*(G(i,1)*G(i,3)-G(i,2)*G(i,2)) - END DO - - !_ELMER_OMP_SIMD - DO i=1,nc - DetJ(i) = SQRT(DetJ(i)) - END DO - - END IF - END SELECT - - IF (AllSuccess) THEN - SELECT CASE(dim) - CASE(1) -!DIR$ LOOP COUNT MAX=3 - DO i=1,cdim - !_ELMER_OMP_SIMD - DO l=1,nc - LtoGMap(l,i,1) = dx(l,i,1)*Metric(l,1) - END DO - END DO - CASE(2) -!DIR$ LOOP COUNT MAX=3 - DO i=1,cdim - !_ELMER_OMP_SIMD - DO l=1,nc - LtoGMap(l,i,1) = dx(l,i,1)*Metric(l,1) + dx(l,i,2)*Metric(l,2) - LtoGMap(l,i,2) = dx(l,i,1)*Metric(l,2) + dx(l,i,2)*Metric(l,3) - END DO - END DO - CASE(3) -!DIR$ LOOP COUNT MAX=3 - DO i=1,cdim - !_ELMER_OMP_SIMD - DO l=1,nc - LtoGMap(l,i,1) = dx(l,i,1)*Metric(l,1) + dx(l,i,2)*Metric(l,2) + dx(l,i,3)*Metric(l,4) - LtoGMap(l,i,2) = dx(l,i,1)*Metric(l,2) + dx(l,i,2)*Metric(l,3) + dx(l,i,3)*Metric(l,5) - LtoGMap(l,i,3) = dx(l,i,1)*Metric(l,4) + dx(l,i,2)*Metric(l,5) + dx(l,i,3)*Metric(l,6) - END DO - END DO - END SELECT - ELSE - - ! Degenerate element! - WRITE( Message,'(A,I0,A,I0,A,I0)') 'Degenerate ',dim,'D element: ',Elm % ElementIndex, ', pt=', i - CALL Error( 'ElementMetricVec', Message ) - WRITE( Message,'(A,G10.3)') 'DetG:',DetJ(i) - CALL Info( 'ElementMetricVec', Message, Level=3 ) - DO i=1,cdim - WRITE( Message,'(A,I0,A,3G10.3)') 'Dir: ',i,' Coord:',Nodes % xyz(i,1),& - Nodes % xyz(i,2), Nodes % xyz(i,3) - CALL Info( 'ElementMetricVec', Message, Level=3 ) - END DO - IF (cdim < dim) THEN - WRITE( Message,'(A,I0,A,I0)') 'Element dim larger than meshdim: ',dim,' vs. ',cdim - CALL Info( 'ElementMetricVec', Message, Level=3 ) - END IF - END IF - - CONTAINS - - FUNCTION GetSymmetricIndex(i,j) RESULT(utind) - IMPLICIT NONE - INTEGER, INTENT(IN) :: i, j - INTEGER :: utind - - IF (i>j) THEN - utind = i*(i-1)/2+j - ELSE - utind = j*(j-1)/2+i - END IF - END FUNCTION GetSymmetricIndex -!------------------------------------------------------------------------------ - END FUNCTION ElementMetricVec -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivatives with -!> respect to global coordinates of a quantity x given at element nodes at -!> local coordinate point u,v,w inside the element. Element basis functions -!> are used to compute the value. This is internal version,and shoudnt -!> usually be called directly by the user, but through the wrapper routine -!> GlobalFirstDerivatives. -!------------------------------------------------------------------------------ - SUBROUTINE GlobalFirstDerivativesInternal( elm,nodes,df,gx,gy,gz, & - Metric,dLBasisdx ) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! Type(Nodes_t) :: nodes -! INPUT: element nodal coordinate arrays -! -! REAL(KIND=dp) :: f(:) -! INPUT: Nodal values of the quantity whose partial derivative we want to know -! -! REAL(KIND=dp) :: gx = @f(u,v)/@x, gy = @f(u,v)/@y, gz = @f(u,v)/@z -! OUTPUT: Values of the partial derivatives -! -! REAL(KIND=dp) :: Metric(:,:) -! INPUT: Contravariant metric tensor of the element coordinate system -! -! REAL(KIND=dp), OPTIONAL :: dLBasisdx(:,:) -! INPUT: Values of partial derivatives with respect to local coordinates -! -! FUNCTION VALUE: -! .TRUE. if element is ok, .FALSE. if degenerated -! -!------------------------------------------------------------------------------ - ! - ! Return value of first derivatives of a quantity f in global - ! coordinates at point (u,v) in gx,gy and gz. - ! - TYPE(Element_t) :: elm - TYPE(Nodes_t) :: nodes - - REAL(KIND=dp) :: df(:),Metric(:,:) - REAL(KIND=dp) :: gx,gy,gz - REAL(KIND=dp) :: dLBasisdx(:,:) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - - REAL(KIND=dp), DIMENSION(:), POINTER :: x,y,z - REAL(KIND=dp) :: dx(3,3),dfc(3),s - - INTEGER :: cdim,dim,i,j,n,NB -!------------------------------------------------------------------------------ - - n = elm % TYPE % NumberOfNodes - dim = elm % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - - x => nodes % x - y => nodes % y - z => nodes % z -!------------------------------------------------------------------------------ -! Partial derivatives of global coordinates with respect to local, and -! partial derivatives of the quantity given, also with respect to local -! coordinates -!------------------------------------------------------------------------------ - SELECT CASE(cdim) - CASE(1) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dLBasisdx(1:n,i) ) - END DO - - CASE(2) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dLBasisdx(1:n,i) ) - dx(2,i) = SUM( y(1:n)*dLBasisdx(1:n,i) ) - END DO - - CASE(3) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dLBasisdx(1:n,i) ) - dx(2,i) = SUM( y(1:n)*dLBasisdx(1:n,i) ) - dx(3,i) = SUM( z(1:n)*dLBasisdx(1:n,i) ) - END DO - END SELECT -!------------------------------------------------------------------------------ -! Contravariant components of partials in element coordinates -!------------------------------------------------------------------------------ - DO i=1,dim - s = 0.0d0 - DO j=1,dim - s = s + Metric(i,j) * df(j) - END DO - dfc(i) = s - END DO -!------------------------------------------------------------------------------ -! Transform partials to space coordinates -!------------------------------------------------------------------------------ - gx = 0.0d0 - gy = 0.0d0 - gz = 0.0d0 - SELECT CASE(cdim) - CASE(1) - gx = SUM( dx(1,1:dim) * dfc(1:dim) ) - - CASE(2) - gx = SUM( dx(1,1:dim) * dfc(1:dim) ) - gy = SUM( dx(2,1:dim) * dfc(1:dim) ) - - CASE(3) - gx = SUM( dx(1,1:dim) * dfc(1:dim) ) - gy = SUM( dx(2,1:dim) * dfc(1:dim) ) - gz = SUM( dx(3,1:dim) * dfc(1:dim) ) - END SELECT - - END SUBROUTINE GlobalFirstDerivativesInternal -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to global coordinates of a quantity f given at element nodes at -!> local coordinate point u,v,w inside the element. Element basis functions -!> are used to compute the value. -!------------------------------------------------------------------------------ - SUBROUTINE GlobalFirstDerivatives( Elm, Nodes, df, gx, gy, gz, & - Metric, dLBasisdx ) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! Type(Nodes_t) :: nodes -! INPUT: element nodal coordinate arrays -! -! REAL(KIND=dp) :: f(:) -! INPUT: Nodal values of the quantity whose partial derivatives we want -! to know -! -! REAL(KIND=dp) :: gx=@f(u,v,w)/@x, gy=@f(u,v,w)/@y, gz=@f(u,v,w)/@z -! OUTPUT: Values of the partial derivatives -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the partial derivative -! -! REAL(KIND=dp)L :: dLBasisdx(:,:) -! INPUT: Values of partial derivatives of basis functions with respect to -! local coordinates -! -! REAL(KIND=dp), OPTIONAL :: dBasisdx(:,:) -! INPUT: Values of partial derivatives of basis functions with respect to -! global coordinates can be given here, if known, otherwise they -! will be computed from the element basis functions. -! -!------------------------------------------------------------------------------ - - TYPE(Element_t) :: elm - TYPE(Nodes_t) :: nodes - - REAL(KIND=dp) :: gx,gy,gz - REAL(KIND=dp) :: dLBasisdx(:,:),Metric(:,:),df(:) - -! Local variables -!------------------------------------------------------------------------------ - INTEGER :: n -!------------------------------------------------------------------------------ - - CALL GlobalFirstDerivativesInternal( Elm, Nodes, df, & - gx, gy, gz, Metric, dLBasisdx ) - - END SUBROUTINE GlobalFirstDerivatives -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Given element structure return value of a quantity x given at element nodes -!> at local coordinate point u inside the element. Element basis functions are -!> used to compute the value. This is just a wrapper routine and will call the -!> real function according to element dimension. -!------------------------------------------------------------------------------ - FUNCTION InterpolateInElement( elm,f,u,v,w,Basis ) RESULT(VALUE) -!------------------------------------------------------------------------------ -! -! DESCRIPTION: -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! REAL(KIND=dp) :: f(:) -! INPUT: Nodal values of the quantity whose value we want to know -! -! REAL(KIND=dp) :: u,v,w -! INPUT: Point at which to evaluate the value -! -! REAL(KIND=dp), OPTIONAL :: Basis(:) -! INPUT: Values of the basis functions at the point u,v,w can be given here, -! if known, otherwise the will be computed from the definition -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: y -! value of the quantity y = x(u,v,w) -! -!------------------------------------------------------------------------------ - - TYPE(Element_t) :: elm - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp) :: f(:) - REAL(KIND=dp), OPTIONAL :: Basis(:) - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: VALUE - INTEGER :: n - - IF ( PRESENT( Basis ) ) THEN -!------------------------------------------------------------------------------ -! Basis function values given, just sum the result ... -!------------------------------------------------------------------------------ - n = elm % TYPE % NumberOfNodes - VALUE = SUM( f(1:n)*Basis(1:n) ) - ELSE -!------------------------------------------------------------------------------ -! ... otherwise compute from the definition. -!------------------------------------------------------------------------------ - SELECT CASE (elm % TYPE % DIMENSION) - CASE (0) - VALUE = f(1) - CASE (1) - VALUE = InterpolateInElement1D( elm,f,u ) - CASE (2) - VALUE = InterpolateInElement2D( elm,f,u,v ) - CASE (3) - VALUE = InterpolateInElement3D( elm,f,u,v,w ) - END SELECT - END IF - - END FUNCTION InterpolateInElement -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Compute elementwise matrix of second partial derivatives -!> at given point u,v,w in global coordinates. -!------------------------------------------------------------------------------ - SUBROUTINE GlobalSecondDerivatives(elm,nodes,f,values,u,v,w,Metric,dBasisdx) -!------------------------------------------------------------------------------ -! -! Parameters: -! -! Input: (Element_t) structure describing the element -! (Nodes_t) element nodal coordinates -! (double precision) F nodal values of the quantity -! (double precision) u,v point at which to evaluate -! -! Output: 3x3 matrix (values) of partial derivatives -! -!------------------------------------------------------------------------------ - - TYPE(Nodes_t) :: nodes - TYPE(Element_t) :: elm - - REAL(KIND=dp) :: u,v,w - REAL(KIND=dp) :: f(:),Metric(:,:) - REAL(KIND=dp) :: values(:,:) - REAL(KIND=dp), OPTIONAL :: dBasisdx(:,:) -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - INTEGER :: i,j,k,l,dim,cdim - - REAL(KIND=dp), DIMENSION(3,3,3) :: C1,C2,ddx - REAL(KIND=dp), DIMENSION(3) :: df - REAL(KIND=dp), DIMENSION(3,3) :: cddf,ddf,dx - - REAL(KIND=dp), DIMENSION(:), POINTER :: x,y,z - REAL(KIND=dp) :: s - - INTEGER :: n -!------------------------------------------------------------------------------ -#if 1 -! -! This is actually not quite correct... -! - IF ( elm % TYPE % BasisFunctionDegree <= 1 ) RETURN -#else -! -! this is ... -! - IF ( elm % TYPE % ElementCode <= 202 .OR. & - elm % TYPE % ElementCode == 303 .OR. & - elm % TYPE % ElementCode == 504 ) RETURN -#endif - - n = elm % TYPE % NumberOfNodes - x => nodes % x - y => nodes % y - z => nodes % z - - dim = elm % TYPE % DIMENSION - cdim = CoordinateSystemDimension() - -!------------------------------------------------------------------------------ -! Partial derivatives of the basis functions are given, just -! sum for the first partial derivatives... -!------------------------------------------------------------------------------ - dx = 0.0d0 - df = 0.0d0 - SELECT CASE( cdim ) - CASE(1) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dBasisdx(1:n,i) ) - df(i) = SUM( f(1:n)*dBasisdx(1:n,i) ) - END DO - - CASE(2) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dBasisdx(1:n,i) ) - dx(2,i) = SUM( y(1:n)*dBasisdx(1:n,i) ) - df(i) = SUM( f(1:n)*dBasisdx(1:n,i) ) - END DO - - CASE(3) - DO i=1,dim - dx(1,i) = SUM( x(1:n)*dBasisdx(1:n,i) ) - dx(2,i) = SUM( y(1:n)*dBasisdx(1:n,i) ) - dx(3,i) = SUM( z(1:n)*dBasisdx(1:n,i) ) - df(i) = SUM( f(1:n)*dBasisdx(1:n,i) ) - END DO - END SELECT -!------------------------------------------------------------------------------ -! Get second partial derivatives with respect to local coordinates -!------------------------------------------------------------------------------ - SELECT CASE( dim ) - CASE(1) -!------------------------------------------------------------------------------ -! Line elements -!------------------------------------------------------------------------------ - ddx(1,1,1) = SecondDerivatives1D( elm,x,u ) - ddx(2,1,1) = SecondDerivatives1D( elm,y,u ) - ddx(3,1,1) = SecondDerivatives1D( elm,z,u ) - - CASE(2) -!------------------------------------------------------------------------------ -! Surface elements -!------------------------------------------------------------------------------ - ddx(1,1:2,1:2) = SecondDerivatives2D( elm,x,u,v ) - ddx(2,1:2,1:2) = SecondDerivatives2D( elm,y,u,v ) - ddx(3,1:2,1:2) = SecondDerivatives2D( elm,z,u,v ) - - CASE(3) -!------------------------------------------------------------------------------ -! Volume elements -!------------------------------------------------------------------------------ - ddx(1,1:3,1:3) = SecondDerivatives3D( elm,x,u,v,w ) - ddx(2,1:3,1:3) = SecondDerivatives3D( elm,y,u,v,w ) - ddx(3,1:3,1:3) = SecondDerivatives3D( elm,z,u,v,w ) - END SELECT -! -!------------------------------------------------------------------------------ -! Christoffel symbols of the second kind of the element coordinate system -!------------------------------------------------------------------------------ - DO i=1,dim - DO j=1,dim - DO k=1,dim - s = 0.0d0 - DO l=1,cdim - s = s + ddx(l,i,j)*dx(l,k) - END DO - C2(i,j,k) = s - END DO - END DO - END DO -!------------------------------------------------------------------------------ -! Christoffel symbols of the first kind -!------------------------------------------------------------------------------ - DO i=1,dim - DO j=1,dim - DO k=1,dim - s = 0.0d0 - DO l=1,dim - s = s + Metric(k,l)*C2(i,j,l) - END DO - C1(i,j,k) = s - END DO - END DO - END DO -!------------------------------------------------------------------------------ -! First add ordinary partials (change of the quantity with coordinates)... -!------------------------------------------------------------------------------ - SELECT CASE(dim) - CASE(1) - ddf(1,1) = SecondDerivatives1D( elm,f,u ) - - CASE(2) - ddf(1:2,1:2) = SecondDerivatives2D( elm,f,u,v ) - - CASE(3) - ddf(1:3,1:3) = SecondDerivatives3D( elm,f,u,v,w ) - END SELECT -!------------------------------------------------------------------------------ -! ... then add change of coordinates -!------------------------------------------------------------------------------ - DO i=1,dim - DO j=1,dim - s = 0.0d0 - DO k=1,dim - s = s - C1(i,j,k)*df(k) - END DO - ddf(i,j) = ddf(i,j) + s - END DO - END DO -!------------------------------------------------------------------------------ -! Convert to contravariant base -!------------------------------------------------------------------------------ - DO i=1,dim - DO j=1,dim - s = 0.0d0 - DO k=1,dim - DO l=1,dim - s = s + Metric(i,k)*Metric(j,l)*ddf(k,l) - END DO - END DO - cddf(i,j) = s - END DO - END DO -!------------------------------------------------------------------------------ -! And finally transform to global coordinates -!------------------------------------------------------------------------------ - Values = 0.0d0 - DO i=1,cdim - DO j=1,cdim - s = 0.0d0 - DO k=1,dim - DO l=1,dim - s = s + dx(i,k)*dx(j,l)*cddf(k,l) - END DO - END DO - Values(i,j) = s - END DO - END DO -!------------------------------------------------------------------------------ - END SUBROUTINE GlobalSecondDerivatives -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ - FUNCTION GetEdgeMap( ElementFamily ) RESULT(EdgeMap) -!------------------------------------------------------------------------------ - INTEGER :: ElementFamily - INTEGER, POINTER :: EdgeMap(:,:) - - INTEGER, TARGET :: Point(1,1) - INTEGER, TARGET :: Line(1,2) - INTEGER, TARGET :: Triangle(3,2) - INTEGER, TARGET :: Quad(4,2) - INTEGER, TARGET :: Tetra(6,2) - INTEGER, TARGET :: Pyramid(8,2) - INTEGER, TARGET :: Wedge(9,2) - INTEGER, TARGET :: Brick(12,2) - - LOGICAL :: Initialized(8) = .FALSE. - - SAVE Line, Triangle, Wedge, Brick, Tetra, Quad, Pyramid, Initialized - - SELECT CASE(ElementFamily) - CASE(1) - EdgeMap => Point - CASE(2) - EdgeMap => Line - CASE(3) - EdgeMap => Triangle - CASE(4) - EdgeMap => Quad - CASE(5) - EdgeMap => Tetra - CASE(6) - EdgeMap => Pyramid - CASE(7) - EdgeMap => Wedge - CASE(8) - EdgeMap => Brick - CASE DEFAULT - WRITE( Message,'(A,I0,A)') 'Element family ',ElementFamily,' is not known!' - CALL Fatal( 'GetEdgeMap', Message ) - END SELECT - - IF ( .NOT. Initialized(ElementFamily) ) THEN - Initialized(ElementFamily) = .TRUE. - SELECT CASE(ElementFamily) - CASE(1) - EdgeMap(1,1) = 1 - - CASE(2) - EdgeMap(1,:) = [ 1,2 ] - - CASE(3) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 3,1 ] - - CASE(4) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 3,4 ] - EdgeMap(4,:) = [ 4,1 ] - - CASE(5) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 3,1 ] - EdgeMap(4,:) = [ 1,4 ] - EdgeMap(5,:) = [ 2,4 ] - EdgeMap(6,:) = [ 3,4 ] - - CASE(6) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 4,3 ] - EdgeMap(4,:) = [ 1,4 ] - EdgeMap(5,:) = [ 1,5 ] - EdgeMap(6,:) = [ 2,5 ] - EdgeMap(7,:) = [ 3,5 ] - EdgeMap(8,:) = [ 4,5 ] - - CASE(7) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 3,1 ] - EdgeMap(4,:) = [ 4,5 ] - EdgeMap(5,:) = [ 5,6 ] - EdgeMap(6,:) = [ 6,4 ] - EdgeMap(7,:) = [ 1,4 ] - EdgeMap(8,:) = [ 2,5 ] - EdgeMap(9,:) = [ 3,6 ] - - CASE(8) - EdgeMap(1,:) = [ 1,2 ] - EdgeMap(2,:) = [ 2,3 ] - EdgeMap(3,:) = [ 4,3 ] - EdgeMap(4,:) = [ 1,4 ] - EdgeMap(5,:) = [ 5,6 ] - EdgeMap(6,:) = [ 6,7 ] - EdgeMap(7,:) = [ 8,7 ] - EdgeMap(8,:) = [ 5,8 ] - EdgeMap(9,:) = [ 1,5 ] - EdgeMap(10,:) = [ 2,6 ] - EdgeMap(11,:) = [ 3,7 ] - EdgeMap(12,:) = [ 4,8 ] - END SELECT - END IF -!------------------------------------------------------------------------------ - END FUNCTION GetEdgeMap -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Figure out element diameter parameter for stablization. -!------------------------------------------------------------------------------ - FUNCTION ElementDiameter( elm, nodes, UseLongEdge ) RESULT(hK) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: element -! INPUT: element structure -! -! Type(Nodes_t) :: nodes -! INPUT: Nodal coordinate arrays of the element -! -! FUNCTION VALUE: -! REAL(KIND=dp) :: hK -! -!------------------------------------------------------------------------------ - TYPE(Element_t) :: elm - TYPE(Nodes_t) :: nodes - LOGICAL, OPTIONAL :: UseLongEdge -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp), DIMENSION(:), POINTER :: X,Y,Z - INTEGER :: i,j,k,Family - INTEGER, POINTER :: EdgeMap(:,:) - REAL(KIND=dp) :: x0,y0,z0,hK,A,S,CX,CY,CZ - REAL(KIND=dp) :: J11,J12,J13,J21,J22,J23,G11,G12,G21,G22 - LOGICAL :: LongEdge=.FALSE. -!------------------------------------------------------------------------------ - - IF(PRESENT(UseLongEdge)) LongEdge = UseLongEdge - - X => Nodes % x - Y => Nodes % y - Z => Nodes % z - - Family = Elm % TYPE % ElementCode / 100 - SELECT CASE( Family ) - - CASE(1) - hK = 0.0d0 - -!------------------------------------------------------------------------------ -! Triangular element -!------------------------------------------------------------------------------ - CASE(3) - J11 = X(2) - X(1) - J12 = Y(2) - Y(1) - J13 = Z(2) - Z(1) - J21 = X(3) - X(1) - J22 = Y(3) - Y(1) - J23 = Z(3) - Z(1) - G11 = J11**2 + J12**2 + J13**2 - G12 = J11*J21 + J12*J22 + J13*J23 - G22 = J21**2 + J22**2 + J23**2 - A = SQRT(G11*G22 - G12**2) / 2.0d0 - - CX = ( X(1) + X(2) + X(3) ) / 3.0d0 - CY = ( Y(1) + Y(2) + Y(3) ) / 3.0d0 - CZ = ( Z(1) + Z(2) + Z(3) ) / 3.0d0 - - s = (X(1)-CX)**2 + (Y(1)-CY)**2 + (Z(1)-CZ)**2 - s = s + (X(2)-CX)**2 + (Y(2)-CY)**2 + (Z(2)-CZ)**2 - s = s + (X(3)-CX)**2 + (Y(3)-CY)**2 + (Z(3)-CZ)**2 - - hK = 16.0d0*A*A / ( 3.0d0 * s ) - -!------------------------------------------------------------------------------ -! Quadrilateral -!------------------------------------------------------------------------------ - CASE(4) - CX = (X(2)-X(1))**2 + (Y(2)-Y(1))**2 + (Z(2)-Z(1))**2 - CY = (X(4)-X(1))**2 + (Y(4)-Y(1))**2 + (Z(4)-Z(1))**2 - hk = 2*CX*CY/(CX+CY) - - CASE DEFAULT - EdgeMap => GetEdgeMap(Family) - - IF(LongEdge) THEN - hK = -1.0 * HUGE(1.0_dp) - ELSE - hK = HUGE(1.0_dp) - END IF - - DO i=1,SIZE(EdgeMap,1) - j=EdgeMap(i,1) - k=EdgeMap(i,2) - x0 = X(j) - X(k) - y0 = Y(j) - Y(k) - z0 = Z(j) - Z(k) - IF(LongEdge) THEN - hk = MAX(hK, x0**2 + y0**2 + z0**2) - ELSE - hk = MIN(hK, x0**2 + y0**2 + z0**2) - END IF - END DO - END SELECT - - hK = SQRT( hK ) -!------------------------------------------------------------------------------ - END FUNCTION ElementDiameter -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Figure out if given point x,y,z is inside a triangle, whose node -!> coordinates are given in nx,ny,nz. Method: Invert the basis -!> functions.... -!------------------------------------------------------------------------------ - FUNCTION TriangleInside( nx,ny,nz,x,y,z ) RESULT(inside) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! REAL(KIND=dp) :: nx(:),ny(:),nz(:) -! INPUT: Node coordinate arrays -! -! REAL(KIND=dp) :: x,y,z -! INPUT: point which to consider -! -! FUNCTION VALUE: -! LOGICAL :: inside -! result of the in/out test -! -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: nx(:),ny(:),nz(:),x,y,z - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - LOGICAL :: inside - - REAL(KIND=dp) :: a00,a01,a10,a11,b00,b01,b10,b11,detA,px,py,u,v -!------------------------------------------------------------------------------ - - inside = .FALSE. - - IF ( MAXVAL(nx) < x .OR. MAXVAL(ny) < y ) RETURN - IF ( MINVAL(nx) > x .OR. MINVAL(ny) > y ) RETURN - - A00 = nx(2) - nx(1) - A01 = nx(3) - nx(1) - A10 = ny(2) - ny(1) - A11 = ny(3) - ny(1) - - detA = A00*A11 - A01*A10 - IF ( ABS(detA) < AEPS ) RETURN - - detA = 1 / detA - - B00 = A11*detA - B01 = -A01*detA - B10 = -A10*detA - B11 = A00*detA - - px = x - nx(1) - py = y - ny(1) - u = 0.0d0 - v = 0.0d0 - - u = B00*px + B01*py - IF ( u < 0.0d0 .OR. u > 1.0d0 ) RETURN - - v = B10*px + B11*py - IF ( v < 0.0d0 .OR. v > 1.0d0 ) RETURN - - inside = (u + v <= 1.0d0) -!------------------------------------------------------------------------------ - END FUNCTION TriangleInside -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Figure out if given point x,y,z is inside a quadrilateral, whose -!> node coordinates are given in nx,ny,nz. Method: Invert the -!> basis functions.... -!------------------------------------------------------------------------------ - FUNCTION QuadInside( nx,ny,nz,x,y,z ) RESULT(inside) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! REAL(KIND=dp) :: nx(:),ny(:),nz(:) -! INPUT: Node coordinate arrays -! -! REAL(KIND=dp) :: x,y,z -! INPUT: point which to consider -! -! FUNCTION VALUE: -! LOGICAL :: inside -! result of the in/out test -! -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: nx(:),ny(:),nz(:),x,y,z -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - LOGICAL :: inside - - REAL(KIND=dp) :: r,a,b,c,d,ax,bx,cx,dx,ay,by,cy,dy,px,py,u,v -!------------------------------------------------------------------------------ - inside = .FALSE. - - IF ( MAXVAL(nx) < x .OR. MAXVAL(ny) < y ) RETURN - IF ( MINVAL(nx) > x .OR. MINVAL(ny) > y ) RETURN - - ax = 0.25*( nx(1) + nx(2) + nx(3) + nx(4) ) - bx = 0.25*( -nx(1) + nx(2) + nx(3) - nx(4) ) - cx = 0.25*( -nx(1) - nx(2) + nx(3) + nx(4) ) - dx = 0.25*( nx(1) - nx(2) + nx(3) - nx(4) ) - - ay = 0.25*( ny(1) + ny(2) + ny(3) + ny(4) ) - by = 0.25*( -ny(1) + ny(2) + ny(3) - ny(4) ) - cy = 0.25*( -ny(1) - ny(2) + ny(3) + ny(4) ) - dy = 0.25*( ny(1) - ny(2) + ny(3) - ny(4) ) - - px = x - ax - py = y - ay - - a = cy*dx - cx*dy - b = bx*cy - by*cx + dy*px - dx*py - c = by*px - bx*py - - u = 0.0d0 - v = 0.0d0 - - IF ( ABS(a) < AEPS ) THEN - r = -c / b - IF ( r < -1.0d0 .OR. r > 1.0d0 ) RETURN - - v = r - u = (px - cx*r)/(bx + dx*r) - inside = (u >= -1.0d0 .AND. u <= 1.0d0) - RETURN - END IF - - d = b*b - 4*a*c - IF ( d < 0.0d0 ) RETURN - - d = SQRT(d) - IF ( b>0 ) THEN - r = -2*c/(b+d) - ELSE - r = (-b+d)/(2*a) - END IF - IF ( r >= -1.0d0 .AND. r <= 1.0d0 ) THEN - v = r - u = (px - cx*r)/(bx + dx*r) - - IF ( u >= -1.0d0 .AND. u <= 1.0d0 ) THEN - inside = .TRUE. - RETURN - END IF - END IF - - IF ( b>0 ) THEN - r = -(b+d)/(2*a) - ELSE - r = 2*c/(-b+d) - END IF - IF ( r >= -1.0d0 .AND. r <= 1.0d0 ) THEN - v = r - u = (px - cx*r)/(bx + dx*r) - inside = u >= -1.0d0 .AND. u <= 1.0d0 - RETURN - END IF -!------------------------------------------------------------------------------ - END FUNCTION QuadInside -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Figure out if given point x,y,z is inside a tetrahedron, whose -!> node coordinates are given in nx,ny,nz. Method: Invert the -!> basis functions.... -!------------------------------------------------------------------------------ - FUNCTION TetraInside( nx,ny,nz,x,y,z ) RESULT(inside) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! REAL(KIND=dp) :: nx(:),ny(:),nz(:) -! INPUT: Node coordinate arrays -! -! REAL(KIND=dp) :: x,y,z -! INPUT: point which to consider -! -! FUNCTION VALUE: -! LOGICAL :: inside -! result of the in/out test -! -!------------------------------------------------------------------------------ - - REAL(KIND=dp) :: nx(:),ny(:),nz(:),x,y,z - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: A00,A01,A02,A10,A11,A12,A20,A21,A22,detA - REAL(KIND=dp) :: B00,B01,B02,B10,B11,B12,B20,B21,B22 - - LOGICAL :: inside - - REAL(KIND=dp) :: px,py,pz,u,v,w -!------------------------------------------------------------------------------ - inside = .FALSE. - - IF ( MAXVAL(nx) < x .OR. MAXVAL(ny) < y .OR. MAXVAL(nz) < z ) RETURN - IF ( MINVAL(nx) > x .OR. MINVAL(ny) > y .OR. MINVAL(nz) > z ) RETURN - - A00 = nx(2) - nx(1) - A01 = nx(3) - nx(1) - A02 = nx(4) - nx(1) - - A10 = ny(2) - ny(1) - A11 = ny(3) - ny(1) - A12 = ny(4) - ny(1) - - A20 = nz(2) - nz(1) - A21 = nz(3) - nz(1) - A22 = nz(4) - nz(1) - - detA = A00*(A11*A22 - A12*A21) - detA = detA + A01*(A12*A20 - A10*A22) - detA = detA + A02*(A10*A21 - A11*A20) - IF ( ABS(detA) < AEPS ) RETURN - - detA = 1 / detA - - px = x - nx(1) - py = y - ny(1) - pz = z - nz(1) - - B00 = (A11*A22 - A12*A21)*detA - B01 = (A21*A02 - A01*A22)*detA - B02 = (A01*A12 - A11*A02)*detA - - u = B00*px + B01*py + B02*pz - IF ( u < 0.0d0 .OR. u > 1.0d0 ) RETURN - - - B10 = (A12*A20 - A10*A22)*detA - B11 = (A00*A22 - A20*A02)*detA - B12 = (A10*A02 - A00*A12)*detA - - v = B10*px + B11*py + B12*pz - IF ( v < 0.0d0 .OR. v > 1.0d0 ) RETURN - - - B20 = (A10*A21 - A11*A20)*detA - B21 = (A01*A20 - A00*A21)*detA - B22 = (A00*A11 - A10*A01)*detA - - w = B20*px + B21*py + B22*pz - IF ( w < 0.0d0 .OR. w > 1.0d0 ) RETURN - - inside = (u + v + w) <= 1.0d0 -!------------------------------------------------------------------------------ - END FUNCTION TetraInside -!------------------------------------------------------------------------------ - - - -!------------------------------------------------------------------------------ -!> Figure out if given point x,y,z is inside a brick, whose node coordinates -!> are given in nx,ny,nz. Method: Divide to tetrahedrons. -!------------------------------------------------------------------------------ - FUNCTION BrickInside( nx,ny,nz,x,y,z ) RESULT(inside) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! REAL(KIND=dp) :: nx(:),ny(:),nz(:) -! INPUT: Node coordinate arrays -! -! REAL(KIND=dp) :: x,y,z -! INPUT: point which to consider -! -! FUNCTION VALUE: -! LOGICAL :: inside -! result of the in/out test -! -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: nx(:),ny(:),nz(:),x,y,z - -!------------------------------------------------------------------------------ -! Local variables -!------------------------------------------------------------------------------ - LOGICAL :: inside - - INTEGER :: i,j - REAL(KIND=dp) :: px(4),py(4),pz(4),r,s,t,maxx,minx,maxy,miny,maxz,minz - - INTEGER :: map(3,12) -!------------------------------------------------------------------------------ - map = RESHAPE( [ 0,1,2, 0,2,3, 4,5,6, 4,6,7, 3,2,6, 3,6,7, & - 1,5,6, 1,6,2, 0,4,7, 0,7,3, 0,1,5, 0,5,4 ], [ 3,12 ] ) + 1 - - inside = .FALSE. - - IF ( MAXVAL(nx) < x .OR. MAXVAL(ny) < y .OR. MAXVAL(nz) < z ) RETURN - IF ( MINVAL(nx) > x .OR. MINVAL(ny) > y .OR. MINVAL(nz) > z ) RETURN - - px(1) = 0.125d0 * SUM(nx) - py(1) = 0.125d0 * SUM(ny) - pz(1) = 0.125d0 * SUM(nz) - - DO i=1,12 - px(2:4) = nx(map(1:3,i)) - py(2:4) = ny(map(1:3,i)) - pz(2:4) = nz(map(1:3,i)) - - IF ( TetraInside( px,py,pz,x,y,z ) ) THEN - inside = .TRUE. - RETURN - END IF - END DO -!------------------------------------------------------------------------------ - END FUNCTION BrickInside -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -!> Check if the current element has been defined passive. -!> This is done by inspecting a looking an the values of "varname Passive" -!> in the Body Force section. It is determined to be passive if it has -!> more positive than negative hits in an element. -!------------------------------------------------------------------------------ - FUNCTION CheckPassiveElement( UElement ) RESULT( IsPassive ) - !------------------------------------------------------------------------------ - TYPE(Element_t), OPTIONAL, TARGET :: UElement - LOGICAL :: IsPassive - !------------------------------------------------------------------------------ - TYPE(Element_t), POINTER :: Element - TYPE(Element_t), POINTER :: CurElementTmp - REAL(KIND=dp), ALLOCATABLE :: Passive(:) - INTEGER :: body_id, bf_id, nlen, NbrNodes,PassNodes, LimitNodes - LOGICAL :: Found - CHARACTER(LEN=MAX_NAME_LEN) :: PassName - LOGICAL :: NoPassiveElements = .FALSE. - TYPE(Solver_t), POINTER :: pSolver, PrevSolver => NULL() - - SAVE Passive, NoPassiveElements, PrevSolver, PassName - !$OMP THREADPRIVATE(Passive, NoPassiveElements, PrevSolver, PassName) - !------------------------------------------------------------------------------ - IsPassive = .FALSE. - pSolver => CurrentModel % Solver - - IF( .NOT. ASSOCIATED( pSolver, PrevSolver ) ) THEN - PrevSolver => pSolver - nlen = CurrentModel % Solver % Variable % NameLen - PassName = GetVarName(CurrentModel % Solver % Variable) // ' Passive' - NoPassiveElements = .NOT. ListCheckPresentAnyBodyForce( CurrentModel, PassName ) - END IF - - IF( NoPassiveElements ) RETURN - - IF (PRESENT(UElement)) THEN - Element => UElement - CurElementTmp => CurrentModel % CurrentElement - CurrentModel % CurrentElement => UElement - ELSE -#ifdef _OPENMP - IF (omp_in_parallel()) THEN - CALL Fatal('CheckPassiveElement', & - 'Need an element to update inside a threaded region') - END IF -#endif - Element => CurrentModel % CurrentElement - END IF - - body_id = Element % BodyId - IF ( body_id <= 0 ) RETURN ! body_id == 0 for boundary elements - - bf_id = ListGetInteger( CurrentModel % Bodies(body_id) % Values, & - 'Body Force', Found, minv=1,maxv=CurrentModel % NumberOfBodyForces ) - IF ( .NOT. Found ) RETURN - - IF ( ListCheckPresent(CurrentModel % BodyForces(bf_id) % Values, PassName) ) THEN - NbrNodes = Element % TYPE % NumberOfNodes - IF ( ALLOCATED(Passive) ) THEN - IF ( SIZE(Passive) < NbrNodes ) THEN - DEALLOCATE(Passive) - ALLOCATE( Passive(NbrNodes) ) - END IF - ELSE - ALLOCATE( Passive(NbrNodes) ) - END IF - Passive(1:NbrNodes) = ListGetReal( CurrentModel % BodyForces(bf_id) % Values, & - PassName, NbrNodes, Element % NodeIndexes ) - PassNodes = COUNT(Passive(1:NbrNodes)>0) - - ! Go through the extremum cases first, and if the element is not either fully - ! active or passive, then check for some possible given criteria for determining - ! the element active / passive. - !------------------------------------------------------------------------------ - IF( PassNodes == 0 ) THEN - CONTINUE - ELSE IF( PassNodes == NbrNodes ) THEN - IsPassive = .TRUE. - ELSE - LimitNodes = ListGetInteger( CurrentModel % BodyForces(bf_id) % Values, & - 'Passive Element Min Nodes',Found ) - IF( Found ) THEN - IsPassive = ( PassNodes >= LimitNodes ) - ELSE - LimitNodes = ListGetInteger( CurrentModel % BodyForces(bf_id) % Values, & - 'Active Element Min Nodes',Found ) - IF( Found ) THEN - IsPassive = ( PassNodes > NbrNodes - LimitNodes ) - ELSE - IsPassive = ( 2*PassNodes > NbrNodes ) - END IF - END IF - END IF - END IF - - IF (PRESENT(UElement)) CurrentModel % CurrentElement => CurElementTmp -!------------------------------------------------------------------------------ - END FUNCTION CheckPassiveElement -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -!> Normal will point from more dense material to less dense -!> or outwards, if no elements on the other side. -!------------------------------------------------------------------------------ - SUBROUTINE CheckNormalDirection( Boundary,Normal,x,y,z,turn ) -!------------------------------------------------------------------------------ - - TYPE(Element_t), POINTER :: Boundary - TYPE(Nodes_t) :: Nodes - REAL(KIND=dp) :: Normal(3),x,y,z - LOGICAL, OPTIONAL :: turn -!------------------------------------------------------------------------------ - - TYPE (Element_t), POINTER :: Element,LeftElement,RightElement - - INTEGER :: LMat,RMat,n,k - - REAL(KIND=dp) :: x1,y1,z1 - REAL(KIND=dp), ALLOCATABLE :: nx(:),ny(:),nz(:) - LOGICAL :: LPassive -!------------------------------------------------------------------------------ - - IF(.NOT. ASSOCIATED( Boundary % BoundaryInfo ) ) RETURN - - k = Boundary % BoundaryInfo % OutBody - - LeftElement => Boundary % BoundaryInfo % Left - - Element => Null() - IF ( ASSOCIATED(LeftELement) ) THEN - RightElement => Boundary % BoundaryInfo % Right - IF ( ASSOCIATED( RightElement ) ) THEN ! we have a body-body boundary - IF ( k > 0 ) THEN ! declared outbody - IF ( LeftElement % BodyId == k ) THEN - Element => RightElement - ELSE - Element => LeftElement - END IF - ELSE IF (LeftElement % BodyId > RightElement % BodyId) THEN ! normal pointing into body with lower body ID - Element => LeftElement - ELSE IF (LeftElement % BodyId < RightElement % BodyId) THEN! normal pointing into body with lower body ID - Element => RightElement - ELSE ! active/passive boundary - LPassive = CheckPassiveElement( LeftElement ) - IF (LPassive .NEQV. CheckPassiveElement( RightElement )) THEN - IF(LPassive) THEN - Element => RightElement - ELSE - Element => LeftElement - END IF - END IF - END IF - ELSE ! body-vacuum boundary from left->right - Element => LeftElement - END IF - ELSE! body-vacuum boundary from right->left - Element => Boundary % BoundaryInfo % Right - END IF - - IF ( .NOT. ASSOCIATED(Element) ) RETURN - - n = Element % TYPE % NumberOfNodes - - ALLOCATE( nx(n), ny(n), nz(n) ) - - nx(1:n) = CurrentModel % Nodes % x(Element % NodeIndexes) - ny(1:n) = CurrentModel % Nodes % y(Element % NodeIndexes) - nz(1:n) = CurrentModel % Nodes % z(Element % NodeIndexes) - - SELECT CASE( Element % TYPE % ElementCode / 100 ) - - CASE(2,4,8) - x1 = InterpolateInElement( Element, nx, 0.0d0, 0.0d0, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 0.0d0, 0.0d0, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 0.0d0, 0.0d0, 0.0d0 ) - CASE(3) - x1 = InterpolateInElement( Element, nx, 1.0d0/3, 1.0d0/3, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/3, 1.0d0/3, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/3, 1.0d0/3, 0.0d0 ) - CASE(5) - x1 = InterpolateInElement( Element, nx, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - CASE(6) - x1 = InterpolateInElement( Element, nx, 0.0d0, 0.0d0, 1.0d0/3 ) - y1 = InterpolateInElement( Element, ny, 0.0d0, 0.0d0, 1.0d0/3 ) - z1 = InterpolateInElement( Element, nz, 0.0d0, 0.0d0, 1.0d0/3 ) - CASE(7) - x1 = InterpolateInElement( Element, nx, 1.0d0/3, 1.0d0/3, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/3, 1.0d0/3, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/3, 1.0d0/3, 0.0d0 ) - CASE DEFAULT - CALL Fatal('CheckNormalDirection','Invalid elementcode for parent element!') - - END SELECT - x1 = x1 - x - y1 = y1 - y - z1 = z1 - z - - IF ( PRESENT(turn) ) turn = .FALSE. - IF ( x1*Normal(1) + y1*Normal(2) + z1*Normal(3) > 0 ) THEN - IF ( Element % BodyId /= k ) THEN - Normal = -Normal - IF ( PRESENT(turn) ) turn = .TRUE. - END IF - ELSE IF ( Element % BodyId == k ) THEN - Normal = -Normal - IF ( PRESENT(turn) ) turn = .TRUE. - END IF - DEALLOCATE( nx,ny,nz ) -!------------------------------------------------------------------------------ - END SUBROUTINE CheckNormalDirection -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Normal will point out from the parent. -!------------------------------------------------------------------------------ - SUBROUTINE CheckNormalDirectionParent( Boundary,Normal,x,y,z,Element,turn ) -!------------------------------------------------------------------------------ - - TYPE(Element_t), POINTER :: Boundary - TYPE(Nodes_t) :: Nodes - REAL(KIND=dp) :: Normal(3),x,y,z - TYPE(Element_t), POINTER :: Element - LOGICAL, OPTIONAL :: turn -!------------------------------------------------------------------------------ - INTEGER :: n,k - REAL(KIND=dp) :: x1,y1,z1 - REAL(KIND=dp), ALLOCATABLE :: nx(:),ny(:),nz(:) - LOGICAL :: LPassive -!------------------------------------------------------------------------------ - - IF( PRESENT( turn ) ) turn = .FALSE. - - IF ( .NOT. ASSOCIATED(Element) ) RETURN - - n = Element % TYPE % NumberOfNodes - - ALLOCATE( nx(n), ny(n), nz(n) ) - - nx(1:n) = CurrentModel % Nodes % x(Element % NodeIndexes) - ny(1:n) = CurrentModel % Nodes % y(Element % NodeIndexes) - nz(1:n) = CurrentModel % Nodes % z(Element % NodeIndexes) - - SELECT CASE( Element % TYPE % ElementCode / 100 ) - - CASE(2,4,8) - x1 = InterpolateInElement( Element, nx, 0.0d0, 0.0d0, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 0.0d0, 0.0d0, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 0.0d0, 0.0d0, 0.0d0 ) - CASE(3) - x1 = InterpolateInElement( Element, nx, 1.0d0/3, 1.0d0/3, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/3, 1.0d0/3, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/3, 1.0d0/3, 0.0d0 ) - CASE(5) - x1 = InterpolateInElement( Element, nx, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/4, 1.0d0/4, 1.0d0/4 ) - CASE(6) - x1 = InterpolateInElement( Element, nx, 0.0d0, 0.0d0, 1.0d0/3 ) - y1 = InterpolateInElement( Element, ny, 0.0d0, 0.0d0, 1.0d0/3 ) - z1 = InterpolateInElement( Element, nz, 0.0d0, 0.0d0, 1.0d0/3 ) - CASE(7) - x1 = InterpolateInElement( Element, nx, 1.0d0/3, 1.0d0/3, 0.0d0 ) - y1 = InterpolateInElement( Element, ny, 1.0d0/3, 1.0d0/3, 0.0d0 ) - z1 = InterpolateInElement( Element, nz, 1.0d0/3, 1.0d0/3, 0.0d0 ) - CASE DEFAULT - CALL Fatal('CheckNormalDirection','Invalid elementcode for parent element!') - - END SELECT - - ! Test vector points from surface to center of parent - x1 = x1 - x - y1 = y1 - y - z1 = z1 - z - - ! Swap the sign if the tentative normal points to the center, it should point outward - IF ( x1*Normal(1) + y1*Normal(2) + z1*Normal(3) > 0 ) THEN - Normal = -Normal - IF ( PRESENT(turn) ) turn = .TRUE. - END IF - - DEALLOCATE( nx,ny,nz ) -!------------------------------------------------------------------------------ - END SUBROUTINE CheckNormalDirectionParent -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Gives the normal vector of a boundary element. -!> For noncurved elements the normal vector does not depend on the local coordinate -!> while otherwise it does. There are different uses of the function where some -!> do not have the luxury of knowing the local coordinates and hence the center -!> point is used as default. -!------------------------------------------------------------------------------ - FUNCTION NormalVector( Boundary,BoundaryNodes,u0,v0,Check,Parent) RESULT(Normal) -!------------------------------------------------------------------------------ - TYPE(Element_t), POINTER :: Boundary - TYPE(Nodes_t) :: BoundaryNodes - REAL(KIND=dp), OPTIONAL :: u0,v0 - LOGICAL, OPTIONAL :: Check - TYPE(Element_t), POINTER, OPTIONAL :: Parent - REAL(KIND=dp) :: Normal(3) -!------------------------------------------------------------------------------ - LOGICAL :: CheckBody, CheckParent - TYPE(ElementType_t),POINTER :: elt - REAL(KIND=dp) :: u,v,Auu,Auv,Avu,Avv,detA,x,y,z - REAL(KIND=dp) :: dxdu,dxdv,dydu,dydv,dzdu,dzdv - REAL(KIND=dp), DIMENSION(:), POINTER :: nx,ny,nz - -!------------------------------------------------------------------------------ - - nx => BoundaryNodes % x - ny => BoundaryNodes % y - nz => BoundaryNodes % z - - SELECT CASE ( Boundary % TYPE % DIMENSION ) - - CASE ( 0 ) - Normal(1) = 1.0_dp - Normal(2:3) = 0.0_dp - - CASE ( 1 ) - IF( PRESENT( u0 ) ) THEN - u = u0 - ELSE - u = 0.0_dp - END IF - - dxdu = FirstDerivative1D( Boundary,nx,u ) - dydu = FirstDerivative1D( Boundary,ny,u ) - - detA = dxdu*dxdu + dydu*dydu - IF ( detA <= 0._dp ) THEN - Normal = 0._dp - RETURN - END IF - detA = 1.0_dp / SQRT(detA) - Normal(1) = -dydu * detA - Normal(2) = dxdu * detA - Normal(3) = 0.0d0 - - CASE ( 2 ) - IF( PRESENT( u0 ) ) THEN - u = u0 - v = v0 - ELSE - IF( Boundary % TYPE % ElementCode / 100 == 3 ) THEN - u = 1.0_dp/3 - v = 1.0_dp/3 - ELSE - u = 0.0_dp - v = 0.0_dp - END IF - END IF - - dxdu = FirstDerivativeInU2D( Boundary,nx,u,v ) - dydu = FirstDerivativeInU2D( Boundary,ny,u,v ) - dzdu = FirstDerivativeInU2D( Boundary,nz,u,v ) - - dxdv = FirstDerivativeInV2D( Boundary,nx,u,v ) - dydv = FirstDerivativeInV2D( Boundary,ny,u,v ) - dzdv = FirstDerivativeInV2D( Boundary,nz,u,v ) - - Auu = dxdu*dxdu + dydu*dydu + dzdu*dzdu - Auv = dxdu*dxdv + dydu*dydv + dzdu*dzdv - Avv = dxdv*dxdv + dydv*dydv + dzdv*dzdv - - detA = 1.0d0 / SQRT(Auu*Avv - Auv*Auv) - - Normal(1) = (dydu * dzdv - dydv * dzdu) * detA - Normal(2) = (dxdv * dzdu - dxdu * dzdv) * detA - Normal(3) = (dxdu * dydv - dxdv * dydu) * detA - - CASE DEFAULT - CALL Fatal('NormalVector','Invalid dimension for determining normal!') - - END SELECT - - - CheckParent = .FALSE. - IF( PRESENT( Parent ) ) CheckParent = ASSOCIATED( Parent ) - - CheckBody = .FALSE. - IF ( PRESENT(Check) ) CheckBody = Check - - IF ( .NOT. ( CheckBody .OR. CheckParent ) ) RETURN - - SELECT CASE( Boundary % TYPE % ElementCode / 100 ) - - CASE(1) - x = nx(1) - y = nx(1) - z = nz(1) - - CASE(2,4) - x = InterpolateInElement( Boundary,nx,0.0d0,0.0d0,0.0d0 ) - y = InterpolateInElement( Boundary,ny,0.0d0,0.0d0,0.0d0 ) - z = InterpolateInElement( Boundary,nz,0.0d0,0.0d0,0.0d0 ) - - CASE(3) - x = InterpolateInElement( Boundary,nx,1.0d0/3,1.0d0/3,0.0d0) - y = InterpolateInElement( Boundary,ny,1.0d0/3,1.0d0/3,0.0d0) - z = InterpolateInElement( Boundary,nz,1.0d0/3,1.0d0/3,0.0d0) - END SELECT - - IF( CheckParent ) THEN - CALL CheckNormalDirectionParent( Boundary, Normal, x, y, z, Parent ) - ELSE - CALL CheckNormalDirection( Boundary,Normal,x,y,z ) - END IF - -!------------------------------------------------------------------------------ - END FUNCTION NormalVector -!------------------------------------------------------------------------------ - -!------------------------------------------------------------------------------ -!> Returns a point that is most importantly supposed to be on the surface -!> For noncurved elements this may simply be the mean while otherwise -!> there may be a need to find the surface node using the local coordinates. -!> Hence the optional parameters. Typically the NormalVector and SurfaceVector -!> should be defined at the same position. -!------------------------------------------------------------------------------ - FUNCTION SurfaceVector( Boundary,BoundaryNodes,u,v ) RESULT(Surface) -!------------------------------------------------------------------------------ - TYPE(Element_t), POINTER :: Boundary - TYPE(Nodes_t) :: BoundaryNodes - REAL(KIND=dp),OPTIONAL :: u,v - REAL(KIND=dp) :: Surface(3) -!------------------------------------------------------------------------------ - REAL(KIND=dp), DIMENSION(:), POINTER :: nx,ny,nz - INTEGER :: i,n -!------------------------------------------------------------------------------ - - nx => BoundaryNodes % x - ny => BoundaryNodes % y - nz => BoundaryNodes % z - n = Boundary % TYPE % NumberOfNodes - - IF( .NOT. PRESENT( u ) ) THEN - Surface(1) = SUM( nx ) / n - Surface(2) = SUM( ny ) / n - Surface(3) = SUM( nz ) / n - ELSE - IF( Boundary % TYPE % DIMENSION == 1 ) THEN - Surface(1) = InterpolateInElement( Boundary,nx,u,0.0_dp,0.0_dp) - Surface(2) = InterpolateInElement( Boundary,ny,u,0.0_dp,0.0_dp) - Surface(3) = InterpolateInElement( Boundary,nz,u,0.0_dp,0.0_dp) - ELSE - Surface(1) = InterpolateInElement( Boundary,nx,u,v,0.0_dp) - Surface(2) = InterpolateInElement( Boundary,ny,u,v,0.0_dp) - Surface(3) = InterpolateInElement( Boundary,nz,u,v,0.0_dp) - END IF - END IF - -!------------------------------------------------------------------------------ - END FUNCTION SurfaceVector -!------------------------------------------------------------------------------ - - -!--------------------------------------------------------------------------- -!> This subroutine tests where the intersection between the line defined by two -!> points and a plane (or line) defined by a boundary element meet. There is -!> an intersection if ( 0 < Lambda < 1 ). Of all intersections the first one is -!> that with the smallest positive lambda. -!--------------------------------------------------------------------------- - FUNCTION LineFaceIntersection(FaceElement,FaceNodes,& - Rinit,Rfin,u,v) RESULT ( Lambda ) -!--------------------------------------------------------------------------- - TYPE(Nodes_t) :: FaceNodes - TYPE(Element_t), POINTER :: FaceElement - REAL(KIND=dp) :: Rinit(3),Rfin(3) - REAL(KIND=dp),OPTIONAL :: u,v - REAL(KIND=dp) :: Lambda - - REAL (KIND=dp) :: Surface(3),t1(3),t2(3),Normal(3),Rproj - REAL (KIND=dp) :: Lambda0 - INTEGER :: third - - third = 3 - -100 CONTINUE - - ! For higher order elements this may be a necessity - IF( PRESENT( u ) .AND. PRESENT(v) ) THEN - Surface = SurfaceVector( FaceElement, FaceNodes, u, v ) - Normal = NormalVector( FaceElement, FaceNodes, u, v ) - - ELSE IF( FaceElement % TYPE % DIMENSION == 2 ) THEN - ! Any point known to be at the surface, even corner node - Surface(1) = FaceNodes % x(1) - Surface(2) = FaceNodes % y(1) - Surface(3) = FaceNodes % z(1) - - ! Tangent vector, nor normalized to unity! - t1(1) = FaceNodes % x(2) - Surface(1) - t1(2) = FaceNodes % y(2) - Surface(2) - t1(3) = FaceNodes % z(2) - Surface(3) - - t2(1) = FaceNodes % x(third) - Surface(1) - t2(2) = FaceNodes % y(third) - Surface(2) - t2(3) = FaceNodes % z(third) - Surface(3) - - ! Normal vector obtained from the cross product of tangent vectoes - ! This is not normalized to unity as value of lambda does not depend on its magnitude - Normal(1) = t1(2)*t2(3) - t1(3)*t2(2) - Normal(2) = t1(3)*t2(1) - t1(1)*t2(3) - Normal(3) = t1(1)*t2(2) - t1(2)*t2(1) - ELSE - Surface(1) = FaceNodes % x(1) - Surface(2) = FaceNodes % y(1) - Surface(3) = 0.0_dp - - Normal(1) = Surface(2) - FaceNodes % y(2) - Normal(2) = FaceNodes % x(2) - Surface(1) - Normal(3) = 0.0_dp - END IF - - ! Project of the line to the face normal - Rproj = SUM( (Rfin - Rinit) * Normal ) - - IF( ABS( Rproj ) < TINY( Rproj ) ) THEN - ! if the intersection cannot be defined make it an impossible one - Lambda = -HUGE( Lambda ) - ELSE - Lambda = SUM( ( Surface - Rinit ) * Normal ) / Rproj - END IF - - IF( FaceElement % NDofs == 4 ) THEN - IF( third == 3 ) THEN - third = 4 - Lambda0 = Lambda - GOTO 100 - END IF - IF( ABS( Lambda0 ) < ABS( Lambda) ) THEN - Lambda = Lambda0 - END IF - END IF - - - END FUNCTION LineFaceIntersection - - -!--------------------------------------------------------------------------- -!> This subroutine performs a similar test as above using slightly different -!> strategy. -!--------------------------------------------------------------------------- - FUNCTION LineFaceIntersection2(FaceElement,FaceNodes,Rinit,Rfin,Intersect) RESULT ( Lambda ) - - TYPE(Nodes_t) :: FaceNodes - TYPE(Element_t), POINTER :: FaceElement - REAL(KIND=dp) :: Rinit(3), Rfin(3),Lambda - LOGICAL :: Intersect -!---------------------------------------------------------------------------- - REAL (KIND=dp) :: A(3,3),B(3),C(3),Eps,Eps2,Eps3,detA,absA,ds - INTEGER :: split, i, n, notriangles, triangle, ElemDim - - Eps = EPSILON( Eps ) - Eps2 = SQRT(TINY(Eps2)) - Eps3 = 1.0d-12 - Lambda = -HUGE( Lambda ) - Intersect = .FALSE. - ElemDim = FaceElement % TYPE % DIMENSION - - ! Then solve the exact points of intersection from a 3x3 or 2x2 linear system - !-------------------------------------------------------------------------- - IF( ElemDim == 2 ) THEN - n = FaceElement % NDofs - ! In 3D rectangular faces are treated as two triangles - IF( n == 4 .OR. n == 8 .OR. n == 9 ) THEN - notriangles = 2 - ELSE - notriangles = 1 - END IF - - DO triangle=1,notriangles - - A(1:3,1) = Rfin(1:3) - Rinit(1:3) - - IF(triangle == 1) THEN - A(1,2) = FaceNodes % x(1) - FaceNodes % x(2) - A(2,2) = FaceNodes % y(1) - FaceNodes % y(2) - A(3,2) = FaceNodes % z(1) - FaceNodes % z(2) - ELSE - A(1,2) = FaceNodes % x(1) - FaceNodes % x(4) - A(2,2) = FaceNodes % y(1) - FaceNodes % y(4) - A(3,2) = FaceNodes % z(1) - FaceNodes % z(4) - END IF - - A(1,3) = FaceNodes % x(1) - FaceNodes % x(3) - A(2,3) = FaceNodes % y(1) - FaceNodes % y(3) - A(3,3) = FaceNodes % z(1) - FaceNodes % z(3) - - ! Check for linearly dependent vectors - detA = A(1,1)*(A(2,2)*A(3,3)-A(2,3)*A(3,2)) & - - A(1,2)*(A(2,1)*A(3,3)-A(2,3)*A(3,1)) & - + A(1,3)*(A(2,1)*A(3,2)-A(2,2)*A(3,1)) - absA = SUM(ABS(A(1,1:3))) * SUM(ABS(A(2,1:3))) * SUM(ABS(A(3,1:3))) - - IF(ABS(detA) <= eps * absA + Eps2) CYCLE -! print *,'detA',detA - - B(1) = FaceNodes % x(1) - Rinit(1) - B(2) = FaceNodes % y(1) - Rinit(2) - B(3) = FaceNodes % z(1) - Rinit(3) - - CALL InvertMatrix( A,3 ) - C(1:3) = MATMUL( A(1:3,1:3),B(1:3) ) - - IF( ANY(C(2:3) < -Eps3) .OR. ANY(C(2:3) > 1.0_dp + Eps3 ) ) CYCLE - IF( C(2)+C(3) > 1.0_dp + Eps3 ) CYCLE - - ! Relate the point of intersection to local coordinates - !IF(corners < 4) THEN - ! u = C(2) - ! v = C(3) - !ELSE IF(corners == 4 .AND. split == 0) THEN - ! u = 2*(C(2)+C(3))-1 - ! v = 2*C(3)-1 - !ELSE - ! ! For the 2nd split of the rectangle the local coordinates switched - ! v = 2*(C(2)+C(3))-1 - ! u = 2*C(3)-1 - !END IF - - Intersect = .TRUE. - Lambda = C(1) - EXIT - - END DO - ELSE - ! In 2D the intersection is between two lines - - A(1:2,1) = Rfin(1:2) - Rinit(1:2) - A(1,2) = FaceNodes % x(1) - FaceNodes % x(2) - A(2,2) = FaceNodes % y(1) - FaceNodes % y(2) - - detA = A(1,1)*A(2,2)-A(1,2)*A(2,1) - absA = SUM(ABS(A(1,1:2))) * SUM(ABS(A(2,1:2))) - - ! Lines are almost parallel => no intersection possible - IF(ABS(detA) <= eps * absA + Eps2) RETURN - - B(1) = FaceNodes % x(1) - Rinit(1) - B(2) = FaceNodes % y(1) - Rinit(2) - - CALL InvertMatrix( A,2 ) - C(1:2) = MATMUL(A(1:2,1:2),B(1:2)) - - IF(C(2) < -Eps3 .OR. C(2) > 1.0_dp + Eps3 ) RETURN - - Intersect = .TRUE. - Lambda = C(1) - -! u = -1.0d0 + 2.0d0 * C(2) - - END IF - -! IF(.NOT. Inside) RETURN - -! stat = ElementInfo( Element, FaceNodes, U, V, W, SqrtElementMetric, & -! Basis, dBasisdx ) - -! Weights(1:n) = Basis(1:n) -! MaxInd = 1 -! DO i=2,n -! IF(Weights(MaxInd) < Weights(i)) MaxInd = i -! END DO - - END FUNCTION LineFaceIntersection2 - - - -!--------------------------------------------------------------------------- -!> This subroutine computes the signed distance of a point from a surface. -!--------------------------------------------------------------------------- - FUNCTION PointFaceDistance(BoundaryElement,BoundaryNodes,& - Coord,Normal,u0,v0) RESULT ( Dist ) -!--------------------------------------------------------------------------- - TYPE(Nodes_t) :: BoundaryNodes - TYPE(Element_t), POINTER :: BoundaryElement - REAL(KIND=dp) :: Coord(3),Normal(3) - REAL(KIND=dp),OPTIONAL :: u0,v0 - REAL(KIND=dp) :: Dist - - REAL (KIND=dp) :: Surface(3),t1(3),t2(3),u,v - - ! For higher order elements this may be a necessity - IF( PRESENT( u0 ) .AND. PRESENT(v0) ) THEN - u = u0 - v = v0 - Surface = SurfaceVector( BoundaryElement, BoundaryNodes, u, v ) - ELSE - u = 0.0_dp - v = 0.0_dp - - ! Any point known to be at the surface, even corner node - Surface(1) = BoundaryNodes % x(1) - Surface(2) = BoundaryNodes % y(1) - Surface(3) = BoundaryNodes % z(1) - END IF - - Normal = NormalVector( BoundaryElement, BoundaryNodes, u, v, .TRUE. ) - - ! Project of the line to the face normal - Dist = SUM( (Surface - Coord ) * Normal ) -END FUNCTION PointFaceDistance - - - -!------------------------------------------------------------------------------ -!> Convert global coordinates x,y,z inside element to local coordinates -!> u,v,w of the element. -!> @todo Change to support p elements -!------------------------------------------------------------------------------ - SUBROUTINE GlobalToLocal( u,v,w,x,y,z,Element,ElementNodes ) -!------------------------------------------------------------------------------ - TYPE(Nodes_t) :: ElementNodes - REAL(KIND=dp) :: x,y,z,u,v,w - TYPE(Element_t), POINTER :: Element -!------------------------------------------------------------------------------ - INTEGER, PARAMETER :: MaxIter = 50 - INTEGER :: i,n - REAL(KIND=dp) :: r,s,t,delta(3),prevdelta(3),J(3,3),J1(3,2),det,swap,acc,err - LOGICAL :: Converged -!------------------------------------------------------------------------------ - - u = 0._dp - v = 0._dp - w = 0._dp - IF (Element % TYPE % DIMENSION==0) RETURN - - n = Element % TYPE % NumberOfNodes - - ! @todo Not supported yet -! IF (ASSOCIATED(Element % PDefs)) THEN -! CALL Fatal('GlobalToLocal','P elements not supported yet!') -! END IF - acc = EPSILON(1.0_dp) - Converged = .FALSE. - - delta = 0._dp - -!------------------------------------------------------------------------------ - DO i=1,Maxiter -!------------------------------------------------------------------------------ - r = InterpolateInElement(Element,ElementNodes % x(1:n),u,v,w) - x - s = InterpolateInElement(Element,ElementNodes % y(1:n),u,v,w) - y - t = InterpolateInElement(Element,ElementNodes % z(1:n),u,v,w) - z - - err = r**2 + s**2 + t**2 - - IF ( err < acc ) THEN - Converged = .TRUE. - EXIT - END IF - - prevdelta = delta - delta = 0.d0 - - SELECT CASE( Element % TYPE % DIMENSION ) - CASE(1) - - J(1,1) = FirstDerivative1D( Element, ElementNodes % x, u ) - J(2,1) = FirstDerivative1D( Element, ElementNodes % y, u ) - J(3,1) = FirstDerivative1D( Element, ElementNodes % z, u ) - - det = SUM( J(1:3,1)**2 ) - delta(1) = (r*J(1,1)+s*J(2,1)+t*J(3,1))/det - - CASE(2) - - J(1,1) = FirstDerivativeInU2D( Element, ElementNodes % x,u,v ) - J(1,2) = FirstDerivativeInV2D( Element, ElementNodes % x,u,v ) - J(2,1) = FirstDerivativeInU2D( Element, ElementNodes % y,u,v ) - J(2,2) = FirstDerivativeInV2D( Element, ElementNodes % y,u,v ) - - SELECT CASE( CoordinateSystemDimension() ) - CASE(3) - J(3,1) = FirstDerivativeInU2D( Element, ElementNodes % z, u, v ) - J(3,2) = FirstDerivativeInV2D( Element, ElementNodes % z, u, v ) - - delta(1) = r - delta(2) = s - delta(3) = t - delta(1:2) = MATMUL( TRANSPOSE(J(1:3,1:2)), delta ) - r = delta(1) - s = delta(2) - - J(1:2,1:2) = MATMUL( TRANSPOSE(J(1:3,1:2)), J(1:3,1:2) ) - delta(3) = 0.0d0 - END SELECT - - CALL SolveLinSys2x2( J(1:2,1:2), delta(1:2), [ r, s] ) - - CASE(3) - J(1,1) = FirstDerivativeInU3D( Element, ElementNodes % x, u, v, w ) - J(1,2) = FirstDerivativeInV3D( Element, ElementNodes % x, u, v, w ) - J(1,3) = FirstDerivativeInW3D( Element, ElementNodes % x, u, v, w ) - - J(2,1) = FirstDerivativeInU3D( Element, ElementNodes % y, u, v, w ) - J(2,2) = FirstDerivativeInV3D( Element, ElementNodes % y, u, v, w ) - J(2,3) = FirstDerivativeInW3D( Element, ElementNodes % y, u, v, w ) - - J(3,1) = FirstDerivativeInU3D( Element, ElementNodes % z, u, v, w ) - J(3,2) = FirstDerivativeInV3D( Element, ElementNodes % z, u, v, w ) - J(3,3) = FirstDerivativeInW3D( Element, ElementNodes % z, u, v, w ) - - CALL SolveLinSys3x3( J, delta, [ r, s, t ] ) - - END SELECT - - IF( i > 10 ) THEN - ! If the same values is suggested over and over again, then exit - ! This may be a sign that the node is off-plane and cannot be - ! described within the element. - IF( SUM( ABS( delta - prevdelta ) ) < acc ) EXIT - - ! Use sloppier criteria when iteration still unsuccessful - IF( i > 20 ) THEN - IF( SUM( ABS( delta - prevdelta ) ) < SQRT( acc ) ) EXIT - END IF - - ! If the iteration does not proceed try with some relaxation - delta = 0.5_dp * delta - END IF - - u = u - delta(1) - v = v - delta(2) - w = w - delta(3) - - -!------------------------------------------------------------------------------ - END DO -!------------------------------------------------------------------------------ - - IF ( .NOT. Converged ) THEN - IF( err > SQRT( acc ) ) THEN - IF( i > MaxIter ) THEN - CALL Warn( 'GlobalToLocal', 'did not converge.') - PRINT *,'rst',i,r,s,t - PRINT *,'err',err,acc,SQRT(acc) - PRINT *,'delta',delta,prevdelta - PRINT *,'uvw',u,v,w - PRINT *,'code',Element % TYPE % ElementCode - PRINT *,'x:',x,ElementNodes % x(1:n) - PRINT *,'y:',y,ElementNodes % y(1:n) - PRINT *,'z:',z,ElementNodes % z(1:n) - ELSE -! CALL Warn( 'GlobalToLocal', 'Node may be out of element') -! PRINT *,'rst',i,r,s,t,acc - END IF - END IF - END IF -!------------------------------------------------------------------------------ - END SUBROUTINE GlobalToLocal -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ - SUBROUTINE InvertMatrix3x3( G,GI,detG ) -!------------------------------------------------------------------------------ - REAL(KIND=dp) :: G(3,3),GI(3,3) - REAL(KIND=dp) :: detG, s -!------------------------------------------------------------------------------ - s = 1.0 / DetG - - GI(1,1) = s * (G(2,2)*G(3,3) - G(3,2)*G(2,3)); - GI(2,1) = -s * (G(2,1)*G(3,3) - G(3,1)*G(2,3)); - GI(3,1) = s * (G(2,1)*G(3,2) - G(3,1)*G(2,2)); - - GI(1,2) = -s * (G(1,2)*G(3,3) - G(3,2)*G(1,3)); - GI(2,2) = s * (G(1,1)*G(3,3) - G(3,1)*G(1,3)); - GI(3,2) = -s * (G(1,1)*G(3,2) - G(3,1)*G(1,2)); - - GI(1,3) = s * (G(1,2)*G(2,3) - G(2,2)*G(1,3)); - GI(2,3) = -s * (G(1,1)*G(2,3) - G(2,1)*G(1,3)); - GI(3,3) = s * (G(1,1)*G(2,2) - G(2,1)*G(1,2)); -!------------------------------------------------------------------------------ - END SUBROUTINE InvertMatrix3x3 -!------------------------------------------------------------------------------ - - -!------------------------------------------------------------------------------ -!> Given element and its face map (for some triangular face of element ), -!> this routine returns global direction of triangle face so that -!> functions are continuous over element boundaries -!------------------------------------------------------------------------------ - FUNCTION getTriangleFaceDirection( Element, FaceMap ) RESULT(globalDir) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: Element -! INPUT: Element to get direction to -! -! INTEGER :: FaceMap(3) -! INPUT: Element triangular face map -! -! FUNCTION VALUE: -! INTEGER :: globalDir(3) -! Global direction of triangular face as local node numbers. -! -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t) :: Element - INTEGER :: i, FaceMap(3), globalDir(3), nodes(3) - - nodes = 0 - - ! Put global nodes of face into sorted order - nodes(1:3) = Element % NodeIndexes( FaceMap ) - CALL sort(3, nodes) - - globalDir = 0 - ! Find local numbers of sorted nodes. These local nodes - ! span continuous functions over element boundaries - DO i=1,Element % TYPE % NumberOfNodes - IF (nodes(1) == Element % NodeIndexes(i)) THEN - globalDir(1) = i - ELSE IF (nodes(2) == Element % NodeIndexes(i)) THEN - globalDir(2) = i - ELSE IF (nodes(3) == Element % NodeIndexes(i)) THEN - globalDir(3) = i - END IF - END DO - END FUNCTION getTriangleFaceDirection - - -!------------------------------------------------------------------------------ -!> Given element and its face map (for some square face of element ), -!> this routine returns global direction of square face so that -!> functions are continuous over element boundaries -!------------------------------------------------------------------------------ - FUNCTION getSquareFaceDirection( Element, FaceMap ) RESULT(globalDir) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! Type(Element_t) :: Element -! INPUT: Element to get direction to -! -! INTEGER :: FaceMap(4) -! INPUT: Element square face map -! -! FUNCTION VALUE: -! INTEGER :: globalDir(3) -! Global direction of square face as local node numbers. -! -!------------------------------------------------------------------------------ - IMPLICIT NONE - - TYPE(Element_t) :: Element - INTEGER :: i, A,B,C,D, FaceMap(4), globalDir(4), nodes(4), minGlobal - - ! Get global nodes - nodes(1:4) = Element % NodeIndexes( FaceMap ) - ! Find min global node - minGlobal = nodes(1) - A = 1 - DO i=2,4 - IF (nodes(i) < minGlobal) THEN - A = i - minGlobal = nodes(i) - END IF - END DO - - ! Now choose node B as the smallest node NEXT to min node - B = MOD(A,4)+1 - C = MOD(A+3,4) - IF (C == 0) C = 4 - D = MOD(A+2,4) - IF (D == 0) D = 4 - IF (nodes(B) > nodes(C)) THEN - i = B - B = C - C = i - END IF - - ! Finally find local numbers of nodes A,B and C. They uniquely - ! define a global face so that basis functions are continuous - ! over element boundaries - globalDir = 0 - DO i=1,Element % TYPE % NumberOfNodes - IF (nodes(A) == Element % NodeIndexes(i)) THEN - globalDir(1) = i - ELSE IF (nodes(B) == Element % NodeIndexes(i)) THEN - globalDir(2) = i - ELSE IF (nodes(C) == Element % NodeIndexes(i)) THEN - globalDir(4) = i - ELSE IF (nodes(D) == Element % NodeIndexes(i)) THEN - globalDir(3) = i - END IF - END DO - END FUNCTION getSquareFaceDirection - - -!------------------------------------------------------------------------------ -!> Function checks if given local numbering of a square face -!> is legal for wedge element -!------------------------------------------------------------------------------ - FUNCTION wedgeOrdering( ordering ) RESULT(retVal) -!------------------------------------------------------------------------------ -! -! ARGUMENTS: -! -! INTEGER :: ordering(4) -! INPUT: Local ordering of a wedge square face -! -! FUNCTION VALUE: -! INTEGER :: retVal -! .TRUE. if given ordering is legal for wedge square face, -! .FALSE. otherwise -! -!------------------------------------------------------------------------------ - IMPLICIT NONE - - INTEGER, DIMENSION(4), INTENT(IN) :: ordering - LOGICAL :: retVal - - retVal = .FALSE. - IF ((ordering(1) >= 1 .AND. ordering(1) <= 3 .AND.& - ordering(2) >= 1 .AND. ordering(2) <= 3) .OR. & - (ordering(1) >= 4 .AND. ordering(1) <= 6 .AND.& - ordering(2) >= 4 .AND. ordering(2) <= 6)) THEN - retVal = .TRUE. - END IF - END FUNCTION wedgeOrdering - - !--------------------------------------------------------- - !> Computes the 3D rotation matrix for a given - !> surface normal vector - !--------------------------------------------------------- - FUNCTION ComputeRotationMatrix(PlaneVector) RESULT ( RotMat ) - - REAL(KIND=dp) :: PlaneVector(3), RotMat(3,3), ex(3), ey(3), ez(3) - INTEGER :: i, MinIndex, MidIndex, MaxIndex - - !Ensure PlaneVector is the unit normal - PlaneVector = PlaneVector / SQRT( SUM(PlaneVector ** 2) ) - - !The new z-axis is normal to the defined surface - ez = PlaneVector - - MaxIndex = MAXLOC(ABS(ez),1) - MinIndex = MINLOC(ABS(ez),1) - - !Special case when calving front perfectly aligned to either - ! x or y axis. In this case, make minindex = 3 (ex points upwards) - IF(ABS(ez(3)) == ABS(ez(2)) .OR. ABS(ez(3)) == ABS(ez(1))) & - MinIndex = 3 - - DO i=1,3 - IF(i == MaxIndex .OR. i == MinIndex) CYCLE - MidIndex = i - END DO - - ex(MinIndex) = 1.0 - ex(MidIndex) = 0.0 - - ex(MaxIndex) = -ez(MinIndex)/ez(MaxIndex) - ex = ex / SQRT( SUM(ex ** 2) ) - - !The new y-axis is orthogonal to new x and z axes - ey = CrossProduct(ez, ex) - ey = ey / SQRT( SUM(ey ** 2) ) !just in case... - - RotMat(1,:) = ex - RotMat(2,:) = ey - RotMat(3,:) = ez - - END FUNCTION ComputeRotationMatrix - -END MODULE ElementDescription - - -!> \} From 102a456e4db73e6c46fc88aa227e7c1afaf3362f Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Mon, 2 Nov 2020 13:52:52 +0200 Subject: [PATCH 66/73] Correct misleading commentaries --- fem/src/ElementDescription.F90 | 90 +++++++++++++++++----------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/fem/src/ElementDescription.F90 b/fem/src/ElementDescription.F90 index 0e333275b9..9ad6cf0a87 100644 --- a/fem/src/ElementDescription.F90 +++ b/fem/src/ElementDescription.F90 @@ -414,8 +414,8 @@ END SUBROUTINE AddElementDescription !------------------------------------------------------------------------------ -!> Read the element description input file and add the element types to a -!> global list. The file is assumed to be found under the name +!> Read the element description input file and add the element types to a +!> global list. The file is assumed to be found under the name !> $ELMER_HOME/lib/elements.def !> This is the first routine the user of the element utilities should call !> in his/her code. @@ -967,19 +967,19 @@ END FUNCTION SecondDerivatives1D !------------------------------------------------------------------------------ -!> Given element structure return value of a quantity x given at element nodes -!> at local coordinate point (u,vb) inside the element. Element basis functions -!> are used to compute the value.This is for 2D elements, and shouldn't probably +!> Given element structure return the value of a quantity x known at element nodes +!> at local coordinate point (u,v) inside the element. Element basis functions +!> are used to compute the value. This is for 2D elements, and shouldn't probably !> be called directly by the user but through the wrapper routine !> InterpolateInElement. !------------------------------------------------------------------------------ FUNCTION InterpolateInElement2D( element,x,u,v ) RESULT(y) !------------------------------------------------------------------------------ TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u !< Point at which to evaluate the partial derivative - REAL(KIND=dp) :: v !< Point at which to evaluate the partial derivative - REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity whose partial derivative we want to know - REAL(KIND=dp) :: y !< value of the quantity y = x(u,v) + REAL(KIND=dp) :: u !< u at the point where the quantity is evaluated + REAL(KIND=dp) :: v !< v at the point where the quantity is evaluated + REAL(KIND=dp), DIMENSION(:) :: x !< Nodal values of the quantity + REAL(KIND=dp) :: y !< The value of the quantity y = x(u,v) !------------------------------------------------------------------------------ ! Local variables !------------------------------------------------------------------------------ @@ -1065,8 +1065,8 @@ END SUBROUTINE NodalBasisFunctions2D !------------------------------------------------------------------------------ -!> Given element structure return value of the first partial derivative with -!> respect to local coordinate u of i quantity x given at element nodes at local +!> Given element structure return the value of the first partial derivative with +!> respect to local coordinate u of a quantity x given at element nodes at local !> coordinate point u,v inside the element. Element basis functions are used to !> compute the value. !------------------------------------------------------------------------------ @@ -1373,8 +1373,8 @@ END FUNCTION InterpolateInElement3D SUBROUTINE NodalBasisFunctions3D( y,element,u,v,w ) !------------------------------------------------------------------------------ TYPE(Element_t) :: element !< element structure - REAL(KIND=dp) :: u,v,w !< Point at which to evaluate the partial derivative - REAL(KIND=dp) :: y(:) !< value of the quantity y = x(u,v,w) + REAL(KIND=dp) :: u,v,w !< Point at which to evaluate the basis functions + REAL(KIND=dp) :: y(:) !< The values of the basis functions !------------------------------------------------------------------------------ ! Local variables !------------------------------------------------------------------------------ @@ -1541,47 +1541,47 @@ FUNCTION FirstDerivativeInV3D( element,x,u,v,w ) RESULT(y) l = elt % BasisFunctionDegree BasisFunctions => elt % BasisFunctions -IF ( Elt % ElementCode == 605 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) + IF ( Elt % ElementCode == 605 ) THEN + IF ( w == 1 ) w = 1.0d0-1.0d-12 + s = 1.0d0 / (1-w) - y = 0.0d0 - y = y + x(1) * ( -(1-u) + u*w * s ) / 4 - y = y + x(2) * ( -(1+u) - u*w * s ) / 4 - y = y + x(3) * ( (1+u) + u*w * s ) / 4 - y = y + x(4) * ( (1-u) - u*w * s ) / 4 + y = 0.0d0 + y = y + x(1) * ( -(1-u) + u*w * s ) / 4 + y = y + x(2) * ( -(1+u) - u*w * s ) / 4 + y = y + x(3) * ( (1+u) + u*w * s ) / 4 + y = y + x(4) * ( (1-u) - u*w * s ) / 4 - RETURN -ELSE IF ( Elt % ElementCode == 613 ) THEN - IF ( w == 1 ) w = 1.0d0-1.0d-12 - s = 1.0d0 / (1-w) + RETURN + ELSE IF ( Elt % ElementCode == 613 ) THEN + IF ( w == 1 ) w = 1.0d0-1.0d-12 + s = 1.0d0 / (1-w) - y = 0.0d0 - y = y + x(1) * ( -( (1-u) * (1-v) - w + u*v*w * s ) + & - (-u-v-1) * ( -(1-u) + u*w * s ) ) / 4 + y = 0.0d0 + y = y + x(1) * ( -( (1-u) * (1-v) - w + u*v*w * s ) + & + (-u-v-1) * ( -(1-u) + u*w * s ) ) / 4 - y = y + x(2) * ( -( (1+u) * (1-v) - w - u*v*w * s ) + & - ( u-v-1) * ( -(1+u) - u*w * s ) ) / 4 + y = y + x(2) * ( -( (1+u) * (1-v) - w - u*v*w * s ) + & + ( u-v-1) * ( -(1+u) - u*w * s ) ) / 4 - y = y + x(3) * ( ( (1+u) * (1+v) - w + u*v*w * s ) + & - ( u+v-1) * ( (1+u) + u*w * s ) ) / 4 + y = y + x(3) * ( ( (1+u) * (1+v) - w + u*v*w * s ) + & + ( u+v-1) * ( (1+u) + u*w * s ) ) / 4 - y = y + x(4) * ( ( (1-u) * (1+v) - w - u*v*w * s ) + & - (-u+v-1) * ( (1-u) - u*w * s ) ) / 4 + y = y + x(4) * ( ( (1-u) * (1+v) - w - u*v*w * s ) + & + (-u+v-1) * ( (1-u) - u*w * s ) ) / 4 - y = y + x(5) * 0.0d0 + y = y + x(5) * 0.0d0 - y = y - x(6) * (1+u-w)*(1-u-w) * s / 2 - y = y + x(7) * ( (1-v-w)*(1+u-w) - (1+v-w)*(1+u-w) ) * s / 2 - y = y + x(8) * (1+u-w)*(1-u-w) * s / 2 - y = y + x(9) * ( (1-v-w)*(1-u-w) - (1+v-w)*(1-u-w) ) * s / 2 + y = y - x(6) * (1+u-w)*(1-u-w) * s / 2 + y = y + x(7) * ( (1-v-w)*(1+u-w) - (1+v-w)*(1+u-w) ) * s / 2 + y = y + x(8) * (1+u-w)*(1-u-w) * s / 2 + y = y + x(9) * ( (1-v-w)*(1-u-w) - (1+v-w)*(1-u-w) ) * s / 2 - y = y - x(10) * w * (1-u-w) * s - y = y - x(11) * w * (1+u-w) * s - y = y + x(12) * w * (1+u-w) * s - y = y + x(13) * w * (1-u-w) * s - RETURN -END IF + y = y - x(10) * w * (1-u-w) * s + y = y - x(11) * w * (1+u-w) * s + y = y + x(12) * w * (1+u-w) * s + y = y + x(13) * w * (1-u-w) * s + RETURN + END IF y = 0.0d0 DO n = 1,elt % NumberOfNodes From 7b785647a822cfe0a19de9243c879c41f89fb274 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Mon, 2 Nov 2020 21:21:23 +0200 Subject: [PATCH 67/73] Remove unnecessary STOP --- fem/src/Solver.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/fem/src/Solver.F90 b/fem/src/Solver.F90 index 4b0464ecdd..ca87b630a0 100644 --- a/fem/src/Solver.F90 +++ b/fem/src/Solver.F90 @@ -65,8 +65,6 @@ PROGRAM Solver CALL FLUSH(6) END IF END IF - - STOP END PROGRAM Solver From ddb58cee63b92b11f91313ae72ad715b5a808b93 Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Mon, 2 Nov 2020 23:04:43 +0200 Subject: [PATCH 68/73] Check for zero timestep size --- fem/src/ElmerSolver.F90 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fem/src/ElmerSolver.F90 b/fem/src/ElmerSolver.F90 index 2a0555f3c5..3d59916555 100644 --- a/fem/src/ElmerSolver.F90 +++ b/fem/src/ElmerSolver.F90 @@ -2201,6 +2201,10 @@ SUBROUTINE ExecSimulation(TimeIntervals, CoupledMinIter, & END IF IF(GotIt) THEN dt = dtfunc + IF(dt < EPSILON(dt) ) THEN + WRITE(Message,'(A,ES12.3)') 'Timestep smaller than epsilon: ',dt + CALL Fatal('ExecSimulation', Message) + END IF ELSE dt = TimestepSizes(interval,1) END IF From 02df65cd8c16f99d6da695dd96967a46e2d3860e Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 4 Nov 2020 10:24:55 +0200 Subject: [PATCH 69/73] Magnetic coenergy calculation enabled --- fem/src/SOLVER.KEYWORDS | 1 + .../modules/MagnetoDynamics/CalcFields.F90 | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fem/src/SOLVER.KEYWORDS b/fem/src/SOLVER.KEYWORDS index a4e664106a..e83a112eea 100644 --- a/fem/src/SOLVER.KEYWORDS +++ b/fem/src/SOLVER.KEYWORDS @@ -1147,6 +1147,7 @@ Solver:Logical: 'Save Heat Flux' Solver:Logical: 'Scan Frequency' Solver:Logical: 'Scan Position' Solver:Logical: 'Second Kind Basis' +Solver:Logical: 'Separate Magnetic Energy' Solver:Logical: 'Shear Stress Output' Solver:Logical: 'Show Norm' Solver:Logical: 'Side Correction' diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index 2f244137d6..d6a6341cee 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -526,7 +526,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) !------------------------------------------------------------------------------ REAL(KIND=dp) :: s,u,v,w, Norm REAL(KIND=dp) :: B(2,3), E(2,3), JatIP(2,3), VP_ip(2,3), JXBatIP(2,3), CC_J(2,3), HdotB - REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(2), w_dens + REAL(KIND=dp) :: detJ, C_ip, PR_ip, ST(3,3), Omega, ThinLinePower, Power, Energy(3), w_dens REAL(KIND=dp) :: Freq, FreqPower, FieldPower, LossCoeff, ValAtIP REAL(KIND=dp) :: Freq2, FreqPower2, FieldPower2, LossCoeff2 REAL(KIND=dp) :: ComponentLoss(2,2), rot_velo(3), angular_velo(3) @@ -1416,15 +1416,16 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) END SELECT END DO END IF + + IF (RealField) THEN + HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + ELSE + HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & + SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) + END IF IF (ASSOCIATED(NF).OR.ASSOCIATED(EL_NF)) THEN NF_ip = 0._dp - IF (RealField) THEN - HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) - ELSE - HdotB = SUM(MATMUL(REAL(Nu), B(1,:)) * B(1,:)) + & - SUM(MATMUL(REAL(Nu), B(2,:)) * B(2,:)) - END IF DO k=1,n DO l=1,3 val = SUM(dBasisdx(k,1:3)*B(1,1:3)) @@ -1445,6 +1446,7 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) Energy(1) = Energy(1) + s*0.5*PR_ip*SUM(E**2) Energy(2) = Energy(2) + s*w_dens + Energy(3) = Energy(3) + (HdotB - w_dens) * s DO p=1,n DO q=1,n @@ -1930,7 +1932,8 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) Power = ParallelReduction(Power) Energy(1) = ParallelReduction(Energy(1)) Energy(2) = ParallelReduction(Energy(2)) - + Energy(3) = ParallelReduction(Energy(3)) + IF (LossEstimation) THEN DO j=1,2 DO i=1,2 @@ -1951,13 +1954,16 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CALL Info( 'MagnetoDynamicsCalcFields', Message ) CALL ListAddConstReal( Model % Simulation, 'res: Eddy current power', Power ) - IF( ListGetLogical( SolverParams,'Separate Magnetic Energy',Found ) ) THEN + IF ( ListGetLogical( SolverParams,'Separate Magnetic Energy',Found ) ) THEN WRITE(Message,'(A,ES12.3)') 'Electric Field Energy: ', Energy(1) CALL Info( 'MagnetoDynamicsCalcFields', Message ) WRITE(Message,'(A,ES12.3)') 'Magnetic Field Energy: ', Energy(2) CALL Info( 'MagnetoDynamicsCalcFields', Message ) + WRITE(Message,'(A,ES12.3)') 'Magnetic Coenergy: ', Energy(3) + CALL Info( 'MagnetoDynamicsCalcFields', Message ) CALL ListAddConstReal(Model % Simulation,'res: Electric Field Energy',Energy(1)) CALL ListAddConstReal(Model % Simulation,'res: Magnetic Field Energy',Energy(2)) + CALL ListAddConstReal(Model % Simulation,'res: Magnetic Coenergy',Energy(3)) ELSE WRITE(Message,'(A,ES12.3)') 'ElectroMagnetic Field Energy: ',SUM(Energy) CALL Info( 'MagnetoDynamicsCalcFields', Message ) From 1d915a05bdd2f08b7f4fd9ff8243c6abd862adb3 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Wed, 4 Nov 2020 11:17:10 +0200 Subject: [PATCH 70/73] A fix to the magnetic energy calculation --- fem/src/modules/MagnetoDynamics/CalcFields.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fem/src/modules/MagnetoDynamics/CalcFields.F90 b/fem/src/modules/MagnetoDynamics/CalcFields.F90 index d6a6341cee..90b927505c 100644 --- a/fem/src/modules/MagnetoDynamics/CalcFields.F90 +++ b/fem/src/modules/MagnetoDynamics/CalcFields.F90 @@ -1965,9 +1965,9 @@ SUBROUTINE MagnetoDynamicsCalcFields(Model,Solver,dt,Transient) CALL ListAddConstReal(Model % Simulation,'res: Magnetic Field Energy',Energy(2)) CALL ListAddConstReal(Model % Simulation,'res: Magnetic Coenergy',Energy(3)) ELSE - WRITE(Message,'(A,ES12.3)') 'ElectroMagnetic Field Energy: ',SUM(Energy) + WRITE(Message,'(A,ES12.3)') 'ElectroMagnetic Field Energy: ',SUM(Energy(1:2)) CALL Info( 'MagnetoDynamicsCalcFields', Message ) - CALL ListAddConstReal(Model % Simulation,'res: ElectroMagnetic Field Energy',SUM(Energy)) + CALL ListAddConstReal(Model % Simulation,'res: ElectroMagnetic Field Energy',SUM(Energy(1:2))) END IF IF(ALLOCATED(Gforce)) DEALLOCATE(Gforce) From 946e8504b574f28a58362322dba1d4dac7194bc1 Mon Sep 17 00:00:00 2001 From: Mika Malinen Date: Thu, 5 Nov 2020 09:14:08 +0200 Subject: [PATCH 71/73] Test the calculation of coenergy --- fem/tests/mgdyn_bh/case.sif | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fem/tests/mgdyn_bh/case.sif b/fem/tests/mgdyn_bh/case.sif index eaa7e9f68e..d742ac508d 100644 --- a/fem/tests/mgdyn_bh/case.sif +++ b/fem/tests/mgdyn_bh/case.sif @@ -68,6 +68,7 @@ Solver 3 Calculate Magnetic Vector Potential = False Calculate Magnetic Flux Density = True Calculate Magnetic Field Strength = Logical True + Separate Magnetic Energy = True Steady State Convergence Tolerance = 0 Linear System Solver = "Iterative" Linear System Preconditioning = None @@ -89,8 +90,8 @@ End Solver 5 Equation = "scalars" procedure = "SaveData" "SaveScalars" - !variable 1 = res: magnetic field energy - show norm index = integer 2 + show norm index = integer 3 +! Filename = "tmp.dat" End From 87d59eed013e5cbf47ae3daf8f95e68cf3faa622 Mon Sep 17 00:00:00 2001 From: Evgeny2 <31275152+Evgeny2@users.noreply.github.com> Date: Thu, 5 Nov 2020 12:30:49 +0000 Subject: [PATCH 72/73] Update vectorhelmholtz.xml --- ElmerGUI/Application/edf-extra/vectorhelmholtz.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml b/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml index 144e0c4a75..42274f45e5 100644 --- a/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml +++ b/ElmerGUI/Application/edf-extra/vectorhelmholtz.xml @@ -308,7 +308,7 @@ Calculate Poynting Vector - Calculate Div Poynting Vector + Calculate Div of Poynting Vector Logical Calculate Divergence of Poyntings vector From fa4609791e332073eb2ecb9c46e358111ca210af Mon Sep 17 00:00:00 2001 From: Peter Raback Date: Fri, 6 Nov 2020 17:04:04 +0200 Subject: [PATCH 73/73] Test case with profile that changes with time --- fem/tests/TimeProfileBC/CMakeLists.txt | 9 ++ fem/tests/TimeProfileBC/ELMERSOLVER_STARTINFO | 1 + fem/tests/TimeProfileBC/TimeProfile.F90 | 54 ++++++++ fem/tests/TimeProfileBC/case.sif | 128 ++++++++++++++++++ fem/tests/TimeProfileBC/runtest.cmake | 3 + fem/tests/TimeProfileBC/slab.grd | 24 ++++ 6 files changed, 219 insertions(+) create mode 100755 fem/tests/TimeProfileBC/CMakeLists.txt create mode 100755 fem/tests/TimeProfileBC/ELMERSOLVER_STARTINFO create mode 100755 fem/tests/TimeProfileBC/TimeProfile.F90 create mode 100755 fem/tests/TimeProfileBC/case.sif create mode 100755 fem/tests/TimeProfileBC/runtest.cmake create mode 100755 fem/tests/TimeProfileBC/slab.grd diff --git a/fem/tests/TimeProfileBC/CMakeLists.txt b/fem/tests/TimeProfileBC/CMakeLists.txt new file mode 100755 index 0000000000..f115861a0d --- /dev/null +++ b/fem/tests/TimeProfileBC/CMakeLists.txt @@ -0,0 +1,9 @@ +INCLUDE(test_macros) +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/fem/src) + +CONFIGURE_FILE(case.sif case.sif COPYONLY) +ADD_ELMERTEST_MODULE(TimeProfileBC TimeProfile TimeProfile.F90) + +file(COPY ELMERSOLVER_STARTINFO TimeProfile.F90 slab.grd DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/") + +ADD_ELMER_TEST(TimeProfileBC) diff --git a/fem/tests/TimeProfileBC/ELMERSOLVER_STARTINFO b/fem/tests/TimeProfileBC/ELMERSOLVER_STARTINFO new file mode 100755 index 0000000000..d21bd7ee27 --- /dev/null +++ b/fem/tests/TimeProfileBC/ELMERSOLVER_STARTINFO @@ -0,0 +1 @@ +case.sif diff --git a/fem/tests/TimeProfileBC/TimeProfile.F90 b/fem/tests/TimeProfileBC/TimeProfile.F90 new file mode 100755 index 0000000000..8fd802e8ae --- /dev/null +++ b/fem/tests/TimeProfileBC/TimeProfile.F90 @@ -0,0 +1,54 @@ +! Returns a piecewise linear profile such that each control point may be +! a function of time or some other global variable. +!-------------------------------------------------------------------------- +FUNCTION TimeProfile( Model, n, tx ) RESULT( f ) + USE DefUtils + + IMPLICIT NONE + + TYPE(Model_t) :: Model + INTEGER :: n + REAL(KIND=dp) :: tx, f + + INTEGER :: tn, tn0 = -1, m, i + LOGICAL :: Found + TYPE(ValueListEntry_t), POINTER :: ptr + TYPE(ValueList_t), POINTER :: BC + CHARACTER(LEN=MAX_NAME_LEN) :: Name + + SAVE tn0, ptr, Name + + BC => GetBC() + tn = GetTimestep() + + IF( tn /= tn0 ) THEN + tn0 = tn + + Name = "Timeprofile" + + ptr => ListFind(BC,Name,Found) + IF(.NOT. Found ) CALL Fatal('TimeProfile','Could not find item: '//TRIM(Name)) + + IF( ptr % TYPE /= LIST_TYPE_VARIABLE_SCALAR ) THEN + CALL Fatal('TimeProfile','Item should be variable scalar: '//TRIM(Name)) + END IF + + IF ( ptr % PROCEDURE /= 0 ) THEN + CALL Fatal('TimeProfile','Item should not be a function: '//TRIM(Name)) + END IF + + m = SIZE( ptr % Fvalues(1,1,:) ) + + DO i=1,m + f = ListGetCReal(BC,TRIM(Name)//' point '//TRIM(I2S(i)),UnfoundFatal=.TRUE.) + ptr % Fvalues(1,1,i) = f + END DO + + PRINT *,'Updated temperature profile: ',ptr % Fvalues(1,1,:) + END IF + + f = InterpolateCurve( ptr % TValues,ptr % FValues(1,1,:), & + tx, ptr % CubicCoeff ) + +END FUNCTION TimeProfile + diff --git a/fem/tests/TimeProfileBC/case.sif b/fem/tests/TimeProfileBC/case.sif new file mode 100755 index 0000000000..b1b2d6c802 --- /dev/null +++ b/fem/tests/TimeProfileBC/case.sif @@ -0,0 +1,128 @@ +! An linear profile changing with time. +! +! P.R. 6.11.2020 + +Header + CHECK KEYWORDS Warn + Mesh DB "." "slab" + Include Path "" + Results Directory "" +End + +Simulation + Max Output Level = 5 + + Coordinate System = "Cartesian" + Coordinate Mapping(3) = 1 2 3 + + Simulation Type = "Transient" + +! Tavels back and forth one time in 1s + Timestep Sizes = 1 + Timestep Intervals = 20 + + Steady State Max Iterations = 1 + Output Intervals = 1 + + Post File = case.vtu +End + +Constants + Gravity(4) = 0 -1 0 9.82 + Stefan Boltzmann = 5.67e-08 +End + +Body 1 + Name = "Body" + Equation = 1 + Material = 1 +End + +Equation 1 + Name = "Equations" + Active Solvers(2) = 1 2 +End + +Solver 1 + Exec Solver = "Always" + Equation = "Heat Equation" + Variable = "Temperature" + + Linear System Solver = direct + Linear System Direct Method = "umfpack" + + Steady State Convergence Tolerance = 1.0e-05 + + Nonlinear System Convergence Tolerance = 1.0e-05 + Nonlinear System Max Iterations = 1 +End + + +Solver 2 + Exec Solver = never + + Equation = SaveLine + Procedure = "SaveData" "SaveLine" + + Filename = f.dat +End + +Material 1 + Name = "Material" + Density = 1 + Heat Conductivity = 1.0 + Heat Capacity = 10.0 +End + + +Boundary Condition 1 + Name = "Bottom" + Target Boundaries(1) = 1 +End + +Boundary Condition 2 + Name = "Right" + Target Boundaries(1) = 2 + Temperature = 0 +End + +Boundary Condition 3 + Name = "Top" + Target Boundaries(1) = 3 + + External Temperature = Variable "coordinate 1" + Procedure "TimeProfile" "TimeProfile" + + Timeprofile = Variable "dummy" + Real + 0.0 0.0 + 0.2 0.0 + 0.4 0.0 + 0.6 0.0 + 1.0 0.0 + End + + Timeprofile point 1 = Variable "time" + Real MATC "1.0/(1.0+tx)" + Timeprofile point 2 = Variable "time" + Real MATC "1.0/(2.0+tx)" + Timeprofile point 3 = Variable "time" + Real MATC "sin(tx)" + Timeprofile point 4 = Variable "time" + Real MATC "-1.0/(1.0+2*tx)" + Timeprofile point 5 = Variable "time" + Real MATC "-1.0/(1.0+3*tx)" + + + Heat Transfer Coefficient = 1.0 + + Save Line = Logical True +End + +Boundary Condition 4 + Name = "Left" + Target Boundaries(1) = 4 + Temperature = 0 +End + +Solver 1 :: Reference Norm = 7.64764652E-02 diff --git a/fem/tests/TimeProfileBC/runtest.cmake b/fem/tests/TimeProfileBC/runtest.cmake new file mode 100755 index 0000000000..9a30ca2d8c --- /dev/null +++ b/fem/tests/TimeProfileBC/runtest.cmake @@ -0,0 +1,3 @@ +include(test_macros) +execute_process(COMMAND ${ELMERGRID_BIN} 1 2 slab) +RUN_ELMER_TEST() diff --git a/fem/tests/TimeProfileBC/slab.grd b/fem/tests/TimeProfileBC/slab.grd new file mode 100755 index 0000000000..ddea43b8c0 --- /dev/null +++ b/fem/tests/TimeProfileBC/slab.grd @@ -0,0 +1,24 @@ +***** ElmerGrid input file for structured grid generation ***** +Version = 210903 +Coordinate System = Cartesian 2D +Subcell Divisions in 2D = 1 1 +Subcell Limits 1 = 0 1 +Subcell Limits 2 = 0 0.1 +Material Structure in 2D + 1 +End +Materials Interval = 1 1 +Boundary Definitions +! type out int + 1 -1 1 1 + 2 -2 1 1 + 3 -3 1 1 + 4 -4 1 1 +End +Element Degree = 1 +Element Innernodes = False +Triangles = False +Element Divisions 1 = 100 +Element Divisions 2 = 10 +Element Ratios 1 = 1.0 +Element Ratios 2 = 1.0